#include "Rendering/Execution/CameraRenderer.h" #include "Components/CameraComponent.h" #include "Rendering/Caches/FullscreenPassSurfaceCache.h" #include "Rendering/Execution/DirectionalShadowExecutionState.h" #include "Rendering/Graph/RenderGraph.h" #include "Rendering/Graph/RenderGraphCompiler.h" #include "Rendering/Graph/RenderGraphExecutor.h" #include "Rendering/Passes/BuiltinDepthOnlyPass.h" #include "Rendering/Passes/BuiltinObjectIdPass.h" #include "Rendering/Passes/BuiltinShadowCasterPass.h" #include "Rendering/Pipelines/BuiltinForwardPipeline.h" #include "Rendering/RenderPipelineAsset.h" #include "Rendering/RenderSurface.h" #include "Rendering/Shadow/DirectionalShadowRuntime.h" #include "RHI/RHIResourceView.h" #include "Scene/Scene.h" #include #include #include namespace XCEngine { namespace Rendering { namespace { constexpr RHI::Format kRenderGraphImportedColorFormat = RHI::Format::R8G8B8A8_UNorm; constexpr RHI::Format kRenderGraphImportedDepthFormat = RHI::Format::D24_UNorm_S8_UInt; std::shared_ptr CreateDefaultPipelineAsset() { static const std::shared_ptr s_defaultPipelineAsset = std::make_shared(); return s_defaultPipelineAsset; } std::unique_ptr CreateDefaultDepthOnlyPass() { return std::make_unique(); } std::unique_ptr CreateDefaultShadowCasterPass() { return std::make_unique(); } std::unique_ptr CreatePipelineFromAsset( const std::shared_ptr& pipelineAsset) { if (pipelineAsset != nullptr) { std::unique_ptr pipeline = pipelineAsset->CreatePipeline(); if (pipeline != nullptr) { return pipeline; } } return std::make_unique(); } struct RenderGraphImportedSurface { std::vector colorTextures = {}; RenderGraphTextureHandle depthTexture = {}; }; using RenderGraphImportedTextureRegistry = std::unordered_map; enum class RenderGraphSurfaceImportUsage { Source = 0, Output = 1 }; Containers::String BuildRenderGraphResourceName( const Containers::String& surfaceName, const char* slotName, size_t index = 0u, bool indexed = false) { std::string name = surfaceName.CStr(); name += '.'; name += slotName; if (indexed) { name += std::to_string(index); } return Containers::String(name.c_str()); } RenderGraphTextureDesc BuildImportedTextureDesc( const RenderSurface& surface, RHI::Format format) { RenderGraphTextureDesc desc = {}; desc.width = surface.GetWidth() > 0u ? surface.GetWidth() : surface.GetRenderAreaWidth(); desc.height = surface.GetHeight() > 0u ? surface.GetHeight() : surface.GetRenderAreaHeight(); desc.format = static_cast(format); desc.textureType = static_cast(RHI::TextureType::Texture2D); desc.sampleCount = std::max(surface.GetSampleCount(), 1u); desc.sampleQuality = surface.GetSampleQuality(); return desc; } RenderGraphImportedTextureOptions BuildImportedTextureOptions( const RenderSurface& surface, bool isDepth, RenderGraphSurfaceImportUsage usage) { const RHI::ResourceStates beforeState = isDepth ? surface.GetDepthStateBefore() : surface.GetColorStateBefore(); const RHI::ResourceStates afterState = isDepth ? surface.GetDepthStateAfter() : surface.GetColorStateAfter(); RenderGraphImportedTextureOptions options = {}; options.initialState = usage == RenderGraphSurfaceImportUsage::Output ? beforeState : afterState; options.finalState = usage == RenderGraphSurfaceImportUsage::Output ? afterState : options.initialState; options.graphOwnsTransitions = false; return options; } RenderGraphTextureHandle ImportRenderGraphTexture( RenderGraphBuilder& builder, RenderGraphImportedTextureRegistry& registry, const Containers::String& name, const RenderGraphTextureDesc& desc, RHI::RHIResourceView* view, const RenderGraphImportedTextureOptions& importedOptions) { if (view == nullptr) { return {}; } const auto existing = registry.find(view); if (existing != registry.end()) { return existing->second; } const RenderGraphTextureHandle handle = builder.ImportTexture(name, desc, view, importedOptions); registry.emplace(view, handle); return handle; } RenderGraphImportedSurface ImportRenderGraphSurface( RenderGraphBuilder& builder, RenderGraphImportedTextureRegistry& registry, const Containers::String& surfaceName, const RenderSurface* surface, RenderGraphSurfaceImportUsage usage) { RenderGraphImportedSurface importedSurface = {}; if (surface == nullptr) { return importedSurface; } const RenderGraphTextureDesc colorDesc = BuildImportedTextureDesc(*surface, kRenderGraphImportedColorFormat); const std::vector& colorAttachments = surface->GetColorAttachments(); importedSurface.colorTextures.reserve(colorAttachments.size()); for (size_t colorIndex = 0; colorIndex < colorAttachments.size(); ++colorIndex) { RHI::RHIResourceView* colorAttachment = colorAttachments[colorIndex]; if (colorAttachment == nullptr) { continue; } importedSurface.colorTextures.push_back( ImportRenderGraphTexture( builder, registry, BuildRenderGraphResourceName(surfaceName, "Color", colorIndex, true), colorDesc, colorAttachment, BuildImportedTextureOptions( *surface, false, usage))); } if (RHI::RHIResourceView* depthAttachment = surface->GetDepthAttachment(); depthAttachment != nullptr) { importedSurface.depthTexture = ImportRenderGraphTexture( builder, registry, BuildRenderGraphResourceName(surfaceName, "Depth"), BuildImportedTextureDesc(*surface, kRenderGraphImportedDepthFormat), depthAttachment, BuildImportedTextureOptions( *surface, true, usage)); } return importedSurface; } RenderGraphTextureHandle GetPrimaryColorTexture( const RenderGraphImportedSurface& surface) { for (RenderGraphTextureHandle texture : surface.colorTextures) { if (texture.IsValid()) { return texture; } } return {}; } void ReadRenderGraphSurface( RenderGraphPassBuilder& passBuilder, const RenderGraphImportedSurface& surface) { for (RenderGraphTextureHandle texture : surface.colorTextures) { if (texture.IsValid()) { passBuilder.ReadTexture(texture); } } if (surface.depthTexture.IsValid()) { passBuilder.ReadTexture(surface.depthTexture); } } void ReadRenderGraphColorSurface( RenderGraphPassBuilder& passBuilder, const RenderGraphImportedSurface& surface) { for (RenderGraphTextureHandle texture : surface.colorTextures) { if (texture.IsValid()) { passBuilder.ReadTexture(texture); } } } void WriteRenderGraphSurface( RenderGraphPassBuilder& passBuilder, const RenderGraphImportedSurface& surface) { for (RenderGraphTextureHandle texture : surface.colorTextures) { if (texture.IsValid()) { passBuilder.WriteTexture(texture); } } if (surface.depthTexture.IsValid()) { passBuilder.WriteTexture(surface.depthTexture); } } void WriteRenderGraphColorSurface( RenderGraphPassBuilder& passBuilder, const RenderGraphImportedSurface& surface) { for (RenderGraphTextureHandle texture : surface.colorTextures) { if (texture.IsValid()) { passBuilder.WriteTexture(texture); } } } bool IsFullscreenSequenceStage( CameraFrameStage stage) { return stage == CameraFrameStage::PostProcess || stage == CameraFrameStage::FinalOutput; } Containers::String BuildRenderGraphSequencePassName( const Containers::String& stageName, size_t passIndex) { std::string name = stageName.CStr(); name += ".Pass"; name += std::to_string(passIndex); return Containers::String(name.c_str()); } RenderGraphTextureDesc BuildFullscreenTransientTextureDesc( const RenderSurface& surface) { RenderGraphTextureDesc desc = {}; desc.width = surface.GetWidth() > 0u ? surface.GetWidth() : surface.GetRenderAreaWidth(); desc.height = surface.GetHeight() > 0u ? surface.GetHeight() : surface.GetRenderAreaHeight(); desc.format = static_cast(kRenderGraphImportedColorFormat); desc.textureType = static_cast(RHI::TextureType::Texture2D); desc.sampleCount = 1u; desc.sampleQuality = 0u; return desc; } Resources::ShaderKeywordSet BuildSceneGlobalShaderKeywords( const RenderSceneData& sceneData) { Resources::ShaderKeywordSet keywords = {}; if (sceneData.lighting.HasMainDirectionalShadow()) { keywords.enabledKeywords.PushBack("XC_MAIN_LIGHT_SHADOWS"); } Resources::NormalizeShaderKeywordSetInPlace(keywords); return keywords; } bool InitializeStandalonePass( RenderPass* pass, const RenderContext& context) { if (pass == nullptr) { return false; } if (pass->Initialize(context)) { return true; } pass->Shutdown(); return false; } bool ExecuteScenePassRequest( RenderPass* pass, const ScenePassRenderRequest& request, const RenderContext& context, const RenderSceneData& baseSceneData) { if (!request.IsRequested()) { return true; } if (!InitializeStandalonePass(pass, context)) { return false; } RenderSceneData sceneData = baseSceneData; if (request.hasCameraDataOverride) { sceneData.cameraData = request.cameraDataOverride; } sceneData.cameraData.viewportWidth = request.surface.GetRenderAreaWidth(); sceneData.cameraData.viewportHeight = request.surface.GetRenderAreaHeight(); sceneData.cameraData.clearFlags = request.clearFlags; if (request.hasClearColorOverride) { sceneData.cameraData.clearColor = request.clearColorOverride; } const RenderPassContext passContext = { context, request.surface, sceneData, nullptr, nullptr, RHI::ResourceStates::Common }; return pass->Execute(passContext); } bool ExecuteStandalonePass( RenderPass* pass, const RenderContext& context, const RenderSurface& surface, const RenderSceneData& sceneData) { if (pass == nullptr) { return false; } if (!InitializeStandalonePass(pass, context)) { return false; } const RenderPassContext passContext = { context, surface, sceneData, nullptr, nullptr, RHI::ResourceStates::Common }; return pass->Execute(passContext); } class ScopedInitializedPassSequence { public: ScopedInitializedPassSequence( RenderPassSequence* sequence, const RenderContext& context) : m_sequence(sequence) { if (m_sequence == nullptr) { return; } m_initialized = m_sequence->Initialize(context); if (!m_initialized) { m_failed = true; m_sequence->Shutdown(); m_sequence = nullptr; } } ScopedInitializedPassSequence(const ScopedInitializedPassSequence&) = delete; ScopedInitializedPassSequence& operator=(const ScopedInitializedPassSequence&) = delete; ~ScopedInitializedPassSequence() { // Request-owned pass sequences may record GPU work that executes only after the // caller closes/submits the command list. Do not destroy sequence-owned GPU // resources here; the sequence owner must shut them down explicitly when the // sequence is no longer needed. } bool IsReady() const { return !m_failed; } bool Execute(const RenderPassContext& context) const { return m_sequence == nullptr || m_sequence->Execute(context); } private: RenderPassSequence* m_sequence = nullptr; bool m_initialized = false; bool m_failed = false; }; bool EnsureInitializedPassSequence( std::unique_ptr& activeSequence, RenderPassSequence* sequence, const RenderContext& context) { if (activeSequence == nullptr) { activeSequence = std::make_unique(sequence, context); } return activeSequence->IsReady(); } struct CameraFrameExecutionState { RenderPipeline* pipeline = nullptr; RenderPass* objectIdPass = nullptr; RenderPass* depthOnlyPass = nullptr; RenderPass* shadowCasterPass = nullptr; FullscreenPassSurfaceCache* postProcessSurfaceCache = nullptr; FullscreenPassSurfaceCache* finalOutputSurfaceCache = nullptr; std::unique_ptr preScenePasses; std::unique_ptr postProcessPasses; std::unique_ptr finalOutputPasses; std::unique_ptr postScenePasses; std::unique_ptr overlayPasses; }; bool ExecutePassSequenceStage( std::unique_ptr& activeSequence, RenderPassSequence* sequence, const RenderContext& context, const RenderPassContext& passContext) { activeSequence = std::make_unique(sequence, context); return activeSequence->IsReady() && activeSequence->Execute(passContext); } void CopyIntermediateSurfaceLayout( const RenderSurface& templateSurface, RenderSurface& destinationSurface) { destinationSurface.SetSize(templateSurface.GetWidth(), templateSurface.GetHeight()); if (templateSurface.HasCustomRenderArea()) { destinationSurface.SetRenderArea(templateSurface.GetRenderArea()); } else { destinationSurface.ResetRenderArea(); } destinationSurface.ClearClearColorOverride(); destinationSurface.SetAutoTransitionEnabled(true); } bool ExecuteFullscreenPassSequenceStage( std::unique_ptr& activeSequence, RenderPassSequence* sequence, const RenderContext& context, const RenderPassContext& passContext, FullscreenPassSurfaceCache* surfaceCache) { activeSequence = std::make_unique(sequence, context); if (!activeSequence->IsReady()) { return false; } if (sequence == nullptr) { return true; } if (sequence->GetPassCount() <= 1u) { return sequence->GetPassCount() == 0u || sequence->ExecutePass(0u, passContext); } if (surfaceCache == nullptr || passContext.sourceSurface == nullptr || passContext.sourceColorView == nullptr || !HasValidColorTarget(passContext.surface)) { return false; } const std::vector& colorAttachments = passContext.surface.GetColorAttachments(); const RHI::Format outputFormat = colorAttachments[0]->GetFormat(); const size_t intermediateSurfaceCount = std::min(2u, sequence->GetPassCount() - 1u); if (!surfaceCache->EnsureSurfaces( context, passContext.surface.GetWidth(), passContext.surface.GetHeight(), outputFormat, intermediateSurfaceCount)) { return false; } const RenderSurface* currentSourceSurface = passContext.sourceSurface; RHI::RHIResourceView* currentSourceColorView = passContext.sourceColorView; RHI::ResourceStates currentSourceColorState = passContext.sourceColorState; for (size_t passIndex = 0; passIndex < sequence->GetPassCount(); ++passIndex) { const bool isLastPass = (passIndex + 1u) == sequence->GetPassCount(); const RenderSurface* outputSurface = &passContext.surface; FullscreenPassSurfaceCache::SurfaceEntry* intermediateEntry = nullptr; if (!isLastPass) { intermediateEntry = surfaceCache->GetSurfaceEntry(passIndex % intermediateSurfaceCount); if (intermediateEntry == nullptr) { return false; } CopyIntermediateSurfaceLayout(passContext.surface, intermediateEntry->surface); intermediateEntry->surface.SetColorStateBefore(intermediateEntry->currentColorState); intermediateEntry->surface.SetColorStateAfter(RHI::ResourceStates::PixelShaderResource); outputSurface = &intermediateEntry->surface; } const RenderPassContext chainedContext = { context, *outputSurface, passContext.sceneData, currentSourceSurface, currentSourceColorView, currentSourceColorState }; if (!sequence->ExecutePass(passIndex, chainedContext)) { return false; } if (intermediateEntry != nullptr) { intermediateEntry->currentColorState = RHI::ResourceStates::PixelShaderResource; currentSourceSurface = &intermediateEntry->surface; currentSourceColorView = intermediateEntry->shaderResourceView; currentSourceColorState = intermediateEntry->currentColorState; } } return true; } bool TryBuildRenderGraphTransientSurface( const RenderSurface& templateSurface, const RenderGraphExecutionContext& graphContext, RenderGraphTextureHandle textureHandle, RenderSurface& outSurface) { if (!textureHandle.IsValid() || !graphContext.IsTransientTexture(textureHandle)) { return false; } RenderGraphTextureDesc textureDesc = {}; RHI::RHIResourceView* renderTargetView = graphContext.ResolveTextureView( textureHandle, RenderGraphTextureViewType::RenderTarget); if (renderTargetView == nullptr || !graphContext.TryGetTextureDesc(textureHandle, textureDesc)) { return false; } outSurface = templateSurface; CopyIntermediateSurfaceLayout(templateSurface, outSurface); outSurface.SetColorAttachment(renderTargetView); outSurface.SetAutoTransitionEnabled(false); outSurface.SetSampleDesc(textureDesc.sampleCount, textureDesc.sampleQuality); outSurface.SetColorStateBefore(RHI::ResourceStates::RenderTarget); outSurface.SetColorStateAfter(RHI::ResourceStates::PixelShaderResource); return true; } bool ExecuteFullscreenPassSequencePass( RenderPassSequence* sequence, size_t passIndex, const RenderPassContext& passContext, const RenderGraphExecutionContext& graphContext, RenderGraphTextureHandle sourceColorHandle, RenderGraphTextureHandle outputColorHandle); RenderPassContext BuildFrameStagePassContext( CameraFrameStage stage, const CameraFramePlan& plan, const RenderSceneData& sceneData) { const RenderSurface* outputSurface = plan.GetOutputSurface(stage); return { plan.request.context, outputSurface != nullptr ? *outputSurface : plan.request.surface, sceneData, plan.GetSourceSurface(stage), plan.GetSourceColorView(stage), plan.GetSourceColorState(stage) }; } bool ExecuteFrameStage( CameraFrameStage stage, const CameraFramePlan& plan, const DirectionalShadowExecutionState& shadowState, const RenderSceneData& sceneData, CameraFrameExecutionState& executionState) { const RenderPassContext passContext = BuildFrameStagePassContext(stage, plan, sceneData); switch (stage) { case CameraFrameStage::PreScenePasses: return ExecutePassSequenceStage( executionState.preScenePasses, plan.GetPassSequence(stage), plan.request.context, passContext); case CameraFrameStage::ShadowCaster: return ExecuteScenePassRequest( executionState.shadowCasterPass, shadowState.shadowCasterRequest, plan.request.context, sceneData); case CameraFrameStage::DepthOnly: return ExecuteScenePassRequest( executionState.depthOnlyPass, plan.request.depthOnly, plan.request.context, sceneData); case CameraFrameStage::MainScene: return executionState.pipeline != nullptr && executionState.pipeline->Render( FrameExecutionContext( plan.request.context, passContext.surface, sceneData, passContext.sourceSurface, passContext.sourceColorView, passContext.sourceColorState)); case CameraFrameStage::PostProcess: return ExecuteFullscreenPassSequenceStage( executionState.postProcessPasses, plan.GetPassSequence(stage), plan.request.context, passContext, executionState.postProcessSurfaceCache); case CameraFrameStage::FinalOutput: return ExecuteFullscreenPassSequenceStage( executionState.finalOutputPasses, plan.GetPassSequence(stage), plan.request.context, passContext, executionState.finalOutputSurfaceCache); case CameraFrameStage::ObjectId: return !plan.request.objectId.IsRequested() || ExecuteStandalonePass( executionState.objectIdPass, plan.request.context, plan.request.objectId.surface, sceneData); case CameraFrameStage::PostScenePasses: return ExecutePassSequenceStage( executionState.postScenePasses, plan.GetPassSequence(stage), plan.request.context, passContext); case CameraFrameStage::OverlayPasses: return ExecutePassSequenceStage( executionState.overlayPasses, plan.GetPassSequence(stage), plan.request.context, passContext); default: return false; } } bool ExecuteRenderGraphPlan( const CameraFramePlan& plan, const DirectionalShadowExecutionState& shadowState, const RenderSceneData& sceneData, CameraFrameExecutionState& executionState) { RenderGraph graph = {}; RenderGraphBuilder graphBuilder(graph); RenderGraphImportedTextureRegistry importedTextures = {}; bool stageExecutionSucceeded = true; for (const CameraFrameStageInfo& stageInfo : kOrderedCameraFrameStages) { if (!plan.HasFrameStage(stageInfo.stage) && stageInfo.stage != CameraFrameStage::MainScene) { continue; } const CameraFrameStage stage = stageInfo.stage; const Containers::String stageName(GetCameraFrameStageName(stageInfo.stage)); RenderPassSequence* const stageSequence = plan.GetPassSequence(stage); if (IsFullscreenSequenceStage(stage) && stageSequence != nullptr && stageSequence->GetPassCount() > 1u) { const RenderPassContext stagePassContext = BuildFrameStagePassContext(stage, plan, sceneData); const RenderGraphImportedSurface sourceSurface = ImportRenderGraphSurface( graphBuilder, importedTextures, stageName + ".Source", stagePassContext.sourceSurface, RenderGraphSurfaceImportUsage::Source); const RenderGraphImportedSurface outputSurface = ImportRenderGraphSurface( graphBuilder, importedTextures, stageName + ".Output", &stagePassContext.surface, RenderGraphSurfaceImportUsage::Output); RenderGraphTextureHandle currentSourceColor = GetPrimaryColorTexture(sourceSurface); const RenderGraphTextureHandle finalOutputColor = GetPrimaryColorTexture(outputSurface); const RenderGraphTextureDesc transientDesc = BuildFullscreenTransientTextureDesc(stagePassContext.surface); for (size_t passIndex = 0; passIndex < stageSequence->GetPassCount(); ++passIndex) { const bool isLastPass = (passIndex + 1u) == stageSequence->GetPassCount(); const size_t sequencePassIndex = passIndex; const RenderGraphTextureHandle passSourceColor = currentSourceColor; const RenderGraphTextureHandle passOutputColor = isLastPass ? finalOutputColor : graphBuilder.CreateTransientTexture( BuildRenderGraphSequencePassName(stageName, sequencePassIndex) + ".Color", transientDesc); graphBuilder.AddRasterPass( BuildRenderGraphSequencePassName(stageName, sequencePassIndex), [&, stage, sequencePassIndex, passSourceColor, passOutputColor]( RenderGraphPassBuilder& passBuilder) { if (passSourceColor.IsValid()) { passBuilder.ReadTexture(passSourceColor); } if (passOutputColor.IsValid()) { passBuilder.WriteTexture(passOutputColor); } passBuilder.SetExecuteCallback( [&, stage, sequencePassIndex, passSourceColor, passOutputColor]( const RenderGraphExecutionContext& executionContext) { if (!stageExecutionSucceeded) { return; } std::unique_ptr& activeSequence = stage == CameraFrameStage::PostProcess ? executionState.postProcessPasses : executionState.finalOutputPasses; if (!EnsureInitializedPassSequence( activeSequence, plan.GetPassSequence(stage), plan.request.context)) { stageExecutionSucceeded = false; return; } stageExecutionSucceeded = ExecuteFullscreenPassSequencePass( plan.GetPassSequence(stage), sequencePassIndex, BuildFrameStagePassContext(stage, plan, sceneData), executionContext, passSourceColor, passOutputColor); }); }); currentSourceColor = passOutputColor; } continue; } const RenderGraphImportedSurface sourceSurface = ImportRenderGraphSurface( graphBuilder, importedTextures, stageName + ".Source", plan.GetSourceSurface(stageInfo.stage), RenderGraphSurfaceImportUsage::Source); const RenderGraphImportedSurface outputSurface = ImportRenderGraphSurface( graphBuilder, importedTextures, stageName + ".Output", plan.GetOutputSurface(stageInfo.stage), RenderGraphSurfaceImportUsage::Output); graphBuilder.AddRasterPass( stageName, [&, sourceSurface, outputSurface, stage](RenderGraphPassBuilder& passBuilder) { if (IsFullscreenSequenceStage(stage)) { ReadRenderGraphColorSurface(passBuilder, sourceSurface); WriteRenderGraphColorSurface(passBuilder, outputSurface); } else { ReadRenderGraphSurface(passBuilder, sourceSurface); WriteRenderGraphSurface(passBuilder, outputSurface); } passBuilder.SetExecuteCallback( [&, stage](const RenderGraphExecutionContext&) { if (!stageExecutionSucceeded) { return; } stageExecutionSucceeded = ExecuteFrameStage( stage, plan, shadowState, sceneData, executionState); }); }); } CompiledRenderGraph compiledGraph = {}; Containers::String errorMessage; if (!RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, Containers::String("CameraRenderer::Render failed: RenderGraph compile failed: ") + errorMessage); return false; } if (!RenderGraphExecutor::Execute(compiledGraph, plan.request.context, &errorMessage)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, Containers::String("CameraRenderer::Render failed: RenderGraph execute failed: ") + errorMessage); return false; } return stageExecutionSucceeded; } RenderEnvironmentData BuildEnvironmentData(const CameraFramePlan& plan) { RenderEnvironmentData environment = {}; const RenderSurface& mainSceneSurface = plan.GetMainSceneSurface(); if (plan.request.camera == nullptr || mainSceneSurface.GetDepthAttachment() == nullptr || !HasRenderClearFlag(plan.request.clearFlags, RenderClearFlags::Color) || !plan.request.camera->IsSkyboxEnabled() || plan.request.camera->GetProjectionType() != Components::CameraProjectionType::Perspective) { return environment; } if (const Resources::Material* skyboxMaterial = plan.request.camera->GetSkyboxMaterial()) { environment.mode = RenderEnvironmentMode::MaterialSkybox; environment.materialSkybox.material = skyboxMaterial; return environment; } environment.mode = RenderEnvironmentMode::ProceduralSkybox; environment.skybox.topColor = plan.request.camera->GetSkyboxTopColor(); environment.skybox.horizonColor = plan.request.camera->GetSkyboxHorizonColor(); environment.skybox.bottomColor = plan.request.camera->GetSkyboxBottomColor(); return environment; } } // namespace CameraRenderer::CameraRenderer() : CameraRenderer(CreateDefaultPipelineAsset()) { } CameraRenderer::CameraRenderer(std::unique_ptr pipeline) : CameraRenderer( std::move(pipeline), std::make_unique(), CreateDefaultDepthOnlyPass(), CreateDefaultShadowCasterPass()) { } CameraRenderer::CameraRenderer( std::unique_ptr pipeline, std::unique_ptr objectIdPass, std::unique_ptr depthOnlyPass, std::unique_ptr shadowCasterPass) : m_pipelineAsset(nullptr) , m_objectIdPass(std::move(objectIdPass)) , m_depthOnlyPass(std::move(depthOnlyPass)) , m_shadowCasterPass(std::move(shadowCasterPass)) , m_directionalShadowRuntime(std::make_unique()) , m_postProcessSurfaceCache(std::make_unique()) , m_finalOutputSurfaceCache(std::make_unique()) { if (m_objectIdPass == nullptr) { m_objectIdPass = std::make_unique(); } if (m_depthOnlyPass == nullptr) { m_depthOnlyPass = CreateDefaultDepthOnlyPass(); } if (m_shadowCasterPass == nullptr) { m_shadowCasterPass = CreateDefaultShadowCasterPass(); } ResetPipeline(std::move(pipeline)); } CameraRenderer::CameraRenderer(std::shared_ptr pipelineAsset) : m_pipelineAsset(std::move(pipelineAsset)) , m_objectIdPass(std::make_unique()) , m_depthOnlyPass(CreateDefaultDepthOnlyPass()) , m_shadowCasterPass(CreateDefaultShadowCasterPass()) , m_directionalShadowRuntime(std::make_unique()) , m_postProcessSurfaceCache(std::make_unique()) , m_finalOutputSurfaceCache(std::make_unique()) { SetPipelineAsset(m_pipelineAsset); } CameraRenderer::~CameraRenderer() { if (m_pipeline) { m_pipeline->Shutdown(); } if (m_objectIdPass != nullptr) { m_objectIdPass->Shutdown(); } if (m_depthOnlyPass != nullptr) { m_depthOnlyPass->Shutdown(); } if (m_shadowCasterPass != nullptr) { m_shadowCasterPass->Shutdown(); } } void CameraRenderer::SetPipeline(std::unique_ptr pipeline) { m_pipelineAsset.reset(); ResetPipeline(std::move(pipeline)); } void CameraRenderer::SetPipelineAsset(std::shared_ptr pipelineAsset) { m_pipelineAsset = pipelineAsset != nullptr ? std::move(pipelineAsset) : CreateDefaultPipelineAsset(); ResetPipeline(CreatePipelineFromAsset(m_pipelineAsset)); } void CameraRenderer::SetObjectIdPass(std::unique_ptr objectIdPass) { if (m_objectIdPass != nullptr) { m_objectIdPass->Shutdown(); } m_objectIdPass = std::move(objectIdPass); if (m_objectIdPass == nullptr) { m_objectIdPass = std::make_unique(); } } void CameraRenderer::SetDepthOnlyPass(std::unique_ptr depthOnlyPass) { if (m_depthOnlyPass != nullptr) { m_depthOnlyPass->Shutdown(); } m_depthOnlyPass = std::move(depthOnlyPass); if (m_depthOnlyPass == nullptr) { m_depthOnlyPass = CreateDefaultDepthOnlyPass(); } } void CameraRenderer::SetShadowCasterPass(std::unique_ptr shadowCasterPass) { if (m_shadowCasterPass != nullptr) { m_shadowCasterPass->Shutdown(); } m_shadowCasterPass = std::move(shadowCasterPass); if (m_shadowCasterPass == nullptr) { m_shadowCasterPass = CreateDefaultShadowCasterPass(); } } void CameraRenderer::ResetPipeline(std::unique_ptr pipeline) { if (m_pipeline != nullptr) { m_pipeline->Shutdown(); } m_pipeline = std::move(pipeline); if (m_pipeline == nullptr) { m_pipelineAsset = CreateDefaultPipelineAsset(); m_pipeline = CreatePipelineFromAsset(m_pipelineAsset); } } bool CameraRenderer::BuildSceneDataForPlan( const CameraFramePlan& plan, const DirectionalShadowExecutionState& shadowState, RenderSceneData& outSceneData) { const RenderSurface& mainSceneSurface = plan.GetMainSceneSurface(); outSceneData = m_sceneExtractor.ExtractForCamera( *plan.request.scene, *plan.request.camera, mainSceneSurface.GetRenderAreaWidth(), mainSceneSurface.GetRenderAreaHeight()); if (!outSceneData.HasCamera()) { return false; } outSceneData.lighting.mainDirectionalShadow = shadowState.shadowData; outSceneData.globalShaderKeywords = BuildSceneGlobalShaderKeywords(outSceneData); outSceneData.cameraData.clearFlags = plan.request.clearFlags; outSceneData.environment = BuildEnvironmentData(plan); if (plan.request.hasClearColorOverride) { outSceneData.cameraData.clearColor = plan.request.clearColorOverride; } return true; } namespace { bool ExecuteFullscreenPassSequencePass( RenderPassSequence* sequence, size_t passIndex, const RenderPassContext& passContext, const RenderGraphExecutionContext& graphContext, RenderGraphTextureHandle sourceColorHandle, RenderGraphTextureHandle outputColorHandle) { if (sequence == nullptr || passIndex >= sequence->GetPassCount()) { return false; } if (sequence->GetPassCount() <= 1u) { return sequence->ExecutePass(passIndex, passContext); } const RenderSurface* currentSourceSurface = passContext.sourceSurface; RHI::RHIResourceView* currentSourceColorView = passContext.sourceColorView; RHI::ResourceStates currentSourceColorState = passContext.sourceColorState; RenderSurface transientSourceSurface = {}; if (sourceColorHandle.IsValid() && graphContext.IsTransientTexture(sourceColorHandle)) { if (!TryBuildRenderGraphTransientSurface( passContext.surface, graphContext, sourceColorHandle, transientSourceSurface)) { return false; } currentSourceSurface = &transientSourceSurface; currentSourceColorView = graphContext.ResolveTextureView( sourceColorHandle, RenderGraphTextureViewType::ShaderResource); if (currentSourceColorView == nullptr) { return false; } currentSourceColorState = RHI::ResourceStates::PixelShaderResource; } const RenderSurface* outputSurface = &passContext.surface; RenderSurface transientOutputSurface = {}; if (outputColorHandle.IsValid() && graphContext.IsTransientTexture(outputColorHandle)) { if (!TryBuildRenderGraphTransientSurface( passContext.surface, graphContext, outputColorHandle, transientOutputSurface)) { return false; } outputSurface = &transientOutputSurface; } const RenderPassContext chainedContext = { passContext.renderContext, *outputSurface, passContext.sceneData, currentSourceSurface, currentSourceColorView, currentSourceColorState }; return sequence->ExecutePass(passIndex, chainedContext); } } // namespace bool CameraRenderer::ExecuteRenderPlan( const CameraFramePlan& plan, const DirectionalShadowExecutionState& shadowState, const RenderSceneData& sceneData) { CameraFrameExecutionState executionState = {}; executionState.pipeline = m_pipeline.get(); executionState.objectIdPass = m_objectIdPass.get(); executionState.depthOnlyPass = m_depthOnlyPass.get(); executionState.shadowCasterPass = m_shadowCasterPass.get(); executionState.postProcessSurfaceCache = m_postProcessSurfaceCache.get(); executionState.finalOutputSurfaceCache = m_finalOutputSurfaceCache.get(); return ExecuteRenderGraphPlan(plan, shadowState, sceneData, executionState); } bool CameraRenderer::Render( const CameraFramePlan& plan) { if (!plan.IsValid() || m_pipeline == nullptr) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "CameraRenderer::Render failed: plan invalid or pipeline missing"); return false; } const RenderSurface& mainSceneSurface = plan.GetMainSceneSurface(); if (mainSceneSurface.GetRenderAreaWidth() == 0 || mainSceneSurface.GetRenderAreaHeight() == 0) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "CameraRenderer::Render failed: main scene surface render area is empty"); return false; } if (plan.request.depthOnly.IsRequested() && !plan.request.depthOnly.IsValid()) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "CameraRenderer::Render failed: depth-only request invalid"); return false; } if (plan.postProcess.IsRequested() && !plan.postProcess.IsValid()) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "CameraRenderer::Render failed: post-process request invalid"); return false; } if (plan.finalOutput.IsRequested() && !plan.finalOutput.IsValid()) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "CameraRenderer::Render failed: final-output request invalid"); return false; } if (plan.request.objectId.IsRequested() && !plan.request.objectId.IsValid()) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "CameraRenderer::Render failed: object-id request invalid"); return false; } DirectionalShadowExecutionState shadowState = {}; if (m_directionalShadowRuntime == nullptr || !m_directionalShadowRuntime->ResolveExecutionState(plan, shadowState)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "CameraRenderer::Render failed: DirectionalShadowRuntime::ResolveExecutionState returned false"); return false; } RenderSceneData sceneData = {}; if (!BuildSceneDataForPlan(plan, shadowState, sceneData)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "CameraRenderer::Render failed: BuildSceneDataForPlan returned false"); return false; } if (!ExecuteRenderPlan(plan, shadowState, sceneData)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "CameraRenderer::Render failed: ExecuteRenderPlan returned false"); return false; } return true; } } // namespace Rendering } // namespace XCEngine