diff --git a/engine/include/XCEngine/Rendering/AGENTS.md b/engine/include/XCEngine/Rendering/AGENTS.md index 8bfc4d32..7a4d101a 100644 --- a/engine/include/XCEngine/Rendering/AGENTS.md +++ b/engine/include/XCEngine/Rendering/AGENTS.md @@ -6,63 +6,123 @@ already made. It is intentionally not a full map of the current implementation. If this file conflicts with the current code, trust the code and update this file in the same change. -## Long-Term Goal +## Next-Stage Goal -The rendering module is moving toward a Unity SRP / URP style model: +The rendering module's next stage is to close the SRP / URP architecture around +Unity's public rendering model. The goal is not merely to have similar class +names; pipeline authors should be able to reason about XCEngine rendering with +the same mental model they use for Unity SRP and URP. + +Unity alignment is the primary design constraint: + +- Treat `GraphicsSettings.renderPipelineAsset`, `RenderPipelineAsset`, + `ScriptableRenderPipelineAsset`, `ScriptableRenderPipeline`, + `ScriptableRenderContext`, `ScriptableRendererData`, + `ScriptableRenderer`, `ScriptableRendererFeature`, + `ScriptableRenderPass`, `RenderPassEvent`, `RenderingData`, and + `UniversalAdditionalCameraData` as the product-facing spine. +- Keep the managed API close to Unity semantics wherever the engine can support + it. If XCEngine must diverge, the divergence should be explicit, documented, + and caused by a real engine constraint rather than native convenience. +- Prefer Unity-style authoring concepts over native implementation concepts. + Public C# code should feel like URP pipeline authoring, not like remote + control over a native built-in renderer. + +The pipeline selection contract must stay explicit and Unity-like: - `GraphicsSettings.renderPipelineAsset == null` selects the built-in pipeline. -- `GraphicsSettings.renderPipelineAsset != null` selects the programmable SRP - path. -- The managed product shape should stay close to: - `ScriptableRenderPipelineAsset -> ScriptableRendererData -> - ScriptableRenderer -> ScriptableRendererFeature/ScriptableRenderPass`. -- `ScriptableRenderPipelineHost` is the native host for managed SRP execution; - it must not become a hidden fallback renderer when managed SRP planning or - recording fails. -- Managed SRP / URP owns renderer selection, stage planning, feature/pass - organization, camera policy, and optional stage requests such as shadow, - depth, post-process, and final output. -- Native rendering owns scene extraction, renderer-list drawing, render graph - execution, fullscreen primitive execution, and built-in private draw kernels. -- Public managed scene drawing should expose drawing capability, not native - built-in injection order. Keep the public surface centered on - `DrawRenderers(...)`, `DrawSkybox()`, `DrawOpaqueRenderers()`, and +- `GraphicsSettings.renderPipelineAsset != null` selects the managed SRP path. +- Runtime startup must not silently assign a project default SRP asset. A + project default can be loaded by project/editor policy, but rendering mode + changes must remain visible through `GraphicsSettings.renderPipelineAsset`. +- If a managed SRP asset cannot create a valid pipeline, recorder, or backend + bridge, creation should fail visibly and let the top-level factory choose the + built-in pipeline. `ScriptableRenderPipelineHost` must not impersonate a + successful managed SRP by drawing through a hidden fallback renderer. + +The URP object model should be the center of future work: + +- `ScriptableRenderPipelineAsset` owns pipeline defaults, renderer-data lists, + default renderer selection, camera policy, final-color policy, shadow policy, + and managed pipeline lifetime. +- `ScriptableRendererData` is the serialized renderer configuration. It owns + renderer feature lists, default feature creation, runtime invalidation, and + renderer instance rebuilds. +- `ScriptableRenderer` owns per-camera pass queue construction. It should + enqueue passes from renderer features, sort by `RenderPassEvent`, partition + work into URP-like renderer blocks, and record only the stage requested by + the native frame plan. +- `ScriptableRendererFeature` owns reusable renderer extensions. Features + should configure camera/frame policy and enqueue `ScriptableRenderPass` + instances without reaching into native private pass order. +- `ScriptableRenderPass` owns pass lifecycle and recording. The long-term pass + surface should be `RecordRenderGraph(...)`, renderer-list drawing, public + texture/resource declarations, and explicit pass data. + +The native/managed boundary should remain strict: + +- Managed SRP / URP owns renderer selection, feature/pass organization, + optional stage planning, camera clear policy, shadow/depth requests, + post-process and final-output policy, and public frame data exposed to user + passes. +- Native rendering owns scene extraction, culling and renderer-list + realization, render graph compilation/execution, RHI resource lifetime, + fullscreen primitive execution, and private built-in draw kernels. +- Managed scene drawing should expose draw capabilities, not native injection + points. Keep the public scene surface centered on `DrawRenderers(...)`, + `DrawSkybox()`, `DrawOpaqueRenderers()`, and `DrawTransparentRenderers()`. -- Do not reintroduce implicit project-default SRP activation, hidden - `ScriptableRenderPipelineHost` fallback, or public managed wrappers for - built-in private scene injection points. -- Future URP alignment should add managed-side renderer/pass/render-graph - capability. It should not reopen native private fallback paths just to make a - feature appear to work. +- Main-scene, shadow, and depth work should be renderer-driven. Managed passes + may request those stages and call public draw APIs, while native continues to + execute renderer lists and low-level draw kernels. + +Managed RenderGraph is the key capability gap for the next stage: + +- Grow the current fullscreen-pass bridge into a URP-style render-graph + authoring surface with explicit texture creation/imports, read/write + declarations, pass data, frame-data/context-container access, and scheduled + renderer-list drawing. +- Post-process and final-output should become ordinary URP feature/pass work + where possible. Native fullscreen kernels may remain execution primitives, + but they should not appear as feature-specific public shortcuts. +- Prefer data-driven frame state (`CameraData`, `LightingData`, `ShadowData`, + `EnvironmentData`, `FinalColorData`, `StageColorData`) over direct scene + object inspection during pass recording. + +Tests should protect Unity-facing semantics while the architecture closes: + +- Prioritize contract tests for null-vs-SRP selection, managed SRP creation + failure, no hidden fallback rendering, renderer-data/default-renderer + selection, camera renderer override, feature/pass enqueue order, stage + planning, render-graph recording, and forbidden public API surface. +- Add URP integration scenes after the API and responsibility boundaries are + stable: default Universal renderer, `RenderObjectsRendererFeature`, + post-process/final-output, shadow/depth, camera override, and camera stack. +- Avoid overfitting tests to temporary native bridge details. Tests should lock + Unity SRP / URP semantics first, implementation mechanics second. + +Guardrails: + +- Do not reintroduce public managed wrappers for native private scene injection + points. +- Do not make `RenderPassEvent` or renderer blocks a thin alias for native + built-in pass order. They should describe URP scheduling semantics. +- Do not add managed APIs that only work by reaching into one built-in native + renderer path. Add a public renderer-list, render-graph, or pass abstraction + instead. +- A managed SRP stage that declares support and then fails to record should + fail visibly. It should not silently continue by drawing the default native + scene. ## Past Execution -- Established the top-level selection rule: explicit null means built-in, - explicit managed asset means SRP, and runtime startup must not silently assign - `ProjectDefaultRenderPipelineAsset`. -- Removed public managed native scene injection APIs such as - `ScriptableRenderContext.RecordSceneInjectionPoint(...)`, - `RecordBefore/After*Injection(...)`, and - `RecordOpaque/Skybox/TransparentScenePhase(...)`. -- Kept `RecordScene()` / `RecordScenePhase(...)` internal and made the public - managed scene surface express draw operations through `DrawRenderers(...)`, - `DrawSkybox()`, `DrawOpaqueRenderers()`, and - `DrawTransparentRenderers()`. -- Moved renderer-backed SRP stage ownership into `ScriptableRenderer`, - `ScriptableRendererFeature`, and `ScriptableRenderPass`; renderer-backed - assets clear native optional stage defaults before managed renderer/features - explicitly request the stages they need. -- Removed `CameraRenderRequestContext.hasDirectionalShadow` and - `ClearDirectionalShadow()` from the ScriptCore public surface. Core keeps only - internal bridge access, while URP policy belongs in renderer asset, renderer - data, and feature code. -- Reordered managed camera request policy so managed / URP first observes the - native directional-shadow baseline, then the final managed policy recomputes - or clears the request without native planning adding it back afterward. -- Prevented URP default renderer feature factories from auto-injecting built-in - private feature wrappers. -- Tightened SRP creation failure semantics: - `ManagedScriptableRenderPipelineAsset::CreatePipeline()` fails when managed - runtime, backend asset, or stage recorder is unavailable; factory fallback - then returns the built-in pipeline instead of letting - `ScriptableRenderPipelineHost` impersonate a successful managed SRP. +- Made the public managed + `ScriptableRenderPass.RecordRenderGraph(RenderGraph, ContextContainer)` path + receive real URP frame data. `RenderingData`, `CameraData`, `LightingData`, + `ShadowData`, `EnvironmentData`, `FinalColorData`, and `StageColorData` are + now `ContextItem`s populated before public render-graph recording. +- Moved managed public render-graph frame data ownership from individual + `ScriptableRenderPass` calls to the `ScriptableRenderer` stage recording + scope. Public `ContextContainer` state is now shared across renderer passes + for the same stage, matching URP's frame-data mental model instead of + pass-local scratch data. diff --git a/managed/GameScripts/RenderPipelineApiProbe.cs b/managed/GameScripts/RenderPipelineApiProbe.cs index b8dcb353..5aaec597 100644 --- a/managed/GameScripts/RenderPipelineApiProbe.cs +++ b/managed/GameScripts/RenderPipelineApiProbe.cs @@ -1173,6 +1173,297 @@ namespace Gameplay } } + internal static class PublicRenderGraphFrameDataProbeState + { + public static bool HasRenderingData; + public static bool HasCameraData; + public static bool HasLightingData; + public static bool HasShadowData; + public static bool HasEnvironmentData; + public static bool HasFinalColorData; + public static bool HasStageColorData; + public static bool RenderingDataMatchesItems; + public static int ObservedStage; + public static int ObservedRendererIndex; + + public static void Reset() + { + HasRenderingData = false; + HasCameraData = false; + HasLightingData = false; + HasShadowData = false; + HasEnvironmentData = false; + HasFinalColorData = false; + HasStageColorData = false; + RenderingDataMatchesItems = false; + ObservedStage = -1; + ObservedRendererIndex = -1; + } + } + + internal sealed class PublicRenderGraphFrameDataProbePass + : ScriptableRenderPass + { + public PublicRenderGraphFrameDataProbePass() + { + renderPassEvent = RenderPassEvent.RenderOpaques; + } + + public override void RecordRenderGraph( + RenderGraph renderGraph, + ContextContainer frameData) + { + PublicRenderGraphFrameDataProbeState.HasRenderingData = + frameData != null && + frameData.Contains(); + PublicRenderGraphFrameDataProbeState.HasCameraData = + frameData != null && + frameData.Contains(); + PublicRenderGraphFrameDataProbeState.HasLightingData = + frameData != null && + frameData.Contains(); + PublicRenderGraphFrameDataProbeState.HasShadowData = + frameData != null && + frameData.Contains(); + PublicRenderGraphFrameDataProbeState.HasEnvironmentData = + frameData != null && + frameData.Contains(); + PublicRenderGraphFrameDataProbeState.HasFinalColorData = + frameData != null && + frameData.Contains(); + PublicRenderGraphFrameDataProbeState.HasStageColorData = + frameData != null && + frameData.Contains(); + + if (!PublicRenderGraphFrameDataProbeState.HasRenderingData) + { + return; + } + + RenderingData renderingData = + frameData.Get(); + PublicRenderGraphFrameDataProbeState.ObservedStage = + (int)renderingData.stage; + PublicRenderGraphFrameDataProbeState.ObservedRendererIndex = + renderingData.rendererIndex; + PublicRenderGraphFrameDataProbeState + .RenderingDataMatchesItems = + object.ReferenceEquals( + renderingData.cameraData, + frameData.Get()) && + object.ReferenceEquals( + renderingData.lightingData, + frameData.Get()) && + object.ReferenceEquals( + renderingData.shadowData, + frameData.Get()) && + object.ReferenceEquals( + renderingData.environmentData, + frameData.Get()) && + object.ReferenceEquals( + renderingData.finalColorData, + frameData.Get()) && + object.ReferenceEquals( + renderingData.stageColorData, + frameData.Get()); + } + } + + internal sealed class PublicRenderGraphFrameDataProbeFeature + : ScriptableRendererFeature + { + private readonly PublicRenderGraphFrameDataProbePass m_pass = + new PublicRenderGraphFrameDataProbePass(); + + public override void AddRenderPasses( + ScriptableRenderer renderer, + RenderingData renderingData) + { + if (renderer == null || + renderingData == null || + !renderingData.isMainSceneStage) + { + return; + } + + renderer.EnqueuePass(m_pass); + } + } + + internal sealed class PublicRenderGraphFrameDataProbeRenderer + : ScriptableRenderer + { + public PublicRenderGraphFrameDataProbeRenderer() + { + AddFeature(new PublicRenderGraphFrameDataProbeFeature()); + } + } + + internal sealed class PublicRenderGraphSharedFrameDataProbeItem + : ContextItem + { + public int Value; + public int WriterOrder; + public RenderingData RenderingData; + + public PublicRenderGraphSharedFrameDataProbeItem() + { + } + + public override void Reset() + { + Value = 0; + WriterOrder = 0; + RenderingData = null; + } + } + + internal static class PublicRenderGraphSharedFrameDataProbeState + { + public static bool WriterVisited; + public static bool ReaderVisited; + public static bool ReaderObservedWriterItem; + public static bool ReaderObservedSameFrameData; + public static bool ReaderObservedSameRenderingData; + public static int ReaderObservedValue; + public static int ReaderOrder; + public static ContextContainer WriterFrameData; + public static int NextOrder; + + public static void Reset() + { + WriterVisited = false; + ReaderVisited = false; + ReaderObservedWriterItem = false; + ReaderObservedSameFrameData = false; + ReaderObservedSameRenderingData = false; + ReaderObservedValue = 0; + ReaderOrder = 0; + WriterFrameData = null; + NextOrder = 0; + } + } + + internal sealed class PublicRenderGraphSharedFrameDataWriterPass + : ScriptableRenderPass + { + public PublicRenderGraphSharedFrameDataWriterPass() + { + renderPassEvent = RenderPassEvent.RenderOpaques; + } + + public override void RecordRenderGraph( + RenderGraph renderGraph, + ContextContainer frameData) + { + PublicRenderGraphSharedFrameDataProbeState.WriterVisited = + frameData != null; + PublicRenderGraphSharedFrameDataProbeState.WriterFrameData = + frameData; + if (frameData == null) + { + return; + } + + PublicRenderGraphSharedFrameDataProbeItem item = + frameData + .GetOrCreate(); + item.Value = 73; + item.WriterOrder = + ++PublicRenderGraphSharedFrameDataProbeState.NextOrder; + item.RenderingData = + frameData.Contains() + ? frameData.Get() + : null; + } + } + + internal sealed class PublicRenderGraphSharedFrameDataReaderPass + : ScriptableRenderPass + { + public PublicRenderGraphSharedFrameDataReaderPass() + { + renderPassEvent = RenderPassEvent.AfterRenderingOpaques; + } + + public override void RecordRenderGraph( + RenderGraph renderGraph, + ContextContainer frameData) + { + PublicRenderGraphSharedFrameDataProbeState.ReaderVisited = + frameData != null; + PublicRenderGraphSharedFrameDataProbeState.ReaderOrder = + ++PublicRenderGraphSharedFrameDataProbeState.NextOrder; + PublicRenderGraphSharedFrameDataProbeState + .ReaderObservedSameFrameData = + object.ReferenceEquals( + PublicRenderGraphSharedFrameDataProbeState + .WriterFrameData, + frameData); + + if (frameData == null || + !frameData + .Contains()) + { + return; + } + + PublicRenderGraphSharedFrameDataProbeItem item = + frameData + .Get(); + PublicRenderGraphSharedFrameDataProbeState + .ReaderObservedWriterItem = + item.WriterOrder > 0 && + item.WriterOrder < + PublicRenderGraphSharedFrameDataProbeState.ReaderOrder; + PublicRenderGraphSharedFrameDataProbeState + .ReaderObservedValue = + item.Value; + PublicRenderGraphSharedFrameDataProbeState + .ReaderObservedSameRenderingData = + frameData.Contains() && + object.ReferenceEquals( + item.RenderingData, + frameData.Get()); + } + } + + internal sealed class PublicRenderGraphSharedFrameDataProbeFeature + : ScriptableRendererFeature + { + private readonly PublicRenderGraphSharedFrameDataWriterPass + m_writerPass = + new PublicRenderGraphSharedFrameDataWriterPass(); + private readonly PublicRenderGraphSharedFrameDataReaderPass + m_readerPass = + new PublicRenderGraphSharedFrameDataReaderPass(); + + public override void AddRenderPasses( + ScriptableRenderer renderer, + RenderingData renderingData) + { + if (renderer == null || + renderingData == null || + !renderingData.isMainSceneStage) + { + return; + } + + renderer.EnqueuePass(m_writerPass); + renderer.EnqueuePass(m_readerPass); + } + } + + internal sealed class PublicRenderGraphSharedFrameDataProbeRenderer + : ScriptableRenderer + { + public PublicRenderGraphSharedFrameDataProbeRenderer() + { + AddFeature( + new PublicRenderGraphSharedFrameDataProbeFeature()); + } + } + internal abstract class ProbeRendererData : UniversalRendererData { @@ -1532,6 +1823,24 @@ namespace Gameplay } } + internal sealed class PublicRenderGraphFrameDataProbeRendererData + : ProbeRendererData + { + protected override ScriptableRenderer CreateProbeRenderer() + { + return new PublicRenderGraphFrameDataProbeRenderer(); + } + } + + internal sealed class PublicRenderGraphSharedFrameDataProbeRendererData + : ProbeRendererData + { + protected override ScriptableRenderer CreateProbeRenderer() + { + return new PublicRenderGraphSharedFrameDataProbeRenderer(); + } + } + internal static class ManagedLifecycleProbeState { public static int CreatePipelineCallCount; @@ -2230,6 +2539,32 @@ namespace Gameplay } } + public sealed class ManagedPublicRenderGraphFrameDataProbeAsset + : UniversalRenderPipelineAsset + { + public ManagedPublicRenderGraphFrameDataProbeAsset() + { + rendererDataList = + ProbeScriptableObjectFactory + .CreateRendererDataList( + ProbeScriptableObjectFactory + .Create()); + } + } + + public sealed class ManagedPublicRenderGraphSharedFrameDataProbeAsset + : UniversalRenderPipelineAsset + { + public ManagedPublicRenderGraphSharedFrameDataProbeAsset() + { + rendererDataList = + ProbeScriptableObjectFactory + .CreateRendererDataList( + ProbeScriptableObjectFactory + .Create()); + } + } + internal sealed class ManagedMainSceneRasterPassProbePipeline : ScriptableRenderPipeline { @@ -3372,5 +3707,106 @@ namespace Gameplay StageColorObservationPass.FinalOutputRecordCallCount; } } -} + public sealed class ManagedPublicRenderGraphFrameDataRuntimeSelectionProbe + : MonoBehaviour + { + public bool ObservedHasRenderingData; + public bool ObservedHasCameraData; + public bool ObservedHasLightingData; + public bool ObservedHasShadowData; + public bool ObservedHasEnvironmentData; + public bool ObservedHasFinalColorData; + public bool ObservedHasStageColorData; + public bool ObservedRenderingDataMatchesItems; + public int ObservedStage; + public int ObservedRendererIndex; + + public void Start() + { + PublicRenderGraphFrameDataProbeState.Reset(); + GraphicsSettings.renderPipelineAsset = + ProbeScriptableObjectFactory + .Create(); + } + + public void Update() + { + ObservedHasRenderingData = + PublicRenderGraphFrameDataProbeState + .HasRenderingData; + ObservedHasCameraData = + PublicRenderGraphFrameDataProbeState + .HasCameraData; + ObservedHasLightingData = + PublicRenderGraphFrameDataProbeState + .HasLightingData; + ObservedHasShadowData = + PublicRenderGraphFrameDataProbeState + .HasShadowData; + ObservedHasEnvironmentData = + PublicRenderGraphFrameDataProbeState + .HasEnvironmentData; + ObservedHasFinalColorData = + PublicRenderGraphFrameDataProbeState + .HasFinalColorData; + ObservedHasStageColorData = + PublicRenderGraphFrameDataProbeState + .HasStageColorData; + ObservedRenderingDataMatchesItems = + PublicRenderGraphFrameDataProbeState + .RenderingDataMatchesItems; + ObservedStage = + PublicRenderGraphFrameDataProbeState + .ObservedStage; + ObservedRendererIndex = + PublicRenderGraphFrameDataProbeState + .ObservedRendererIndex; + } + } + + public sealed class ManagedPublicRenderGraphSharedFrameDataRuntimeSelectionProbe + : MonoBehaviour + { + public bool ObservedWriterVisited; + public bool ObservedReaderVisited; + public bool ObservedReaderSawWriterItem; + public bool ObservedSameFrameData; + public bool ObservedSameRenderingData; + public int ObservedValue; + public int ObservedReaderOrder; + + public void Start() + { + PublicRenderGraphSharedFrameDataProbeState.Reset(); + GraphicsSettings.renderPipelineAsset = + ProbeScriptableObjectFactory + .Create(); + } + + public void Update() + { + ObservedWriterVisited = + PublicRenderGraphSharedFrameDataProbeState + .WriterVisited; + ObservedReaderVisited = + PublicRenderGraphSharedFrameDataProbeState + .ReaderVisited; + ObservedReaderSawWriterItem = + PublicRenderGraphSharedFrameDataProbeState + .ReaderObservedWriterItem; + ObservedSameFrameData = + PublicRenderGraphSharedFrameDataProbeState + .ReaderObservedSameFrameData; + ObservedSameRenderingData = + PublicRenderGraphSharedFrameDataProbeState + .ReaderObservedSameRenderingData; + ObservedValue = + PublicRenderGraphSharedFrameDataProbeState + .ReaderObservedValue; + ObservedReaderOrder = + PublicRenderGraphSharedFrameDataProbeState + .ReaderOrder; + } + } +} diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/CameraData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/CameraData.cs index f6c5e71d..ad776a54 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/CameraData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/CameraData.cs @@ -3,7 +3,7 @@ using XCEngine.Rendering; namespace XCEngine.Rendering.Universal { - public sealed class CameraData + public sealed class CameraData : ContextItem { internal static readonly CameraData Default = new CameraData( diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/EnvironmentData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/EnvironmentData.cs index 20425621..ec68dbaa 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/EnvironmentData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/EnvironmentData.cs @@ -1,8 +1,9 @@ using XCEngine; +using XCEngine.Rendering; namespace XCEngine.Rendering.Universal { - public sealed class EnvironmentData + public sealed class EnvironmentData : ContextItem { internal static readonly EnvironmentData Default = new EnvironmentData( diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/FinalColorData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/FinalColorData.cs index 43772885..f791dbc0 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/FinalColorData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/FinalColorData.cs @@ -1,8 +1,9 @@ using XCEngine; +using XCEngine.Rendering; namespace XCEngine.Rendering.Universal { - public sealed class FinalColorData + public sealed class FinalColorData : ContextItem { internal static readonly FinalColorData Default = new FinalColorData( diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/LightingData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/LightingData.cs index eb013c41..30943a09 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/LightingData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/LightingData.cs @@ -1,8 +1,9 @@ using XCEngine; +using XCEngine.Rendering; namespace XCEngine.Rendering.Universal { - public sealed class LightingData + public sealed class LightingData : ContextItem { internal static readonly LightingData Default = new LightingData( diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderingData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderingData.cs index 01d06044..265e3bdb 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderingData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderingData.cs @@ -3,7 +3,7 @@ using XCEngine.Rendering; namespace XCEngine.Rendering.Universal { - public sealed class RenderingData + public sealed class RenderingData : ContextItem { internal RenderingData(CameraFrameStage stage) : this( diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderPass.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderPass.cs index ebe94225..3621ce98 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderPass.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderPass.cs @@ -73,7 +73,9 @@ namespace XCEngine.Rendering.Universal internal bool Record( ScriptableRenderContext context, - RenderingData renderingData) + RenderingData renderingData, + RenderGraph renderGraph, + ContextContainer frameData) { RenderingData passRenderingData = renderingData; @@ -85,13 +87,12 @@ namespace XCEngine.Rendering.Universal { if (HasPublicRenderGraphOverride()) { - using (ContextContainer frameData = - new ContextContainer()) - { - RecordRenderGraph( - new RenderGraph(context), - frameData); - } + PopulateRenderGraphFrameData( + frameData, + passRenderingData); + RecordRenderGraph( + renderGraph, + frameData); return true; } @@ -441,6 +442,40 @@ namespace XCEngine.Rendering.Universal typeof(ScriptableRenderPass); } + internal static ContextContainer BuildRenderGraphFrameData( + RenderingData renderingData) + { + ContextContainer frameData = + new ContextContainer(); + PopulateRenderGraphFrameData( + frameData, + renderingData); + return frameData; + } + + internal static void PopulateRenderGraphFrameData( + ContextContainer frameData, + RenderingData renderingData) + { + if (frameData == null) + { + return; + } + + if (renderingData == null) + { + return; + } + + frameData.Set(renderingData); + frameData.Set(renderingData.cameraData); + frameData.Set(renderingData.lightingData); + frameData.Set(renderingData.shadowData); + frameData.Set(renderingData.environmentData); + frameData.Set(renderingData.finalColorData); + frameData.Set(renderingData.stageColorData); + } + private static bool TryResolveDefaultFullscreenTargets( ScriptableRenderContext context, out RenderGraphTextureHandle sourceColor, diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs index 6451d3a7..5e7fbc28 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using XCEngine; using XCEngine.Rendering; +using XCEngine.Rendering.RenderGraphModule; namespace XCEngine.Rendering.Universal { @@ -170,8 +171,17 @@ namespace XCEngine.Rendering.Universal RenderingData renderingData = context.renderingData; BuildPassQueue(renderingData); - bool recorded = - RecordRendererStage(context); + bool recorded; + using (ContextContainer frameData = + ScriptableRenderPass.BuildRenderGraphFrameData( + renderingData)) + { + recorded = + RecordRendererStage( + context, + new RenderGraph(context.renderContext), + frameData); + } if (recorded) { FinishCameraStackRendering(); @@ -299,7 +309,9 @@ namespace XCEngine.Rendering.Universal } protected virtual bool RecordRendererStage( - RendererRecordingContext context) + RendererRecordingContext context, + RenderGraph renderGraph, + ContextContainer frameData) { if (context == null || context.renderContext == null) @@ -314,38 +326,52 @@ namespace XCEngine.Rendering.Universal return RecordRendererBlock( RendererBlock.ShadowCaster, context, + renderGraph, + frameData, ref recordedAnyPass) && recordedAnyPass; case CameraFrameStage.DepthOnly: return RecordRendererBlock( RendererBlock.DepthPrepass, context, + renderGraph, + frameData, ref recordedAnyPass) && recordedAnyPass; case CameraFrameStage.MainScene: return RecordRendererBlock( RendererBlock.MainOpaque, context, + renderGraph, + frameData, ref recordedAnyPass) && RecordRendererBlock( RendererBlock.MainSkybox, context, + renderGraph, + frameData, ref recordedAnyPass) && RecordRendererBlock( RendererBlock.MainTransparent, context, + renderGraph, + frameData, ref recordedAnyPass) && recordedAnyPass; case CameraFrameStage.PostProcess: return RecordRendererBlock( RendererBlock.PostProcess, context, + renderGraph, + frameData, ref recordedAnyPass) && recordedAnyPass; case CameraFrameStage.FinalOutput: return RecordRendererBlock( RendererBlock.FinalOutput, context, + renderGraph, + frameData, ref recordedAnyPass) && recordedAnyPass; default: @@ -356,6 +382,8 @@ namespace XCEngine.Rendering.Universal private bool RecordRendererBlock( RendererBlock block, RendererRecordingContext context, + RenderGraph renderGraph, + ContextContainer frameData, ref bool recordedAnyPass) { if (context == null || @@ -388,7 +416,9 @@ namespace XCEngine.Rendering.Universal if (!renderPass.Record( context.renderContext, - context.renderingData)) + context.renderingData, + renderGraph, + frameData)) { return false; } diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ShadowData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ShadowData.cs index 13d6c86e..01aef53e 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ShadowData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ShadowData.cs @@ -1,8 +1,9 @@ using XCEngine; +using XCEngine.Rendering; namespace XCEngine.Rendering.Universal { - public sealed class ShadowData + public sealed class ShadowData : ContextItem { internal static readonly ShadowData Default = new ShadowData( diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/StageColorData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/StageColorData.cs index 4607b838..1b46ff76 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/StageColorData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/StageColorData.cs @@ -1,8 +1,9 @@ using XCEngine; +using XCEngine.Rendering; namespace XCEngine.Rendering.Universal { - public sealed class StageColorData + public sealed class StageColorData : ContextItem { internal static readonly StageColorData Default = new StageColorData( diff --git a/managed/XCEngine.ScriptCore/Rendering/Core/ContextContainer.cs b/managed/XCEngine.ScriptCore/Rendering/Core/ContextContainer.cs index 220e6a59..40a573bf 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Core/ContextContainer.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Core/ContextContainer.cs @@ -56,6 +56,20 @@ namespace XCEngine.Rendering return Create(); } + internal void Set( + T item) + where T : ContextItem + { + ThrowIfDisposed(); + if (item == null) + { + m_items.Remove(typeof(T)); + return; + } + + m_items[typeof(T)] = item; + } + public void Dispose() { if (m_disposed) diff --git a/tests/scripting/test_mono_script_runtime.cpp b/tests/scripting/test_mono_script_runtime.cpp index 61f522c9..d03dd359 100644 --- a/tests/scripting/test_mono_script_runtime.cpp +++ b/tests/scripting/test_mono_script_runtime.cpp @@ -758,6 +758,271 @@ TEST_F( host->GetStageRecorder()->Shutdown(); } +TEST_F( + MonoScriptRuntimeTest, + PublicRecordRenderGraphReceivesUniversalFrameDataContextItems) { + Scene* runtimeScene = + CreateScene("ManagedPublicRenderGraphFrameDataScene"); + GameObject* selectionObject = + runtimeScene->CreateGameObject( + "ManagedPublicRenderGraphFrameDataSelection"); + ScriptComponent* selectionScript = + AddScript( + selectionObject, + "Gameplay", + "ManagedPublicRenderGraphFrameDataRuntimeSelectionProbe"); + ASSERT_NE(selectionScript, nullptr); + + engine->OnRuntimeStart(runtimeScene); + engine->OnUpdate(0.016f); + + XCEngine::Rendering::CameraRenderer renderer; + const auto* asset = + dynamic_cast< + const XCEngine::Rendering::Pipelines::ManagedScriptableRenderPipelineAsset*>( + renderer.GetPipelineAsset()); + ASSERT_NE(asset, nullptr); + EXPECT_EQ(asset->GetDescriptor().assemblyName, "GameScripts"); + EXPECT_EQ(asset->GetDescriptor().namespaceName, "Gameplay"); + EXPECT_EQ( + asset->GetDescriptor().className, + "ManagedPublicRenderGraphFrameDataProbeAsset"); + + auto* host = + dynamic_cast( + renderer.GetPipeline()); + ASSERT_NE(host, nullptr); + ASSERT_NE(host->GetStageRecorder(), nullptr); + ASSERT_TRUE( + host->GetStageRecorder()->Initialize( + XCEngine::Rendering::RenderContext{})) + << runtime->GetLastError(); + ASSERT_TRUE( + host->SupportsStageRenderGraph( + XCEngine::Rendering::RenderPipelineStageSupportContext{ + XCEngine::Rendering::CameraFrameStage::MainScene, + 0 })) + << runtime->GetLastError(); + + XCEngine::Rendering::RenderGraph graph; + XCEngine::Rendering::RenderGraphBuilder graphBuilder(graph); + XCEngine::Rendering::RenderGraphTextureDesc colorDesc = {}; + colorDesc.width = 16u; + colorDesc.height = 16u; + colorDesc.format = + static_cast( + XCEngine::RHI::Format::R8G8B8A8_UNorm); + XCEngine::Rendering::RenderGraphTextureDesc depthDesc = colorDesc; + depthDesc.format = + static_cast( + XCEngine::RHI::Format::D32_Float); + const XCEngine::Rendering::RenderGraphTextureHandle colorTarget = + graphBuilder.CreateTransientTexture( + "ManagedPublicFrameDataColor", + colorDesc); + const XCEngine::Rendering::RenderGraphTextureHandle depthTarget = + graphBuilder.CreateTransientTexture( + "ManagedPublicFrameDataDepth", + depthDesc); + + const XCEngine::Rendering::RenderSceneData sceneData = {}; + const XCEngine::Rendering::RenderSurface surface(16u, 16u); + bool executionSucceeded = true; + XCEngine::Rendering::RenderGraphBlackboard blackboard = {}; + const XCEngine::Rendering::RenderPipelineStageRenderGraphContext graphContext = { + graphBuilder, + "ManagedPublicFrameData", + XCEngine::Rendering::CameraFrameStage::MainScene, + {}, + sceneData, + surface, + nullptr, + nullptr, + XCEngine::RHI::ResourceStates::Common, + {}, + { colorTarget }, + depthTarget, + {}, + &executionSucceeded, + &blackboard, + XCEngine::Rendering::CameraFrameColorSource::ExplicitSurface, + false, + {}, + 0 + }; + + EXPECT_TRUE( + host->GetStageRecorder()->RecordStageRenderGraph(graphContext)) + << runtime->GetLastError(); + engine->OnUpdate(0.016f); + + const auto expectBoolField = + [&](const char* fieldName) { + bool value = false; + EXPECT_TRUE(runtime->TryGetFieldValue( + selectionScript, + fieldName, + value)); + EXPECT_TRUE(value) << fieldName; + }; + expectBoolField("ObservedHasRenderingData"); + expectBoolField("ObservedHasCameraData"); + expectBoolField("ObservedHasLightingData"); + expectBoolField("ObservedHasShadowData"); + expectBoolField("ObservedHasEnvironmentData"); + expectBoolField("ObservedHasFinalColorData"); + expectBoolField("ObservedHasStageColorData"); + expectBoolField("ObservedRenderingDataMatchesItems"); + + int observedStage = -1; + int observedRendererIndex = -1; + EXPECT_TRUE(runtime->TryGetFieldValue( + selectionScript, + "ObservedStage", + observedStage)); + EXPECT_TRUE(runtime->TryGetFieldValue( + selectionScript, + "ObservedRendererIndex", + observedRendererIndex)); + EXPECT_EQ( + observedStage, + static_cast( + XCEngine::Rendering::CameraFrameStage::MainScene)); + EXPECT_EQ(observedRendererIndex, 0); + + host->GetStageRecorder()->Shutdown(); +} + +TEST_F( + MonoScriptRuntimeTest, + PublicRecordRenderGraphSharesFrameDataAcrossRendererPasses) { + Scene* runtimeScene = + CreateScene("ManagedPublicRenderGraphSharedFrameDataScene"); + GameObject* selectionObject = + runtimeScene->CreateGameObject( + "ManagedPublicRenderGraphSharedFrameDataSelection"); + ScriptComponent* selectionScript = + AddScript( + selectionObject, + "Gameplay", + "ManagedPublicRenderGraphSharedFrameDataRuntimeSelectionProbe"); + ASSERT_NE(selectionScript, nullptr); + + engine->OnRuntimeStart(runtimeScene); + engine->OnUpdate(0.016f); + + XCEngine::Rendering::CameraRenderer renderer; + const auto* asset = + dynamic_cast< + const XCEngine::Rendering::Pipelines::ManagedScriptableRenderPipelineAsset*>( + renderer.GetPipelineAsset()); + ASSERT_NE(asset, nullptr); + EXPECT_EQ(asset->GetDescriptor().assemblyName, "GameScripts"); + EXPECT_EQ(asset->GetDescriptor().namespaceName, "Gameplay"); + EXPECT_EQ( + asset->GetDescriptor().className, + "ManagedPublicRenderGraphSharedFrameDataProbeAsset"); + + auto* host = + dynamic_cast( + renderer.GetPipeline()); + ASSERT_NE(host, nullptr); + ASSERT_NE(host->GetStageRecorder(), nullptr); + ASSERT_TRUE( + host->GetStageRecorder()->Initialize( + XCEngine::Rendering::RenderContext{})) + << runtime->GetLastError(); + ASSERT_TRUE( + host->SupportsStageRenderGraph( + XCEngine::Rendering::RenderPipelineStageSupportContext{ + XCEngine::Rendering::CameraFrameStage::MainScene, + 0 })) + << runtime->GetLastError(); + + XCEngine::Rendering::RenderGraph graph; + XCEngine::Rendering::RenderGraphBuilder graphBuilder(graph); + XCEngine::Rendering::RenderGraphTextureDesc colorDesc = {}; + colorDesc.width = 16u; + colorDesc.height = 16u; + colorDesc.format = + static_cast( + XCEngine::RHI::Format::R8G8B8A8_UNorm); + XCEngine::Rendering::RenderGraphTextureDesc depthDesc = colorDesc; + depthDesc.format = + static_cast( + XCEngine::RHI::Format::D32_Float); + const XCEngine::Rendering::RenderGraphTextureHandle colorTarget = + graphBuilder.CreateTransientTexture( + "ManagedPublicSharedFrameDataColor", + colorDesc); + const XCEngine::Rendering::RenderGraphTextureHandle depthTarget = + graphBuilder.CreateTransientTexture( + "ManagedPublicSharedFrameDataDepth", + depthDesc); + + const XCEngine::Rendering::RenderSceneData sceneData = {}; + const XCEngine::Rendering::RenderSurface surface(16u, 16u); + bool executionSucceeded = true; + XCEngine::Rendering::RenderGraphBlackboard blackboard = {}; + const XCEngine::Rendering::RenderPipelineStageRenderGraphContext graphContext = { + graphBuilder, + "ManagedPublicSharedFrameData", + XCEngine::Rendering::CameraFrameStage::MainScene, + {}, + sceneData, + surface, + nullptr, + nullptr, + XCEngine::RHI::ResourceStates::Common, + {}, + { colorTarget }, + depthTarget, + {}, + &executionSucceeded, + &blackboard, + XCEngine::Rendering::CameraFrameColorSource::ExplicitSurface, + false, + {}, + 0 + }; + + EXPECT_TRUE( + host->GetStageRecorder()->RecordStageRenderGraph(graphContext)) + << runtime->GetLastError(); + engine->OnUpdate(0.016f); + + const auto expectBoolField = + [&](const char* fieldName) { + bool value = false; + EXPECT_TRUE(runtime->TryGetFieldValue( + selectionScript, + fieldName, + value)); + EXPECT_TRUE(value) << fieldName; + }; + expectBoolField("ObservedWriterVisited"); + expectBoolField("ObservedReaderVisited"); + expectBoolField("ObservedReaderSawWriterItem"); + expectBoolField("ObservedSameFrameData"); + expectBoolField("ObservedSameRenderingData"); + + int observedValue = 0; + EXPECT_TRUE(runtime->TryGetFieldValue( + selectionScript, + "ObservedValue", + observedValue)); + EXPECT_EQ(observedValue, 73); + + int observedReaderOrder = 0; + EXPECT_TRUE(runtime->TryGetFieldValue( + selectionScript, + "ObservedReaderOrder", + observedReaderOrder)); + EXPECT_GT(observedReaderOrder, 1); + + host->GetStageRecorder()->Shutdown(); +} + TEST_F( MonoScriptRuntimeTest, DefaultCameraRendererUsesScriptCoreUniversalPipelineAssetAndRespectsRendererData) {