We're drawing a rectangle now, optimized using an index buffer!

parent 22ef97bf
......@@ -171,9 +171,16 @@ struct Vertex {
// Interleaved - each Vertex struct entry now has { vec2 pos, vec3 color }
const std::vector<Vertex> vertices = {
{{0.0f, -0.5f}, {1.0f, 1.0f, 1.0f}},
{{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}},
{{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}
{{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}},
{{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}},
{{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}},
{{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}}
};
// Represents the contents of the index buffer.
// Index buffers let us reuse vertex data without redundancy - like a vert reused twice in the corners of a rectangle drawn with triangles.
const std::vector<uint32_t> indices = { //Can use uint16_t as well depending on # of entries.
0, 1, 2, 2, 3, 0
};
......@@ -215,8 +222,12 @@ class HelloTriangleApplication {
VkSemaphore imageAvailableSemaphore; //Signals that an image has been acquired.
VkSemaphore renderFinishedSemaphore; //Signals that the image has been rendered and is ready for presentation.
//It's reccommended to store multiple buffers, like index/vertex, into a single VkBuffer, and use offsets to access.
// That makes data more cache-friendly, and one can even reuse the same memory chunk if not used during the same render ops - called "aliasing".
VkBuffer vertexBuffer; //The buffer that holds vertex data, which we can access.
VkDeviceMemory vertexBufferMemory; //The handle to the memory where the vertex buffer is actually stored (accessed from the vert buffer).
VkBuffer indexBuffer; //The buffer holding indexes to the vertex data, so we can access vert data with less redundancy.
VkDeviceMemory indexBufferMemory;
static void onWindowResized(GLFWwindow* window, int width, int height) {
//Handles GLFW resize events.
......@@ -1347,14 +1358,18 @@ class HelloTriangleApplication {
// Buffer to record to, graphics/compute pipeline, then the pipeline.
vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
//GET/DRAW THE TRIANGLE! :P
//GET/DRAW THE TRIANGLE(s) (we're actually drawing a rectangle as of writing)! :P
VkBuffer vertexBuffers[] = {vertexBuffer};
VkDeviceSize offsets[] = {0};
vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); //Bind the vertex buffers to bindings.
vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT32); //We can only bind one index buffer for use when drawing.
// /\ Fields: command buffer, offset, # of bindings to specify vert buffers for, array of vertex buffers to bind, byte offsets to read vertex data from.
//Buffer to record to, vertexCount, instanceCount (1 if not doing that), firstVertex (vertex buffer offset), firstInstance (instance offset).
vkCmdDraw(commandBuffers[i], static_cast<uint32_t>(vertices.size()), 1, 0, 0); //vertexCount is now dynamic to vertices.
//Buffer to record to, indicesCount, instanceCount (1 if not doing that), firstIndex (index buffer offset), indexOffset (offset to add to the indices), firstInstance (instance offset).
vkCmdDrawIndexed(commandBuffers[i], static_cast<uint32_t>(indices.size()), 1, 0, 0, 0); //vertexCount is now dynamic to vertices.
// /\ The "Indexed" part lets us use the index buffer.
//End the render pass.
vkCmdEndRenderPass(commandBuffers[i]);
......@@ -1436,6 +1451,9 @@ class HelloTriangleApplication {
allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
// Use a memory type that lets us write to it from the CPU (HOST_VISIBLE_BIT), and also use COHERENT_BIT, for vertex buffer.
//NOTE: In a real world app, don't call vkAllocateMemory for each buffer - max simultaneous memory allocations can be as low as 4096, even on high end hardware.
// The right way to do it is to create a custom alocator to split up a single allocation among many different objects, using "offset" parameters.
// This can be implemented oneself - or one can use the VulkanMemoryAllocator library! But for these kinds of things here, we're not coming close to hitting that limit.
if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) {
throw std::runtime_error("Failed to allocate vertex buffer memory!");
}
......@@ -1529,6 +1547,33 @@ class HelloTriangleApplication {
vkFreeMemory(device, stagingBufferMemory, nullptr); //Kill the temp staging buffer and its memory.
}
void createIndexBuffer() {
//Essentially identical to the vertex buffer creation - except, using indices instead of vertices.
VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size();
VkBuffer stagingBuffer; //The temp buffer we write to from CPU.
VkDeviceMemory stagingBufferMemory; //The memory for the staging buffer.
createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
void* data;
vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data);
memcpy(data, indices.data(), (size_t) bufferSize); //memcpy copies into the "data" pointer, from "vertices.data()" pointer, using bufferInfo.size bytes).
vkUnmapMemory(device, stagingBufferMemory); //Unmap once CPU --> GPU memory copy is complete.
createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory);
copyBuffer(stagingBuffer, indexBuffer, bufferSize); //Do the buffer copy. The GPU gets to read from optimized local memory, the CPU gets to actually write memory at all!
vkDestroyBuffer(device, stagingBuffer, nullptr);
vkFreeMemory(device, stagingBufferMemory, nullptr); //Kill the temp staging buffer and its memory.
}
void initVulkan() {
createInstance(); //The Vulkan instance is the link between application and Vulkan.
setupDebugCallback(); //Setup the debug callback that interfaces with the validation layer.
......@@ -1542,6 +1587,7 @@ class HelloTriangleApplication {
createFramebuffers(); //Make the framebuffers for all the images in the swap chain - and use the one that corresponds to the retrieved image at draw time.
createCommandPool(); //Commands, like draw/memory transfer, are executed by command buffer objects. All the hard setup can be done in multiple threads, then!
createVertexBuffer(); //Vertex buffers store vertex data to be read by the GPU.
createIndexBuffer(); //Index buffers store indices to the vertex buffer, to reduce redundancy.
createCommandBuffers(); // We need one command buffer for each image in the swap chain, because they bind to framebuffers.
createSemaphore(); //Create the sephamores we need to synchronize things.
}
......@@ -1639,6 +1685,9 @@ class HelloTriangleApplication {
void cleanup() {
cleanupSwapChain(); //Does swapchain related cleanup.
vkDestroyBuffer(device, indexBuffer, nullptr); //Kill the index buffer. It does not depend on the swap chain.
vkFreeMemory(device, indexBufferMemory, nullptr); //We need to free all allocated memory manually. This is the memory holding the index buffer.
vkDestroyBuffer(device, vertexBuffer, nullptr); //Kill the vertex buffer. It does not depend on the swap chain.
vkFreeMemory(device, vertexBufferMemory, nullptr); //We need to free all allocated memory manually. This is the memory holding the vertex buffer.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment