#include <stdafx.h> #include "Context.h" #include "SwapChain.h" #include "Buffer/DepthBuffer.h" #include "RenderPass.h" #include "Scene.h" #include "Texture.h" #include "VulkanWindow.h" #include "Model.h" namespace fpr { SwapChain::SwapChain( fpr::Device* device, vk::SurfaceKHR& surface, std::optional<vk::UniqueSwapchainKHR> old_swapchain, glm::ivec2 tile_size) { if(old_swapchain != std::nullopt) { m_old_swapchain = std::move(*old_swapchain); is_recreation = true; } CreateSwapchain(device, surface); CreateImageViews(device); m_depth_buffer = std::make_unique<fpr::DepthBuffer>(device, this); CreateRenderPasses(device); CreateFrameBuffers(device); CreateSynchronisation(device); // Construct push constants. Used in shaders. glm::ivec2 viewport_size = { m_extent.width, m_extent.height }; m_viewport_push_constant.viewport_size = viewport_size; m_viewport_push_constant.tile_size = tile_size; m_viewport_push_constant.horizontal_tile_count = (viewport_size.x + 1) / (tile_size.x - 1); m_viewport_push_constant.vertical_tile_count = (viewport_size.y + 1) / (tile_size.y - 1); m_viewport_push_constant.MAX_LIGHTS_PER_TILE = FPR_MAX_LIGHTS_PER_TILE; m_viewport_push_constant.MAX_LIGHTS = FPR_MAX_LIGHTS; } vk::UniqueSwapchainKHR& SwapChain::GetUniqueHandle() { return m_swapchain; } void SwapChain::CreateSwapchain(fpr::Device* device, vk::SurfaceKHR& surface) { auto [surface_capabilities, surface_formats, present_modes] = QuerySwapChainDetails(device, surface); auto fifo_or_immediate = [&](const std::vector<vk::PresentModeKHR>& present_modes) { auto search_result = std::find(present_modes.begin(), present_modes.end(), vk::PresentModeKHR::eFifo); if(search_result != present_modes.end()) return *search_result; else return vk::PresentModeKHR::eImmediate; }; m_surface_format = QueryFirstSuitableFormat(surface_formats); m_present_mode = fifo_or_immediate(present_modes); m_extent = QueryExtenfromCapabilities(surface_capabilities); vk::SwapchainCreateInfoKHR swap_chain_create_info{}; // Determine how many swapchain images can be handled. Allowing for double/triple buffering. uint32_t image_count = surface_capabilities.minImageCount + 1; if(surface_capabilities.maxImageCount > 0 && image_count > surface_capabilities.maxImageCount) { if(image_count < surface_capabilities.maxImageCount) { image_count++; } else { image_count = surface_capabilities.minImageCount; } } MAX_IMAGES_IN_FLIGHT = image_count; swap_chain_create_info.setSurface(surface); swap_chain_create_info.imageFormat = m_surface_format.format; swap_chain_create_info.imageColorSpace = m_surface_format.colorSpace; swap_chain_create_info.imageExtent = m_extent; swap_chain_create_info.presentMode = m_present_mode; swap_chain_create_info.minImageCount = image_count; swap_chain_create_info.imageArrayLayers = 1; swap_chain_create_info.imageUsage = vk::ImageUsageFlagBits::eColorAttachment; swap_chain_create_info.preTransform = surface_capabilities.currentTransform; swap_chain_create_info.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; swap_chain_create_info.clipped = VK_TRUE; swap_chain_create_info.oldSwapchain = is_recreation ? *m_old_swapchain : VK_NULL_HANDLE; uint32_t queue_family_indices[] = { device->GetGraphicsQueueIndex(), device->GetPresentQueueIndex() }; if(queue_family_indices[0] != queue_family_indices[1]) { swap_chain_create_info.imageSharingMode = vk::SharingMode::eConcurrent; swap_chain_create_info.queueFamilyIndexCount = 2; swap_chain_create_info.pQueueFamilyIndices = queue_family_indices; } else { swap_chain_create_info.imageSharingMode = vk::SharingMode::eExclusive; swap_chain_create_info.queueFamilyIndexCount = 0; swap_chain_create_info.pQueueFamilyIndices = nullptr; } auto [swapchain_result, swapchain] = device->GetLogicalDeviceHandle().createSwapchainKHRUnique(swap_chain_create_info); // todo: swapchain result m_swapchain = std::move(swapchain); } void SwapChain::CreateImageViews(fpr::Device* device) { auto [images_result, swapchain_images] = device->GetLogicalDeviceHandle().getSwapchainImagesKHR(*m_swapchain); for(const auto& image : swapchain_images) { vk::ImageViewCreateInfo image_view_create_info{}; image_view_create_info.setImage(image) .setViewType(IMAGE_VIEW_TYPE) .setFormat(m_surface_format.format) .setSubresourceRange(vk::ImageSubresourceRange() .setAspectMask(ASPECT_FLAGS) .setBaseArrayLayer(0) .setBaseMipLevel(0) .setLayerCount(1) .setLevelCount(1)); auto [result_signal, image_view] = device->GetLogicalDeviceHandle().createImageViewUnique(image_view_create_info); assert(("Failed to create image view!", result_signal == vk::Result::eSuccess)); m_swapchain_images.emplace_back(std::make_pair(image, std::move(image_view))); } } void SwapChain::SyncPresentation(fpr::Device* device, uint32_t image_index) { // Sync swapchain presentation so it happens after the final color pass. vk::PresentInfoKHR present_info{}; present_info.setImageIndices(image_index); present_info.setSwapchains(m_swapchain.get()); present_info.setWaitSemaphores(m_render_done_semaphores[m_current_frame].get()); [[maybe_unused]] auto present_res = device->GetGraphicsQueue().presentKHR(present_info); assert(present_res == vk::Result::eSuccess); } void SwapChain::SyncCompute(fpr::Device* device) { // Sync the compute light culling shader. // Depth prepass -> Compute -> Final shading vk::SubmitInfo compute_submit_info{}; vk::PipelineStageFlags stages = { vk::PipelineStageFlagBits::eComputeShader }; compute_submit_info.setSignalSemaphores(m_compute_semaphores[m_current_frame].get()) .setCommandBuffers(m_compute_command_buffers[m_current_frame].get()) .setWaitSemaphores(m_earlyz_semaphores[m_current_frame].get()) .setWaitDstStageMask(stages); [[maybe_unused]] vk::Result submit_res = device->GetComputeQueue().submit(compute_submit_info); assert(("Failed to sync compute pass.", submit_res == vk::Result::eSuccess)); } void SwapChain::SyncColorPass(fpr::Device* device) { // Sync the final shading stage. // Depth prepass -> Compute -> Final shading std::array<vk::PipelineStageFlags, 2> wait_flags = { vk::PipelineStageFlagBits::eFragmentShader, vk::PipelineStageFlagBits::eColorAttachmentOutput }; std::array<vk::Semaphore, 2> wait_semaphores{ m_compute_semaphores[m_current_frame].get(), m_image_ready_semaphores[m_current_frame].get() }; vk::SubmitInfo submit_info{}; submit_info.setWaitSemaphores(wait_semaphores); submit_info.commandBufferCount = 1; submit_info.pCommandBuffers = &m_command_buffers[m_current_frame].get(); submit_info.setWaitDstStageMask(wait_flags); submit_info.signalSemaphoreCount = 1; submit_info.pSignalSemaphores = &m_render_done_semaphores[m_current_frame].get(); [[maybe_unused]] auto submit_res = device->GetGraphicsQueue().submit(submit_info, *m_draw_fence); assert(submit_res == vk::Result::eSuccess); } void SwapChain::CreateRenderPasses(fpr::Device* device) { auto& context = fpr::Context::Get(); vk::Format depth_format = m_depth_buffer->GetDepthImageFormat(); const fpr::RenderPassOptions& color_pass_options = RenderPass::MakeDefaultRenderPass(m_surface_format.format, depth_format); const fpr::RenderPassOptions& early_z_options = RenderPass::MakeDefaultDepthPass(depth_format); context.m_render_graph->RenderPasses.Add("Default", std::make_unique<fpr::RenderPass>(color_pass_options, device)); context.m_render_graph->RenderPasses.Add("EarlyZ", std::make_unique<fpr::RenderPass>(early_z_options, device)); } const ViewportConstants& SwapChain::GetViewportConstants() const { return m_viewport_push_constant; } void SwapChain::SyncDepthPass(fpr::Device* device) { // Depth prepass -> Compute -> Final shading vk::SubmitInfo depth_submit_info{}; depth_submit_info.setSignalSemaphores(m_earlyz_semaphores[m_current_frame].get()) .setCommandBuffers(m_depth_command_buffers[m_current_frame].get()); [[maybe_unused]] vk::Result depth_submit_res = device->GetGraphicsQueue().submit(depth_submit_info); assert(("Failed to sync depth pass.", depth_submit_res == vk::Result::eSuccess)); } void SwapChain::CreateFrameBuffers(fpr::Device* device) { // Create swapchain framebuffers. // One is used for color shading and swapchain image presentation. // Other is used for the depth buffer. auto& context = fpr::Context::Get(); auto renderpass = context.m_render_graph->RenderPasses.Get("Default"); auto depth_pass = context.m_render_graph->RenderPasses.Get("EarlyZ"); m_swapchain_framebuffers.reserve(m_swapchain_images.size()); for(size_t i = 0; i < m_swapchain_images.size(); ++i) { std::array<vk::ImageView, 2> attachments = { m_swapchain_images[i].second.get(), m_depth_buffer->GetDepthBufferView() }; vk::FramebufferCreateInfo framebuffer_create_info{}; framebuffer_create_info.renderPass = renderpass->GetvkRenderPass(); framebuffer_create_info.pAttachments = attachments.data(); framebuffer_create_info.attachmentCount = (uint32_t)attachments.size(); framebuffer_create_info.width = m_extent.width; framebuffer_create_info.height = m_extent.height; framebuffer_create_info.layers = 1; auto [fb_result, framebuffer] = device->GetLogicalDeviceHandle().createFramebufferUnique(framebuffer_create_info); assert(("Failed to create framebuffer!!", fb_result == vk::Result::eSuccess)); m_swapchain_framebuffers.push_back(std::move(framebuffer)); } std::array<vk::ImageView, 1> depth_image_view = { m_depth_buffer->GetDepthBufferView() }; for(size_t i = 0; i < m_swapchain_images.size(); ++i) { vk::FramebufferCreateInfo framebuffer_create_info = {}; framebuffer_create_info.renderPass = depth_pass->GetvkRenderPass(); framebuffer_create_info.setAttachments(depth_image_view); framebuffer_create_info.width = m_extent.width; framebuffer_create_info.height = m_extent.height; framebuffer_create_info.layers = 1; auto [fb_result, depth_framebuffer] = device->GetLogicalDeviceHandle().createFramebufferUnique(framebuffer_create_info); assert(("Failed to create depth framebuffer!!", fb_result == vk::Result::eSuccess)); m_depth_framebuffers.emplace_back(std::move(depth_framebuffer)); } } void SwapChain::RecordCommands() { RecordDepthCommands(); RecordComputeCommands(); RecordRenderCommands(); } void SwapChain::CreateCommandBuffers() { // Tree command buffers are required. // Grapphics command buffer, depth pre pass command buffer and compute command buffer. auto& context = fpr::Context::Get(); vk::CommandBufferAllocateInfo command_buffer_alloc_info{}; command_buffer_alloc_info.commandPool = context.GetGraphicsCmdPool(); command_buffer_alloc_info.level = vk::CommandBufferLevel::ePrimary; command_buffer_alloc_info.commandBufferCount = (uint32_t)m_swapchain_framebuffers.size(); auto [cmd_alloc_result, cmd_buffers] = context.GetDevice()->GetLogicalDeviceHandle().allocateCommandBuffersUnique(command_buffer_alloc_info); assert(("Failed to create command buffers!", cmd_alloc_result == vk::Result::eSuccess)); m_command_buffers = std::move(cmd_buffers); vk::CommandBufferAllocateInfo depth_buffer_alloc{}; depth_buffer_alloc.commandPool = context.GetGraphicsCmdPool(); depth_buffer_alloc.level = vk::CommandBufferLevel::ePrimary; depth_buffer_alloc.commandBufferCount = (uint32_t)m_swapchain_framebuffers.size(); auto [depth_cmd_alloc_result, depth_cmd_buffers] = context.GetDevice()->GetLogicalDeviceHandle().allocateCommandBuffersUnique(depth_buffer_alloc); assert(("Failed to create depth command buffers", depth_cmd_alloc_result == vk::Result::eSuccess)); m_depth_command_buffers = std::move(depth_cmd_buffers); vk::CommandBufferAllocateInfo compute_buffer_alloc{}; compute_buffer_alloc.commandPool = context.GetComputeCmdPool(); compute_buffer_alloc.level = vk::CommandBufferLevel::ePrimary; compute_buffer_alloc.commandBufferCount = (uint32_t)m_swapchain_framebuffers.size(); auto [compute_cmd_alloc_result, compute_cmd_buffers] = context.GetDevice()->GetLogicalDeviceHandle().allocateCommandBuffersUnique(compute_buffer_alloc); assert(("Failed to create compute command buffers", compute_cmd_alloc_result == vk::Result::eSuccess)); m_compute_command_buffers = std::move(compute_cmd_buffers); } void SwapChain::CreateSynchronisation(fpr::Device* device) { // Create all the synchronisation objets. // Fences and semaphores are used to ensure that buffers being read from aren't currently being wriitten to and vice // versa. m_image_ready_semaphores.resize(MAX_IMAGES_IN_FLIGHT); m_render_done_semaphores.resize(MAX_IMAGES_IN_FLIGHT); m_earlyz_semaphores.resize(MAX_IMAGES_IN_FLIGHT); m_compute_semaphores.resize(MAX_IMAGES_IN_FLIGHT); vk::SemaphoreCreateInfo semaphore_create_info{}; vk::FenceCreateInfo fence_create_info(vk::FenceCreateFlagBits::eSignaled); // Simple lambda that creates an unique handle to a semaphore. Used to construct all the semaphores used for // synchronisation. auto create_semaphore = [&](vk::UniqueSemaphore& s) { auto [semaphore_result, semaphore] = device->GetLogicalDeviceHandle().createSemaphoreUnique(semaphore_create_info); assert(("Semaphore creation failed!", semaphore_result == vk::Result::eSuccess)); s = std::move(semaphore); }; // Simple lambda that creates an unique handle to a fence auto create_fence = [&](vk::UniqueFence& f) { auto [fence_result, fence] = device->GetLogicalDeviceHandle().createFenceUnique(fence_create_info); assert(("Failed to create fence!", fence_result == vk::Result::eSuccess)); f = std::move(fence); }; std::for_each(m_image_ready_semaphores.begin(), m_image_ready_semaphores.end(), create_semaphore); std::for_each(m_render_done_semaphores.begin(), m_render_done_semaphores.end(), create_semaphore); std::for_each(m_earlyz_semaphores.begin(), m_earlyz_semaphores.end(), create_semaphore); std::for_each(m_compute_semaphores.begin(), m_compute_semaphores.end(), create_semaphore); create_fence(m_draw_fence); } vk::Result SwapChain::SubmitFrame() { auto device = fpr::Context::Get().GetDevice(); uint32_t image_index; [[maybe_unused]] vk::Result fence_result = device->GetLogicalDeviceHandle().waitForFences(*m_draw_fence, false, std::numeric_limits<uint64_t>::max()); [[maybe_unused]] vk::Result fence_reset_result = device->GetLogicalDeviceHandle().resetFences(*m_draw_fence); assert(("Timed out waiting for draw fence", fence_result == vk::Result::eSuccess)); assert(("Failed to reset draw fence", fence_reset_result == vk::Result::eSuccess)); // Ensure that an image is ready and the prior frame has been submitted. vk::Result acquire_image_result = device->GetLogicalDeviceHandle().acquireNextImageKHR( *m_swapchain, std::numeric_limits<uint64_t>::max(), *m_image_ready_semaphores[m_current_frame], nullptr, &image_index); if(acquire_image_result != vk::Result::eSuccess) return acquire_image_result; // Synchronise the different command buffers so they execute in sequence. SyncDepthPass(device); SyncCompute(device); SyncColorPass(device); // Synchronise presentation and present image. SyncPresentation(device, image_index); m_current_frame = (m_current_frame + 1) % MAX_IMAGES_IN_FLIGHT; return acquire_image_result; } SwapChain::SwapchainDetails SwapChain::QuerySwapChainDetails(fpr::Device* device, vk::SurfaceKHR& surface) { auto [capabilities_result, surface_capabilities] = device->GetPhysicalDeviceHandle().getSurfaceCapabilitiesKHR(surface); auto [formats_result, formats] = device->GetPhysicalDeviceHandle().getSurfaceFormatsKHR(surface); auto [modes_result, present_modes] = device->GetPhysicalDeviceHandle().getSurfacePresentModesKHR(surface); return std::make_tuple(surface_capabilities, formats, present_modes); } vk::SurfaceFormatKHR SwapChain::QueryFirstSuitableFormat(const std::vector<vk::SurfaceFormatKHR>& formats) { for(const auto& available_format : formats) { for(const auto& wanted_format : wanted_formats) { if(wanted_format == available_format.format && wanted_color_space == available_format.colorSpace) { return available_format; } } } return vk::SurfaceFormatKHR{}; } const std::vector<SwapChain::SwapChainImage>& SwapChain::GetImages() { return m_swapchain_images; } void SwapChain::RecordRenderCommands() { auto& context = fpr::Context::Get(); auto render_graph = context.m_render_graph.get(); auto graphics_pipeline = render_graph->GraphicsPipelines.Get("Graphics"); for(size_t i = 0; i < m_command_buffers.size(); ++i) { vk::CommandBufferBeginInfo cmd_begin_info; cmd_begin_info.flags = vk::CommandBufferUsageFlagBits::eSimultaneousUse; [[maybe_unused]] auto cmd_begin_result = m_command_buffers[i]->begin(cmd_begin_info); assert(cmd_begin_result == vk::Result::eSuccess); vk::RenderPassBeginInfo render_pass_info = {}; render_pass_info.renderPass = render_graph->RenderPasses.Get("Default")->GetvkRenderPass(); render_pass_info.framebuffer = m_swapchain_framebuffers[i].get(); render_pass_info.setRenderArea({ { 0, 0 }, m_extent }); render_pass_info.setClearValues(m_clear_values); m_command_buffers[i]->beginRenderPass(render_pass_info, vk::SubpassContents::eInline); //Viewport can be inverted in the Y axis. However, it is less overheadd to invert the projection matrix. vk::Viewport viewport; viewport.x = 0.0f; viewport.y = 0.0f; // static_cast<float>(m_extent.height); viewport.width = static_cast<float>(m_extent.width); viewport.height = static_cast<float>(m_extent.height); // -static_cast<float>(m_extent.height); viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; vk::Rect2D scissor{}; scissor.setOffset({ 0, 0 }); scissor.extent = m_extent; m_command_buffers[i]->setViewport(0, viewport); m_command_buffers[i]->setScissor(0, scissor); m_command_buffers[i]->bindPipeline(vk::PipelineBindPoint::eGraphics, graphics_pipeline->GetPipeline()); m_command_buffers[i]->pushConstants<ViewportConstants>( graphics_pipeline->GetLayout(), vk::ShaderStageFlagBits::eFragment, 0, m_viewport_push_constant); fpr::Scene* scene = context.m_loaded_scene; auto cam_desc_set = render_graph->DescriptorSets.Get("Camera" + std::to_string(i)); auto light_desc_set = render_graph->DescriptorSets.Get("PointLight" + std::to_string(i)); //Draw each mesh of each model. for(auto& model : scene->GetModels()) { m_command_buffers[i]->bindIndexBuffer(model->GetIndexBuffer().GetBuffer(), 0, vk::IndexType::eUint32); for(auto&& mesh : model->GetMeshes()) { // Each mesh is stored in the same vertex buffer as other meshes that make up a modele, thus an offset is needed. m_command_buffers[i]->bindVertexBuffers(0, model->GetVertexBuffer().GetBuffer(), mesh.GetVertexOffset()); std::vector<vk::DescriptorSet> desc_sets = { cam_desc_set, }; m_command_buffers[i]->bindDescriptorSets( vk::PipelineBindPoint::eGraphics, graphics_pipeline->GetLayout(), 0, cam_desc_set, nullptr); m_command_buffers[i]->bindDescriptorSets( vk::PipelineBindPoint::eGraphics, graphics_pipeline->GetLayout(), 1, render_graph->DescriptorSets.Get("Model" + std::to_string(i)), (uint32_t)(mesh.GetMeshIndex() * context.GetRequiredDynamicAlignment())); m_command_buffers[i]->bindDescriptorSets( vk::PipelineBindPoint::eGraphics, graphics_pipeline->GetLayout(), 2, mesh.GetTexture()->GetDescriptorSet(), nullptr); m_command_buffers[i]->bindDescriptorSets( vk::PipelineBindPoint::eGraphics, graphics_pipeline->GetLayout(), 3, light_desc_set, nullptr); //Each mesh is stored in the same index buffer as other meshes that make up a modele, thus an offset is needed. m_command_buffers[i]->drawIndexed(mesh.GetIndexCount(), 1, (uint32_t)mesh.GetIndexOffset(), 0, 0); } } m_command_buffers[i]->endRenderPass(); [[maybe_unused]] auto cmd_end_result = m_command_buffers[i]->end(); assert(cmd_end_result == vk::Result::eSuccess); } } void SwapChain::RecordDepthCommands() { //Depth prepass. auto& context = fpr::Context::Get(); fpr::RenderGraph* render_graph = context.m_render_graph.get(); fpr::Pipeline* depth_pipeline = render_graph->GraphicsPipelines.Get("Depth"); for(size_t i = 0; i < m_depth_command_buffers.size(); ++i) { vk::CommandBufferBeginInfo cmd_begin_info; cmd_begin_info.flags = vk::CommandBufferUsageFlagBits::eSimultaneousUse; [[maybe_unused]] auto cmd_begin_result = m_depth_command_buffers[i]->begin(cmd_begin_info); assert(cmd_begin_result == vk::Result::eSuccess); vk::RenderPassBeginInfo render_pass_info = {}; render_pass_info.renderPass = context.m_render_graph->RenderPasses.Get("EarlyZ")->GetvkRenderPass(); render_pass_info.framebuffer = m_depth_framebuffers[i].get(); render_pass_info.setRenderArea({ { 0, 0 }, m_extent }); render_pass_info.setClearValues(m_clear_values[1]); m_depth_command_buffers[i]->beginRenderPass(render_pass_info, vk::SubpassContents::eInline); //Viewport not inverted, instead projection matrix is. vk::Viewport viewport; viewport.x = 0.0f; viewport.y = 0.0f; viewport.width = static_cast<float>(m_extent.width); viewport.height = static_cast<float>(m_extent.height); viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; vk::Rect2D scissor{}; scissor.setOffset({ 0, 0 }); scissor.extent = m_extent; m_depth_command_buffers[i]->setViewport(0, viewport); m_depth_command_buffers[i]->setScissor(0, scissor); m_depth_command_buffers[i]->bindPipeline(vk::PipelineBindPoint::eGraphics, depth_pipeline->GetPipeline()); auto scene = context.m_loaded_scene; //Buffers used in the depth prepass shader. std::array<vk::DescriptorSet, 2> desc_sets = { render_graph->DescriptorSets.Get("Camera" + std::to_string(i)), render_graph->DescriptorSets.Get("Model" + std::to_string(i)), }; for(auto&& model : scene->GetModels()) { m_depth_command_buffers[i]->bindIndexBuffer(model->GetIndexBuffer().GetBuffer(), 0, vk::IndexType::eUint32); for(auto&& mesh : model->GetMeshes()) { m_depth_command_buffers[i]->bindVertexBuffers(0U, model->GetVertexBuffer().GetBuffer(), mesh.GetVertexOffset()); m_depth_command_buffers[i]->bindDescriptorSets( vk::PipelineBindPoint::eGraphics, depth_pipeline->GetLayout(), 0, render_graph->DescriptorSets.Get("Camera" + std::to_string(i)), nullptr); m_depth_command_buffers[i]->bindDescriptorSets( vk::PipelineBindPoint::eGraphics, depth_pipeline->GetLayout(), 1, render_graph->DescriptorSets.Get("Model" + std::to_string(i)), (uint32_t)(mesh.GetMeshIndex() * context.GetRequiredDynamicAlignment())); m_depth_command_buffers[i]->drawIndexed(mesh.GetIndexCount(), 1, (uint32_t)mesh.GetIndexOffset(), 0, 0); } } m_depth_command_buffers[i]->endRenderPass(); [[maybe_unused]] auto cmd_end_result = m_depth_command_buffers[i]->end(); assert(cmd_end_result == vk::Result::eSuccess); } } void SwapChain::RecordComputeCommands() { auto& context = fpr::Context::Get(); fpr::RenderGraph* render_graph = context.m_render_graph.get(); fpr::Pipeline* compute_pipeline = render_graph->ComputePipelines.Get("LightCulling"); for(size_t i = 0; i < m_compute_command_buffers.size(); ++i) { vk::CommandBufferBeginInfo cmd_begin_info; cmd_begin_info.flags = vk::CommandBufferUsageFlagBits::eSimultaneousUse; [[maybe_unused]] auto cmd_begin_result = m_compute_command_buffers[i]->begin(cmd_begin_info); assert(cmd_begin_result == vk::Result::eSuccess); //Need to retrieve the actual buffers, not descriptors. Light culling compute shaders require barriers to buffers. fpr::Buffer* culled_buffer = render_graph->Buffers.Get("VisibleLights" + std::to_string(m_current_frame)); fpr::Buffer* light_buffer = render_graph->Buffers.Get("PointLight" + std::to_string(m_current_frame)); std::vector<vk::BufferMemoryBarrier> barriers; barriers.emplace_back( vk::AccessFlagBits::eShaderRead, vk::AccessFlagBits::eShaderWrite, 0, 0, culled_buffer->GetBuffer(), 0, culled_buffer->GetSize()); barriers.emplace_back( vk::AccessFlagBits::eShaderRead, vk::AccessFlagBits::eShaderWrite, 0, 0, light_buffer->GetBuffer(), 0, light_buffer->GetSize()); //Pre cull barrier. m_compute_command_buffers[i]->pipelineBarrier( vk::PipelineStageFlagBits::eFragmentShader, vk::PipelineStageFlagBits::eComputeShader, {}, nullptr, barriers, nullptr); std::array<vk::DescriptorSet, 2> desc_sets = { render_graph->DescriptorSets.Get("Camera" + std::to_string(i)), render_graph->DescriptorSets.Get("PointLight" + std::to_string(i)) }; m_compute_command_buffers[i]->bindDescriptorSets( vk::PipelineBindPoint::eCompute, compute_pipeline->GetLayout(), 0, desc_sets, nullptr); m_compute_command_buffers[i]->bindDescriptorSets( vk::PipelineBindPoint::eCompute, compute_pipeline->GetLayout(), 2, render_graph->DescriptorSets.Get("DepthSampler"), nullptr); m_compute_command_buffers[i]->pushConstants<ViewportConstants>( compute_pipeline->GetLayout(), vk::ShaderStageFlagBits::eCompute, 0, m_viewport_push_constant); glm::vec3 group_count = { m_viewport_push_constant.horizontal_tile_count, m_viewport_push_constant.vertical_tile_count, 1 }; //Bind compute pipeline and dispatch shader for each tile. m_compute_command_buffers[i]->bindPipeline(vk::PipelineBindPoint::eCompute, compute_pipeline->GetPipeline()); m_compute_command_buffers[i]->dispatch((uint32_t)group_count.x, (uint32_t)group_count.y, (uint32_t)group_count.z); barriers.clear(); barriers.emplace_back( vk::AccessFlagBits::eShaderWrite, vk::AccessFlagBits::eShaderRead, 0, 0, culled_buffer->GetBuffer(), 0, culled_buffer->GetSize()); barriers.emplace_back( vk::AccessFlagBits::eShaderWrite, vk::AccessFlagBits::eShaderRead, 0, 0, light_buffer->GetBuffer(), 0, light_buffer->GetSize()); //Post cull barrier. m_compute_command_buffers[i]->pipelineBarrier( vk::PipelineStageFlagBits::eComputeShader, vk::PipelineStageFlagBits::eFragmentShader, {}, nullptr, barriers, nullptr); [[maybe_unused]] auto cmd_end_result = m_compute_command_buffers[i]->end(); assert(cmd_end_result == vk::Result::eSuccess); } } vk::Extent2D SwapChain::QueryExtenfromCapabilities(vk::SurfaceCapabilitiesKHR capabilities) { vk::Extent2D result{}; if(capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) { result = capabilities.currentExtent; } else { int width; int height; glfwGetFramebufferSize(fpr::Context::Get().GetWindow()->GetWindowHandle(), &width, &height); vk::Extent2D new_extent{}; uint32_t width_constrained = std::min(capabilities.maxImageExtent.width, static_cast<uint32_t>(width)); uint32_t height_constrained = std::min(capabilities.maxImageExtent.height, static_cast<uint32_t>(height)); new_extent.width = std::max(static_cast<uint32_t>(width), width_constrained); new_extent.height = std::max(static_cast<uint32_t>(height), height_constrained); result = new_extent; } return result; } const vk::Format& SwapChain::GetSwapchainImageFormat() const FPR_NOEXCEPT { return m_surface_format.format; } uint32_t SwapChain::GetCurrentFrameIdx() const { return m_current_frame; } uint32_t SwapChain::GetMaxFrameIdx() const { return MAX_IMAGES_IN_FLIGHT; } const vk::Extent2D& SwapChain::GetCurrentExtent() { return m_extent; } fpr::DepthBuffer* SwapChain::GetDepthBuffer() FPR_NOEXCEPT { return m_depth_buffer.get(); } } // namespace fpr