From cb4500d97f2716af87ed037c1a1e457bb1642b84 Mon Sep 17 00:00:00 2001 From: Lillian Salehi Date: Sat, 12 Oct 2024 04:14:22 -0500 Subject: [PATCH] Update documentation and clean code for Uniform Buffers, added time variable to descriptor set, preparing for Texture mapping. --- Flowgraph.puml | 41 ++++++++++++++++++++++++++----- Flowgraph.svg | 1 + src/entrypoint.cpp | 1 - src/entrypoint.h | 1 + src/global.h | 9 ++++--- src/graphics/buffers.cpp | 12 ++++++--- src/graphics/graphicspipeline.cpp | 3 ++- src/shaders/vertex.vert | 4 +-- 8 files changed, 54 insertions(+), 18 deletions(-) create mode 100644 Flowgraph.svg diff --git a/Flowgraph.puml b/Flowgraph.puml index b567b22..badb654 100644 --- a/Flowgraph.puml +++ b/Flowgraph.puml @@ -77,13 +77,20 @@ partition "**DeviceControl**" { end note } +:**Graphics**::createRenderPass(...); +note right + This is pretty simple, it sets up the image bit depth + and the color bit depth! Basically, the format of the + displayed images, simple, but important! +end note +:**Buffers**::createDescriptorSetLayout(); +note right + This function creates a table of pointers to the stored + data that we want, in this case it would be pointing to + pre-programmed model view and projection values, and + a time variable. +end note partition "**Graphics**" { - :createRenderPass(...); - note right - This is pretty simple, it sets up the image bit depth - and the color bit depth! Basically, the format of the - displayed images, simple, but important! - end note :createGraphicsPipeline(...); note right This is a complex function that goes through every @@ -116,6 +123,28 @@ partition "**Buffers**" { at corners to triangulate. this saves cycles at scale, complex objects rejoice! end note + :createUniformBuffer(); + note right + Map the buffer data to memory (The struct) as a pointer + we can use this as a reference of where to write data to + when the fence lets us write data. + (see **recordCommandBuffer()**). + end note + :createDescriptorPool(); + note right + Here we create a pool to manage the memory and allocate + space for the descriptor sets! Very useful and the same + structure as command buffers & pools. + end note + :createDescriptorSetLayout(); + note right + //Descriptor set **layouts** specify the types of resources accessible// + //by the graphical pipeline. A descriptor set is the actual buffer// + //or resource that gets bound to descriptors and passed in.// + These differ from Vertex & Index buffers, as they are not unique + to the graphics pipeline. Specification of compute vs. graphics is + therefore necessary. (see **createDescriptorSets()**) + end note } :Graphics::createCommandBuffer(); note right diff --git a/Flowgraph.svg b/Flowgraph.svg new file mode 100644 index 0000000..736cc70 --- /dev/null +++ b/Flowgraph.svg @@ -0,0 +1 @@ +Main ExecutionMain Executionmainrun()initWindow()glfwInit()Create window and initialize default settingswindow = glfwCreateWindow(...)Set the user-defined pointer of the windowglfwSetWindowUserPointer(...)This is a callback to resizing of the windowwe call and set a bool to true, which we getand rebuild the swap chain when true.glfwSetFramebufferSizeCallback(...)initVulkan()createInstance()Debug::checkUnavailableValidationLayers()VkApplicationInfoappInfo{}set appInfo data, Vulkan version,Engine version and name, and titleVkApplicationCreateInfocreateInfo{}createInfo.pApplicationInfo = &appInfoDebug::vulkanDebugSetup(createInfo, vulkaninstance)Setup debug messenger, print data to consoleDebug::setupDebugMessenger(VkInstance&)This function handles Window System Integrationautomatically across platforms based on build environment.Basically, this is an abstraction of the Window across platformsglfwCreateWindowSurface(...)DeviceControlEnumerate through GPU's in thesystem and choose a compatible one.in the future, this should choose the BEST GPU, not just the first one that is compatible..pickPhysicalDevice(...)Logical devices interface with thephysical device and defines queuescreateLogicalDevice(...)Swap Chains are used to handle buffer ownershipinfrastructure. Being platform agnostic has itscomplications, this is a perfect example.This process is HEAVILY documented.createSwapChain(...)This is a cool function, quite a simpledescription of images that will be shown on thescreen! It also determineshowto access the imagecreateImageViews(...)This is pretty simple, it sets up the image bit depthand the color bit depth! Basically, the format of thedisplayed images, simple, but important!Graphics::createRenderPass(...)This function creates a table of pointers to the storeddata that we want, in this case it would be pointing topre-programmed model view and projection values, anda time variable.Buffers::createDescriptorSetLayout()GraphicsThis is a complex function that goes through everystep of the render pipeline and sets the settings wedesire for each step!HEAVILYdocumented.createGraphicsPipeline(...)This function creates framebuffers for all the imagesthat are queued to be displayed, very important!createFramebuffers(...)Commands in Vulkan are not executed using function callsYou have to record the ops you want to perform to commandbuffer objects, pools manage the memory used for buffers.createCommandPool(...)BuffersVertex buffers are incredibly useful, in essence,you can read data from memory as vertex input to thevertex shader rather than hardcoded data!createVertexBuffer()Index buffers are cool, basically, you can storesome vertices that would normally be duplicatedat corners to triangulate. this saves cycles atscale, complex objects rejoice!createIndexBuffer()Map the buffer data to memory (The struct) as a pointerwe can use this as a reference of where to write data towhen the fence lets us write data.(seerecordCommandBuffer).createUniformBuffer()Here we create a pool to manage the memory and allocatespace for the descriptor sets! Very useful and the samestructure as command buffers & pools.createDescriptorPool()Descriptor setlayoutsspecify the types of resources accessibleby the graphical pipeline. A descriptor set is the actual bufferor resource that gets bound to descriptors and passed in.These differ from Vertex & Index buffers, as they are not uniqueto the graphics pipeline. Specification of compute vs. graphics istherefore necessary. (seecreateDescriptorSets)createDescriptorSetLayout()This is the partner to the commandPool creator,storing the commands we wish to perform whilstwaiting in a queue. These are very efficient.Graphics::createCommandBuffer()This isHEAVILYdocumented, create Semaphoresand Fences, for halting and starting execution, basicallya traffic controller for the GPU.RenderPresent::createSyncObject()glfwPollEvents()RenderPresent::drawFrame()mainLoop()!glfwWindowShouldClose(...)vkDeviceWaitIdle(...)This function initiates a series of shutdowndestroy functions, safely stopping execution;cleanup()return EXIT_SUCCESSMain execution from the run function in the entrypointThis dictates basic flow of the vulkan boilerplate system. \ No newline at end of file diff --git a/src/entrypoint.cpp b/src/entrypoint.cpp index 7674c66..d7fcd52 100644 --- a/src/entrypoint.cpp +++ b/src/entrypoint.cpp @@ -1,5 +1,4 @@ #include "entrypoint.h" -#include "global.h" DeviceControl::devicelibrary deviceLibs; Debug::vulkandebuglibs debugController; Graphics::graphicspipeline graphicsPipeline; diff --git a/src/entrypoint.h b/src/entrypoint.h index adc0ab7..aa83926 100644 --- a/src/entrypoint.h +++ b/src/entrypoint.h @@ -3,6 +3,7 @@ #include "debug/vulkandebuglibs.h" #include "graphics/graphicspipeline.h" #include "graphics/render.h" +#include "global.h" class EntryApp { public: static EntryApp& getInstance(); diff --git a/src/global.h b/src/global.h index 2ace031..bbad09d 100644 --- a/src/global.h +++ b/src/global.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -29,11 +30,11 @@ namespace Global { extern VkDescriptorSetLayout descriptorSetLayout; extern uint32_t currentFrame; extern std::vector descriptorSets; - struct UniformBufferObject { - glm::mat4 model; - glm::mat4 view; - glm::mat4 proj; + float time; + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; struct Vertex { glm::vec2 pos; diff --git a/src/graphics/buffers.cpp b/src/graphics/buffers.cpp index 1218530..ffa319e 100644 --- a/src/graphics/buffers.cpp +++ b/src/graphics/buffers.cpp @@ -165,7 +165,7 @@ namespace Buffers { std::vector bufferslibrary::getIndices() { return indices; } - +// ------------------------------ Uniform Buffer Setup -------------------------------- // void bufferslibrary::createDescriptorSetLayout() { // Create a table of pointers to data, a Descriptor Set! VkDescriptorSetLayoutBinding uboLayoutBinding{}; @@ -187,10 +187,9 @@ namespace Buffers { throw std::runtime_error("Failed to create descriptor set layout!"); } } - //void createMVPDescriptor() { - - //} void bufferslibrary::createUniformBuffers() { + // Map the uniform buffer to memory as a pointer we can use to write data to later. This stays mapped to memory for the applications lifetime. + // This technique is called "persistent mapping", not having to map the buffer every time we need to update it increases performance, though not free VkDeviceSize bufferSize = sizeof(Global::UniformBufferObject); uniformBuffers.resize(Global::MAX_FRAMES_IN_FLIGHT); @@ -203,12 +202,16 @@ namespace Buffers { } } void bufferslibrary::updateUniformBuffer(uint32_t currentImage) { + // Update the uniform buffer every frame to change the position, but notably, use chrono to know exactly how much to move + // so we aren't locked to the framerate as the world time. + static auto startTime = std::chrono::high_resolution_clock::now(); // Calculate the time in seconds since rendering has began to floating point precision. auto currentTime = std::chrono::high_resolution_clock::now(); float time = std::chrono::duration(currentTime - startTime).count(); Global::UniformBufferObject ubo{}; + ubo.time = time; // Modify the model projection transformation to rotate around the Z over time. ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); // Modify the view transformation to look at the object from above at a 45 degree angle. @@ -229,6 +232,7 @@ namespace Buffers { } } void bufferslibrary::createDescriptorPool() { + // Create a pool to be used to allocate descriptor sets. VkDescriptorPoolSize poolSize{}; poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; poolSize.descriptorCount = static_cast(Global::MAX_FRAMES_IN_FLIGHT); diff --git a/src/graphics/graphicspipeline.cpp b/src/graphics/graphicspipeline.cpp index 0907bd0..e8ed75d 100644 --- a/src/graphics/graphicspipeline.cpp +++ b/src/graphics/graphicspipeline.cpp @@ -238,7 +238,8 @@ namespace Graphics { } } void graphicspipeline::createCommandPool() { - + // Commands in Vulkan are not executed using function calls, you have to record the ops you wish to perform + // to command buffers, pools manage the memory used by the buffer! Global::QueueFamilyIndices queueFamilyIndices = Global::findQueueFamilies(Global::physicalDevice); VkCommandPoolCreateInfo poolInfo{}; diff --git a/src/shaders/vertex.vert b/src/shaders/vertex.vert index 3646155..d1245e6 100644 --- a/src/shaders/vertex.vert +++ b/src/shaders/vertex.vert @@ -1,6 +1,7 @@ #version 450 layout(binding = 0) uniform UniformBufferObject { + float time; mat4 model; mat4 view; mat4 proj; @@ -14,6 +15,5 @@ layout(location = 0) out vec3 fragColor; void main() { gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); - fragColor = inColor; + fragColor = inColor + sin(ubo.time*2); } -