From b6bf6facd1c090bc955763a937cca117fa87ead9 Mon Sep 17 00:00:00 2001 From: asuessenbach Date: Wed, 8 Apr 2026 12:08:03 +0200 Subject: [PATCH] Align docu in 03_Index_buffer.adoc and sources in 21_index_buffer.cpp --- attachments/21_index_buffer.cpp | 93 +++++++++++++---------- en/04_Vertex_buffers/03_Index_buffer.adoc | 48 ++++++------ 2 files changed, 77 insertions(+), 64 deletions(-) diff --git a/attachments/21_index_buffer.cpp b/attachments/21_index_buffer.cpp index e0edb023..957b735a 100644 --- a/attachments/21_index_buffer.cpp +++ b/attachments/21_index_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)}}}; } }; @@ -122,7 +121,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; } @@ -243,7 +242,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}; @@ -285,6 +284,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 @@ -323,12 +323,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; @@ -396,7 +400,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}; @@ -447,18 +454,30 @@ class HelloTriangleApplication commandPool = vk::raii::CommandPool(device, poolInfo); } + 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 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); } @@ -467,34 +486,24 @@ class HelloTriangleApplication { 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 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); - } - 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); @@ -527,7 +536,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, @@ -555,9 +565,10 @@ class HelloTriangleApplication commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexTypeValue::value); - 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, @@ -590,11 +601,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/03_Index_buffer.adoc b/en/04_Vertex_buffers/03_Index_buffer.adoc index f6b85e64..760cbfd0 100644 --- a/en/04_Vertex_buffers/03_Index_buffer.adoc +++ b/en/04_Vertex_buffers/03_Index_buffer.adoc @@ -48,48 +48,50 @@ const std::vector indices = { It is possible to use either `uint16_t` or `uint32_t` for your index buffer depending on the number of entries in `vertices`. We can stick to `uint16_t` for now because we're using less than 65535 unique vertices. -Just like the vertex data, the indices need to be uploaded into a `VkBuffer` for the GPU to be able to access them. +Just like the vertex data, the indices need to be uploaded into a `vk::raii::Buffer` for the GPU to be able to access them. Define two new class members to hold the resources for the index buffer: [,c++] ---- -vk::raii::Buffer vertexBuffer = nullptr; +vk::raii::Buffer vertexBuffer = nullptr; vk::raii::DeviceMemory vertexBufferMemory = nullptr; -vk::raii::Buffer indexBuffer = nullptr; -vk::raii::DeviceMemory indexBufferMemory = nullptr; +vk::raii::Buffer indexBuffer = nullptr; +vk::raii::DeviceMemory indexBufferMemory = nullptr; ---- The `createIndexBuffer` function that we'll add now is almost identical to `createVertexBuffer`: [,c++] ---- -void initVulkan() { +void initVulkan() +{ ... createVertexBuffer(); createIndexBuffer(); ... } -void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); +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(); + 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); + copyBuffer(stagingBuffer, indexBuffer, bufferSize); } ---- There are only two notable differences. The `bufferSize` is now equal to the number of indices times the size of the index type, either `uint16_t` or `uint32_t`. -The usage of the `indexBuffer` should be `VK_BUFFER_USAGE_INDEX_BUFFER_BIT` instead of `VK_BUFFER_USAGE_VERTEX_BUFFER_BIT`, which makes sense. +The usage of the `indexBuffer` should be `vk::BufferUsageFlagBits::eIndexBuffer` instead of `vk::BufferUsageFlagBits::eVertexBuffer`, which makes sense. Other than that, the process is exactly the same. We create a staging buffer to copy the contents of `indices` to and then copy it to the final device local index buffer. @@ -103,21 +105,21 @@ It's unfortunately not possible to use different indices for each vertex attribu [,c++] ---- commandBuffers[frameIndex].bindVertexBuffers(0, *vertexBuffer, {0}); -commandBuffers[frameIndex].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint16 ); +commandBuffers[frameIndex].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); ---- -An index buffer is bound with `vkCmdBindIndexBuffer` which has the index buffer, a byte offset into it, and the type of index data as parameters. -As mentioned before, the possible types are `VK_INDEX_TYPE_UINT16` and `VK_INDEX_TYPE_UINT32`. +An index buffer is bound with `vk::raii::CommandBuffer::bindIndexBuffer` which has the index buffer, a byte offset into it, and the type of index data as parameters. +As mentioned before, the possible types are `vk::IndexType::eUint16` and `vk::IndexType::eUint32`. Just binding an index buffer doesn't change anything yet, we also need to change the drawing command to tell Vulkan to use the index buffer. -Remove the `vkCmdDraw` line and replace it with `vkCmdDrawIndexed`: +Remove the `vk::raii::CommandBuffer::draw` line and replace it with `vk::raii::CommandBuffer::drawIndexed`: [,c++] ---- -commandBuffers[frameIndex].drawIndexed(indices.size(), 1, 0, 0, 0); +commandBuffer.drawIndexed(static_cast(indices.size()), 1, 0, 0, 0); ---- -A call to this function is very similar to `vkCmdDraw`. +A call to this function is very similar to `vk::raii::CommandBuffer::draw`. The first two parameters specify the number of indices and the number of instances. We're not using instancing, so just specify `1` instance. The number of indices represents the number of vertices that will be passed to the vertex shader. @@ -133,7 +135,7 @@ You now know how to save memory by reusing vertices with index buffers. This will become especially important in a future chapter where we're going to load complex 3D models. The previous chapter already mentioned that you should allocate multiple resources like buffers from a single memory allocation, but in fact you should go a step further. -https://developer.nvidia.com/vulkan-memory-management[Driver developers recommend] that you also store multiple buffers, like the vertex and index buffer, into a single `VkBuffer` and use offsets in commands like `vkCmdBindVertexBuffers`. +https://developer.nvidia.com/vulkan-memory-management[Driver developers recommend] that you also store multiple buffers, like the vertex and index buffer, into a single `vk::raii::Buffer` and use offsets in commands like `vk::raii::CommandBuffer::bindVertexBuffers`. The advantage is that your data is more cache friendly in that case, because it's closer together. It is even possible to reuse the same chunk of memory for multiple resources if they are not used during the same render operations, provided that their data is refreshed, of course. This is known as _aliasing_ and some Vulkan functions have explicit flags to specify that you want to do this.