Support multiple frames rendering at once

This commit is contained in:
Bailey Harrison 2022-10-24 14:30:05 +01:00
parent e811349e5b
commit 46895c0d4b
4 changed files with 121 additions and 46 deletions

View File

@ -14,6 +14,7 @@ namespace engine::gfx {
enum class BufferType { enum class BufferType {
VERTEX, VERTEX,
INDEX, INDEX,
UNIFORM,
}; };
enum class Primitive { enum class Primitive {

View File

@ -28,7 +28,7 @@ namespace engine {
void renderFrame(); void renderFrame();
// creates the equivalent of an OpenGL shader program & vertex attrib configuration // creates the equivalent of an OpenGL shader program & vertex attrib configuration
gfx::Pipeline* createPipeline(const char* vertShaderPath, const char* fragShaderPath, const gfx::VertexFormat& vertexFormat); gfx::Pipeline* createPipeline(const char* vertShaderPath, const char* fragShaderPath, const gfx::VertexFormat& vertexFormat, uint64_t uniformBufferSize);
void destroyPipeline(const gfx::Pipeline* pipeline); void destroyPipeline(const gfx::Pipeline* pipeline);
gfx::Buffer* createBuffer(gfx::BufferType type, uint64_t size, const void* data); gfx::Buffer* createBuffer(gfx::BufferType type, uint64_t size, const void* data);

View File

@ -10,6 +10,7 @@
static engine::gfx::Pipeline* pipeline; static engine::gfx::Pipeline* pipeline;
static engine::gfx::Buffer* vb; static engine::gfx::Buffer* vb;
static engine::gfx::Buffer* ib; static engine::gfx::Buffer* ib;
static engine::gfx::Buffer* ub;
namespace engine { namespace engine {
@ -28,7 +29,12 @@ namespace engine {
}; };
vertFormat.attributeDescriptions.push_back({0, gfx::VertexAttribFormat::VEC2, 0}); vertFormat.attributeDescriptions.push_back({0, gfx::VertexAttribFormat::VEC2, 0});
vertFormat.attributeDescriptions.push_back({1, gfx::VertexAttribFormat::VEC3, offsetof(Vertex, col)}); vertFormat.attributeDescriptions.push_back({1, gfx::VertexAttribFormat::VEC3, offsetof(Vertex, col)});
pipeline = m_gfx->createPipeline(resMan.getFilePath("shader.vert.spv").string().c_str(), resMan.getFilePath("shader.frag.spv").string().c_str(), vertFormat); struct UBO {
glm::mat4 model{};
glm::mat4 view{};
glm::mat4 proj{};
};
pipeline = m_gfx->createPipeline(resMan.getFilePath("shader.vert.spv").string().c_str(), resMan.getFilePath("shader.frag.spv").string().c_str(), vertFormat, sizeof(UBO));
const std::vector<Vertex> vertices = { const std::vector<Vertex> vertices = {
{ { 0.5f, -0.5f}, {1.0f, 0.0f, 0.0f} }, { { 0.5f, -0.5f}, {1.0f, 0.0f, 0.0f} },
@ -42,12 +48,16 @@ namespace engine {
}; };
ib = m_gfx->createBuffer(gfx::BufferType::INDEX, sizeof(uint32_t) * indices.size(), indices.data()); ib = m_gfx->createBuffer(gfx::BufferType::INDEX, sizeof(uint32_t) * indices.size(), indices.data());
UBO initialUbo{};
// ub = m_gfx->createBuffer(gfx::BufferType::UNIFORM, sizeof(UBO), &initialUbo);
} }
Application::~Application() Application::~Application()
{ {
m_gfx->destroyBuffer(vb); // m_gfx->destroyBuffer(ub);
m_gfx->destroyBuffer(ib); m_gfx->destroyBuffer(ib);
m_gfx->destroyBuffer(vb);
m_gfx->destroyPipeline(pipeline); m_gfx->destroyPipeline(pipeline);
} }

View File

@ -31,6 +31,8 @@
namespace engine { namespace engine {
static constexpr uint32_t FRAMES_IN_FLIGHT = 2; // This improved FPS by 5x!
// structures and enums // structures and enums
struct LayerInfo { struct LayerInfo {
@ -63,8 +65,8 @@ namespace engine {
VkRenderPass renderpass; VkRenderPass renderpass;
VkSemaphore acquireSemaphore = VK_NULL_HANDLE; // waits until the image is available std::array<VkSemaphore, FRAMES_IN_FLIGHT> acquireSemaphores{}; // waits until the image is available
VkSemaphore releaseSemaphore = VK_NULL_HANDLE; // waits until rendering finishes std::array<VkSemaphore, FRAMES_IN_FLIGHT> releaseSemaphores{}; // waits until rendering finishes
}; };
struct DrawCall { struct DrawCall {
@ -91,6 +93,7 @@ namespace engine {
struct gfx::Pipeline { struct gfx::Pipeline {
VkPipelineLayout layout = VK_NULL_HANDLE; VkPipelineLayout layout = VK_NULL_HANDLE;
VkPipeline handle = VK_NULL_HANDLE; VkPipeline handle = VK_NULL_HANDLE;
std::vector<gfx::Buffer*> uniformBuffers{};
}; };
@ -515,14 +518,18 @@ namespace engine {
// create the swapchain semaphores // create the swapchain semaphores
VkSemaphoreCreateInfo semaphoreInfo{}; VkSemaphoreCreateInfo semaphoreInfo{};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
if (swapchain->acquireSemaphore == VK_NULL_HANDLE) { for (auto& acquireSemaphore : swapchain->acquireSemaphores) {
res = vkCreateSemaphore(device, &semaphoreInfo, nullptr, &swapchain->acquireSemaphore); if (acquireSemaphore == VK_NULL_HANDLE) {
res = vkCreateSemaphore(device, &semaphoreInfo, nullptr, &acquireSemaphore);
assert(res == VK_SUCCESS); assert(res == VK_SUCCESS);
} }
if (swapchain->releaseSemaphore == VK_NULL_HANDLE) { }
res = vkCreateSemaphore(device, &semaphoreInfo, nullptr, &swapchain->releaseSemaphore); for (auto& releaseSemaphore : swapchain->releaseSemaphores) {
if (releaseSemaphore == VK_NULL_HANDLE) {
res = vkCreateSemaphore(device, &semaphoreInfo, nullptr, &releaseSemaphore);
assert(res == VK_SUCCESS); assert(res == VK_SUCCESS);
} }
}
} }
@ -588,8 +595,6 @@ namespace engine {
} }
// class definitions // class definitions
struct GFXDevice::Impl { struct GFXDevice::Impl {
@ -608,16 +613,21 @@ namespace engine {
Queue gfxQueue{}; Queue gfxQueue{};
Queue presentQueue{}; Queue presentQueue{};
VkCommandPool commandPool = VK_NULL_HANDLE; VkCommandPool commandPool = VK_NULL_HANDLE;
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
VmaAllocator allocator = nullptr; VmaAllocator allocator = nullptr;
Swapchain swapchain{}; Swapchain swapchain{};
VkFence inFlightFence = VK_NULL_HANDLE; uint64_t FRAMECOUNT = 0;
std::array<VkCommandBuffer, FRAMES_IN_FLIGHT> commandBuffers{};
std::array<VkFence, FRAMES_IN_FLIGHT> inFlightFences{};
std::map<const gfx::Pipeline*, std::queue<DrawCall>> drawQueues{}; std::map<const gfx::Pipeline*, std::queue<DrawCall>> drawQueues{};
VkDescriptorSetLayoutBinding uboLayoutBinding{};
VkDescriptorSetLayout uboLayout{};
}; };
GFXDevice::GFXDevice(const char* appName, const char* appVersion, SDL_Window* window) GFXDevice::GFXDevice(const char* appName, const char* appVersion, SDL_Window* window)
@ -939,9 +949,11 @@ namespace engine {
.commandBufferCount = 1 .commandBufferCount = 1
}; };
res = vkAllocateCommandBuffers(pimpl->device, &gfxCmdBufInfo, &pimpl->commandBuffer); for (int i = 0; i < FRAMES_IN_FLIGHT; i++) {
res = vkAllocateCommandBuffers(pimpl->device, &gfxCmdBufInfo, &pimpl->commandBuffers[i]);
assert(res == VK_SUCCESS); assert(res == VK_SUCCESS);
} }
}
@ -1001,12 +1013,28 @@ namespace engine {
// for testing purposes, create a pipeline with a simple shader
VkFenceCreateInfo fenceInfo{}; VkFenceCreateInfo fenceInfo{};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
fenceInfo.pNext = nullptr; fenceInfo.pNext = nullptr;
res = vkCreateFence(pimpl->device, &fenceInfo, nullptr, &pimpl->inFlightFence); for (int i = 0; i < FRAMES_IN_FLIGHT; i++) {
res = vkCreateFence(pimpl->device, &fenceInfo, nullptr, &pimpl->inFlightFences[i]);
assert(res == VK_SUCCESS);
}
// create uniform buffer stuff
pimpl->uboLayoutBinding.binding = 0;
pimpl->uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
pimpl->uboLayoutBinding.descriptorCount = 1;
pimpl->uboLayoutBinding.stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS;
pimpl->uboLayoutBinding.pImmutableSamplers = nullptr;
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo{ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO };
descriptorSetLayoutInfo.bindingCount = 1;
descriptorSetLayoutInfo.pBindings = &pimpl->uboLayoutBinding;
res = vkCreateDescriptorSetLayout(pimpl->device, &descriptorSetLayoutInfo, nullptr, &pimpl->uboLayout);
assert(res == VK_SUCCESS);
} }
@ -1014,10 +1042,16 @@ namespace engine {
{ {
TRACE("Destroying GFXDevice..."); TRACE("Destroying GFXDevice...");
vkDestroyFence(pimpl->device, pimpl->inFlightFence, nullptr); vkDestroyDescriptorSetLayout(pimpl->device, pimpl->uboLayout, nullptr);
vkDestroySemaphore(pimpl->device, pimpl->swapchain.releaseSemaphore, nullptr); for (int i = 0; i < FRAMES_IN_FLIGHT; i++) {
vkDestroySemaphore(pimpl->device, pimpl->swapchain.acquireSemaphore, nullptr);
vkDestroyFence(pimpl->device, pimpl->inFlightFences[i], nullptr);
vkDestroySemaphore(pimpl->device, pimpl->swapchain.releaseSemaphores[i], nullptr);
vkDestroySemaphore(pimpl->device, pimpl->swapchain.acquireSemaphores[i], nullptr);
}
for (VkImageView view : pimpl->swapchain.imageViews) { for (VkImageView view : pimpl->swapchain.imageViews) {
vkDestroyImageView(pimpl->device, view, nullptr); vkDestroyImageView(pimpl->device, view, nullptr);
} }
@ -1067,13 +1101,15 @@ namespace engine {
{ {
VkResult res; VkResult res;
res = vkWaitForFences(pimpl->device, 1, &pimpl->inFlightFence, VK_TRUE, UINT64_MAX); const uint32_t frameIndex = pimpl->FRAMECOUNT % FRAMES_IN_FLIGHT;
res = vkWaitForFences(pimpl->device, 1, &pimpl->inFlightFences[frameIndex], VK_TRUE, UINT64_MAX);
assert(res == VK_SUCCESS); assert(res == VK_SUCCESS);
res = vkResetFences(pimpl->device, 1, &pimpl->inFlightFence); res = vkResetFences(pimpl->device, 1, &pimpl->inFlightFences[frameIndex]);
assert(res == VK_SUCCESS); assert(res == VK_SUCCESS);
uint32_t imageIndex = 0; uint32_t imageIndex = 0;
res = vkAcquireNextImageKHR(pimpl->device, pimpl->swapchain.swapchain, UINT64_MAX, pimpl->swapchain.acquireSemaphore, VK_NULL_HANDLE, &imageIndex); res = vkAcquireNextImageKHR(pimpl->device, pimpl->swapchain.swapchain, UINT64_MAX, pimpl->swapchain.acquireSemaphores[frameIndex], VK_NULL_HANDLE, &imageIndex);
if (res == VK_ERROR_OUT_OF_DATE_KHR) { if (res == VK_ERROR_OUT_OF_DATE_KHR) {
// recreate swapchain // recreate swapchain
waitIdle(); waitIdle();
@ -1084,7 +1120,7 @@ namespace engine {
assert(res == VK_SUCCESS || res == VK_SUBOPTIMAL_KHR); assert(res == VK_SUCCESS || res == VK_SUBOPTIMAL_KHR);
} }
res = vkResetCommandBuffer(pimpl->commandBuffer, 0); res = vkResetCommandBuffer(pimpl->commandBuffers[frameIndex], 0);
assert(res == VK_SUCCESS); assert(res == VK_SUCCESS);
// now record command buffer // now record command buffer
@ -1092,7 +1128,7 @@ namespace engine {
VkCommandBufferBeginInfo beginInfo{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; VkCommandBufferBeginInfo beginInfo{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
beginInfo.flags = 0; beginInfo.flags = 0;
beginInfo.pInheritanceInfo = nullptr; beginInfo.pInheritanceInfo = nullptr;
res = vkBeginCommandBuffer(pimpl->commandBuffer, &beginInfo); res = vkBeginCommandBuffer(pimpl->commandBuffers[frameIndex], &beginInfo);
assert(res == VK_SUCCESS); assert(res == VK_SUCCESS);
VkRenderPassBeginInfo renderPassInfo{ VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO }; VkRenderPassBeginInfo renderPassInfo{ VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO };
@ -1105,7 +1141,7 @@ namespace engine {
renderPassInfo.clearValueCount = 1; renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &clearColor; renderPassInfo.pClearValues = &clearColor;
vkCmdBeginRenderPass(pimpl->commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); vkCmdBeginRenderPass(pimpl->commandBuffers[frameIndex], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
VkViewport viewport{}; VkViewport viewport{};
viewport.x = 0.0f; viewport.x = 0.0f;
@ -1114,28 +1150,28 @@ namespace engine {
viewport.height = (float)pimpl->swapchain.extent.height; viewport.height = (float)pimpl->swapchain.extent.height;
viewport.minDepth = 0.0f; viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f; viewport.maxDepth = 1.0f;
vkCmdSetViewport(pimpl->commandBuffer, 0, 1, &viewport); vkCmdSetViewport(pimpl->commandBuffers[frameIndex], 0, 1, &viewport);
VkRect2D scissor{}; VkRect2D scissor{};
scissor.offset = { 0, 0 }; scissor.offset = { 0, 0 };
scissor.extent = pimpl->swapchain.extent; scissor.extent = pimpl->swapchain.extent;
vkCmdSetScissor(pimpl->commandBuffer, 0, 1, &scissor); vkCmdSetScissor(pimpl->commandBuffers[frameIndex], 0, 1, &scissor);
// run queued draw calls // run queued draw calls
VkDeviceSize offsets[] = { 0 }; VkDeviceSize offsets[] = { 0 };
for (auto& [pipeline, queue] : pimpl->drawQueues) { for (auto& [pipeline, queue] : pimpl->drawQueues) {
vkCmdBindPipeline(pimpl->commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->handle); vkCmdBindPipeline(pimpl->commandBuffers[frameIndex], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->handle);
while (queue.empty() == false) { while (queue.empty() == false) {
DrawCall call = queue.front(); DrawCall call = queue.front();
vkCmdBindVertexBuffers(pimpl->commandBuffer, 0, 1, &call.vertexBuffer->buffer, offsets); vkCmdBindVertexBuffers(pimpl->commandBuffers[frameIndex], 0, 1, &call.vertexBuffer->buffer, offsets);
if (call.indexBuffer == nullptr) { if (call.indexBuffer == nullptr) {
// do a simple draw call // do a simple draw call
vkCmdDraw(pimpl->commandBuffer, call.count, 1, 0, 0); vkCmdDraw(pimpl->commandBuffers[frameIndex], call.count, 1, 0, 0);
} else { } else {
vkCmdBindIndexBuffer(pimpl->commandBuffer, call.indexBuffer->buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdBindIndexBuffer(pimpl->commandBuffers[frameIndex], call.indexBuffer->buffer, 0, VK_INDEX_TYPE_UINT32);
vkCmdDrawIndexed(pimpl->commandBuffer, call.count, 1, 0, 0, 0); vkCmdDrawIndexed(pimpl->commandBuffers[frameIndex], call.count, 1, 0, 0, 0);
} }
queue.pop(); queue.pop();
} }
@ -1143,9 +1179,9 @@ namespace engine {
pimpl->drawQueues.clear(); pimpl->drawQueues.clear();
vkCmdEndRenderPass(pimpl->commandBuffer); vkCmdEndRenderPass(pimpl->commandBuffers[frameIndex]);
res = vkEndCommandBuffer(pimpl->commandBuffer); res = vkEndCommandBuffer(pimpl->commandBuffers[frameIndex]);
assert(res == VK_SUCCESS); assert(res == VK_SUCCESS);
} }
@ -1153,19 +1189,19 @@ namespace engine {
VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
submitInfo.waitSemaphoreCount = 1; submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &pimpl->swapchain.acquireSemaphore; submitInfo.pWaitSemaphores = &pimpl->swapchain.acquireSemaphores[frameIndex];
submitInfo.pWaitDstStageMask = waitStages; submitInfo.pWaitDstStageMask = waitStages;
submitInfo.commandBufferCount = 1; submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &pimpl->commandBuffer; submitInfo.pCommandBuffers = &pimpl->commandBuffers[frameIndex];
submitInfo.signalSemaphoreCount = 1; submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &pimpl->swapchain.releaseSemaphore; submitInfo.pSignalSemaphores = &pimpl->swapchain.releaseSemaphores[frameIndex];
res = vkQueueSubmit(pimpl->gfxQueue.handle, 1, &submitInfo, pimpl->inFlightFence); res = vkQueueSubmit(pimpl->gfxQueue.handle, 1, &submitInfo, pimpl->inFlightFences[frameIndex]);
assert(res == VK_SUCCESS); assert(res == VK_SUCCESS);
VkPresentInfoKHR presentInfo{ VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; VkPresentInfoKHR presentInfo{ VK_STRUCTURE_TYPE_PRESENT_INFO_KHR };
presentInfo.waitSemaphoreCount = 1; presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = &pimpl->swapchain.releaseSemaphore; presentInfo.pWaitSemaphores = &pimpl->swapchain.releaseSemaphores[frameIndex];
VkSwapchainKHR swapchains[] = { pimpl->swapchain.swapchain }; VkSwapchainKHR swapchains[] = { pimpl->swapchain.swapchain };
presentInfo.swapchainCount = 1; presentInfo.swapchainCount = 1;
@ -1182,15 +1218,40 @@ namespace engine {
else { else {
assert(res == VK_SUCCESS); assert(res == VK_SUCCESS);
} }
pimpl->FRAMECOUNT++;
} }
gfx::Pipeline* GFXDevice::createPipeline(const char* vertShaderPath, const char* fragShaderPath, const gfx::VertexFormat& vertexFormat) gfx::Pipeline* GFXDevice::createPipeline(const char* vertShaderPath, const char* fragShaderPath, const gfx::VertexFormat& vertexFormat, uint64_t uniformBufferSize)
{ {
VkResult res; VkResult res;
gfx::Pipeline* pipeline = new gfx::Pipeline; gfx::Pipeline* pipeline = new gfx::Pipeline;
// create uniform buffers
pipeline->uniformBuffers.resize(FRAMES_IN_FLIGHT);
for (int i = 0; i < FRAMES_IN_FLIGHT; i++) {
auto buf = new gfx::Buffer{};
buf->size = uniformBufferSize;
buf->type = gfx::BufferType::UNIFORM;
VkBufferCreateInfo bufferInfo{ VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufferInfo.size = buf->size;
bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VmaAllocationCreateInfo allocInfo{};
allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST; // prefer CPU memory for uniforms
allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
res = vmaCreateBuffer(pimpl->allocator, &bufferInfo, &allocInfo, &buf->buffer, &buf->allocation, nullptr);
assert(res == VK_SUCCESS);
pipeline->uniformBuffers[i] = buf;
}
// get vertex attrib layout: // get vertex attrib layout:
VkVertexInputBindingDescription bindingDescription{ }; VkVertexInputBindingDescription bindingDescription{ };
bindingDescription.binding = 0; bindingDescription.binding = 0;
@ -1320,9 +1381,8 @@ namespace engine {
VkPipelineLayoutCreateInfo layoutInfo{}; VkPipelineLayoutCreateInfo layoutInfo{};
layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
// everything is 0 because we're not using uniforms layoutInfo.setLayoutCount = 1;
layoutInfo.setLayoutCount = 0; layoutInfo.pSetLayouts = &pimpl->uboLayout;
layoutInfo.pSetLayouts = nullptr;
layoutInfo.pushConstantRangeCount = 0; layoutInfo.pushConstantRangeCount = 0;
layoutInfo.pPushConstantRanges = nullptr; layoutInfo.pPushConstantRanges = nullptr;
@ -1363,6 +1423,10 @@ namespace engine {
vkDestroyPipeline(pimpl->device, pipeline->handle, nullptr); vkDestroyPipeline(pimpl->device, pipeline->handle, nullptr);
vkDestroyPipelineLayout(pimpl->device, pipeline->layout, nullptr); vkDestroyPipelineLayout(pimpl->device, pipeline->layout, nullptr);
for (int i = 0; i < FRAMES_IN_FLIGHT; i++) {
destroyBuffer(pipeline->uniformBuffers[i]);
}
delete pipeline; delete pipeline;
} }