diff --git a/base/vulkanTextureLoader.hpp b/base/vulkanTextureLoader.hpp index c1f674db..fc076628 100644 --- a/base/vulkanTextureLoader.hpp +++ b/base/vulkanTextureLoader.hpp @@ -1,9 +1,7 @@ /* -* Simple texture loader for Vulkan +* Texture loader for Vulkan * -* Note : No mip maps (yet), only uses optimal tiling (unless linear is forced) -* -* Copyright (C) 2015 by Sascha Willems - www.saschawillems.de +* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ @@ -68,6 +66,7 @@ namespace vkTools texture->width = (uint32_t)tex2D[0].dimensions().x; texture->height = (uint32_t)tex2D[0].dimensions().y; + texture->mipLevels = tex2D.levels(); VkResult err; @@ -80,17 +79,9 @@ namespace vkTools // optimal tiling instead // On most implementations linear tiling will only support a very // limited amount of formats and features (mip maps, cubemaps, arrays, etc.) - VkBool32 useStaging = true; + VkBool32 useStaging = !forceLinear; - // Only use linear tiling if forced - if (forceLinear) - { - useStaging = formatProperties.linearTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT; - } - - VkImageCreateInfo imageCreateInfo = {}; - imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - imageCreateInfo.pNext = NULL; + VkImageCreateInfo imageCreateInfo = vkTools::initializers::imageCreateInfo(); imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; imageCreateInfo.format = format; imageCreateInfo.extent = { texture->width, texture->height, 1 }; @@ -100,75 +91,72 @@ namespace vkTools imageCreateInfo.tiling = VK_IMAGE_TILING_LINEAR; imageCreateInfo.usage = (useStaging) ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : VK_IMAGE_USAGE_SAMPLED_BIT; imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - imageCreateInfo.flags = 0; - VkMemoryAllocateInfo memAllocInfo = {}; - memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - memAllocInfo.pNext = NULL; - memAllocInfo.allocationSize = 0; - memAllocInfo.memoryTypeIndex = 0; - - VkImage mappableImage; - VkDeviceMemory mappableMemory; - - // Create base image, if linear texturing is forced - // this can directly be used - err = vkCreateImage(device, &imageCreateInfo, nullptr, &mappableImage); - assert(!err); - - // Get memory requirements for this image - // like size and alignment + VkMemoryAllocateInfo memAllocInfo = vkTools::initializers::memoryAllocateInfo(); VkMemoryRequirements memReqs; - vkGetImageMemoryRequirements(device, mappableImage, &memReqs); - // Set memory allocation size to required memory size - memAllocInfo.allocationSize = memReqs.size; - // Get memory type that can be mapped to host memory - getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &memAllocInfo.memoryTypeIndex); - - // Allocate host memory - err = vkAllocateMemory(device, &memAllocInfo, nullptr, &(mappableMemory)); + // Use a separate command buffer for texture loading + VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo(); + err = vkBeginCommandBuffer(cmdBuffer, &cmdBufInfo); assert(!err); - // Bind allocated image for use - err = vkBindImageMemory(device, mappableImage, mappableMemory, 0); - assert(!err); - - // Get sub resource layout - // Mip map count, array layer, etc. - VkImageSubresource subRes = {}; - subRes.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subRes.mipLevel = 0; - subRes.arrayLayer = 0; - - VkSubresourceLayout subResLayout; - void *data; - - // Get sub resources layout - // Includes row pitch, size offsets, etc. - vkGetImageSubresourceLayout(device, mappableImage, &subRes, &subResLayout); - - // Map image memory - err = vkMapMemory(device, mappableMemory, 0, memReqs.size, 0, &data); - assert(!err); - - // Copy image data into memory - memcpy(data, tex2D[subRes.mipLevel].data(), tex2D[subRes.mipLevel].size()); - - vkUnmapMemory(device, mappableMemory); - if (useStaging) { - VkCommandBufferBeginInfo cmdBufInfo = {}; - cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - cmdBufInfo.pNext = NULL; + // Load all available mip levels into linear textures + // and copy to optimal tiling target + struct MipLevel { + VkImage image; + VkDeviceMemory memory; + }; + std::vector mipLevels; + mipLevels.resize(texture->mipLevels); - err = vkBeginCommandBuffer(cmdBuffer, &cmdBufInfo); - assert(!err); + // Copy mip levels + for (uint32_t level = 0; level < texture->mipLevels; ++level) + { + imageCreateInfo.extent.width = tex2D[level].dimensions().x; + imageCreateInfo.extent.height = tex2D[level].dimensions().y; + imageCreateInfo.extent.depth = 1; + + err = vkCreateImage(device, &imageCreateInfo, nullptr, &mipLevels[level].image); + assert(!err); + + vkGetImageMemoryRequirements(device, mipLevels[level].image, &memReqs); + memAllocInfo.allocationSize = memReqs.size; + getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &memAllocInfo.memoryTypeIndex); + err = vkAllocateMemory(device, &memAllocInfo, nullptr, &mipLevels[level].memory); + assert(!err); + err = vkBindImageMemory(device, mipLevels[level].image, mipLevels[level].memory, 0); + assert(!err); + + VkImageSubresource subRes = {}; + subRes.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + + VkSubresourceLayout subResLayout; + void *data; + + vkGetImageSubresourceLayout(device, mipLevels[level].image, &subRes, &subResLayout); + assert(!err); + err = vkMapMemory(device, mipLevels[level].memory, 0, memReqs.size, 0, &data); + assert(!err); + memcpy(data, tex2D[level].data(), tex2D[level].size()); + vkUnmapMemory(device, mipLevels[level].memory); + + // Image barrier for linear image (base) + // Linear image will be used as a source for the copy + setImageLayout( + cmdBuffer, + mipLevels[level].image, + VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + } // Setup texture as blit target with optimal tiling imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + imageCreateInfo.mipLevels = texture->mipLevels; + imageCreateInfo.extent = { texture->width, texture->height, 1 }; err = vkCreateImage(device, &imageCreateInfo, nullptr, &texture->image); assert(!err); @@ -177,66 +165,161 @@ namespace vkTools memAllocInfo.allocationSize = memReqs.size; - // Get device only memory type getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &memAllocInfo.memoryTypeIndex); - - // Allocate device memory err = vkAllocateMemory(device, &memAllocInfo, nullptr, &texture->deviceMemory); assert(!err); - - // Bind allocated image for use err = vkBindImageMemory(device, texture->image, texture->deviceMemory, 0); assert(!err); - // Image barrier for linear image (base) - // Linear image will be used as a source for the blit - setImageLayout(cmdBuffer, - mappableImage, - VK_IMAGE_ASPECT_COLOR_BIT, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - // Image barrier for optimal image (target) - // Optimal image will be used as a target for the blit - setImageLayout(cmdBuffer, + // Optimal image will be used as destination for the copy + setImageLayout( + cmdBuffer, texture->image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - // Copy region for image blit - VkImageCopy copyRegion = {}; + // Copy mip levels one by one + for (uint32_t level = 0; level < texture->mipLevels; ++level) + { + // Copy region for image blit + VkImageCopy copyRegion = {}; - copyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - copyRegion.srcSubresource.baseArrayLayer = 0; - copyRegion.srcSubresource.mipLevel = 0; - copyRegion.srcSubresource.layerCount = 1; - copyRegion.srcOffset = { 0, 0, 0 }; + copyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copyRegion.srcSubresource.baseArrayLayer = 0; + copyRegion.srcSubresource.mipLevel = 0; + copyRegion.srcSubresource.layerCount = 1; + copyRegion.srcOffset = { 0, 0, 0 }; - copyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - copyRegion.dstSubresource.baseArrayLayer = 0; - copyRegion.dstSubresource.mipLevel = 0; - copyRegion.dstSubresource.layerCount = 1; - copyRegion.dstOffset = { 0, 0, 0 }; + copyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copyRegion.dstSubresource.baseArrayLayer = 0; + // Set mip level to copy the linear image to + copyRegion.dstSubresource.mipLevel = level; + copyRegion.dstSubresource.layerCount = 1; + copyRegion.dstOffset = { 0, 0, 0 }; - copyRegion.extent.width = texture->width; - copyRegion.extent.height = texture->height; - copyRegion.extent.depth = 1; + copyRegion.extent.width = tex2D[level].dimensions().x; + copyRegion.extent.height = tex2D[level].dimensions().y; + copyRegion.extent.depth = 1; - // Put image copy into command buffer - vkCmdCopyImage(cmdBuffer, - mappableImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - texture->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, ©Region); + // Put image copy into command buffer + vkCmdCopyImage( + cmdBuffer, + mipLevels[level].image, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + texture->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ©Region); - // Change texture image layout to shader read after the copy + // Change texture image layout to shader read after the copy + texture->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + setImageLayout( + cmdBuffer, + texture->image, + VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + texture->imageLayout); + } + + // Submit command buffer containing copy and image layout commands + err = vkEndCommandBuffer(cmdBuffer); + assert(!err); + + VkFence nullFence = { VK_NULL_HANDLE }; + + VkSubmitInfo submitInfo = vkTools::initializers::submitInfo(); + submitInfo.waitSemaphoreCount = 0; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &cmdBuffer; + + err = vkQueueSubmit(queue, 1, &submitInfo, nullFence); + assert(!err); + + err = vkQueueWaitIdle(queue); + assert(!err); + + // Clean up linear images + // No longer required after mip levels + // have been transformed over to optimal tiling + for (auto& level : mipLevels) + { + vkDestroyImage(device, level.image, nullptr); + vkFreeMemory(device, level.memory, nullptr); + } + } + else + { + // Prefer using optimal tiling, as linear tiling + // may support only a small set of features + // depending on implementation (e.g. no mip maps, only one layer, etc.) + + // Check if this support is supported for linear tiling + assert(formatProperties.linearTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT); + + VkImage mappableImage; + VkDeviceMemory mappableMemory; + + // Load mip map level 0 to linear tiling image + err = vkCreateImage(device, &imageCreateInfo, nullptr, &mappableImage); + assert(!err); + + // Get memory requirements for this image + // like size and alignment + vkGetImageMemoryRequirements(device, mappableImage, &memReqs); + // Set memory allocation size to required memory size + memAllocInfo.allocationSize = memReqs.size; + + // Get memory type that can be mapped to host memory + getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &memAllocInfo.memoryTypeIndex); + + // Allocate host memory + err = vkAllocateMemory(device, &memAllocInfo, nullptr, &mappableMemory); + assert(!err); + + // Bind allocated image for use + err = vkBindImageMemory(device, mappableImage, mappableMemory, 0); + assert(!err); + + // Get sub resource layout + // Mip map count, array layer, etc. + VkImageSubresource subRes = {}; + subRes.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subRes.mipLevel = 0; + + VkSubresourceLayout subResLayout; + void *data; + + // Get sub resources layout + // Includes row pitch, size offsets, etc. + vkGetImageSubresourceLayout(device, mappableImage, &subRes, &subResLayout); + assert(!err); + + // Map image memory + err = vkMapMemory(device, mappableMemory, 0, memReqs.size, 0, &data); + assert(!err); + + // Copy image data into memory + memcpy(data, tex2D[subRes.mipLevel].data(), tex2D[subRes.mipLevel].size()); + + vkUnmapMemory(device, mappableMemory); + + // Linear tiled images don't need to be staged + // and can be directly used as textures + texture->image = mappableImage; + texture->deviceMemory = mappableMemory; texture->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - setImageLayout(cmdBuffer, - texture->image, - VK_IMAGE_ASPECT_COLOR_BIT, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + + // Setup image memory barrier + setImageLayout( + cmdBuffer, + texture->image, + VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, texture->imageLayout); + // Submit command buffer containing copy and image layout commands err = vkEndCommandBuffer(cmdBuffer); assert(!err); @@ -253,30 +336,8 @@ namespace vkTools err = vkQueueWaitIdle(queue); assert(!err); } - else - { - // Linear tiled images don't need to be staged - // and can be directly used as textures - - texture->image = mappableImage; - texture->deviceMemory = mappableMemory; - texture->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - - // Setup image memory barrier - setImageLayout(cmdBuffer, - texture->image, - VK_IMAGE_ASPECT_COLOR_BIT, - VK_IMAGE_LAYOUT_UNDEFINED, - texture->imageLayout); - } // Create sampler - // In Vulkan textures are accessed by samplers - // This separates all the sampling information from the - // texture data - // This means you could have multiple sampler objects - // for the same texture with different settings - // This is similar to the samplers available with OpenGL 3.3 VkSamplerCreateInfo sampler = {}; sampler.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; sampler.magFilter = VK_FILTER_LINEAR; @@ -286,10 +347,13 @@ namespace vkTools sampler.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; sampler.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; sampler.mipLodBias = 0.0f; - sampler.maxAnisotropy = 0; sampler.compareOp = VK_COMPARE_OP_NEVER; sampler.minLod = 0.0f; - sampler.maxLod = 0.0f; + // Max level-of-detail should match mip level count + sampler.maxLod = (useStaging) ? (float)texture->mipLevels : 0.0f; + // Enable anisotropic filtering + sampler.maxAnisotropy = 8; + sampler.anisotropyEnable = VK_TRUE; sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; err = vkCreateSampler(device, &sampler, nullptr, &texture->sampler); assert(!err); @@ -306,15 +370,12 @@ namespace vkTools view.format = format; view.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }; view.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + // Linear tiling usually won't support mip maps + // Only set mip map count if optimal tiling is used + view.subresourceRange.levelCount = (useStaging) ? texture->mipLevels : 1; view.image = texture->image; err = vkCreateImageView(device, &view, nullptr, &texture->view); assert(!err); - - if (useStaging) - { - vkDestroyImage(device, mappableImage, nullptr); - vkFreeMemory(device, mappableMemory, nullptr); - } } // Clean up vulkan resources used by a texture object