diff --git a/base/vulkanTextureLoader.hpp b/base/vulkanTextureLoader.hpp index 57ae0c87..b26908b8 100644 --- a/base/vulkanTextureLoader.hpp +++ b/base/vulkanTextureLoader.hpp @@ -844,6 +844,190 @@ namespace vkTools texture->descriptor.sampler = texture->sampler; } + /** + * Creates a 2D texture from a buffer + * + * @param buffer Buffer containing texture data to upload + * @param bufferSize Size of the buffer in machine units + * @param width Width of the texture to create + * @param hidth Height of the texture to create + * @param format Vulkan format of the image data stored in the file + * @param texture Pointer to the texture object to load the image into + * @param (Optional) filter Texture filtering for the sampler (defaults to VK_FILTER_LINEAR) + * @param (Optional) imageUsageFlags Usage flags for the texture's image (defaults to VK_IMAGE_USAGE_SAMPLED_BIT) + */ + void createTexture(void* buffer, VkDeviceSize bufferSize, VkFormat format, uint32_t width, uint32_t height, VulkanTexture *texture, VkFilter filter = VK_FILTER_LINEAR, VkImageUsageFlags imageUsageFlags = VK_IMAGE_USAGE_SAMPLED_BIT) + { + assert(buffer); + + texture->width = width; + texture->height = height; + texture->mipLevels = 1; + + VkMemoryAllocateInfo memAllocInfo = vkTools::initializers::memoryAllocateInfo(); + VkMemoryRequirements memReqs; + + // Use a separate command buffer for texture loading + VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo(); + VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuffer, &cmdBufInfo)); + + // Create a host-visible staging buffer that contains the raw image data + VkBuffer stagingBuffer; + VkDeviceMemory stagingMemory; + + VkBufferCreateInfo bufferCreateInfo = vkTools::initializers::bufferCreateInfo(); + bufferCreateInfo.size = bufferSize; + // This buffer is used as a transfer source for the buffer copy + bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VK_CHECK_RESULT(vkCreateBuffer(vulkanDevice->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer)); + + // Get memory requirements for the staging buffer (alignment, memory type bits) + vkGetBufferMemoryRequirements(vulkanDevice->logicalDevice, stagingBuffer, &memReqs); + + memAllocInfo.allocationSize = memReqs.size; + // Get memory type index for a host visible buffer + memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + + VK_CHECK_RESULT(vkAllocateMemory(vulkanDevice->logicalDevice, &memAllocInfo, nullptr, &stagingMemory)); + VK_CHECK_RESULT(vkBindBufferMemory(vulkanDevice->logicalDevice, stagingBuffer, stagingMemory, 0)); + + // Copy texture data into staging buffer + uint8_t *data; + VK_CHECK_RESULT(vkMapMemory(vulkanDevice->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void **)&data)); + memcpy(data, buffer, bufferSize); + vkUnmapMemory(vulkanDevice->logicalDevice, stagingMemory); + + VkBufferImageCopy bufferCopyRegion = {}; + bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + bufferCopyRegion.imageSubresource.mipLevel = 0; + bufferCopyRegion.imageSubresource.baseArrayLayer = 0; + bufferCopyRegion.imageSubresource.layerCount = 1; + bufferCopyRegion.imageExtent.width = width; + bufferCopyRegion.imageExtent.height = height; + bufferCopyRegion.imageExtent.depth = 1; + bufferCopyRegion.bufferOffset = 0; + + // Create optimal tiled target image + VkImageCreateInfo imageCreateInfo = vkTools::initializers::imageCreateInfo(); + imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; + imageCreateInfo.format = format; + imageCreateInfo.mipLevels = texture->mipLevels; + imageCreateInfo.arrayLayers = 1; + imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageCreateInfo.extent = { texture->width, texture->height, 1 }; + imageCreateInfo.usage = imageUsageFlags; + // Ensure that the TRANSFER_DST bit is set for staging + if (!(imageCreateInfo.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT)) + { + imageCreateInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; + } + VK_CHECK_RESULT(vkCreateImage(vulkanDevice->logicalDevice, &imageCreateInfo, nullptr, &texture->image)); + + vkGetImageMemoryRequirements(vulkanDevice->logicalDevice, texture->image, &memReqs); + + memAllocInfo.allocationSize = memReqs.size; + + memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_CHECK_RESULT(vkAllocateMemory(vulkanDevice->logicalDevice, &memAllocInfo, nullptr, &texture->deviceMemory)); + VK_CHECK_RESULT(vkBindImageMemory(vulkanDevice->logicalDevice, texture->image, texture->deviceMemory, 0)); + + VkImageSubresourceRange subresourceRange = {}; + subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subresourceRange.baseMipLevel = 0; + subresourceRange.levelCount = texture->mipLevels; + subresourceRange.layerCount = 1; + + // Image barrier for optimal image (target) + // 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, + subresourceRange); + + // Copy mip levels from staging buffer + vkCmdCopyBufferToImage( + cmdBuffer, + stagingBuffer, + texture->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + &bufferCopyRegion + ); + + // Change texture image layout to shader read after all mip levels have been copied + 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, + subresourceRange); + + // Submit command buffer containing copy and image layout commands + VK_CHECK_RESULT(vkEndCommandBuffer(cmdBuffer)); + + // Create a fence to make sure that the copies have finished before continuing + VkFence copyFence; + VkFenceCreateInfo fenceCreateInfo = vkTools::initializers::fenceCreateInfo(VK_FLAGS_NONE); + VK_CHECK_RESULT(vkCreateFence(vulkanDevice->logicalDevice, &fenceCreateInfo, nullptr, ©Fence)); + + VkSubmitInfo submitInfo = vkTools::initializers::submitInfo(); + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &cmdBuffer; + + VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, copyFence)); + + VK_CHECK_RESULT(vkWaitForFences(vulkanDevice->logicalDevice, 1, ©Fence, VK_TRUE, DEFAULT_FENCE_TIMEOUT)); + + vkDestroyFence(vulkanDevice->logicalDevice, copyFence, nullptr); + + // Clean up staging resources + vkFreeMemory(vulkanDevice->logicalDevice, stagingMemory, nullptr); + vkDestroyBuffer(vulkanDevice->logicalDevice, stagingBuffer, nullptr); + + // Create sampler + VkSamplerCreateInfo sampler = {}; + sampler.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + sampler.magFilter = filter; + sampler.minFilter = filter; + sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + sampler.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + sampler.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + sampler.mipLodBias = 0.0f; + sampler.compareOp = VK_COMPARE_OP_NEVER; + sampler.minLod = 0.0f; + sampler.maxLod = 0.0f; + VK_CHECK_RESULT(vkCreateSampler(vulkanDevice->logicalDevice, &sampler, nullptr, &texture->sampler)); + + // Create image view + VkImageViewCreateInfo view = {}; + view.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + view.pNext = NULL; + view.image = VK_NULL_HANDLE; + view.viewType = VK_IMAGE_VIEW_TYPE_2D; + 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 }; + view.subresourceRange.levelCount = 1; + view.image = texture->image; + VK_CHECK_RESULT(vkCreateImageView(vulkanDevice->logicalDevice, &view, nullptr, &texture->view)); + + // Fill descriptor image info that can be used for setting up descriptor sets + texture->descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + texture->descriptor.imageView = texture->view; + texture->descriptor.sampler = texture->sampler; + } + /** * Free all Vulkan resources used by a texture object *