// Copyright (C) 2023 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 #include namespace Nz { ForwardFramePipeline::ForwardFramePipeline(ElementRendererRegistry& elementRegistry) : m_elementRegistry(elementRegistry), m_renderablePool(4096), m_lightPool(64), m_skeletonInstances(1024), m_viewerPool(8), m_worldInstances(2048), m_generationCounter(0), m_rebuildFrameGraph(true) { } ForwardFramePipeline::~ForwardFramePipeline() { // Force viewer passes to unregister their materials m_viewerPool.Clear(); } const std::vector& ForwardFramePipeline::FrustumCull(const Frustumf& frustum, UInt32 mask, std::size_t& visibilityHash) const { auto CombineHash = [](std::size_t currentHash, std::size_t newHash) { return currentHash * 23 + newHash; }; m_visibleRenderables.clear(); for (const RenderableData& renderableData : m_renderablePool) { if ((mask & renderableData.renderMask) == 0) continue; const WorldInstancePtr& worldInstance = m_worldInstances.RetrieveFromIndex(renderableData.worldInstanceIndex)->worldInstance; // Get global AABB BoundingVolumef boundingVolume(renderableData.renderable->GetAABB()); boundingVolume.Update(worldInstance->GetWorldMatrix()); if (frustum.Intersect(boundingVolume) == IntersectionSide::Outside) continue; auto& visibleRenderable = m_visibleRenderables.emplace_back(); visibleRenderable.instancedRenderable = renderableData.renderable; visibleRenderable.scissorBox = renderableData.scissorBox; visibleRenderable.worldInstance = worldInstance.get(); if (renderableData.skeletonInstanceIndex != NoSkeletonInstance) visibleRenderable.skeletonInstance = m_skeletonInstances.RetrieveFromIndex(renderableData.skeletonInstanceIndex)->skeleton.get(); else visibleRenderable.skeletonInstance = nullptr; visibilityHash = CombineHash(visibilityHash, std::hash()(&renderableData) + renderableData.generation); } return m_visibleRenderables; } void ForwardFramePipeline::ForEachRegisteredMaterialInstance(FunctionRef callback) { for (RenderableData& renderable : m_renderablePool) { std::size_t matCount = renderable.renderable->GetMaterialCount(); for (std::size_t j = 0; j < matCount; ++j) { if (MaterialInstance* mat = renderable.renderable->GetMaterial(j).get()) callback(*mat); } } } void ForwardFramePipeline::QueueTransfer(TransferInterface* transfer) { m_transferSet.insert(transfer); } std::size_t ForwardFramePipeline::RegisterLight(const Light* light, UInt32 renderMask) { std::size_t lightIndex; LightData* lightData = m_lightPool.Allocate(lightIndex); lightData->light = 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(); } }); lightData->onLightShadowCastingChanged.Connect(lightData->light->OnLightShadowCastingChanged, [=](Light* light, bool isCastingShadows) { if (isCastingShadows) { m_shadowCastingLights.UnboundedSet(lightIndex); lightData->shadowData = light->InstanciateShadowData(*this, m_elementRegistry); } else { m_shadowCastingLights.Reset(lightIndex); lightData->shadowData.reset(); } m_rebuildFrameGraph = true; }); if (lightData->light->IsShadowCaster()) { m_shadowCastingLights.UnboundedSet(lightIndex); lightData->shadowData = light->InstanciateShadowData(*this, m_elementRegistry); m_rebuildFrameGraph = true; } return lightIndex; } std::size_t ForwardFramePipeline::RegisterRenderable(std::size_t worldInstanceIndex, std::size_t skeletonInstanceIndex, const InstancedRenderable* instancedRenderable, UInt32 renderMask, const Recti& scissorBox) { std::size_t renderableIndex; RenderableData* renderableData = m_renderablePool.Allocate(renderableIndex); renderableData->generation = m_generationCounter++; renderableData->renderable = instancedRenderable; renderableData->renderMask = renderMask; renderableData->scissorBox = scissorBox; renderableData->skeletonInstanceIndex = skeletonInstanceIndex; 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) { RegisterMaterialInstance(newMaterial.get()); for (auto& viewerData : m_viewerPool) { if (viewerData.depthPrepass) viewerData.depthPrepass->RegisterMaterialInstance(*newMaterial); viewerData.forwardPass->RegisterMaterialInstance(*newMaterial); } } const auto& prevMaterial = instancedRenderable->GetMaterial(materialIndex); if (prevMaterial) { UnregisterMaterialInstance(prevMaterial.get()); for (auto& viewerData : m_viewerPool) { if (viewerData.depthPrepass) viewerData.depthPrepass->UnregisterMaterialInstance(*prevMaterial); viewerData.forwardPass->UnregisterMaterialInstance(*prevMaterial); } } }); std::size_t matCount = instancedRenderable->GetMaterialCount(); for (std::size_t i = 0; i < matCount; ++i) { if (MaterialInstance* mat = instancedRenderable->GetMaterial(i).get()) { RegisterMaterialInstance(mat); for (auto& viewerData : m_viewerPool) { if (viewerData.depthPrepass) viewerData.depthPrepass->RegisterMaterialInstance(*mat); viewerData.forwardPass->RegisterMaterialInstance(*mat); } } } return renderableIndex; } std::size_t ForwardFramePipeline::RegisterSkeleton(SkeletonInstancePtr skeletonInstance) { std::size_t skeletonInstanceIndex; SkeletonInstanceData& skeletonInstanceData = *m_skeletonInstances.Allocate(skeletonInstanceIndex); skeletonInstanceData.skeleton = std::move(skeletonInstance); skeletonInstanceData.onTransferRequired.Connect(skeletonInstanceData.skeleton->OnTransferRequired, [this](TransferInterface* transferInterface) { m_transferSet.insert(transferInterface); }); m_transferSet.insert(skeletonInstanceData.skeleton.get()); return skeletonInstanceIndex; } std::size_t ForwardFramePipeline::RegisterViewer(AbstractViewer* viewerInstance, Int32 renderOrder) { std::size_t depthPassIndex = Graphics::Instance()->GetMaterialPassRegistry().GetPassIndex("DepthPass"); std::size_t viewerIndex; auto& viewerData = *m_viewerPool.Allocate(viewerIndex); viewerData.renderOrder = renderOrder; viewerData.debugDrawPass = std::make_unique(*this, viewerInstance); viewerData.depthPrepass = std::make_unique(*this, m_elementRegistry, viewerInstance, depthPassIndex, "Depth pre-pass"); viewerData.forwardPass = std::make_unique(*this, m_elementRegistry, viewerInstance); viewerData.viewer = viewerInstance; viewerData.onTransferRequired.Connect(viewerInstance->GetViewerInstance().OnTransferRequired, [this](TransferInterface* transferInterface) { m_transferSet.insert(transferInterface); }); m_transferSet.insert(&viewerInstance->GetViewerInstance()); m_rebuildFrameGraph = true; return viewerIndex; } std::size_t ForwardFramePipeline::RegisterWorldInstance(WorldInstancePtr worldInstance) { std::size_t worldInstanceIndex; WorldInstanceData& worldInstanceData = *m_worldInstances.Allocate(worldInstanceIndex); worldInstanceData.worldInstance = std::move(worldInstance); worldInstanceData.onTransferRequired.Connect(worldInstanceData.worldInstance->OnTransferRequired, [this](TransferInterface* transferInterface) { m_transferSet.insert(transferInterface); }); m_transferSet.insert(worldInstanceData.worldInstance.get()); return worldInstanceIndex; } const Light* ForwardFramePipeline::RetrieveLight(std::size_t lightIndex) const { return m_lightPool.RetrieveFromIndex(lightIndex)->light; } const Texture* ForwardFramePipeline::RetrieveLightShadowmap(std::size_t lightIndex) const { if (!m_shadowCastingLights.UnboundedTest(lightIndex)) return nullptr; return m_lightPool.RetrieveFromIndex(lightIndex)->shadowData->RetrieveLightShadowmap(m_bakedFrameGraph); } void ForwardFramePipeline::Render(RenderFrame& renderFrame) { m_currentRenderFrame = &renderFrame; Graphics* graphics = Graphics::Instance(); // Destroy instances at the end of the frame for (std::size_t skeletonInstanceIndex : m_removedSkeletonInstances.IterBits()) { renderFrame.PushForRelease(std::move(*m_skeletonInstances.RetrieveFromIndex(skeletonInstanceIndex))); m_skeletonInstances.Free(skeletonInstanceIndex); } m_removedSkeletonInstances.Clear(); for (std::size_t viewerIndex : m_removedViewerInstances.IterBits()) { renderFrame.PushForRelease(std::move(*m_viewerPool.RetrieveFromIndex(viewerIndex))); m_viewerPool.Free(viewerIndex); } m_removedViewerInstances.Clear(); for (std::size_t worldInstanceIndex : m_removedWorldInstances.IterBits()) { renderFrame.PushForRelease(std::move(*m_worldInstances.RetrieveFromIndex(worldInstanceIndex))); m_worldInstances.Free(worldInstanceIndex); } m_removedWorldInstances.Clear(); bool frameGraphInvalidated; if (m_rebuildFrameGraph) { renderFrame.PushForRelease(std::move(m_bakedFrameGraph)); m_bakedFrameGraph = BuildFrameGraph(); m_bakedFrameGraph.Resize(renderFrame); frameGraphInvalidated = true; } else frameGraphInvalidated = m_bakedFrameGraph.Resize(renderFrame); // Update UBOs and materials renderFrame.Execute([&](CommandBufferBuilder& builder) { builder.BeginDebugRegion("CPU to GPU transfers", Color::Yellow()); { builder.PreTransferBarrier(); for (TransferInterface* transferInterface : m_transferSet) transferInterface->OnTransfer(renderFrame, builder); m_transferSet.clear(); OnTransfer(this, renderFrame, builder); builder.PostTransferBarrier(); } builder.EndDebugRegion(); }, QueueType::Transfer); // Shadow map handling for (std::size_t i = m_shadowCastingLights.FindFirst(); i != m_shadowCastingLights.npos; i = m_shadowCastingLights.FindNext(i)) { LightData* lightData = m_lightPool.RetrieveFromIndex(i); lightData->shadowData->PrepareRendering(renderFrame); } // 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 = 5; const auto& visibleRenderables = FrustumCull(frustum, renderMask, visibilityHash); // Lights update don't trigger a rebuild of the depth pre-pass std::size_t depthVisibilityHash = visibilityHash; m_visibleLights.clear(); for (auto it = m_lightPool.begin(); it != m_lightPool.end(); ++it) { const LightData& lightData = *it; std::size_t lightIndex = it.GetIndex(); const BoundingVolumef& boundingVolume = lightData.light->GetBoundingVolume(); // TODO: Use more precise tests for point lights (frustum/sphere is cheap) if (renderMask & lightData.renderMask && frustum.Intersect(boundingVolume) != IntersectionSide::Outside) { m_visibleLights.push_back(lightIndex); auto CombineHash = [](std::size_t currentHash, std::size_t newHash) { return currentHash * 23 + newHash; }; visibilityHash = CombineHash(visibilityHash, std::hash()(lightData.light)); } } if (viewerData.depthPrepass) viewerData.depthPrepass->Prepare(renderFrame, frustum, visibleRenderables, depthVisibilityHash); viewerData.forwardPass->Prepare(renderFrame, frustum, visibleRenderables, m_visibleLights, visibilityHash); viewerData.debugDrawPass->Prepare(renderFrame); } if (frameGraphInvalidated) { 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::SampledTextureBinding { m_bakedFrameGraph.GetAttachmentTexture(viewerData.debugColorAttachment).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::SampledTextureBinding { 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.BindRenderPipeline(*graphics->GetBlitPipeline(false)); builder.BindRenderShaderBinding(0, *data.blitShaderBinding); builder.Draw(3); } builder.EndDebugRegion(); } builder.EndRenderPass(); }, QueueType::Graphics); } // reset at the end instead of the beginning so debug draw can be used before calling this method DebugDrawer& debugDrawer = GetDebugDrawer(); debugDrawer.Reset(renderFrame); } void ForwardFramePipeline::UnregisterLight(std::size_t lightIndex) { m_lightPool.Free(lightIndex); m_shadowCastingLights.UnboundedReset(lightIndex); } 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) { const auto& material = renderable.renderable->GetMaterial(i); UnregisterMaterialInstance(material.get()); for (auto& viewerData : m_viewerPool) { if (viewerData.depthPrepass) viewerData.depthPrepass->UnregisterMaterialInstance(*material); viewerData.forwardPass->UnregisterMaterialInstance(*material); } } m_renderablePool.Free(renderableIndex); } void ForwardFramePipeline::UnregisterSkeleton(std::size_t skeletonIndex) { // Defer world instance release m_removedSkeletonInstances.UnboundedSet(skeletonIndex); } void ForwardFramePipeline::UnregisterViewer(std::size_t viewerIndex) { // Defer world instance release m_removedViewerInstances.UnboundedSet(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::UpdateRenderableSkeletonInstance(std::size_t renderableIndex, std::size_t skeletonIndex) { RenderableData* renderableData = m_renderablePool.RetrieveFromIndex(renderableIndex); renderableData->skeletonInstanceIndex = skeletonIndex; // 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 (std::size_t i : m_shadowCastingLights.IterBits()) { LightData* lightData = m_lightPool.RetrieveFromIndex(i); lightData->shadowData->RegisterToFrameGraph(frameGraph); } for (auto& viewerData : m_viewerPool) { viewerData.forwardColorAttachment = frameGraph.AddAttachment({ "Forward output", PixelFormat::RGBA8 }); viewerData.debugColorAttachment = frameGraph.AddAttachmentProxy("Debug draw output", viewerData.forwardColorAttachment); viewerData.depthStencilAttachment = frameGraph.AddAttachment({ "Depth-stencil buffer", Graphics::Instance()->GetPreferredDepthStencilFormat() }); if (viewerData.depthPrepass) viewerData.depthPrepass->RegisterToFrameGraph(frameGraph, viewerData.depthStencilAttachment); FramePass& forwardPass = viewerData.forwardPass->RegisterToFrameGraph(frameGraph, viewerData.forwardColorAttachment, viewerData.depthStencilAttachment, viewerData.depthPrepass != nullptr); for (std::size_t i : m_shadowCastingLights.IterBits()) { LightData* lightData = m_lightPool.RetrieveFromIndex(i); lightData->shadowData->RegisterPassInputs(forwardPass); } viewerData.debugDrawPass->RegisterToFrameGraph(frameGraph, viewerData.forwardColorAttachment, viewerData.debugColorAttachment); } 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->debugColorAttachment); mergePass.AddOutput(renderTargetData.finalAttachment); mergePass.SetClearColor(0, Color::Black()); mergePass.SetCommandCallback([&targetViewers](CommandBufferBuilder& builder, const FramePassEnvironment& /*env*/) { Graphics* graphics = Graphics::Instance(); builder.BindRenderPipeline(*graphics->GetBlitPipeline(false)); bool first = true; for (const ViewerData* viewerData : targetViewers) { Recti renderRect = viewerData->viewer->GetViewport(); builder.SetScissor(renderRect); builder.SetViewport(renderRect); const ShaderBindingPtr& blitShaderBinding = viewerData->blitShaderBinding; builder.BindRenderShaderBinding(0, *blitShaderBinding); builder.Draw(3); if (first) { builder.BindRenderPipeline(*graphics->GetBlitPipeline(true)); first = false; } } }); frameGraph.AddBackbufferOutput(renderTargetData.finalAttachment); } return frameGraph.Bake(); } void ForwardFramePipeline::RegisterMaterialInstance(MaterialInstance* materialInstance) { auto it = m_materialInstances.find(materialInstance); if (it == m_materialInstances.end()) { it = m_materialInstances.emplace(materialInstance, MaterialInstanceData{}).first; it->second.onTransferRequired.Connect(materialInstance->OnTransferRequired, [this](TransferInterface* transferInterface) { m_transferSet.insert(transferInterface); }); m_transferSet.insert(materialInstance); } it->second.usedCount++; } void ForwardFramePipeline::UnregisterMaterialInstance(MaterialInstance* materialInstance) { auto it = m_materialInstances.find(materialInstance); assert(it != m_materialInstances.end()); MaterialInstanceData& materialInstanceData = it->second; assert(materialInstanceData.usedCount > 0); if (--materialInstanceData.usedCount == 0) m_materialInstances.erase(it); } }