diff --git a/attachments/19_vertex_buffer.cpp b/attachments/19_vertex_buffer.cpp index 01bb993e..77721ad6 100644 --- a/attachments/19_vertex_buffer.cpp +++ b/attachments/19_vertex_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)}}}; } }; @@ -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}; @@ -442,11 +449,15 @@ class HelloTriangleApplication void createVertexBuffer() { - vk::BufferCreateInfo bufferInfo{.size = sizeof(vertices[0]) * vertices.size(), .usage = vk::BufferUsageFlagBits::eVertexBuffer, .sharingMode = vk::SharingMode::eExclusive}; + vk::BufferCreateInfo bufferInfo{.size = sizeof(vertices[0]) * vertices.size(), + .usage = vk::BufferUsageFlagBits::eVertexBuffer, + .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::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent)}; + vk::MemoryAllocateInfo memoryAllocateInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent)}; vertexBufferMemory = vk::raii::DeviceMemory(device, memoryAllocateInfo); vertexBuffer.bindMemory(*vertexBufferMemory, 0); @@ -482,7 +493,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, @@ -509,9 +521,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, diff --git a/en/04_Vertex_buffers/00_Vertex_input_description.adoc b/en/04_Vertex_buffers/00_Vertex_input_description.adoc index 1758ad9b..9f298949 100644 --- a/en/04_Vertex_buffers/00_Vertex_input_description.adoc +++ b/en/04_Vertex_buffers/00_Vertex_input_description.adoc @@ -93,8 +93,9 @@ struct Vertex { glm::vec2 pos; glm::vec3 color; - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; + static vk::VertexInputBindingDescription getBindingDescription() + { + return {.binding = 0, .stride = sizeof(Vertex), .inputRate = vk::VertexInputRate::eVertex}; } }; ---- @@ -122,11 +123,11 @@ We're going to add another helper function to `Vertex` to fill in these structs. ... -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) ) - }; + static std::array getAttributeDescriptions() + { + 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)}}}; + } } ---- @@ -134,9 +135,9 @@ As the function prototype indicates, there are going to be two of these structur An attribute description struct describes how to extract a vertex attribute from a chunk of vertex data originating from a binding description. We have two attributes, position and color, so we need two attribute description structs. -The `binding` parameter tells Vulkan from which binding the per-vertex data comes. The `location` parameter references the `location` directive of the input in the vertex shader. The input in the vertex shader with location `0` is the position, which has two 32-bit float components. +The `binding` parameter tells Vulkan from which binding the per-vertex data comes. The `format` parameter describes the type of data for the attribute. A bit confusingly, the formats are specified using the same enumeration as color formats. @@ -170,12 +171,12 @@ Find the `vertexInputInfo` struct and modify it to reference the two description [,c++] ---- -auto bindingDescription = Vertex::getBindingDescription(); -auto attributeDescriptions = Vertex::getAttributeDescriptions(); -vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast( attributeDescriptions.size() ), - .pVertexAttributeDescriptions = attributeDescriptions.data() }; +auto bindingDescription = Vertex::getBindingDescription(); +auto attributeDescriptions = Vertex::getAttributeDescriptions(); +vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; ---- The pipeline is now ready to accept vertex data in the format of the `vertices` container and pass it on to our vertex shader. diff --git a/en/04_Vertex_buffers/01_Vertex_buffer_creation.adoc b/en/04_Vertex_buffers/01_Vertex_buffer_creation.adoc index c26f4e29..7fc47f1b 100644 --- a/en/04_Vertex_buffers/01_Vertex_buffer_creation.adoc +++ b/en/04_Vertex_buffers/01_Vertex_buffer_creation.adoc @@ -15,7 +15,8 @@ Create a new function `createVertexBuffer` and call it from `initVulkan` right b [,c++] ---- -void initVulkan() { +void initVulkan() +{ createInstance(); setupDebugMessenger(); createSurface(); @@ -32,16 +33,18 @@ void initVulkan() { ... -void createVertexBuffer() { - +void createVertexBuffer() +{ } ---- -Creating a buffer requires us to fill a `VkBufferCreateInfo` structure. +Creating a buffer requires us to fill a `vk::BufferCreateInfo` structure. [,c++] ---- -vk::BufferCreateInfo bufferInfo{ .size = sizeof(vertices[0]) * vertices.size(), .usage = vk::BufferUsageFlagBits::eVertexBuffer, .sharingMode = vk::SharingMode::eExclusive }; +vk::BufferCreateInfo bufferInfo{.size = sizeof(vertices[0]) * vertices.size(), + .usage = vk::BufferUsageFlagBits::eVertexBuffer, + .sharingMode = vk::SharingMode::eExclusive}; ---- The `size` field specifies the size of the buffer in bytes. Calculating @@ -58,7 +61,7 @@ The buffer will only be used from the graphics queue, so we can stick to exclusi There is also a `flags` field, which is used to configure sparse buffer memory. It's not relevant right now, so we'll leave it at the default value of `0`. -We can now create the buffer with `vkCreateBuffer`. +We can now create the buffer with `vk::raii::Buffer` constructor. Define a class member to hold the buffer handle and call it `vertexBuffer`. [,c++] @@ -68,7 +71,9 @@ vk::raii::Buffer vertexBuffer = nullptr; ... void createVertexBuffer() { - vk::BufferCreateInfo bufferInfo{ .size = sizeof(vertices[0]) * vertices.size(), .usage = vk::BufferUsageFlagBits::eVertexBuffer, .sharingMode = vk::SharingMode::eExclusive }; + vk::BufferCreateInfo bufferInfo{.size = sizeof(vertices[0]) * vertices.size(), + .usage = vk::BufferUsageFlagBits::eVertexBuffer, + .sharingMode = vk::SharingMode::eExclusive}; vertexBuffer = vk::raii::Buffer(device, bufferInfo); } ---- @@ -79,14 +84,14 @@ The buffer should be available for use in rendering commands until the end of == Memory requirements The buffer has been created, but it doesn't have any memory assigned to it yet. -The first step of allocating memory for the buffer is to query its memory requirements using the aptly named `vkGetBufferMemoryRequirements` function. +The first step of allocating memory for the buffer is to query its memory requirements using the aptly named `vk::raii::Buffer::getMemoryRequirements` function. [,c++] ---- vk::MemoryRequirements memRequirements = vertexBuffer.getMemoryRequirements(); ---- -The `VkMemoryRequirements` struct has three fields: +The `vk::MemoryRequirements` struct has three fields: * `size`: The size of the required memory in bytes may differ from `bufferInfo.size`. * `alignment`: The offset in bytes where the buffer begins in the allocated region of memory, depends on `bufferInfo.usage` and `bufferInfo.flags`. @@ -104,14 +109,14 @@ uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) } ---- -First we need to query info about the available types of memory using `vkGetPhysicalDeviceMemoryProperties`. +First we need to query info about the available types of memory using `vk::raii::PhysicalDevice::getMemoryProperties`. [,c++] ---- vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); ---- -The `VkPhysicalDeviceMemoryProperties` structure has two arrays `memoryTypes` and `memoryHeaps`. +The `vk::PhysicalDeviceMemoryProperties` structure has two arrays `memoryTypes` and `memoryHeaps`. Memory heaps are distinct memory resources like dedicated VRAM and swap space in RAM for when VRAM runs out. The different types of memory exist within these heaps. Right now we'll only concern ourselves with the type of memory and not the heap it comes from, but you can imagine that this can affect performance. @@ -120,8 +125,10 @@ Let's first find a memory type that is suitable for the buffer itself: [,c++] ---- -for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i))) { +for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) +{ + if ((typeFilter & (1 << i))) + { return i; } } @@ -134,18 +141,20 @@ That means that we can find the index of a suitable memory type by simply iterat However, we're not just interested in a memory type that is suitable for the vertex buffer. We also need to be able to write our vertex data to that memory. -The `memoryTypes` array consists of `VkMemoryType` structs that specify the +The `memoryTypes` array consists of `vk::MemoryType` structs that specify the heap and properties of each memory type. The properties define special features of the memory, like being able to map it so we can write to it from the CPU. -This property is indicated with `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`, but we also need to use the `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` property. +This property is indicated with `vk::MemoryPropertyFlagBits::eHostVisible`, but we also need to use the `vk::MemoryPropertyFlagBits::eHostCoherent` property. We'll see why when we map the memory. We can now modify the loop to also check for the support of this property: [,c++] ---- -for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { +for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) +{ + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { return i; } } @@ -156,27 +165,29 @@ If there is a memory type suitable for the buffer that also has all the properti == Memory allocation -We now have a way to determine the right memory type, so we can actually allocate the memory by filling in the `VkMemoryAllocateInfo` structure. +We now have a way to determine the right memory type, so we can actually allocate the memory by filling in the `vk::MemoryAllocateInfo` structure. [,c++] ---- -vk::MemoryAllocateInfo memoryAllocateInfo{ .allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) }; +vk::MemoryAllocateInfo memoryAllocateInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent)}; ---- Memory allocation is now as simple as specifying the size and type, both of which are derived from the memory requirements of the vertex buffer and the desired property. -Create a class member to store the handle to the memory and allocate it with `vkAllocateMemory`. +Create a class member to store the handle to the memory and allocate it with the `vk::raii::DeviceMemory` constructor. [,c++] ---- -vk::raii::Buffer vertexBuffer = nullptr; +vk::raii::Buffer vertexBuffer = nullptr; vk::raii::DeviceMemory vertexBufferMemory = nullptr; ... -vertexBufferMemory = vk::raii::DeviceMemory( device, memoryAllocateInfo ); +vertexBufferMemory = vk::raii::DeviceMemory(device, memoryAllocateInfo); ---- -If memory allocation was successful, then we can now associate this memory with the buffer using `vkBindBufferMemory`: +If memory allocation was successful, then we can now associate this memory with the buffer using `vk::raii::Buffer::bindBufferMemory`: [,c++] ---- @@ -184,13 +195,13 @@ vertexBuffer.bindMemory( *vertexBufferMemory, 0 ); ---- The first parameter is self-explanatory, and the second parameter is the offset within the region of memory. -Since this memory is allocated specifically for this the vertex buffer, the offset is simply `0`. +Since this memory is allocated specifically for this vertex buffer, the offset is simply `0`. If the offset is non-zero, then it is required to be divisible by `memRequirements.alignment`. == Filling the vertex buffer It is now time to copy the vertex data to the buffer. -This is done by https://en.wikipedia.org/wiki/Memory-mapped_I/O[mapping the buffer memory] into CPU accessible memory with `vkMapMemory`. +This is done by https://en.wikipedia.org/wiki/Memory-mapped_I/O[mapping the buffer memory] into CPU accessible memory with `vk::raii::DeviceMemory::mapMemory`. [,c++] ---- @@ -207,19 +218,19 @@ memcpy(data, vertices.data(), bufferInfo.size); vertexBufferMemory.unmapMemory(); ---- -You can now simply `memcpy` the vertex data to the mapped memory and unmap it again using `vkUnmapMemory`. +You can now simply `memcpy` the vertex data to the mapped memory and unmap it again using `vk::raii::DeviceMemory::unmapMemory`. Unfortunately, the driver may not immediately copy the data into the buffer memory, for example, because of caching. It is also possible that writes to the buffer are not visible in the mapped memory yet. There are two ways to deal with that problem: -* Use a memory heap that is host coherent, indicated with `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` -* Call `vkFlushMappedMemoryRanges` after writing to the mapped memory, and call `vkInvalidateMappedMemoryRanges` before reading from the mapped memory +* Use a memory heap that is host coherent, indicated with `vk::MemoryPropertyFlagBits::eHostCoherent` +* Call `vk::raii::Device::flushMappedMemoryRanges` after writing to the mapped memory, and call `vk::raii::Device::invalidateMappedMemoryRanges` before reading from the mapped memory We went for the first approach, which ensures that the mapped memory always matches the contents of the allocated memory. Do keep in mind that this may lead to slightly worse performance than explicit flushing, but we'll see why that doesn't matter in the next chapter. Flushing memory ranges or using a coherent memory heap means that the driver will be aware of our writings to the buffer, but it doesn't mean that they are actually visible on the GPU yet. -The transfer of data to the GPU is an operation that happens in the background, and the specification simply https://docs.vulkan.org/spec/latest/chapters/synchronization.html#synchronization-submission-host-writes[tells us] that it is guaranteed to be complete as of the next call to `vkQueueSubmit`. +The transfer of data to the GPU is an operation that happens in the background, and the specification simply https://docs.vulkan.org/spec/latest/chapters/synchronization.html#synchronization-submission-host-writes[tells us] that it is guaranteed to be complete as of the next call to `vk::raii::Queue::submit`. == Binding the vertex buffer @@ -228,17 +239,18 @@ We're going to extend the `recordCommandBuffer` function to do that. [,c++] ---- -commandBuffers[frameIndex].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); +commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); -commandBuffers[frameIndex].bindVertexBuffers(0, *vertexBuffer, {0}); +commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); -commandBuffers[frameIndex].draw(3, 1, 0, 0); +commandBuffer.draw(static_cast(vertices.size()), 1, 0, 0); ---- -The `vkCmdBindVertexBuffers` function is used to bind vertex buffers to bindings, like the one we set up in the previous chapter. -The first two parameters, besides the command buffer, specify the offset and number of bindings we're going to specify vertex buffers for. -The last two parameters specify the array of vertex buffers to bind and the byte offsets to start reading vertex data from. -You should also change the call to `vkCmdDraw` to pass the number of vertices in the buffer as opposed to the hardcoded number `3`. +The `vk::raii::CommandBuffer::bindVertexBuffers` function is used to bind vertex buffers to bindings, like the one we set up in the previous chapter. +The first parameter is the offset into the bindings. +The second parameter is an array of buffers to bind. +And the last parameter is an array of the same size of byte offsets to start reading vertex data from. +You should also change the call to `vk::CommandBuffer::draw` to pass the number of vertices in the buffer as opposed to the hardcoded number `3`. Now run the program and you should see the familiar triangle again: