From 4365cc45d1ae60fd5ce826979e322bcf104e2e15 Mon Sep 17 00:00:00 2001 From: connellpaxton Date: Sun, 21 Jan 2024 22:36:35 -0500 Subject: [PATCH] Successfully cleared the screen. created synchronization objects for rendering and presentation --- CMakeLists.txt | 1 - Renderer/CommandBuffer.cpp | 6 ++- Renderer/CommandBuffer.hpp | 4 ++ Renderer/Renderer.cpp | 85 +++++++++++++++++++++++++++++++++++--- Renderer/Renderer.hpp | 6 +++ Renderer/Swapchain.cpp | 26 +++++++++--- Renderer/Swapchain.hpp | 12 +++++- pléascach.cpp | 10 ++--- renderer/RenderPass.cpp | 81 ++++++++++++++++++++++++++++++++++++ renderer/RenderPass.hpp | 16 +++++++ 10 files changed, 228 insertions(+), 19 deletions(-) create mode 100644 renderer/RenderPass.cpp create mode 100644 renderer/RenderPass.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 08be228..4f8fc6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,6 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_FLAGS "-DDEBUG") -set(CMAKE_CXX_FLAGS "-fsanitize=address -g -DDEBUG -D_DEBUG") project(Pleascach) diff --git a/Renderer/CommandBuffer.cpp b/Renderer/CommandBuffer.cpp index a359e95..c6e5550 100644 --- a/Renderer/CommandBuffer.cpp +++ b/Renderer/CommandBuffer.cpp @@ -2,6 +2,8 @@ #include +#include + CommandBuffer::CommandBuffer(vk::Device dev, u32 queue_family) { /* (For now) allow command buffers to be individually recycled */ auto pool_info = vk::CommandPoolCreateInfo { @@ -18,7 +20,6 @@ CommandBuffer::CommandBuffer(vk::Device dev, u32 queue_family) { .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1, }; - command_buffer = dev.allocateCommandBuffers(alloc_info)[0]; } @@ -26,7 +27,6 @@ CommandBuffer::CommandBuffer(vk::Device dev, u32 queue_family) { void CommandBuffer::begin() { auto begin_info = vk::CommandBufferBeginInfo{ .flags = static_cast(0), - .pInheritanceInfo = nullptr, }; command_buffer.begin(begin_info); @@ -50,8 +50,10 @@ void CommandBuffer::end() { void CommandBuffer::recycle() { command_buffer.reset(); + } void CommandBuffer::cleanup(vk::Device dev) { + dev.freeCommandBuffers(command_pool, command_buffer); dev.destroyCommandPool(command_pool); } diff --git a/Renderer/CommandBuffer.hpp b/Renderer/CommandBuffer.hpp index 86b80e0..96e4b7a 100644 --- a/Renderer/CommandBuffer.hpp +++ b/Renderer/CommandBuffer.hpp @@ -29,4 +29,8 @@ struct CommandBuffer { vk::CommandBuffer command_buffer; vk::CommandPool command_pool; + + operator vk::CommandBuffer* () { + return &command_buffer; + } }; diff --git a/Renderer/Renderer.cpp b/Renderer/Renderer.cpp index f9d08a6..4f5d660 100644 --- a/Renderer/Renderer.cpp +++ b/Renderer/Renderer.cpp @@ -145,8 +145,6 @@ Renderer::Renderer(Window& win) : win(win) { Log::error("Failed to get surface from window\n"); } - swapchain = std::make_unique(dev, surface, win.getDimensions()); - queue = dev.getQueue(queue_family, 0); @@ -185,7 +183,7 @@ Renderer::Renderer(Window& win) : win(win) { .memoryTypeIndex = mem::choose_heap(phys_dev, depth_mem_reqs, vk::MemoryPropertyFlagBits::eDeviceLocal), }; - auto depth_alloc = dev.allocateMemory(depth_alloc_info); + depth_alloc = dev.allocateMemory(depth_alloc_info); dev.bindImageMemory(depth_image, depth_alloc, 0); auto depth_view_info = vk::ImageViewCreateInfo { @@ -208,29 +206,101 @@ Renderer::Renderer(Window& win) : win(win) { }; depth_image_view = dev.createImageView(depth_view_info); + + render_fence = dev.createFence(vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); + + image_wait_semaphore = dev.createSemaphore(vk::SemaphoreCreateInfo{}); + render_wait_semaphore = dev.createSemaphore(vk::SemaphoreCreateInfo{}); + + render_pass = std::make_unique(dev); + swapchain = std::make_unique(dev, surface, win.getDimensions(), *render_pass, depth_image_view); + + command_buffer = std::make_unique(dev, queue_family); + } void Renderer::draw() { + Log::info("draw() called \n"); + + dev.waitForFences(render_fence, true, UINT64_MAX); + dev.resetFences(render_fence); + /* check if the swapchain is still good (no resize) */ - auto image_ret = dev.acquireNextImageKHR(*swapchain, UINT64_MAX); + auto image_ret = dev.acquireNextImageKHR(*swapchain, UINT64_MAX, image_wait_semaphore); if (image_ret.result == vk::Result::eErrorOutOfDateKHR) { swapchain->recreate(win.getDimensions()); } current_image_idx = image_ret.value; + /* prepare command buffer for recording commands */ command_buffer->recycle(); command_buffer->begin(); + + vk::ClearValue clear_values[] = { + vk::ClearColorValue(1.0f, 0.0f, 1.0f, 1.0f), + vk::ClearDepthStencilValue {.depth = 1.0f} + }; + /* use renderpass to transform images from unspecified layout to a presentable one while clearing */ + auto render_pass_info = vk::RenderPassBeginInfo { + .renderPass = *render_pass, + .framebuffer = swapchain->framebuffers[current_image_idx], + .renderArea = { + .offset = { 0, 0 }, + .extent = swapchain->extent, + }, + .clearValueCount = std::size(clear_values), + .pClearValues = clear_values, + }; + auto viewport = vk::Viewport{ + .x = 0.0f, + .y = 0.0f, + .width = static_cast(swapchain->extent.width), + .height = static_cast(swapchain->extent.height), + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + auto scissor = vk::Rect2D{ + .offset = {0, 0}, + .extent = swapchain->extent, + }; + + + /* no secondary command buffers (yet), so contents are passed inline */ + command_buffer->command_buffer.beginRenderPass(render_pass_info, vk::SubpassContents::eInline); + command_buffer->command_buffer.setViewport(0, viewport); + command_buffer->command_buffer.setScissor(0, scissor); + command_buffer->command_buffer.endRenderPass(); + command_buffer->end(); + + + vk::PipelineStageFlags stage_flags = vk::PipelineStageFlagBits::eColorAttachmentOutput; + + /* submit our command buffer to the queue */ + auto submit_info = vk::SubmitInfo{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &image_wait_semaphore, + .pWaitDstStageMask = &stage_flags, + .commandBufferCount = 1, + .pCommandBuffers = &command_buffer->command_buffer, + .signalSemaphoreCount = 1, + .pSignalSemaphores = &render_wait_semaphore, + }; + + queue.submit(submit_info, render_fence); } void Renderer::present() { + Log::info("present() called \n"); auto present_info = vk::PresentInfoKHR{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &render_wait_semaphore, .swapchainCount = 1, - .pSwapchains = &swapchain->operator vk::SwapchainKHR &(), + .pSwapchains = &swapchain->operator vk::SwapchainKHR & (), .pImageIndices = ¤t_image_idx, }; @@ -239,6 +309,7 @@ void Renderer::present() { break; case vk::Result::eSuboptimalKHR: case vk::Result::eErrorOutOfDateKHR: + Log::info("Recreating swapchain"); swapchain->recreate(win.getDimensions()); break; default: @@ -250,9 +321,13 @@ void Renderer::present() { Renderer::~Renderer() { dev.destroyImage(depth_image); dev.destroyImageView(depth_image_view); + dev.freeMemory(depth_alloc); + swapchain.reset(); dev.waitIdle(); + command_buffer->cleanup(dev); + render_pass->cleanup(dev); dev.destroy(); instance.destroySurfaceKHR(surface); instance.destroy(); diff --git a/Renderer/Renderer.hpp b/Renderer/Renderer.hpp index 3673321..fc6e62a 100644 --- a/Renderer/Renderer.hpp +++ b/Renderer/Renderer.hpp @@ -7,6 +7,7 @@ #include #include +#include struct Window; @@ -23,6 +24,8 @@ struct Renderer { vk::Instance instance; vk::Device dev; + vk::Fence render_fence; + vk::Semaphore image_wait_semaphore, render_wait_semaphore; vk::SurfaceKHR surface; std::unique_ptr swapchain; @@ -30,8 +33,11 @@ struct Renderer { vk::Queue queue; std::unique_ptr command_buffer; + std::unique_ptr render_pass; + uint32_t current_image_idx; vk::Image depth_image; vk::ImageView depth_image_view; + vk::DeviceMemory depth_alloc; }; diff --git a/Renderer/Swapchain.cpp b/Renderer/Swapchain.cpp index de52917..62e692d 100644 --- a/Renderer/Swapchain.cpp +++ b/Renderer/Swapchain.cpp @@ -1,10 +1,12 @@ #include -Swapchain::Swapchain(vk::Device& dev, const vk::SurfaceKHR& surface, const vk::Extent2D& extent) : dev(dev), surface(surface) { +Swapchain::Swapchain(vk::Device& dev, const vk::SurfaceKHR& surface, const vk::Extent2D& extent, RenderPass render_pass, vk::ImageView depth_image_view) + : dev(dev), surface(surface), render_pass(render_pass), depth_image_view(depth_image_view) { create(extent); } void Swapchain::create(const vk::Extent2D& extent, vk::SwapchainKHR old_swapchain) { + this->extent = extent; auto format = vk::Format::eB8G8R8A8Unorm; @@ -33,8 +35,9 @@ void Swapchain::create(const vk::Extent2D& extent, vk::SwapchainKHR old_swapchai images = dev.getSwapchainImagesKHR(swapchain); views.resize(images.size()); - for(size_t i = 0; i < views.size(); i++) { - auto color_image_info = vk::ImageViewCreateInfo { + framebuffers.resize(images.size()); + for (size_t i = 0; i < views.size(); i++) { + auto color_image_info = vk::ImageViewCreateInfo{ .image = images[i], .viewType = vk::ImageViewType::e2D, .format = format, @@ -52,8 +55,19 @@ void Swapchain::create(const vk::Extent2D& extent, vk::SwapchainKHR old_swapchai .layerCount = 1, } }; - views[i] = dev.createImageView(color_image_info); + + vk::ImageView attachments[] = { views[i], depth_image_view }; + auto framebuffer_info = vk::FramebufferCreateInfo { + .renderPass = render_pass, + .attachmentCount = static_cast(std::size(attachments)), + .pAttachments = attachments, + .width = extent.width, + .height = extent.height, + .layers = 1, + }; + + framebuffers[i] = dev.createFramebuffer(framebuffer_info); } } @@ -61,10 +75,12 @@ void Swapchain::create(const vk::Extent2D& extent, vk::SwapchainKHR old_swapchai void Swapchain::recreate(const vk::Extent2D& extent) { dev.waitIdle(); cleanup(); - create(extent); + create(extent, swapchain); } void Swapchain::cleanup() { + for(auto& fb : framebuffers) + dev.destroyFramebuffer(fb); dev.destroySwapchainKHR(swapchain); } diff --git a/Renderer/Swapchain.hpp b/Renderer/Swapchain.hpp index 61e71e0..00e2a4d 100644 --- a/Renderer/Swapchain.hpp +++ b/Renderer/Swapchain.hpp @@ -3,8 +3,12 @@ #define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS #include + +#include + + struct Swapchain { - Swapchain(vk::Device& dev, const vk::SurfaceKHR& surface, const vk::Extent2D& extent); + Swapchain(vk::Device& dev, const vk::SurfaceKHR& surface, const vk::Extent2D& extent, RenderPass render_pass, vk::ImageView depth_image_view); vk::SwapchainKHR swapchain; inline operator vk::SwapchainKHR& () { return swapchain; @@ -14,6 +18,12 @@ struct Swapchain { vk::SurfaceKHR surface; std::vector images; std::vector views; + std::vector framebuffers; + + RenderPass render_pass; + vk::ImageView depth_image_view; + + vk::Extent2D extent; void create(const vk::Extent2D& extent, vk::SwapchainKHR old_swapchain = nullptr); void recreate(const vk::Extent2D& extent); diff --git a/pléascach.cpp b/pléascach.cpp index f46c938..21cb0b6 100644 --- a/pléascach.cpp +++ b/pléascach.cpp @@ -3,13 +3,11 @@ #include #include - - #include -int main() { +int main(int argc, char* argv[]) { try { - Window win("Test", 256, 512); + Window win(argv[0], 256, 512); auto in = win.getInput(); Renderer ren(win); @@ -23,7 +21,6 @@ int main() { switch (event.tag) { case InputEvent::Tag::RESIZE: Log::info("Event Processed: Resized to %dx%d\n", event.resize.width, event.resize.height); - win.setDimensions(event.resize.width, event.resize.width); /* no need to have a resize() function in the renderer, b/c swapchain images will be * automatically marked out-of-date, and recreation will be triggered in our code */ @@ -36,6 +33,9 @@ int main() { break; } } + + ren.draw(); + ren.present(); } } catch (const std::string& e) { diff --git a/renderer/RenderPass.cpp b/renderer/RenderPass.cpp new file mode 100644 index 0000000..b0f6929 --- /dev/null +++ b/renderer/RenderPass.cpp @@ -0,0 +1,81 @@ +#include + +RenderPass::RenderPass(vk::Device dev, vk::Format image_format, vk::Format depth_format) { + /* transform color image from UNDEFINED format to presentable one for rendering */ + auto color_attach_desc = vk::AttachmentDescription { + .format = image_format, + .samples = vk::SampleCountFlagBits::e1, + /* since its set to clear, it clears the initial image to the specified + clear color instead of loading the data */ + .loadOp = vk::AttachmentLoadOp::eClear, + /* we care about the data after the end of the renderpass, so store */ + .storeOp = vk::AttachmentStoreOp::eStore, + /* for the depth stencil buffer, we care about neither */ + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::ePresentSrcKHR, + }; + + auto color_attach_ref = vk::AttachmentReference { .attachment = 0, .layout = vk::ImageLayout::eColorAttachmentOptimal }; + + auto depth_attach_desc = vk::AttachmentDescription{ + .format = depth_format, + .samples = vk::SampleCountFlagBits::e1, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .stencilLoadOp = vk::AttachmentLoadOp::eClear, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + }; + + auto depth_attach_ref = vk::AttachmentReference{ .attachment = 1, .layout = vk::ImageLayout::eDepthStencilAttachmentOptimal }; + + /* only one subpass for now, uses both attachments */ + auto subpass_desc = vk::SubpassDescription { + .pipelineBindPoint = vk::PipelineBindPoint::eGraphics, + .colorAttachmentCount = 1, + .pColorAttachments = &color_attach_ref, + .pDepthStencilAttachment = &depth_attach_ref, + }; + + + /* designates producer and consumer of the image to position subpass */ + auto color_dep = vk::SubpassDependency{ + .srcSubpass = vk::SubpassExternal, + .dstSubpass = 0, + .srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput, + .dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput, + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite, + }; + + auto depth_dep = vk::SubpassDependency { + .srcSubpass = vk::SubpassExternal, + .dstSubpass = 0, + .srcStageMask = vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests, + .dstStageMask = vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests, + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite, + }; + + vk::AttachmentDescription descs[] = { color_attach_desc, depth_attach_desc }; + vk::SubpassDependency deps[] = { color_dep, depth_dep}; + vk::SubpassDescription subpasses[] = {subpass_desc}; + + auto pass_info = vk::RenderPassCreateInfo { + .attachmentCount = static_cast(std::size(descs)), + .pAttachments = descs, + .subpassCount = static_cast(std::size(subpasses)), + .pSubpasses = subpasses, + .dependencyCount = static_cast(std::size(deps)), + .pDependencies = deps, + }; + + render_pass = dev.createRenderPass(pass_info); +} + +void RenderPass::cleanup(vk::Device dev) { + dev.destroyRenderPass(render_pass); +} \ No newline at end of file diff --git a/renderer/RenderPass.hpp b/renderer/RenderPass.hpp new file mode 100644 index 0000000..738cd43 --- /dev/null +++ b/renderer/RenderPass.hpp @@ -0,0 +1,16 @@ +#pragma once + +#define VULKAN_HPP_NO_STRUCT_CONSTRUCTORS +#include + +struct RenderPass { + vk::RenderPass render_pass; + + RenderPass(vk::Device dev, vk::Format image_format = vk::Format::eB8G8R8A8Unorm, vk::Format depth_format = vk::Format::eD16Unorm); + + void cleanup(vk::Device dev); + + operator vk::RenderPass&() { + return render_pass; + } +}; \ No newline at end of file