diff --git a/attachments/24_texture_image.cpp b/attachments/24_texture_image.cpp index 9b396509..b8d61315 100644 --- a/attachments/24_texture_image.cpp +++ b/attachments/24_texture_image.cpp @@ -47,14 +47,13 @@ struct Vertex static vk::VertexInputBindingDescription getBindingDescription() { - return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + return {.binding = 0, .stride = sizeof(Vertex), .inputRate = vk::VertexInputRate::eVertex}; } static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), - vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color))}; + return {{{.location = 0, .binding = 0, .format = vk::Format::eR32G32Sfloat, .offset = offsetof(Vertex, pos)}, + {.location = 1, .binding = 0, .format = vk::Format::eR32G32B32Sfloat, .offset = offsetof(Vertex, color)}}}; } }; @@ -147,7 +146,7 @@ class HelloTriangleApplication static void framebufferResizeCallback(GLFWwindow *window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); app->framebufferResized = true; } @@ -273,7 +272,7 @@ class HelloTriangleApplication vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( - vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{.messageSeverity = severityFlags, .messageType = messageTypeFlags, .pfnUserCallback = &debugCallback}; @@ -315,6 +314,7 @@ class HelloTriangleApplication vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT>(); bool supportsRequiredFeatures = features.template get().shaderDrawParameters && features.template get().dynamicRendering && + features.template get().synchronization2 && features.template get().extendedDynamicState; // Return true if the physicalDevice meets all the criteria @@ -353,12 +353,16 @@ class HelloTriangleApplication } // query for required features (Vulkan 1.1 and 1.3) - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features - {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; + vk::StructureChain + featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; // create a Device float queuePriority = 0.5f; @@ -418,7 +422,8 @@ class HelloTriangleApplication void createDescriptorSetLayout() { - vk::DescriptorSetLayoutBinding uboLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr); + vk::DescriptorSetLayoutBinding uboLayoutBinding{ + .binding = 0, .descriptorType = vk::DescriptorType::eUniformBuffer, .descriptorCount = 1, .stageFlags = vk::ShaderStageFlagBits::eVertex}; vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = 1, .pBindings = &uboLayoutBinding}; descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); } @@ -433,7 +438,10 @@ class HelloTriangleApplication auto bindingDescription = Vertex::getBindingDescription(); auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; @@ -441,7 +449,7 @@ class HelloTriangleApplication .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, + .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, .lineWidth = 1.0f}; @@ -457,7 +465,7 @@ class HelloTriangleApplication std::vector dynamicStates = {vk::DynamicState::eViewport, vk::DynamicState::eScissor}; vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); vk::StructureChain pipelineCreateInfoChain = { @@ -495,9 +503,8 @@ class HelloTriangleApplication throw std::runtime_error("failed to load texture image!"); } - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + auto [stagingBuffer, stagingBufferMemory] = + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); void *data = stagingBufferMemory.mapMemory(0, imageSize); memcpy(data, pixels, imageSize); @@ -505,31 +512,52 @@ class HelloTriangleApplication stbi_image_free(pixels); - createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + std::tie(textureImage, textureImageMemory) = createImage(texWidth, + texHeight, + vk::Format::eR8G8B8A8Srgb, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal); - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); + transitionImageLayout(commandBuffer, textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); + copyBufferToImage(commandBuffer, stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(commandBuffer, textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + endSingleTimeCommands(std::move(commandBuffer)); } - void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + std::pair createImage( + uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties) { - vk::ImageCreateInfo imageInfo{.imageType = vk::ImageType::e2D, .format = format, .extent = {width, height, 1}, .mipLevels = 1, .arrayLayers = 1, .samples = vk::SampleCountFlagBits::e1, .tiling = tiling, .usage = usage, .sharingMode = vk::SharingMode::eExclusive}; + vk::ImageCreateInfo imageInfo{.imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; - image = vk::raii::Image(device, imageInfo); + vk::raii::Image image = vk::raii::Image(device, imageInfo); vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; - imageMemory = vk::raii::DeviceMemory(device, allocInfo); + vk::raii::DeviceMemory imageMemory = vk::raii::DeviceMemory(device, allocInfo); image.bindMemory(imageMemory, 0); + + return {std::move(image), std::move(imageMemory)}; } - void transitionImageLayout(const vk::raii::Image &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) + void transitionImageLayout(vk::raii::CommandBuffer &commandBuffer, const vk::raii::Image &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { - auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{.oldLayout = oldLayout, .newLayout = newLayout, .image = image, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + vk::ImageMemoryBarrier barrier{.oldLayout = oldLayout, + .newLayout = newLayout, + .srcQueueFamilyIndex = vk::QueueFamilyIgnored, + .dstQueueFamilyIndex = vk::QueueFamilyIgnored, + .image = image, + .subresourceRange = {.aspectMask = vk::ImageAspectFlagBits::eColor, .levelCount = 1, .layerCount = 1}}; vk::PipelineStageFlags sourceStage; vk::PipelineStageFlags destinationStage; @@ -554,72 +582,81 @@ class HelloTriangleApplication { throw std::invalid_argument("unsupported layout transition!"); } - commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); - endSingleTimeCommands(*commandBuffer); + commandBuffer.pipelineBarrier(sourceStage, destinationStage, {}, {}, {}, barrier); } - void copyBufferToImage(const vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) + void copyBufferToImage(vk::raii::CommandBuffer &commandBuffer, const vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{.bufferOffset = 0, .bufferRowLength = 0, .bufferImageHeight = 0, .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, .imageOffset = {0, 0, 0}, .imageExtent = {width, height, 1}}; - commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); + vk::BufferImageCopy region{.bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {.aspectMask = vk::ImageAspectFlagBits::eColor, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + commandBuffer.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, region); } void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + auto [stagingBuffer, stagingBufferMemory] = + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); memcpy(dataStaging, vertices.data(), bufferSize); stagingBufferMemory.unmapMemory(); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + std::tie(vertexBuffer, vertexBufferMemory) = + createBuffer(bufferSize, vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal); copyBuffer(stagingBuffer, vertexBuffer, bufferSize); } + std::pair createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties) + { + vk::BufferCreateInfo bufferInfo{.size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive}; + vk::raii::Buffer buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + vk::raii::DeviceMemory bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(*bufferMemory, 0); + return {std::move(buffer), std::move(bufferMemory)}; + } + void createIndexBuffer() { vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + auto [stagingBuffer, stagingBufferMemory] = + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); void *data = stagingBufferMemory.mapMemory(0, bufferSize); memcpy(data, indices.data(), (size_t) bufferSize); stagingBufferMemory.unmapMemory(); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + std::tie(indexBuffer, indexBufferMemory) = + createBuffer(bufferSize, vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal); copyBuffer(stagingBuffer, indexBuffer, bufferSize); } void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + auto [buffer, bufferMem] = createBuffer( + bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); uniformBuffers.emplace_back(std::move(buffer)); uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory.back().mapMemory(0, bufferSize)); } } void createDescriptorPool() { - vk::DescriptorPoolSize poolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT); + vk::DescriptorPoolSize poolSize{.type = vk::DescriptorType::eUniformBuffer, .descriptorCount = MAX_FRAMES_IN_FLIGHT}; vk::DescriptorPoolCreateInfo poolInfo{.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, .maxSets = MAX_FRAMES_IN_FLIGHT, .poolSizeCount = 1, .pPoolSizes = &poolSize}; descriptorPool = vk::raii::DescriptorPool(device, poolInfo); } @@ -627,40 +664,37 @@ class HelloTriangleApplication void createDescriptorSets() { std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{.descriptorPool = descriptorPool, .descriptorSetCount = static_cast(layouts.size()), .pSetLayouts = layouts.data()}; + vk::DescriptorSetAllocateInfo allocInfo{.descriptorPool = descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; descriptorSets = device.allocateDescriptorSets(allocInfo); for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { vk::DescriptorBufferInfo bufferInfo{.buffer = uniformBuffers[i], .offset = 0, .range = sizeof(UniformBufferObject)}; - vk::WriteDescriptorSet descriptorWrite{.dstSet = descriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pBufferInfo = &bufferInfo}; + vk::WriteDescriptorSet descriptorWrite{.dstSet = descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}; device.updateDescriptorSets(descriptorWrite, {}); } } - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + vk::raii::CommandBuffer beginSingleTimeCommands() { - vk::BufferCreateInfo bufferInfo{.size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive}; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() - { - vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandBuffer = std::move(vk::raii::CommandBuffers(device, allocInfo).front()); vk::CommandBufferBeginInfo beginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; - commandBuffer->begin(beginInfo); + commandBuffer.begin(beginInfo); - return commandBuffer; + return std::move(commandBuffer); } - void endSingleTimeCommands(vk::raii::CommandBuffer &commandBuffer) + void endSingleTimeCommands(vk::raii::CommandBuffer &&commandBuffer) { commandBuffer.end(); @@ -671,13 +705,9 @@ class HelloTriangleApplication void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + vk::raii::CommandBuffer commandCopyBuffer = beginSingleTimeCommands(); commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); - queue.waitIdle(); + endSingleTimeCommands(std::move(commandCopyBuffer)); } uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) @@ -706,7 +736,8 @@ class HelloTriangleApplication { auto &commandBuffer = commandBuffers[frameIndex]; commandBuffer.begin({}); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + + // Before starting rendering, transition the swapchain image to vk::ImageLayout::eColorAttachmentOptimal transition_image_layout( imageIndex, vk::ImageLayout::eUndefined, @@ -733,11 +764,12 @@ class HelloTriangleApplication commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); + commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexTypeValue::value); commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[frameIndex], nullptr); - commandBuffer.drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffer.drawIndexed(static_cast(indices.size()), 1, 0, 0, 0); commandBuffer.endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC + + // After rendering, transition the swapchain image to vk::ImageLayout::ePresentSrcKHR transition_image_layout( imageIndex, vk::ImageLayout::eColorAttachmentOptimal, @@ -770,11 +802,11 @@ class HelloTriangleApplication .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = swapChainImages[imageIndex], .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1}}; + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; vk::DependencyInfo dependency_info = { .dependencyFlags = {}, .imageMemoryBarrierCount = 1, @@ -808,7 +840,8 @@ class HelloTriangleApplication UniformBufferObject ubo{}; ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj = + glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); ubo.proj[1][1] *= -1; memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); diff --git a/en/06_Texture_mapping/00_Images.adoc b/en/06_Texture_mapping/00_Images.adoc index 37c9b39b..223d9635 100644 --- a/en/06_Texture_mapping/00_Images.adoc +++ b/en/06_Texture_mapping/00_Images.adoc @@ -32,8 +32,8 @@ We've actually already seen some of these layouts when we specified the render p * `vk::ImageLayout::ePresentSrcKHR`: Optimal for presentation * `vk::ImageLayout::eColorAttachmentOptimal`: Optimal as attachment for writing colors from the fragment shader -* `vk::ImageLayout::eTransferSrcOptimal`: Optimal as source in a transfer operation, like `vkCmdCopyImageToBuffer` -* `vk::ImageLayout::eTransferDstOptimal`: Optimal as destination in a transfer operation, like `vkCmdCopyBufferToImage` +* `vk::ImageLayout::eTransferSrcOptimal`: Optimal as source in a transfer operation, like `vk::raii::CommandBuffer::copyImageToBuffer` +* `vk::ImageLayout::eTransferDstOptimal`: Optimal as destination in a transfer operation, like `vk::raii::CommandBuffer::copyBufferToImage` * `vk::ImageLayout::eShaderReadOnlyOptimal`: Optimal for sampling from a shader One of the most common ways to transition the layout of an image is a _pipeline barrier_. @@ -56,7 +56,8 @@ One code file needs to include the header with the `STB_IMAGE_IMPLEMENTATION` de [,c++] ---- -void initVulkan() { +void initVulkan() +{ ... createCommandPool(); createTextureImage(); @@ -66,8 +67,8 @@ void initVulkan() { ... -void createTextureImage() { - +void createTextureImage() +{ } ---- @@ -85,12 +86,14 @@ Loading an image with this library is really easy: [,c++] ---- -void createTextureImage() { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); +void createTextureImage() +{ + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); vk::DeviceSize imageSize = texWidth * texHeight * 4; - if (!pixels) { + if (!pixels) + { throw std::runtime_error("failed to load texture image!"); } } @@ -108,18 +111,13 @@ Next we need to upload the image to the GPU for optimal access during shader rea NOTE: Images should always reside in GPU memory. Leaving them in host only visible memory would require the GPU to read data via the PCI interface for each frame, which has a much smaller bandwidth than the GPU's memory. That would cause a big performance impact. Staging is the process of getting image data into the GPU's memory. It is not always required, as devices may offer a memory type that's both host visible and device local. It's possible to skip staging on such configurations. -Add variables for this temporary buffer to the `createTextureImage` function: - -[,c++] ----- -vk::raii::Buffer stagingBuffer({}); -vk::raii::DeviceMemory stagingBufferMemory({}); ----- - The buffer should be in host visible memory (`eHostVisible`) so that we can map it. It should also be host coherent (`eHostCoherent`), to ensure the data written to it is immediately available (and not cached in some way). And it should be usable as a transfer source ('eTransferSrc') so that we can copy it to an image later on: +The automatically typed return values are a vk::raii::Buffer and a vk::raii::DeviceMemory, respectively. [,c++] ---- +auto [stagingBuffer, stagingBufferMemory] = + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); ---- @@ -148,7 +146,7 @@ Add the following new class members: [,c++] ---- -vk::raii::Image textureImage = nullptr; +vk::raii::Image textureImage = nullptr; vk::raii::DeviceMemory textureImageMemory = nullptr; ---- @@ -156,9 +154,15 @@ The parameters for an image are specified in a `vk::ImageCreateInfo` struct: [,c++] ---- -vk::ImageCreateInfo imageInfo{ .imageType = vk::ImageType::e2D, .format = format, - .extent = {width, height, 1}, .mipLevels = 1, .arrayLayers = 1, .samples = vk::SampleCountFlagBits::e1, - .tiling = tiling, .usage = usage, .sharingMode = vk::SharingMode::eExclusive}; +vk::ImageCreateInfo imageInfo{.imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; ---- The image type, specified in the `imageType` field, tells Vulkan with what kind of coordinate system the texels in the image are going to be addressed. @@ -170,7 +174,7 @@ Our texture will not be an array and we won't be using mipmapping for now. Vulkan supports many possible image formats, but we should use the same format for the texels as the pixels in the buffer, otherwise the copy operation will fail. -The `tiling` file specifies how texels are arranged in memory. Vulkan supports two fundamentally different modes for this: +The `tiling` specifies how texels are arranged in memory. Vulkan supports two fundamentally different modes for this: * `vk::ImageTiling::eOptimal`: Texels are laid out in an implementation-dependent arrangement. This results in a more efficient memory access. * `vk::ImageTiling::eLinear`: Texels are laid out in memory in row-major order, possibly with some padding on each row. @@ -185,7 +189,7 @@ There are only two possible values for the `initialLayout` of an image: * `vk::ImageLayout::ePreinitialized`: Not usable by the GPU, but the first transition will preserve the texels. There are very few situations where it is necessary for the texels to be preserved during the first transition. -One example, however, would be if you wanted to use an linear tiled image as a staging image. +One example, however, would be if you wanted to use a linear tiled image as a staging image. In that case, you'd want to upload the texel data to it and then transition the image to be a transfer source without losing the data. In our case, however, we're first going to transition the image to be a transfer destination and then copy texel data to it from a buffer object, so we don't need this property and can safely use `vk::ImageLayout::eUndefined`. @@ -198,10 +202,6 @@ The image will only be used by one queue family: the one that supports graphics The `samples` flag is related to multisampling. This is only relevant for images that will be used as attachments, so stick to one sample. -There are some optional flags for images that are related to sparse images. -Sparse images are images where only certain regions are actually backed by memory. -If you were using a 3D texture for a voxel terrain, for example, then you could use this to avoid allocating memory to store large volumes of "air" values. -We won't be using it in this tutorial, so leave it to its default value of `0`. [,c++] ---- @@ -219,7 +219,7 @@ We will get back to this in the depth buffer chapter, where we'll implement such ---- vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; imageMemory = vk::raii::DeviceMemory(device, allocInfo); image.bindMemory(imageMemory, 0); ---- @@ -232,19 +232,28 @@ Create the function and move the image object creation and memory allocation to [,c++] ---- -void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ .imageType = vk::ImageType::e2D, .format = format, - .extent = {width, height, 1}, .mipLevels = 1, .arrayLayers = 1, - .samples = vk::SampleCountFlagBits::e1, .tiling = tiling, - .usage = usage, .sharingMode = vk::SharingMode::eExclusive }; - - image = vk::raii::Image(device, imageInfo); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) }; - imageMemory = vk::raii::DeviceMemory(device, allocInfo); - image.bindMemory(imageMemory, 0); +std::pair createImage( + uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties ) +{ + vk::ImageCreateInfo imageInfo{.imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + + vk::raii::Image image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + vk::raii::DeviceMemory imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(imageMemory, 0); + + return {std::move(image), std::move(imageMemory)}; } ---- @@ -254,28 +263,32 @@ The `createTextureImage` function can now be simplified to: [,c++] ---- -void createTextureImage() { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); - vk::DeviceSize imageSize = texWidth * texHeight * 4; +void createTextureImage() +{ + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + vk::DeviceSize imageSize = texWidth * texHeight * 4; - if (!pixels) { - throw std::runtime_error("failed to load texture image!"); - } + if (!pixels) + { + throw std::runtime_error("failed to load texture image!"); + } - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + auto [stagingBuffer, stagingBufferMemory] = + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, imageSize); - stagingBufferMemory.unmapMemory(); + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, imageSize); + stagingBufferMemory.unmapMemory(); - stbi_image_free(pixels); + stbi_image_free(pixels); - vk::raii::Image textureImageTemp({}); - vk::raii::DeviceMemory textureImageMemoryTemp({}); - createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImageTemp, textureImageMemoryTemp); + std::tie(textureImage, textureImageMemory) = createImage(texWidth, + texHeight, + vk::Format::eR8G8B8A8Srgb, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal); } ---- @@ -295,22 +308,24 @@ The function we're going to write now involves recording and executing a command [,c++] ---- -vk::raii::CommandBuffer beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); +vk::raii::CommandBuffer beginSingleTimeCommands() +{ + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandBuffer = std::move(vk::raii::CommandBuffers(device, allocInfo).front()); - vk::CommandBufferBeginInfo beginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }; - commandBuffer.begin(beginInfo); + vk::CommandBufferBeginInfo beginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer.begin(beginInfo); - return commandBuffer; + return std::move(commandBuffer); } -void endSingleTimeCommands(vk::raii::CommandBuffer& commandBuffer) { - commandBuffer.end(); +void endSingleTimeCommands(vk::raii::CommandBuffer &&commandBuffer) +{ + commandBuffer.end(); - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - graphicsQueue.submit(submitInfo, nullptr); - graphicsQueue.waitIdle(); + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); } ---- @@ -319,10 +334,11 @@ You can now simplify that function to: [,c++] ---- -void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::raii::CommandBuffer commandCopyBuffer = beginSingleTimeCommands(); - commandCopyBuffer.copyBuffer(srcBuffer, dstBuffer, vk::BufferCopy(0, 0, size)); - endSingleTimeCommands(commandCopyBuffer); +void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) +{ + vk::raii::CommandBuffer commandCopyBuffer = beginSingleTimeCommands(); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + endSingleTimeCommands(std::move(commandCopyBuffer)); } ---- @@ -331,10 +347,8 @@ Create a new function to handle layout transitions: [,c++] ---- -void transitionImageLayout(const vk::raii::Image& image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { - auto commandBuffer = beginSingleTimeCommands(); - - endSingleTimeCommands(commandBuffer); +void transitionImageLayout(vk::raii::CommandBuffer &commandBuffer, const vk::raii::Image &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) +{ } ---- @@ -344,40 +358,44 @@ There is an equivalent _buffer memory barrier_ to do this for buffers. [,c++] ---- -vk::ImageMemoryBarrier barrier{ .oldLayout = oldLayout, .newLayout = newLayout, .image = image, .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; +vk::ImageMemoryBarrier barrier{.oldLayout = oldLayout, + .newLayout = newLayout, + .srcQueueFamilyIndex = vk::QueueFamilyIgnored, + .dstQueueFamilyIndex = vk::QueueFamilyIgnored, + .image = image, + .subresourceRange = {.aspectMask = vk::ImageAspectFlagBits::eColor, .levelCount = 1, .layerCount = 1}}; ---- + `oldLayout` and `newLayout` specify the the layout transition. It is possible to use `vk::ImageLayout::eUndefined` as `oldLayout` if you don't care about the existing contents of the image. -If you are using the barrier to transfer queue family ownership, then `oldLayout` and `newLayout` fields should be the indices of the queue families. -They must be set to `VK_QUEUE_FAMILY_IGNORED` if you don't want to do this (not the default value!). +If you are using the barrier to transfer queue family ownership, then `srcQueueFamilyIndex` and `dstQueueFamilyIndex` fields should be the indices of the queue families. +They must be set to `vk::QueueFamilyIgnored` if you don't want to do this (not the default value!). The `image` and `subresourceRange` specify the image that is affected and the specific part of the image. Our image is not an array and does not have mipmapping levels, so only one level and layer are specified. Barriers are primarily used for synchronization purposes, so you must specify which types of operations that involve the resource must happen before the barrier, and which operations that involve the resource must wait on the barrier. -We need to do that despite already using `queue.waitIdle()` to manually synchronize. The right values depend on the old and new layout, so we'll get back to this once we've figured out which transitions we're going to use. [,c++] ---- -commandBuffer.pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); +commandBuffer.pipelineBarrier(sourceStage, destinationStage, {}, {}, {}, barrier); ---- All types of pipeline barriers are submitted using the same function. -The first parameter after the command buffer specifies in which pipeline stage the operations occur that should happen before the barrier. +The first parameter specifies in which pipeline stage the operations occur that should happen before the barrier. The second parameter specifies the pipeline stage in which operations will wait on the barrier. The pipeline stages that you are allowed to specify before and after the barrier depend on how you use the resource before and after the barrier. The allowed values are listed in https://docs.vulkan.org/spec/latest/chapters/synchronization.html#synchronization-access-types-supported[this table] of the specification. For example, if you're going to read from a uniform after the barrier, you would specify a usage of `vk::AccessFlagBits::eUniformRead` and the earliest shader that will read from the uniform as pipeline stage, for example `vk::PipelineStageFlagBits::eFragmentShader`. It would not make sense to specify a non-shader pipeline stage for this type of usage and the validation layers will warn you when you specify a pipeline stage that does not match the type of usage. -The third parameter is either `0` or `vk::DependencyFlagBits::eByRegion`. +The third parameter is either `{}` or `vk::DependencyFlagBits::eByRegion`. The latter turns the barrier into a per-region condition. That means that the implementation is allowed to already begin reading from the parts of a resource that were written so far, for example. -The last three pairs of parameter reference arrays of pipeline barriers of the three available types: memory barriers, buffer memory barriers, and image memory barriers like the one we're using here. -Note that we're not using the `VkFormat` parameter yet, but we'll be using that one for special transitions in the depth buffer chapter. +The last three parameters reference arrays of pipeline barriers of the three available types: memory barriers, buffer memory barriers, and image memory barriers like the one we're using here. == Copying buffer to image @@ -385,10 +403,8 @@ Before we get back to `createTextureImage`, we're going to write one more helper [,c++] ---- -void copyBufferToImage(const vk::raii::Buffer& buffer, vk::raii::Image& image, uint32_t width, uint32_t height) { - vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); - - endSingleTimeCommands(commandBuffer); +void copyBufferToImage(vk::raii::CommandBuffer &commandBuffer, const vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) +{ } ---- @@ -397,8 +413,12 @@ This happens through `vk::BufferImageCopy` structs: [,c++] ---- -vk::BufferImageCopy region{ .bufferOffset = 0, .bufferRowLength = 0, .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, .imageOffset = {0, 0, 0}, .imageExtent = {width, height, 1} }; +vk::BufferImageCopy region{.bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {.aspectMask = vk::ImageAspectFlagBits::eColor, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; ---- Most of these fields are self-explanatory. @@ -408,16 +428,14 @@ For example, you could have some padding bytes between rows of the image. Specifying `0` for both indicates that the pixels are simply tightly packed like they are in our case. The `imageSubresource`, `imageOffset` and `imageExtent` fields indicate to which part of the image we want to copy the pixels. -Buffer to image copy operations are enqueued using the `vkCmdCopyBufferToImage` function: +Buffer to image copy operations are enqueued using the `vk::raii::CommandBuffer::copyBufferToImage` function: [,c++] ---- -commandBuffer.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); -// Submit the buffer copy to the graphics queue -endSingleTimeCommands(*commandBuffer); +commandBuffer.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, region); ---- -The fourth parameter indicates which layout the image is currently using. +The third parameter indicates which layout the image is currently using. I'm assuming here that the image has already been transitioned to the layout that is optimal for copying pixels to. Right now we're only copying one chunk of pixels to the whole image, but it's possible to specify an array of `vk::BufferImageCopy` to perform many different copies from this buffer to the image in one operation. @@ -435,18 +453,21 @@ This is easy to do with the functions we just created: [,c++] ---- -transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); -copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); +vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); +transitionImageLayout(commandBuffer, textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); +copyBufferToImage(commandBuffer, stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); ---- The image was created with the `vk::ImageLayout::eUndefined` layout, so that one should be specified as old layout when transitioning `textureImage`. Remember that we can do this because we don't care about its contents before performing the copy operation. -To be able to start sampling from the texture image in the shader, we need one last transition to prepare it for shader access: +To be able to start sampling from the texture image in the shader, we need one last transition to prepare it for shader access. +Finally, the single-time command buffer can be ended. [,c++] ---- -transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); +transitionImageLayout(commandBuffer, textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); +endSingleTimeCommands(std::move(commandBuffer)); ---- == Transition barrier masks @@ -466,24 +487,27 @@ These rules are specified using the following access masks and pipeline stages: vk::PipelineStageFlags sourceStage; vk::PipelineStageFlags destinationStage; -if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; +if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) +{ + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; -} else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; -} else { - throw std::invalid_argument("unsupported layout transition!"); + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; } +else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) +{ + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; +} +else +{ + throw std::invalid_argument("unsupported layout transition!"); +} commandBuffer.pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); -endSingleTimeCommands(*commandBuffer); ---- As you can see in the aforementioned table, transfer writes must occur in the pipeline transfer stage. @@ -505,11 +529,6 @@ There is actually a special type of image layout that supports all operations, ` But unless using certain extensions, which we don't do in the tutorial, using the general layout might come with a performance penalty as it may disable certain optimizations on some GPUs. It is required for some special cases, like using an image as both input and output, or for reading an image after it has left the preinitialized layout. -All the helper functions that submit commands so far have been set up to execute synchronously by waiting for the queue to become idle. -For practical applications it is recommended to combine these operations in a single command buffer and execute them asynchronously for higher throughput, especially the transitions and copy in the `createTextureImage` function. -Try to experiment with this by creating a `setupCommandBuffer` that the helper functions record commands into, and add a `flushSetupCommands` to execute the commands that have been recorded so far. -It's best to do this after the texture mapping works to check if the texture resources are still set up correctly. - The image now contains the texture, but we still need a way to access it from the graphics pipeline. We'll work on that in the xref:./01_Image_view_and_sampler.adoc[next chapter].