From c95cc5efbd9283b39de276c4f59a85809a470213 Mon Sep 17 00:00:00 2001 From: asuessenbach Date: Tue, 7 Apr 2026 10:33:21 +0200 Subject: [PATCH] Align docu in 02_Staging_buffer.adoc with sources in 20_staging_buffer.cpp --- attachments/20_staging_buffer.cpp | 90 +++++++------ en/04_Vertex_buffers/02_Staging_buffer.adoc | 133 +++++++++----------- 2 files changed, 110 insertions(+), 113 deletions(-) diff --git a/attachments/20_staging_buffer.cpp b/attachments/20_staging_buffer.cpp index 3ce7fb99..64980f7f 100644 --- a/attachments/20_staging_buffer.cpp +++ b/attachments/20_staging_buffer.cpp @@ -40,14 +40,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)}}}; } }; @@ -116,7 +115,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; } @@ -236,7 +235,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}; @@ -278,6 +277,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 @@ -316,12 +316,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; @@ -389,7 +393,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}; @@ -440,36 +447,39 @@ class HelloTriangleApplication commandPool = vk::raii::CommandPool(device, poolInfo); } - void createVertexBuffer() + std::pair createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties) { - vk::BufferCreateInfo stagingInfo{.size = sizeof(vertices[0]) * vertices.size(), .usage = vk::BufferUsageFlagBits::eTransferSrc, .sharingMode = vk::SharingMode::eExclusive}; - vk::raii::Buffer stagingBuffer(device, stagingInfo); - vk::MemoryRequirements memRequirementsStaging = stagingBuffer.getMemoryRequirements(); - vk::MemoryAllocateInfo memoryAllocateInfoStaging{.allocationSize = memRequirementsStaging.size, .memoryTypeIndex = findMemoryType(memRequirementsStaging.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent)}; - vk::raii::DeviceMemory stagingBufferMemory(device, memoryAllocateInfoStaging); + 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)}; + } - stagingBuffer.bindMemory(stagingBufferMemory, 0); - void *dataStaging = stagingBufferMemory.mapMemory(0, stagingInfo.size); - memcpy(dataStaging, vertices.data(), stagingInfo.size); - stagingBufferMemory.unmapMemory(); + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::BufferCreateInfo bufferInfo{.size = sizeof(vertices[0]) * vertices.size(), .usage = vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, .sharingMode = vk::SharingMode::eExclusive}; - vertexBuffer = vk::raii::Buffer(device, bufferInfo); + auto [stagingBuffer, stagingBufferMemory] = + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); - vk::MemoryRequirements memRequirements = vertexBuffer.getMemoryRequirements(); - vk::MemoryAllocateInfo memoryAllocateInfo{.allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal)}; - vertexBufferMemory = vk::raii::DeviceMemory(device, memoryAllocateInfo); + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); - vertexBuffer.bindMemory(*vertexBufferMemory, 0); + std::tie(vertexBuffer, vertexBufferMemory) = + createBuffer(bufferSize, vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal); - copyBuffer(stagingBuffer, vertexBuffer, stagingInfo.size); + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); } 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}); + commandCopyBuffer.begin({.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy(0, 0, size)); commandCopyBuffer.end(); queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); @@ -502,7 +512,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, @@ -529,9 +540,10 @@ 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.draw(3, 1, 0, 0); + commandBuffer.draw(static_cast(vertices.size()), 1, 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, @@ -564,11 +576,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, diff --git a/en/04_Vertex_buffers/02_Staging_buffer.adoc b/en/04_Vertex_buffers/02_Staging_buffer.adoc index e9dd93bc..c1c04d15 100644 --- a/en/04_Vertex_buffers/02_Staging_buffer.adoc +++ b/en/04_Vertex_buffers/02_Staging_buffer.adoc @@ -5,25 +5,25 @@ == Introduction The vertex buffer we have right now works correctly, but the memory type that allows us to access it from the CPU may not be the most optimal memory type for the graphics card itself to read from. -The most optimal memory has the `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT` flag and is usually not accessible by the CPU on dedicated graphics cards. +The most optimal memory has the `vk::MemoryPropertyFlagBits::eDeviceLocal` flag and is usually not accessible by the CPU on dedicated graphics cards. In this chapter, we're going to create two vertex buffers. One _staging buffer_ in CPU accessible memory to upload the data from the vertex array to, and the final vertex buffer in device local memory. We'll then use a buffer copy command to move the data from the staging buffer to the actual vertex buffer. == Transfer queue -The buffer copy command requires a queue family that supports transfer operations, which is indicated using `VK_QUEUE_TRANSFER_BIT`. -The good news is that any queue family with `VK_QUEUE_GRAPHICS_BIT` or `VK_QUEUE_COMPUTE_BIT` capabilities already implicitly support `VK_QUEUE_TRANSFER_BIT` operations. +The buffer copy command requires a queue family that supports transfer operations, which is indicated using `vk::QueueFlagBits::eTransfer`. +The good news is that any queue family with `vk::QueueFlagBits::eGraphics` or `vk::QueueFlagBits::eCompute` capabilities already implicitly support `vk::QueueFlagBits::eTransfer` operations. The implementation is not required to explicitly list it in `queueFlags` in those cases. If you like a challenge, then you can still try to use a different queue family specifically for transfer operations. It will require you to make the following modifications to your program: -* Modify `QueueFamilyIndices` and `findQueueFamilies` to explicitly look for a queue family with the `VK_QUEUE_TRANSFER_BIT` bit, but not the `VK_QUEUE_GRAPHICS_BIT`. -* Modify `createLogicalDevice` to request a handle to the transfer queue +* Introduce an `uint32_t transferQueueIndex` in addition to the `uint32_t queueIndex` (which maybe should be renamed to `graphicsQueueIndex` now), and explicitly look for a queue family with the `vk::QueueFlagBits::eTransfer` bit, but not the `vk::QueueFlagBits::eGraphics`. +* Modify `createLogicalDevice` to request a handle to a `vk::raii::Queue transferQueue' (and maybe rename the `queue` to `graphicsQueue`) * Create a second command pool for command buffers that are submitted on the transfer queue family -* Change the `sharingMode` of resources to be `VK_SHARING_MODE_CONCURRENT` and specify both the graphics and transfer queue families -* Submit any transfer commands like `vkCmdCopyBuffer` (which we'll be using in this chapter) to the transfer queue instead of the graphics queue +* Change the `sharingMode` of resources to be `vk::SharingMode::eConcurrent` and specify both the graphics and transfer queue families +* Submit any transfer commands like `vk::raii::CommandBuffer::copyBuffer` (which we'll be using in this chapter) to the transfer queue instead of the graphics queue It's a bit of work, but it'll teach you a lot about how resources are shared between queue families. @@ -34,29 +34,35 @@ Create a new function `createBuffer` and move the code in `createVertexBuffer` ( [,c++] ---- -void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - 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::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)}; } ---- Make sure to add parameters for the buffer size, memory properties and usage so that we can use this function to create many different types of buffers. -The last two parameters are output variables to write the handles to. +This function returns both, the generated buffer and the device memory bound to it as a `std::pair`. As `vk::raii`-handles can't be copied, you have to move them out of this function. You can now remove the buffer creation and memory allocation code from `createVertexBuffer` and just call `createBuffer` instead: [,c++] ---- -void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, vertexBuffer, vertexBufferMemory); - void* data = vertexBufferMemory.mapMemory(0, bufferSize); - memcpy(data, vertices.data(), (size_t) bufferSize); - vertexBufferMemory.unmapMemory(); +void createVertexBuffer() +{ + 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(); } ---- @@ -68,43 +74,31 @@ We're now going to change `createVertexBuffer` to only use a host visible buffer [,c++] ---- -void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - - vk::BufferCreateInfo stagingInfo{ .size = bufferSize, .usage = vk::BufferUsageFlagBits::eTransferSrc, .sharingMode = vk::SharingMode::eExclusive }; - vk::raii::Buffer stagingBuffer(device, stagingInfo); - vk::MemoryRequirements memRequirementsStaging = stagingBuffer.getMemoryRequirements(); - vk::MemoryAllocateInfo memoryAllocateInfoStaging{ .allocationSize = memRequirementsStaging.size, .memoryTypeIndex = findMemoryType(memRequirementsStaging.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) }; - vk::raii::DeviceMemory stagingBufferMemory(device, memoryAllocateInfoStaging); - - stagingBuffer.bindMemory(stagingBufferMemory, 0); - void* dataStaging = stagingBufferMemory.mapMemory(0, stagingInfo.size); - memcpy(dataStaging, vertices.data(), stagingInfo.size); - stagingBufferMemory.unmapMemory(); +void createVertexBuffer() +{ + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::BufferCreateInfo bufferInfo{ .size = bufferSize, .usage = vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, .sharingMode = vk::SharingMode::eExclusive }; - vertexBuffer = vk::raii::Buffer(device, bufferInfo); + auto [stagingBuffer, stagingBufferMemory] = + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); - vk::MemoryRequirements memRequirements = vertexBuffer.getMemoryRequirements(); - vk::MemoryAllocateInfo memoryAllocateInfo{ .allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal) }; - vertexBufferMemory = vk::raii::DeviceMemory( device, memoryAllocateInfo ); + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); - vertexBuffer.bindMemory( *vertexBufferMemory, 0 ); + std::tie(vertexBuffer, vertexBufferMemory) = + createBuffer(bufferSize, vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal); - copyBuffer(stagingBuffer, vertexBuffer, stagingInfo.size); + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); } ---- We're now using a new `stagingBuffer` with `stagingBufferMemory` for mapping and copying the vertex data. In this chapter, we're going to use two new buffer usage flags: -Note, we have to create a temporary pointer to a new vk::raii::Buffer object -because the vk::raii::Buffer has the constructor deleted and thus doesn't -play well with std::make_unique, this is just a trick to get it to work. -* `VK_BUFFER_USAGE_TRANSFER_SRC_BIT`: Buffer can be used as source in a memory transfer operation. -* `VK_BUFFER_USAGE_TRANSFER_DST_BIT`: Buffer can be used as destination in a memory transfer operation. +* `vk::BufferUsageFlagBits::eTransferSrc`: Buffer can be used as source in a memory transfer operation. +* `vk::BufferUsageFlagBits::eTransferDst`: Buffer can be used as destination in a memory transfer operation. -The `vertexBuffer` is now allocated from a memory type that is device local, which generally means that we're not able to use `vkMapMemory`. +The `vertexBuffer` is now allocated from a memory type that is device local, which generally means that we're not able to use `vk::raii::DeviceMemory::mapMemory`. However, we can copy data from the `stagingBuffer` to the `vertexBuffer`. We have to indicate that we intend to do that by specifying the transfer source flag for the `stagingBuffer` and the transfer destination flag for the `vertexBuffer`, along with the vertex buffer usage flag. @@ -112,21 +106,22 @@ We're now going to write a function to copy the contents from one buffer to anot [,c++] ---- -void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - +void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) +{ } ---- Memory transfer operations are executed using command buffers, just like drawing commands. Therefore we must first allocate a temporary command buffer. You may wish to create a separate command pool for these kinds of short-lived buffers, because the implementation may be able to apply memory allocation optimizations. -You should use the `VK_COMMAND_POOL_CREATE_TRANSIENT_BIT` flag during command pool generation in that case. +You should use the `vk::CommandPoolCreateFlagBits::eTransient` flag during command pool generation in that case. [,c++] ---- -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()); +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()); } ---- @@ -134,21 +129,21 @@ And immediately start recording the command buffer: [,c++] ---- -commandCopyBuffer.begin(vk::CommandBufferBeginInfo { .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); +commandCopyBuffer.begin({.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); ---- We're only going to use the command buffer once and wait with returning from the function until the copy operation has finished executing. -It's good practice to tell the driver about our intent using `VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT`. +It's good practice to tell the driver about our intent using `vk::CommandBufferUsageFlagBits::eOneTimeSubmit`. [,c++] ---- -commandCopyBuffer.copyBuffer(srcBuffer, dstBuffer, vk::BufferCopy(0, 0, size)); +commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy(0, 0, size)); ---- -Contents of buffers are transferred using the `vkCmdCopyBuffer` command. +Contents of buffers are transferred using the `vk:raii::CommandBuffer::copyBuffer` command. It takes the source and destination buffers as arguments, and an array of regions to copy. -The regions are defined in `VkBufferCopy` structs and consist of a source buffer offset, destination buffer offset and size. -It is not possible to specify `VK_WHOLE_SIZE` here, unlike the `vkMapMemory` command. +The regions are defined in `vk::BufferCopy` structs and consist of a source buffer offset, destination buffer offset and size. +It is not possible to specify `vk::WholeSize` here, unlike the `vk::raii::DeviceMemory::mapMemory` command. [,c++] ---- @@ -160,14 +155,14 @@ Now execute the command buffer to complete the transfer: [,c++] ---- -graphicsQueue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); -graphicsQueue.waitIdle(); +queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); +queue.waitIdle(); ---- Unlike the draw commands, there are no events we need to wait on this time. We just want to execute the transfer on the buffers immediately. There are again two possible ways to wait on this transfer to complete. -We could use a fence and wait with `vkWaitForFences`, or simply wait for the transfer queue to become idle with `vkQueueWaitIdle`. +We could use a fence and wait with `vk::raii::Device::waitForFences`, or simply wait for the transfer queue to become idle with `vk::raii::Queue::waitIdle`. A fence would allow you to schedule multiple transfers simultaneously and wait for all of them complete, instead of executing one at a time. That may give the driver more opportunities to optimize. @@ -175,20 +170,10 @@ We can now call `copyBuffer` from the `createVertexBuffer` function to move the [,c++] ---- -vk::BufferCreateInfo bufferInfo{ .size = bufferSize, .usage = vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, .sharingMode = vk::SharingMode::eExclusive }; -vertexBuffer = vk::raii::Buffer(device, bufferInfo); - -vk::MemoryRequirements memRequirements = vertexBuffer.getMemoryRequirements(); -vk::MemoryAllocateInfo memoryAllocateInfo{ .allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal) }; -vertexBufferMemory = vk::raii::DeviceMemory( device, memoryAllocateInfo ); - -vertexBuffer.bindMemory( *vertexBufferMemory, 0 ); - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); ---- -After copying the data from the staging buffer to the device buffer, the RAII - buffer object will clean itself up and free the memory. +After copying the data from the staging buffer to the device buffer, the RAII buffer object will clean itself up and free the memory. Run your program to verify that you're seeing the familiar triangle again. The improvement may not be visible right now, but its vertex data is now being loaded from high performance memory. @@ -196,7 +181,7 @@ This will matter when we're going to start rendering more complex geometry. == Conclusion -It should be noted that in a real world application, you're not supposed to actually call `vkAllocateMemory` for every individual buffer. +It should be noted that in a real world application, you're not supposed to actually call `vk::raii::Device::allocateMemory` for every individual buffer. The maximum number of simultaneous memory allocations is limited by the `maxMemoryAllocationCount` physical device limit, which may be as low as `4096` even on high end hardware like an NVIDIA GTX 1080. The right way to allocate memory for a large number of objects at the same time is to create a custom allocator that splits up a single allocation among many different objects by using the `offset` parameters that we've seen in many functions.