// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) // This file is part of the "Nazara Engine - Graphics module" // For conditions of distribution and use, see copyright notice in Config.hpp #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Nz { ForwardFramePipeline::ForwardFramePipeline() : m_renderablePool(4096), m_lightPool(64), m_viewerPool(8), m_worldInstances(2048), m_rebuildFrameGraph(true) { } ForwardFramePipeline::~ForwardFramePipeline() { // Force viewer passes to unregister their materials m_viewerPool.Clear(); } void ForwardFramePipeline::InvalidateViewer(std::size_t viewerIndex) { m_invalidatedViewerInstances.Set(viewerIndex); } void ForwardFramePipeline::InvalidateWorldInstance(std::size_t worldInstanceIndex) { m_invalidatedWorldInstances.Set(worldInstanceIndex); } std::size_t ForwardFramePipeline::RegisterLight(std::shared_ptr light, UInt32 renderMask) { std::size_t lightIndex; LightData* lightData = m_lightPool.Allocate(lightIndex); lightData->light = std::move(light); lightData->renderMask = renderMask; lightData->onLightInvalidated.Connect(lightData->light->OnLightDataInvalided, [=](Light*) { //TODO: Switch lights to storage buffers so they can all be part of GPU memory for (auto& viewerData : m_viewerPool) { UInt32 viewerRenderMask = viewerData.viewer->GetRenderMask(); if (viewerRenderMask & renderMask) viewerData.forwardPass->InvalidateElements(); } }); return lightIndex; } void ForwardFramePipeline::RegisterMaterialPass(MaterialPass* materialPass) { auto it = m_activeMaterialPasses.find(materialPass); if (it == m_activeMaterialPasses.end()) { it = m_activeMaterialPasses.emplace(materialPass, MaterialPassData{}).first; it->second.onMaterialPassInvalided.Connect(materialPass->OnMaterialPassInvalidated, [=](const MaterialPass* /*material*/) { m_invalidatedMaterialPasses.insert(materialPass); }); m_invalidatedMaterialPasses.insert(materialPass); } it->second.usedCount++; } std::size_t ForwardFramePipeline::RegisterRenderable(std::size_t worldInstanceIndex, const InstancedRenderable* instancedRenderable, UInt32 renderMask, const Recti& scissorBox) { std::size_t renderableIndex; RenderableData* renderableData = m_renderablePool.Allocate(renderableIndex); renderableData->renderable = instancedRenderable; renderableData->renderMask = renderMask; renderableData->scissorBox = scissorBox; renderableData->worldInstanceIndex = worldInstanceIndex; renderableData->onElementInvalidated.Connect(instancedRenderable->OnElementInvalidated, [=](InstancedRenderable* /*instancedRenderable*/) { // TODO: Invalidate only relevant viewers and passes for (auto& viewerData : m_viewerPool) { UInt32 viewerRenderMask = viewerData.viewer->GetRenderMask(); if (viewerRenderMask & renderMask) { if (viewerData.depthPrepass) viewerData.depthPrepass->InvalidateElements(); viewerData.forwardPass->InvalidateElements(); } } }); renderableData->onMaterialInvalidated.Connect(instancedRenderable->OnMaterialInvalidated, [this](InstancedRenderable* instancedRenderable, std::size_t materialIndex, const std::shared_ptr& newMaterial) { if (newMaterial) { for (auto& viewerData : m_viewerPool) { if (viewerData.depthPrepass) viewerData.depthPrepass->RegisterMaterial(*newMaterial); viewerData.forwardPass->RegisterMaterial(*newMaterial); } } const auto& prevMaterial = instancedRenderable->GetMaterial(materialIndex); if (prevMaterial) { for (auto& viewerData : m_viewerPool) { if (viewerData.depthPrepass) viewerData.depthPrepass->UnregisterMaterial(*prevMaterial); viewerData.forwardPass->UnregisterMaterial(*prevMaterial); } } }); std::size_t matCount = instancedRenderable->GetMaterialCount(); for (std::size_t i = 0; i < matCount; ++i) { if (Material* mat = instancedRenderable->GetMaterial(i).get()) { for (auto& viewerData : m_viewerPool) { if (viewerData.depthPrepass) viewerData.depthPrepass->RegisterMaterial(*mat); viewerData.forwardPass->RegisterMaterial(*mat); } } } return renderableIndex; } std::size_t ForwardFramePipeline::RegisterViewer(AbstractViewer* viewerInstance, Int32 renderOrder) { std::size_t viewerIndex; auto& viewerData = *m_viewerPool.Allocate(viewerIndex); viewerData.renderOrder = renderOrder; viewerData.depthPrepass = std::make_unique(*this, viewerInstance); viewerData.forwardPass = std::make_unique(*this, viewerInstance); viewerData.viewer = viewerInstance; m_invalidatedViewerInstances.UnboundedSet(viewerIndex); m_rebuildFrameGraph = true; return viewerIndex; } std::size_t ForwardFramePipeline::RegisterWorldInstance(WorldInstancePtr worldInstance) { std::size_t worldInstanceIndex; m_worldInstances.Allocate(worldInstanceIndex, std::move(worldInstance)); m_invalidatedWorldInstances.UnboundedSet(worldInstanceIndex); return worldInstanceIndex; } void ForwardFramePipeline::Render(RenderFrame& renderFrame) { m_currentRenderFrame = &renderFrame; Graphics* graphics = Graphics::Instance(); // Destroy world instances at the end of the frame for (std::size_t worldInstanceIndex = m_removedWorldInstances.FindFirst(); worldInstanceIndex != m_removedWorldInstances.npos; worldInstanceIndex = m_removedWorldInstances.FindNext(worldInstanceIndex)) { renderFrame.PushForRelease(*m_worldInstances.RetrieveFromIndex(worldInstanceIndex)); m_worldInstances.Free(worldInstanceIndex); } m_removedWorldInstances.Clear(); if (m_rebuildFrameGraph) { renderFrame.PushForRelease(std::move(m_bakedFrameGraph)); m_bakedFrameGraph = BuildFrameGraph(); } // Update UBOs and materials UploadPool& uploadPool = renderFrame.GetUploadPool(); renderFrame.Execute([&](CommandBufferBuilder& builder) { builder.BeginDebugRegion("UBO Update", Color::Yellow); { builder.PreTransferBarrier(); for (std::size_t viewerIndex = m_invalidatedViewerInstances.FindFirst(); viewerIndex != m_invalidatedViewerInstances.npos; viewerIndex = m_invalidatedViewerInstances.FindNext(viewerIndex)) { ViewerData* viewerData = m_viewerPool.RetrieveFromIndex(viewerIndex); viewerData->viewer->GetViewerInstance().UpdateBuffers(uploadPool, builder); } m_invalidatedViewerInstances.Reset(); for (std::size_t worldInstanceIndex = m_invalidatedWorldInstances.FindFirst(); worldInstanceIndex != m_invalidatedWorldInstances.npos; worldInstanceIndex = m_invalidatedWorldInstances.FindNext(worldInstanceIndex)) { WorldInstancePtr& worldInstance = *m_worldInstances.RetrieveFromIndex(worldInstanceIndex); worldInstance->UpdateBuffers(uploadPool, builder); } m_invalidatedWorldInstances.Reset(); for (MaterialPass* materialPass : m_invalidatedMaterialPasses) materialPass->Update(renderFrame, builder); m_invalidatedMaterialPasses.clear(); builder.PostTransferBarrier(); } builder.EndDebugRegion(); }, QueueType::Transfer); auto CombineHash = [](std::size_t currentHash, std::size_t newHash) { return currentHash * 23 + newHash; }; // Render queues handling for (auto& viewerData : m_viewerPool) { UInt32 renderMask = viewerData.viewer->GetRenderMask(); // Frustum culling const Matrix4f& viewProjMatrix = viewerData.viewer->GetViewerInstance().GetViewProjMatrix(); Frustumf frustum = Frustumf::Extract(viewProjMatrix); std::size_t visibilityHash = 5U; m_visibleRenderables.clear(); for (const RenderableData& renderableData : m_renderablePool) { if ((renderMask & renderableData.renderMask) == 0) continue; WorldInstancePtr& worldInstance = *m_worldInstances.RetrieveFromIndex(renderableData.worldInstanceIndex); // Get global AABB BoundingVolumef boundingVolume(renderableData.renderable->GetAABB()); boundingVolume.Update(worldInstance->GetWorldMatrix()); if (!frustum.Contains(boundingVolume)) continue; auto& visibleRenderable = m_visibleRenderables.emplace_back(); visibleRenderable.instancedRenderable = renderableData.renderable; visibleRenderable.scissorBox = renderableData.scissorBox; visibleRenderable.worldInstance = worldInstance.get(); visibilityHash = CombineHash(visibilityHash, std::hash()(&renderableData)); } // Lights update don't trigger a rebuild of the depth pre-pass std::size_t depthVisibilityHash = visibilityHash; m_visibleLights.clear(); for (const LightData& lightData : m_lightPool) { const BoundingVolumef& boundingVolume = lightData.light->GetBoundingVolume(); // TODO: Use more precise tests for point lights (frustum/sphere is cheap) if (renderMask & lightData.renderMask && frustum.Contains(boundingVolume)) { m_visibleLights.push_back(lightData.light.get()); visibilityHash = CombineHash(visibilityHash, std::hash()(lightData.light.get())); } } if (viewerData.depthPrepass) viewerData.depthPrepass->Prepare(renderFrame, frustum, m_visibleRenderables, depthVisibilityHash); viewerData.forwardPass->Prepare(renderFrame, frustum, m_visibleRenderables, m_visibleLights, visibilityHash); } if (m_bakedFrameGraph.Resize(renderFrame)) { const std::shared_ptr& sampler = graphics->GetSamplerCache().Get({}); for (auto& viewerData : m_viewerPool) { if (viewerData.blitShaderBinding) renderFrame.PushForRelease(std::move(viewerData.blitShaderBinding)); viewerData.blitShaderBinding = graphics->GetBlitPipelineLayout()->AllocateShaderBinding(0); viewerData.blitShaderBinding->Update({ { 0, ShaderBinding::TextureBinding { m_bakedFrameGraph.GetAttachmentTexture(viewerData.colorAttachment).get(), sampler.get() } } }); } for (auto&& [_, renderTargetData] : m_renderTargets) { if (renderTargetData.blitShaderBinding) renderFrame.PushForRelease(std::move(renderTargetData.blitShaderBinding)); renderTargetData.blitShaderBinding = graphics->GetBlitPipelineLayout()->AllocateShaderBinding(0); renderTargetData.blitShaderBinding->Update({ { 0, ShaderBinding::TextureBinding { m_bakedFrameGraph.GetAttachmentTexture(renderTargetData.finalAttachment).get(), sampler.get() } } }); } } m_bakedFrameGraph.Execute(renderFrame); m_rebuildFrameGraph = false; // Final blit (TODO: Make part of frame graph) const Vector2ui& frameSize = renderFrame.GetSize(); for (auto&& [renderTargetPtr, renderTargetData] : m_renderTargets) { Recti renderRegion(0, 0, frameSize.x, frameSize.y); const RenderTarget& renderTarget = *renderTargetPtr; const auto& data = renderTargetData; renderFrame.Execute([&](CommandBufferBuilder& builder) { const std::shared_ptr& sourceTexture = m_bakedFrameGraph.GetAttachmentTexture(data.finalAttachment); builder.TextureBarrier(PipelineStage::ColorOutput, PipelineStage::FragmentShader, MemoryAccess::ColorWrite, MemoryAccess::ShaderRead, TextureLayout::ColorOutput, TextureLayout::ColorInput, *sourceTexture); std::array clearValues; clearValues[0].color = Color::Black; clearValues[1].depth = 1.f; clearValues[1].stencil = 0; builder.BeginRenderPass(renderTarget.GetFramebuffer(renderFrame.GetFramebufferIndex()), renderTarget.GetRenderPass(), renderRegion, { clearValues[0], clearValues[1] }); { builder.BeginDebugRegion("Main window rendering", Color::Green); { builder.SetScissor(renderRegion); builder.SetViewport(renderRegion); builder.BindPipeline(*graphics->GetBlitPipeline(false)); builder.BindVertexBuffer(0, *graphics->GetFullscreenVertexBuffer()); builder.BindShaderBinding(0, *data.blitShaderBinding); builder.Draw(3); } builder.EndDebugRegion(); } builder.EndRenderPass(); }, QueueType::Graphics); } } void ForwardFramePipeline::UnregisterLight(std::size_t lightIndex) { m_lightPool.Free(lightIndex); } void ForwardFramePipeline::UnregisterMaterialPass(MaterialPass* materialPass) { auto it = m_activeMaterialPasses.find(materialPass); assert(it != m_activeMaterialPasses.end()); MaterialPassData& materialData = it->second; assert(materialData.usedCount > 0); if (--materialData.usedCount == 0) m_activeMaterialPasses.erase(materialPass); } void ForwardFramePipeline::UnregisterRenderable(std::size_t renderableIndex) { RenderableData& renderable = *m_renderablePool.RetrieveFromIndex(renderableIndex); std::size_t matCount = renderable.renderable->GetMaterialCount(); for (std::size_t i = 0; i < matCount; ++i) { for (auto& viewerData : m_viewerPool) { const auto& material = renderable.renderable->GetMaterial(i); if (viewerData.depthPrepass) viewerData.depthPrepass->UnregisterMaterial(*material); viewerData.forwardPass->UnregisterMaterial(*material); } } m_renderablePool.Free(renderableIndex); } void ForwardFramePipeline::UnregisterViewer(std::size_t viewerIndex) { m_viewerPool.Free(viewerIndex); m_invalidatedViewerInstances.Reset(viewerIndex); m_rebuildFrameGraph = true; } void ForwardFramePipeline::UnregisterWorldInstance(std::size_t worldInstance) { // Defer world instance release m_removedWorldInstances.UnboundedSet(worldInstance); } void ForwardFramePipeline::UpdateLightRenderMask(std::size_t lightIndex, UInt32 renderMask) { LightData* lightData = m_lightPool.RetrieveFromIndex(lightIndex); lightData->renderMask = renderMask; } void ForwardFramePipeline::UpdateRenderableRenderMask(std::size_t renderableIndex, UInt32 renderMask) { RenderableData* renderableData = m_renderablePool.RetrieveFromIndex(renderableIndex); renderableData->renderMask = renderMask; } void ForwardFramePipeline::UpdateRenderableScissorBox(std::size_t renderableIndex, const Recti& scissorBox) { RenderableData* renderableData = m_renderablePool.RetrieveFromIndex(renderableIndex); renderableData->scissorBox = scissorBox; // TODO: Invalidate only relevant viewers and passes for (auto& viewerData : m_viewerPool) { UInt32 viewerRenderMask = viewerData.viewer->GetRenderMask(); if (viewerRenderMask & renderableData->renderMask) { if (viewerData.depthPrepass) viewerData.depthPrepass->InvalidateElements(); viewerData.forwardPass->InvalidateElements(); } } } void ForwardFramePipeline::UpdateViewerRenderMask(std::size_t viewerIndex, Int32 renderOrder) { ViewerData* viewerData = m_viewerPool.RetrieveFromIndex(viewerIndex); if (viewerData->renderOrder != renderOrder) { viewerData->renderOrder = renderOrder; m_rebuildFrameGraph = true; } } BakedFrameGraph ForwardFramePipeline::BuildFrameGraph() { FrameGraph frameGraph; for (auto& viewerData : m_viewerPool) { viewerData.colorAttachment = frameGraph.AddAttachment({ "Color", PixelFormat::RGBA8 }); viewerData.depthStencilAttachment = frameGraph.AddAttachment({ "Depth-stencil buffer", Graphics::Instance()->GetPreferredDepthStencilFormat() }); if (viewerData.depthPrepass) viewerData.depthPrepass->RegisterToFrameGraph(frameGraph, viewerData.depthStencilAttachment); viewerData.forwardPass->RegisterToFrameGraph(frameGraph, viewerData.colorAttachment, viewerData.depthStencilAttachment, viewerData.depthPrepass != nullptr); } using ViewerPair = std::pair; StackArray viewers = NazaraStackArray(ViewerPair, m_viewerPool.size()); auto viewerIt = viewers.begin(); for (auto& viewerData : m_viewerPool) { const RenderTarget& renderTarget = viewerData.viewer->GetRenderTarget(); *viewerIt++ = std::make_pair(&renderTarget, &viewerData); } std::sort(viewers.begin(), viewers.end(), [](const ViewerPair& lhs, const ViewerPair& rhs) { return lhs.second->renderOrder < rhs.second->renderOrder; }); m_renderTargets.clear(); for (auto&& [renderTarget, viewerData] : viewers) { auto& renderTargetData = m_renderTargets[renderTarget]; renderTargetData.viewers.push_back(viewerData); } for (auto&& [renderTarget, renderTargetData] : m_renderTargets) { const auto& targetViewers = renderTargetData.viewers; FramePass& mergePass = frameGraph.AddPass("Merge pass"); renderTargetData.finalAttachment = frameGraph.AddAttachment({ "Viewer output", PixelFormat::RGBA8 }); for (const ViewerData* viewerData : targetViewers) mergePass.AddInput(viewerData->colorAttachment); mergePass.AddOutput(renderTargetData.finalAttachment); mergePass.SetClearColor(0, Color::Black); mergePass.SetCommandCallback([&targetViewers](CommandBufferBuilder& builder, const Nz::FramePassEnvironment& env) { builder.SetScissor(env.renderRect); builder.SetViewport(env.renderRect); Graphics* graphics = Graphics::Instance(); builder.BindPipeline(*graphics->GetBlitPipeline(false)); builder.BindVertexBuffer(0, *graphics->GetFullscreenVertexBuffer()); bool first = true; for (const ViewerData* viewerData : targetViewers) { const ShaderBindingPtr& blitShaderBinding = viewerData->blitShaderBinding; builder.BindShaderBinding(0, *blitShaderBinding); builder.Draw(3); if (first) { builder.BindPipeline(*graphics->GetBlitPipeline(true)); first = false; } } }); frameGraph.AddBackbufferOutput(renderTargetData.finalAttachment); } return frameGraph.Bake(); } }