Align URP render graph frame data scope

This commit is contained in:
2026-04-26 20:51:16 +08:00
parent ee05558f86
commit db2d3eb414
13 changed files with 918 additions and 73 deletions

View File

@@ -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.

View File

@@ -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<RenderingData>();
PublicRenderGraphFrameDataProbeState.HasCameraData =
frameData != null &&
frameData.Contains<CameraData>();
PublicRenderGraphFrameDataProbeState.HasLightingData =
frameData != null &&
frameData.Contains<LightingData>();
PublicRenderGraphFrameDataProbeState.HasShadowData =
frameData != null &&
frameData.Contains<ShadowData>();
PublicRenderGraphFrameDataProbeState.HasEnvironmentData =
frameData != null &&
frameData.Contains<EnvironmentData>();
PublicRenderGraphFrameDataProbeState.HasFinalColorData =
frameData != null &&
frameData.Contains<FinalColorData>();
PublicRenderGraphFrameDataProbeState.HasStageColorData =
frameData != null &&
frameData.Contains<StageColorData>();
if (!PublicRenderGraphFrameDataProbeState.HasRenderingData)
{
return;
}
RenderingData renderingData =
frameData.Get<RenderingData>();
PublicRenderGraphFrameDataProbeState.ObservedStage =
(int)renderingData.stage;
PublicRenderGraphFrameDataProbeState.ObservedRendererIndex =
renderingData.rendererIndex;
PublicRenderGraphFrameDataProbeState
.RenderingDataMatchesItems =
object.ReferenceEquals(
renderingData.cameraData,
frameData.Get<CameraData>()) &&
object.ReferenceEquals(
renderingData.lightingData,
frameData.Get<LightingData>()) &&
object.ReferenceEquals(
renderingData.shadowData,
frameData.Get<ShadowData>()) &&
object.ReferenceEquals(
renderingData.environmentData,
frameData.Get<EnvironmentData>()) &&
object.ReferenceEquals(
renderingData.finalColorData,
frameData.Get<FinalColorData>()) &&
object.ReferenceEquals(
renderingData.stageColorData,
frameData.Get<StageColorData>());
}
}
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<PublicRenderGraphSharedFrameDataProbeItem>();
item.Value = 73;
item.WriterOrder =
++PublicRenderGraphSharedFrameDataProbeState.NextOrder;
item.RenderingData =
frameData.Contains<RenderingData>()
? frameData.Get<RenderingData>()
: 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<PublicRenderGraphSharedFrameDataProbeItem>())
{
return;
}
PublicRenderGraphSharedFrameDataProbeItem item =
frameData
.Get<PublicRenderGraphSharedFrameDataProbeItem>();
PublicRenderGraphSharedFrameDataProbeState
.ReaderObservedWriterItem =
item.WriterOrder > 0 &&
item.WriterOrder <
PublicRenderGraphSharedFrameDataProbeState.ReaderOrder;
PublicRenderGraphSharedFrameDataProbeState
.ReaderObservedValue =
item.Value;
PublicRenderGraphSharedFrameDataProbeState
.ReaderObservedSameRenderingData =
frameData.Contains<RenderingData>() &&
object.ReferenceEquals(
item.RenderingData,
frameData.Get<RenderingData>());
}
}
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<PublicRenderGraphFrameDataProbeRendererData>());
}
}
public sealed class ManagedPublicRenderGraphSharedFrameDataProbeAsset
: UniversalRenderPipelineAsset
{
public ManagedPublicRenderGraphSharedFrameDataProbeAsset()
{
rendererDataList =
ProbeScriptableObjectFactory
.CreateRendererDataList(
ProbeScriptableObjectFactory
.Create<PublicRenderGraphSharedFrameDataProbeRendererData>());
}
}
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<ManagedPublicRenderGraphFrameDataProbeAsset>();
}
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<ManagedPublicRenderGraphSharedFrameDataProbeAsset>();
}
public void Update()
{
ObservedWriterVisited =
PublicRenderGraphSharedFrameDataProbeState
.WriterVisited;
ObservedReaderVisited =
PublicRenderGraphSharedFrameDataProbeState
.ReaderVisited;
ObservedReaderSawWriterItem =
PublicRenderGraphSharedFrameDataProbeState
.ReaderObservedWriterItem;
ObservedSameFrameData =
PublicRenderGraphSharedFrameDataProbeState
.ReaderObservedSameFrameData;
ObservedSameRenderingData =
PublicRenderGraphSharedFrameDataProbeState
.ReaderObservedSameRenderingData;
ObservedValue =
PublicRenderGraphSharedFrameDataProbeState
.ReaderObservedValue;
ObservedReaderOrder =
PublicRenderGraphSharedFrameDataProbeState
.ReaderOrder;
}
}
}

View File

@@ -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(

View File

@@ -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(

View File

@@ -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(

View File

@@ -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(

View File

@@ -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(

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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(

View File

@@ -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(

View File

@@ -56,6 +56,20 @@ namespace XCEngine.Rendering
return Create<T>();
}
internal void Set<T>(
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)

View File

@@ -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<XCEngine::Rendering::Pipelines::ScriptableRenderPipelineHost*>(
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::Core::uint32>(
XCEngine::RHI::Format::R8G8B8A8_UNorm);
XCEngine::Rendering::RenderGraphTextureDesc depthDesc = colorDesc;
depthDesc.format =
static_cast<XCEngine::Core::uint32>(
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<int>(
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<XCEngine::Rendering::Pipelines::ScriptableRenderPipelineHost*>(
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::Core::uint32>(
XCEngine::RHI::Format::R8G8B8A8_UNorm);
XCEngine::Rendering::RenderGraphTextureDesc depthDesc = colorDesc;
depthDesc.format =
static_cast<XCEngine::Core::uint32>(
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) {