using System.Numerics; using System.Runtime.InteropServices; using AcDream.App.Rendering; using AcDream.App.Rendering.Gpu; using AcDream.App.Rendering.Packs; using AcDream.App.Rendering.Scene; using AcDream.App.Rendering.Wb; using AcDream.App.Tests.Rendering.Gpu; using AcDream.Core.World; using DatReaderWriter.Enums; namespace AcDream.App.Tests.Rendering; public sealed class DirectionalShadowGpuTests { [Fact] public void CompleteCasterClassDiagnostics_AddsExactTerrainCommandCount() { DirectionalShadowCasterBuildStats stats = default; stats = stats with { CasterClasses = new DirectionalShadowCasterClassDiagnostics( TerrainCommands: 0, OutdoorStatics: 2, Buildings: 3, AnimatedStatics: 4, LocalPlayers: 5, RemotePlayers: 6, NonPlayerCreatures: 7, OtherLiveDynamics: 8, EquippedChildren: 9), }; DirectionalShadowCasterClassDiagnostics completed = DirectionalSunShadowRenderer.CompleteCasterClassDiagnostics( in stats, terrainCommandCount: 11); Assert.Equal(11, completed.TerrainCommands); Assert.Equal(stats.CasterClasses with { TerrainCommands = 11 }, completed); } [Fact] public void Binding6HostLayout_MatchesCheckedInStd140Block() { Assert.Equal(336, DirectionalShadowUniforms.SizeInBytes); Assert.Equal(DirectionalShadowUniforms.SizeInBytes, Marshal.SizeOf()); Assert.Equal(0, Offset(nameof(DirectionalShadowUniforms.WorldToClip0))); Assert.Equal(64, Offset(nameof(DirectionalShadowUniforms.WorldToClip1))); Assert.Equal(128, Offset(nameof(DirectionalShadowUniforms.WorldToClip2))); Assert.Equal(192, Offset(nameof(DirectionalShadowUniforms.WorldToClip3))); Assert.Equal(256, Offset(nameof(DirectionalShadowUniforms.SplitFarMeters))); Assert.Equal(272, Offset(nameof(DirectionalShadowUniforms.Control))); Assert.Equal(288, Offset(nameof(DirectionalShadowUniforms.BiasMeters))); Assert.Equal(304, Offset(nameof(DirectionalShadowUniforms.TextureAndFlags))); Assert.Equal(320, Offset(nameof(DirectionalShadowUniforms.LightDirectionAndSource))); Assert.Equal(16, Marshal.SizeOf()); string common = File.ReadAllText(Path.Combine( RepositoryRoot(), "src", "AcDream.App", "Rendering", "Shaders", "directional_shadow_common.glsl")); Assert.Contains("ACDREAM_PACK_UBO_SET binding = 6", common, StringComparison.Ordinal); Assert.Contains("mat4 uShadowWorldToClip[4]", common, StringComparison.Ordinal); Assert.Contains("uvec4 uShadowTextureAndFlags", common, StringComparison.Ordinal); Assert.Contains("vec4 uShadowLightDirectionAndSource", common, StringComparison.Ordinal); } [Fact] public void ConservativeReceiverBias_UsesFarthestCascadeAndShaderScalesInnerMaps() { DirectionalShadowQuality quality = DirectionalShadowQuality.For( DirectionalShadowPreset.Low); var nearBias = new DirectionalShadowWorldBias(0.01f, 0.02f, 0.03f); var farBias = new DirectionalShadowWorldBias(0.11f, 0.12f, 0.13f); DirectionalShadowCascade[] cascades = [ Cascade(0, 20f, nearBias), Cascade(1, quality.MaximumReachMeters, farBias), ]; var environment = new DirectionalShadowEnvironmentState( DirectionalShadowGateReason.Enabled, Vector3.Normalize(new Vector3(0.3f, 0.4f, 0.8f)), 1f, 1f, 1f, AuthoredCelestialShadowSourceKind.DominantMoon, SourceObjectIndex: 2, SourceGfxObjId: AuthoredCelestialShadowSourceResolver.DominantMoonGfxObjId); DirectionalShadowUniforms uniforms = DirectionalShadowUniforms.Create( cascades, environment, quality, new GpuTextureSlot(7)); Assert.Equal(farBias.ConstantDepthMeters, uniforms.BiasMeters.X); Assert.Equal(farBias.SlopeDepthMeters, uniforms.BiasMeters.Y); Assert.Equal(farBias.NormalOffsetMeters, uniforms.BiasMeters.Z); string receiver = File.ReadAllText(Path.Combine( RepositoryRoot(), "src", "AcDream.App", "Rendering", "Shaders", "directional_shadow_receiver.glsl")); Assert.Contains("farDensity / max(cascadeDensity, 1e-7)", receiver, StringComparison.Ordinal); Assert.Contains("uShadowBiasMeters.xyz * acdreamShadowBiasScale(cascade)", receiver, StringComparison.Ordinal); } [Fact] public void Uniforms_CarrySelectedCelestialDirectionAndSourceKind() { DirectionalShadowQuality quality = DirectionalShadowQuality.For( DirectionalShadowPreset.Low); DirectionalShadowCascade[] cascades = [ Cascade(0, 20f, new DirectionalShadowWorldBias(0.01f, 0.02f, 0.03f)), Cascade(1, quality.MaximumReachMeters, new DirectionalShadowWorldBias(0.11f, 0.12f, 0.13f)), ]; Vector3 direction = Vector3.Normalize(new Vector3(0.3f, 0.4f, 0.8f)); var environment = new DirectionalShadowEnvironmentState( DirectionalShadowGateReason.Enabled, direction, 1f, 1f, 1f, AuthoredCelestialShadowSourceKind.DominantMoon, SourceObjectIndex: 2, SourceGfxObjId: AuthoredCelestialShadowSourceResolver.DominantMoonGfxObjId); DirectionalShadowUniforms uniforms = DirectionalShadowUniforms.Create( cascades, environment, quality, new GpuTextureSlot(7)); Assert.Equal( direction, new Vector3( uniforms.LightDirectionAndSource.X, uniforms.LightDirectionAndSource.Y, uniforms.LightDirectionAndSource.Z)); Assert.Equal( (float)AuthoredCelestialShadowSourceKind.DominantMoon, uniforms.LightDirectionAndSource.W); } [Fact] public void UniformReachAndTerminalFadeUseResidentClampedFinalSplit() { DirectionalShadowQuality quality = DirectionalShadowQuality.For( DirectionalShadowPreset.Low); DirectionalShadowCascade[] cascades = [ Cascade(0, 18f, new DirectionalShadowWorldBias(0.01f, 0.02f, 0.03f)), Cascade(1, 48f, new DirectionalShadowWorldBias(0.04f, 0.05f, 0.06f)), ]; DirectionalShadowUniforms uniforms = DirectionalShadowUniforms.Create( cascades, EnabledEnvironment(), quality, new GpuTextureSlot(7)); Assert.Equal(48f, uniforms.Control.Z); Assert.Equal(1f, uniforms.Control.W); } [Fact] public void MultiviewShadersSelectExactViewMatrixAndPreserveCutout() { string shaderRoot = Path.Combine( RepositoryRoot(), "src", "AcDream.App", "Rendering", "Shaders"); string vertex = File.ReadAllText(Path.Combine( shaderRoot, "directional_shadow_world_cutout_multiview.vert")); string fragment = File.ReadAllText(Path.Combine( shaderRoot, "directional_shadow_world_cutout_multiview.frag")); Assert.Contains("GL_EXT_multiview", vertex, StringComparison.Ordinal); Assert.Contains("uShadowWorldToClip[int(gl_ViewIndex)]", vertex, StringComparison.Ordinal); Assert.Contains("Instances[instanceIndex].transform", vertex, StringComparison.Ordinal); Assert.Contains("texel.a < 0.05", fragment, StringComparison.Ordinal); } [Theory] [InlineData(1)] [InlineData(5)] public void DirectionalDepthTarget_RejectsLayerCountsOutsideTwoThroughFour(int layers) { using var device = new RecordingGpuDevice(); Assert.Throws(() => device.CreateDirectionalDepthTarget( new GpuDirectionalDepthTargetDescription("bad", 1024, layers))); } [Fact] public void DirectionalDepthTarget_ExposesOneSampleableArrayAndLayerPasses() { using var device = new RecordingGpuDevice(); using IGpuDirectionalDepthTarget target = device.CreateDirectionalDepthTarget( new GpuDirectionalDepthTargetDescription("shadow", 1536, 3)); Assert.Equal(GpuTextureKind.Texture2DArray, target.DepthTexture.Kind); Assert.Equal(3, target.DepthTexture.LayerCount); Assert.Equal(1536, target.DepthTexture.Width); using IGpuFrame frame = device.BeginFrame(); using (frame.BeginPass(GpuPassDescription.DirectionalDepth("cascade-2", target, 2))) { } Assert.Throws(() => frame.BeginPass(GpuPassDescription.DirectionalDepth("cascade-3", target, 3))); } [Fact] public void DirectionalDepthMultiview_RequiresExactFullMaskAndDeviceCapability() { using var device = new RecordingGpuDevice(); using IGpuDirectionalDepthTarget target = device.CreateDirectionalDepthTarget( new GpuDirectionalDepthTargetDescription("shadow", 1024, 2)); using IGpuFrame frame = device.BeginFrame(); using (frame.BeginPass(GpuPassDescription.DirectionalDepthMultiview( "both-cascades", target, 0b11))) { } Assert.Equal(0b11u, Assert.Single(device.OfKind()).ViewMask); Assert.Throws(() => frame.BeginPass( GpuPassDescription.DirectionalDepthMultiview("partial", target, 0b01))); using var unsupported = new RecordingGpuDevice { Capabilities = device.Capabilities with { SupportsMultiview = false }, }; using IGpuDirectionalDepthTarget unsupportedTarget = unsupported.CreateDirectionalDepthTarget( new GpuDirectionalDepthTargetDescription("shadow", 1024, 2)); using IGpuFrame unsupportedFrame = unsupported.BeginFrame(); Assert.Throws(() => unsupportedFrame.BeginPass( GpuPassDescription.DirectionalDepthMultiview( "unsupported", unsupportedTarget, 0b11))); } [Theory] [InlineData(DirectionalShadowPreset.Low, 2, 768, true)] [InlineData(DirectionalShadowPreset.Low, 2, 768, false)] [InlineData(DirectionalShadowPreset.Medium, 3, 1536, false)] [InlineData(DirectionalShadowPreset.High, 4, 2048, false)] internal void Renderer_ReplaysOnePreparedProductAcrossEveryQualityCascade( DirectionalShadowPreset preset, int expectedCascades, int expectedResolution, bool multiviewCascades) { using var device = new RecordingGpuDevice(); int baselineSlots = device.LiveTextureSlotCount; using var renderer = new DirectionalSunShadowRenderer( device, preset, multiviewCascades: multiviewCascades); Assert.Equal(baselineSlots + 1, device.LiveTextureSlotCount); RecordingGpuDirectionalDepthTarget target = Assert.Single(device.CreatedDirectionalDepthTargets); Assert.Equal(expectedCascades, target.Description.LayerCount); Assert.Equal(expectedResolution, target.Description.Resolution); Assert.All(device.CreatedPipelines, pipeline => Assert.False(pipeline.Description.HasColorAttachment)); DirectionalShadowPreparedDraws world = CreateWorldDraws(device.DefaultTextureSlot); DirectionalShadowTerrainPreparedDraws terrain = CreateTerrainDraws(); using IGpuBuffer worldVertices = Buffer(device, "world-v", GpuBufferUsage.Vertex); using IGpuBuffer worldIndices = Buffer(device, "world-i", GpuBufferUsage.Index); using IGpuBuffer terrainVertices = Buffer(device, "terrain-v", GpuBufferUsage.Vertex); using IGpuBuffer terrainIndices = Buffer(device, "terrain-i", GpuBufferUsage.Index); var worldGeometry = new DirectionalShadowMeshGeometry(worldVertices, worldIndices); var terrainGeometry = new DirectionalShadowTerrainGeometry(terrainVertices, terrainIndices); var environment = new DirectionalShadowEnvironmentState( DirectionalShadowGateReason.Enabled, Vector3.Normalize(new Vector3(0.2f, 0.3f, 1f)), 0.94f, 0.8f, 1.25f, AuthoredCelestialShadowSourceKind.SecondaryMoon, SourceObjectIndex: 7, SourceGfxObjId: AuthoredCelestialShadowSourceResolver.SecondaryMoonGfxObjId); device.Clear(); using IGpuFrame frame = device.BeginFrame(); WorldTransformFrameSlice sharedTransforms = PublishSharedTransforms( frame, world.Transforms); DirectionalSunShadowDiagnostics diagnostics = renderer.RenderPrepared( frame, environment, Matrix4x4.Identity, Matrix4x4.CreatePerspectiveFieldOfView(MathF.PI / 3f, 16f / 9f, 0.1f, 500f), cameraNearMeters: 0.1f, casterDepthPaddingMeters: 48f, world, terrain, worldGeometry, terrainGeometry, sharedTransforms); int expectedDraws = (multiviewCascades ? 1 : expectedCascades) * 3; Assert.Equal(expectedCascades, diagnostics.CascadeCount); Assert.Equal(expectedDraws, diagnostics.DrawCalls); Assert.Equal(environment.Strength, diagnostics.Strength); Assert.Equal(1, diagnostics.WorldOpaqueCommands); Assert.Equal(1, diagnostics.WorldAlphaCutoutCommands); Assert.Equal(1, diagnostics.TerrainCommands); Assert.Equal(1ul, diagnostics.WorldPreparationSequence); Assert.Equal(1ul, diagnostics.TerrainPreparationSequence); int expectedPasses = multiviewCascades ? 1 : expectedCascades; Assert.Equal(expectedPasses, device.OfKind().Count()); Assert.Equal(expectedPasses, device.OfKind().Count()); Assert.Equal(expectedPasses, device.OfKind() .Count(call => call.Binding == GpuBindingModel.UniformDirectionalShadow)); GpuRecordedUniformBind shadowUniformBind = Assert.Single( device.OfKind() .DistinctBy(call => (call.BufferName, call.OffsetBytes, call.SizeBytes)), call => call.Binding == GpuBindingModel.UniformDirectionalShadow); DirectionalShadowUniforms shadowUniforms = MemoryMarshal.Read( device.RingBytes.Slice( checked((int)shadowUniformBind.OffsetBytes), checked((int)shadowUniformBind.SizeBytes))); Assert.Equal( DirectionalShadowQuality.For(preset).MaximumReachMeters, shadowUniforms.BiasMeters.W); Assert.Equal(expectedDraws, device.OfKind().Count()); Assert.Equal(2, device.OfKind().Count()); GpuRecordedRingAllocation transformAllocation = Assert.Single( device.OfKind(), call => call.Usage == GpuRingUsage.Storage && call.ByteCount == WorldTransformCapacityPolicy.InitialBindingSizeBytes); RecordingGpuBuffer batchBuffer = Assert.Single( device.CreatedBuffers, buffer => buffer.Name == "directional-shadow-world-batches-1" && buffer.Usage.HasFlag(GpuBufferUsage.Storage) && buffer.Residency == GpuMemoryResidency.DeviceLocal); Span batchBytes = stackalloc byte[32]; batchBuffer.Read(0, batchBytes); ReadOnlySpan batchWords = MemoryMarshal.Cast(batchBytes); Assert.Equal( 0u, batchWords[3]); Assert.Equal( DirectionalShadowBatchFlags.AlphaCutout, batchWords[7]); Assert.Contains( device.CreatedBuffers, buffer => buffer.Name == "directional-shadow-world-commands-1" && buffer.Usage.HasFlag(GpuBufferUsage.Indirect) && buffer.Residency == GpuMemoryResidency.DeviceLocal); Assert.Contains( device.CreatedBuffers, buffer => buffer.Name == "directional-shadow-terrain-commands-1" && buffer.Usage.HasFlag(GpuBufferUsage.Indirect) && buffer.Residency == GpuMemoryResidency.DeviceLocal); Assert.DoesNotContain( device.OfKind(), call => call.Usage == GpuRingUsage.Storage && call.ByteCount == world.Transforms.Length * Marshal.SizeOf()); Assert.All( device.OfKind() .Where(call => call.Binding == GpuBindingModel.StorageInstances), call => { Assert.Equal(transformAllocation.OffsetBytes, call.OffsetBytes); Assert.Equal(WorldTransformCapacityPolicy.InitialBindingSizeBytes, call.SizeBytes); }); for (int cascade = 0; cascade < (multiviewCascades ? 1 : expectedCascades); cascade++) { Assert.Contains( device.OfKind(), call => call.Constants.RenderPass == cascade); } if (multiviewCascades) { GpuRecordedPassBegin pass = Assert.Single(device.OfKind()); Assert.Equal(0b11u, pass.ViewMask); Assert.Equal( 1, device.OfKind().Count(call => call.PipelineName == "directional-shadow-world-cutout-multiview")); Assert.Contains(device.OfKind(), call => call.PipelineName == "directional-shadow-world-opaque-multiview"); Assert.Contains(device.OfKind(), call => call.PipelineName == "directional-shadow-terrain-multiview"); } } /// /// Campaign VM VM6 review fix round (A3): the receiver pass must not /// depend on the caster pass's own AtmosphericFrame bind surviving /// un-reset until the receiver pass runs later in the frame — it must /// bind set 3/binding 5 itself, from the EXACT buffer/offset/size the /// caster bound. This asserts the seam that makes that possible: /// TryGetCurrentFrameBinding's DirectionalShadowFrameBinding.AtmosphericFrame /// carries the identical buffer identity, offset, and size the caster's /// own recorded GpuRecordedUniformBind calls used — the value /// WbDrawDispatcher.BindDirectionalShadowReceiver reads to issue its own /// bind. /// [Fact] public void CasterFrameBindingCarriesTheExactAtmosphericFrameBufferForTheReceiverSeam() { using var device = new RecordingGpuDevice(); using var renderer = new DirectionalSunShadowRenderer(device, DirectionalShadowPreset.Medium); DirectionalShadowPreparedDraws world = CreateWorldDraws(device.DefaultTextureSlot); DirectionalShadowTerrainPreparedDraws terrain = CreateTerrainDraws(); using IGpuBuffer worldVertices = Buffer(device, "world-v", GpuBufferUsage.Vertex); using IGpuBuffer worldIndices = Buffer(device, "world-i", GpuBufferUsage.Index); using IGpuBuffer terrainVertices = Buffer(device, "terrain-v", GpuBufferUsage.Vertex); using IGpuBuffer terrainIndices = Buffer(device, "terrain-i", GpuBufferUsage.Index); var worldGeometry = new DirectionalShadowMeshGeometry(worldVertices, worldIndices); var terrainGeometry = new DirectionalShadowTerrainGeometry(terrainVertices, terrainIndices); var environment = new DirectionalShadowEnvironmentState( DirectionalShadowGateReason.Enabled, Vector3.Normalize(new Vector3(0.1f, 0.2f, 1f)), 0.9f, 0.75f, 1.1f, AuthoredCelestialShadowSourceKind.Sun, SourceObjectIndex: -1, SourceGfxObjId: 0); using IGpuBuffer atmosphericBuffer = Buffer(device, "test-atmospheric-frame", GpuBufferUsage.Uniform); var atmosphericFrame = new AtmosphericFrameBufferBinding( atmosphericBuffer, OffsetBytes: 64u, SizeBytes: 192u); device.Clear(); using IGpuFrame frame = device.BeginFrame(); WorldTransformFrameSlice sharedTransforms = PublishSharedTransforms(frame, world.Transforms); renderer.RenderPrepared( frame, environment, Matrix4x4.Identity, Matrix4x4.CreatePerspectiveFieldOfView(MathF.PI / 3f, 16f / 9f, 0.1f, 500f), cameraNearMeters: 0.1f, casterDepthPaddingMeters: 48f, world, terrain, worldGeometry, terrainGeometry, sharedTransforms, atmosphericFrame: atmosphericFrame); // The caster pass's OWN recorded bind used exactly this buffer/offset/size. GpuRecordedUniformBind casterAtmosphericBind = Assert.Single( device.OfKind() .DistinctBy(call => (call.BufferName, call.OffsetBytes, call.SizeBytes)), call => call.Binding == GpuBindingModel.UniformAtmosphericFrame); Assert.Equal("test-atmospheric-frame", casterAtmosphericBind.BufferName); Assert.Equal(64u, casterAtmosphericBind.OffsetBytes); Assert.Equal(192u, casterAtmosphericBind.SizeBytes); // The receiver seam carries forward the IDENTICAL binding — this is // what BindDirectionalShadowReceiver reads to bind set 3/binding 5 // itself, rather than depending on the caster's bind surviving // un-reset until the receiver pass runs. Assert.True(renderer.TryGetCurrentFrameBinding(frame, out DirectionalShadowFrameBinding binding)); Assert.True(binding.AtmosphericFrame.IsBound); Assert.Same(atmosphericBuffer, binding.AtmosphericFrame.Buffer); Assert.Equal(atmosphericFrame.OffsetBytes, binding.AtmosphericFrame.OffsetBytes); Assert.Equal(atmosphericFrame.SizeBytes, binding.AtmosphericFrame.SizeBytes); } /// /// Campaign VM VM6 review fix round 2 (A3 test gap): the previous test /// proves the caster's real bind lands unchanged in /// DirectionalShadowFrameBinding.AtmosphericFrame. This proves the OTHER /// half of the seam — that WbDrawDispatcher.BindDirectionalShadowReceiver, /// given that exact binding, actually emits an encoder bind for /// UniformAtmosphericFrame with the SAME buffer/offset/size, not a stale /// or default one. Together the two tests prove the caster and receiver /// agree end to end without standing up the full /// WbDrawDispatcher/mesh-manager/world-pass-scope dependency chain — /// BindDirectionalShadowReceiver only needs a bare pass encoder. /// [Fact] public void BindDirectionalShadowReceiverEmitsAtmosphericFrameWithTheExactCasterBufferOffsetAndSize() { using var device = new RecordingGpuDevice(); using IGpuBuffer shadowBuffer = Buffer(device, "shadow-frame", GpuBufferUsage.Uniform); using IGpuBuffer atmosphericBuffer = Buffer(device, "test-atmospheric-frame", GpuBufferUsage.Uniform); var binding = new DirectionalShadowFrameBinding( FrameSerial: 1, Enabled: true, Buffer: shadowBuffer, OffsetBytes: 0u, SizeBytes: 128u, TextureSlot: GpuTextureSlot.Unassigned, CascadeCount: 4, AtmosphericFrame: new AtmosphericFrameBufferBinding( atmosphericBuffer, OffsetBytes: 64u, SizeBytes: 192u)); device.Clear(); var target = device.CreateRenderTarget(new GpuRenderTargetDescription( "test-world-hdr", 640, 480, GpuTextureFormat.Rgba16FloatRenderTarget, GpuTextureFormat.Depth24Stencil8, SampleCount: 1)); using IGpuFrame frame = device.BeginFrame(); using (IGpuPassEncoder encoder = frame.BeginPass(new GpuPassDescription { Name = "test-world-hdr", Color = new GpuColorAttachment( target, GpuLoadOp.Clear, GpuStoreOp.Store, Vector4.Zero), Depth = new GpuDepthAttachment(GpuLoadOp.Clear, GpuStoreOp.Store, 1f, 0), SampleCount = 1, })) { WbDrawDispatcher.BindDirectionalShadowReceiver(encoder, in binding); } frame.End(); GpuRecordedUniformBind atmosphericBind = Assert.Single( device.OfKind(), call => call.Binding == GpuBindingModel.UniformAtmosphericFrame); Assert.Equal("test-atmospheric-frame", atmosphericBind.BufferName); Assert.Equal(64u, atmosphericBind.OffsetBytes); Assert.Equal(192u, atmosphericBind.SizeBytes); // The shadow-map binding fired too — BindDirectionalShadowReceiver // is not a no-op that only happens to satisfy the assertion above. GpuRecordedUniformBind shadowBind = Assert.Single( device.OfKind(), call => call.Binding == GpuBindingModel.UniformDirectionalShadow); Assert.Equal("shadow-frame", shadowBind.BufferName); } /// /// Campaign VM VM6 review fix round 4, test (a): the offline pixel gate /// found foliage wind welded to "directional shadows rendered this /// frame" — a shadow-gated-off frame used to leave /// TryGetCurrentFrameBinding returning false, which fell the world /// receiver pipeline back to the plain (no-wind) mesh pipeline. This /// proves the fix directly against PublishDisabledReceiverBinding /// (made internal for exactly this reason — see its doc comment): /// given a bound AtmosphericFrame, it publishes a binding that IS /// bindable (TryGetCurrentFrameBinding true) but is NOT a valid shadow /// (IsValidFor false, Enabled false), and the written block's flags /// word is exactly 0 (directional_shadow_receiver.glsl's bit-0-clear /// "full visibility" contract). Round 5 correction: the written /// direction (0,0,1) is asserted below purely as the documented /// normalize()-cannot-NaN GUARD value it is — round 4 mistakenly /// treated writing a plausible-looking direction as sufficient; round /// 5 fixed the receiver vertex shaders (mesh_atmospheric.vert, /// terrain_atmospheric.vert) to ignore this value entirely and fall /// back to the authored uLights direction whenever the flags bit is /// clear, so (0,0,1) is never read as a real light direction. /// [Fact] public void PublishDisabledReceiverBinding_IsBindableButNotValidWithZeroFlagsAndUnitDirection() { using var device = new RecordingGpuDevice(); using var renderer = new DirectionalSunShadowRenderer(device, DirectionalShadowPreset.Low); using IGpuBuffer atmosphericBuffer = Buffer(device, "wind-only-frame", GpuBufferUsage.Uniform); var atmosphericFrame = new AtmosphericFrameBufferBinding( atmosphericBuffer, OffsetBytes: 0u, SizeBytes: 192u); device.Clear(); using IGpuFrame frame = device.BeginFrame(); renderer.PublishDisabledReceiverBinding(frame, atmosphericFrame); Assert.True(renderer.TryGetCurrentFrameBinding(frame, out DirectionalShadowFrameBinding binding)); Assert.True(binding.IsBindableFor(frame)); Assert.False(binding.IsValidFor(frame)); Assert.False(binding.Enabled); Assert.Equal(0, binding.CascadeCount); Assert.False(binding.TextureSlot.IsAssigned); Assert.True(binding.AtmosphericFrame.IsBound); Assert.Same(atmosphericBuffer, binding.AtmosphericFrame.Buffer); DirectionalShadowUniforms written = MemoryMarshal.Read( device.RingBytes.Slice( (int)binding.OffsetBytes, DirectionalShadowUniforms.SizeInBytes)); Assert.Equal(0u, written.TextureAndFlags.W); // (0,0,1) is the NaN guard, not a light direction — see the doc // comment above and PublishDisabledReceiverBinding's own comment. Assert.Equal(new Vector4(0f, 0f, 1f, 0f), written.LightDirectionAndSource); } [Fact] public void PublishDisabledReceiverBinding_NoOpsWhenAtmosphericFrameIsUnboundPreservingTodaysBehaviour() { // Campaign VM VM6 review fix round 4, test (b): a declared (non // built-in) pack never supplies an AtmosphericFrame binding, so // this must stay a pure no-op for it — TryGetCurrentFrameBinding // keeps returning false exactly like before this fix, and a // declared pack's receiver shader (which never declares set 3 // binding 5) is unaffected. using var device = new RecordingGpuDevice(); using var renderer = new DirectionalSunShadowRenderer(device, DirectionalShadowPreset.Low); device.Clear(); using IGpuFrame frame = device.BeginFrame(); renderer.PublishDisabledReceiverBinding(frame, default); Assert.False(renderer.TryGetCurrentFrameBinding(frame, out _)); Assert.Empty(device.OfKind()); } /// /// Campaign VM VM6 review fix round 5, test T1 (indoor early-out): /// no test calls DirectionalSunShadowRenderer.Render because it needs /// a real WbDrawDispatcher + TerrainModernRenderer to construct (still /// true — nothing in this suite stands either up), so the gate /// prologue Render runs before touching either is extracted into /// EvaluateGateAndPublishDisabledBinding (behaviour-preserving; see /// its own doc comment) and tested directly here. PlayerInsideCell is /// the simplest trigger for Render's FIRST early-out /// (!environment.ShouldRender). /// [Fact] public void EvaluateGateAndPublishDisabledBinding_PlayerInsideCellPublishesBindableNotValidBinding() { using var device = new RecordingGpuDevice(); using var renderer = new DirectionalSunShadowRenderer(device, DirectionalShadowPreset.Low); using IGpuBuffer atmosphericBuffer = Buffer(device, "gate-indoor-frame", GpuBufferUsage.Uniform); var atmosphericFrame = new AtmosphericFrameBufferBinding(atmosphericBuffer, 0u, 192u); var input = new DirectionalSunShadowRenderInput( new DirectionalShadowEnvironmentInput( PackEnabled: true, PortalOrLoginCoverVisible: false, PlayerInsideCell: true, Source: AuthoredCelestialShadowSource.None(), Atmosphere: default), Matrix4x4.Identity, Matrix4x4.Identity, new DirectionalShadowCasterFrame(), AtmosphericFrame: atmosphericFrame); device.Clear(); using IGpuFrame frame = device.BeginFrame(); DirectionalSunShadowDiagnostics? gated = renderer.EvaluateGateAndPublishDisabledBinding( frame, in input, out DirectionalShadowEnvironmentState environment, out long environmentGateTicks); Assert.NotNull(gated); Assert.Equal(DirectionalShadowGateReason.Indoor, gated.Value.GateReason); Assert.Equal(DirectionalShadowGateReason.Indoor, environment.Reason); Assert.True(renderer.TryGetCurrentFrameBinding(frame, out DirectionalShadowFrameBinding binding)); Assert.True(binding.IsBindableFor(frame)); Assert.False(binding.IsValidFor(frame)); } /// /// Campaign VM VM6 review fix round 5, test T1 (resident-window /// early-out): Render's SECOND early-out. Reaching it needs a fully /// valid environment (ShouldRender true), so the environment input /// below constructs a real celestial source above the horizon with /// energy, and a fully-progressed atmosphere snapshot — anything less /// would gate off at the FIRST check instead of reaching /// ResidentMaximumReachMeters. /// [Fact] public void EvaluateGateAndPublishDisabledBinding_ResidentWindowUnavailablePublishesBindableNotValidBinding() { using var device = new RecordingGpuDevice(); using var renderer = new DirectionalSunShadowRenderer(device, DirectionalShadowPreset.Low); using IGpuBuffer atmosphericBuffer = Buffer(device, "gate-resident-frame", GpuBufferUsage.Uniform); var atmosphericFrame = new AtmosphericFrameBufferBinding(atmosphericBuffer, 0u, 192u); var validSource = new AuthoredCelestialShadowSource( AuthoredCelestialShadowSourceKind.Sun, ObjectIndex: 0, GfxObjId: 1u, SurfaceToLightDirection: Vector3.UnitZ, ElevationSin: 0.5f, AuthoredEnergy: 1f); var validAtmosphere = new AtmosphereSnapshot( WeatherKind.Clear, Intensity: 1f, FogColor: Vector3.Zero, FogStart: 0f, FogEnd: 0f, FogMode: default, LightningFlash: 0f, Override: default); var input = new DirectionalSunShadowRenderInput( new DirectionalShadowEnvironmentInput( PackEnabled: true, PortalOrLoginCoverVisible: false, PlayerInsideCell: false, Source: validSource, Atmosphere: validAtmosphere, ActiveDayGroupMultiplier: 1f), Matrix4x4.Identity, Matrix4x4.Identity, new DirectionalShadowCasterFrame(), CameraNearMeters: 0.1f, ResidentMaximumReachMeters: 0.05f, // <= CameraNearMeters AtmosphericFrame: atmosphericFrame); device.Clear(); using IGpuFrame frame = device.BeginFrame(); DirectionalSunShadowDiagnostics? gated = renderer.EvaluateGateAndPublishDisabledBinding( frame, in input, out DirectionalShadowEnvironmentState environment, out long environmentGateTicks); Assert.NotNull(gated); Assert.Equal(DirectionalShadowGateReason.ResidentWindowUnavailable, gated.Value.GateReason); Assert.Equal(DirectionalShadowGateReason.ResidentWindowUnavailable, environment.Reason); Assert.True(renderer.TryGetCurrentFrameBinding(frame, out DirectionalShadowFrameBinding binding)); Assert.True(binding.IsBindableFor(frame)); Assert.False(binding.IsValidFor(frame)); } [Fact] public void ShouldSelectReceiverPipelineComposesWithTheRealBindingSourceAfterADisabledPublish() { // Campaign VM VM6 review fix round 5, test T2: proves the ACTUAL // composition WbDrawDispatcher.PipelinesFor and // TerrainModernRenderer both use — TryGetCurrentFrameBinding // feeding ShouldSelectReceiverPipeline — selects the receiver // pipeline for the atmospheric world pass once a disabled binding // is published, and still refuses a non-atmospheric pass name. // ShouldSelectReceiverPipeline itself is unchanged this round; // this is the composition its own DirectionalShadowReceiverTests // parametrized coverage does not exercise. using var device = new RecordingGpuDevice(); using var renderer = new DirectionalSunShadowRenderer(device, DirectionalShadowPreset.Low); using IGpuBuffer atmosphericBuffer = Buffer(device, "gate-pipeline-frame", GpuBufferUsage.Uniform); var atmosphericFrame = new AtmosphericFrameBufferBinding(atmosphericBuffer, 0u, 192u); device.Clear(); using IGpuFrame frame = device.BeginFrame(); renderer.PublishDisabledReceiverBinding(frame, atmosphericFrame); bool bindingValid = renderer.TryGetCurrentFrameBinding(frame, out _); Assert.True(bindingValid); Assert.True(DirectionalShadowReceiverPolicy.ShouldSelectReceiverPipeline( DirectionalShadowReceiverPolicy.AtmosphericWorldPassName, sourcePresent: true, bindingValid)); Assert.False(DirectionalShadowReceiverPolicy.ShouldSelectReceiverPipeline( "vk-world", sourcePresent: true, bindingValid)); } [Fact] public void BindDirectionalShadowReceiver_WithADisabledBindingEmitsBothShadowAndAtmosphericBinds() { // Campaign VM VM6 review fix round 4, test (c): the whole point of // publishing a disabled-content binding instead of leaving // TryGetCurrentFrameBinding at false is that BindDirectionalShadowReceiver // (fixed in this same round to check Buffer, not Enabled) actually // emits BOTH binds for it — the safe all-zero shadow block AND the // real wind data — so the world receiver pipeline (selected because // the binding is bindable) has everything it declares. using var device = new RecordingGpuDevice(); using IGpuBuffer shadowBuffer = Buffer(device, "disabled-shadow-frame", GpuBufferUsage.Uniform); using IGpuBuffer atmosphericBuffer = Buffer(device, "disabled-atmospheric-frame", GpuBufferUsage.Uniform); var binding = new DirectionalShadowFrameBinding( FrameSerial: 1, Enabled: false, Buffer: shadowBuffer, OffsetBytes: 0u, SizeBytes: DirectionalShadowUniforms.SizeInBytes, TextureSlot: GpuTextureSlot.Unassigned, CascadeCount: 0, AtmosphericFrame: new AtmosphericFrameBufferBinding( atmosphericBuffer, OffsetBytes: 0u, SizeBytes: 192u)); device.Clear(); var target = device.CreateRenderTarget(new GpuRenderTargetDescription( "test-world-hdr", 640, 480, GpuTextureFormat.Rgba16FloatRenderTarget, GpuTextureFormat.Depth24Stencil8, SampleCount: 1)); using IGpuFrame frame = device.BeginFrame(); using (IGpuPassEncoder encoder = frame.BeginPass(new GpuPassDescription { Name = "test-world-hdr", Color = new GpuColorAttachment( target, GpuLoadOp.Clear, GpuStoreOp.Store, Vector4.Zero), Depth = new GpuDepthAttachment(GpuLoadOp.Clear, GpuStoreOp.Store, 1f, 0), SampleCount = 1, })) { WbDrawDispatcher.BindDirectionalShadowReceiver(encoder, in binding); } frame.End(); GpuRecordedUniformBind shadowBind = Assert.Single( device.OfKind(), call => call.Binding == GpuBindingModel.UniformDirectionalShadow); Assert.Equal("disabled-shadow-frame", shadowBind.BufferName); GpuRecordedUniformBind atmosphericBind = Assert.Single( device.OfKind(), call => call.Binding == GpuBindingModel.UniformAtmosphericFrame); Assert.Equal("disabled-atmospheric-frame", atmosphericBind.BufferName); } [Fact] public void StableTopology_ReusesRetainedCommandBuffersWithoutFrameRingCopies() { using var device = new RecordingGpuDevice(); using var renderer = new DirectionalSunShadowRenderer( device, DirectionalShadowPreset.Low, multiviewCascades: true); DirectionalShadowPreparedDraws world = CreateWorldDraws( device.DefaultTextureSlot); DirectionalShadowTerrainPreparedDraws terrain = CreateTerrainDraws(); using IGpuBuffer worldVertices = Buffer(device, "world-v", GpuBufferUsage.Vertex); using IGpuBuffer worldIndices = Buffer(device, "world-i", GpuBufferUsage.Index); using IGpuBuffer terrainVertices = Buffer(device, "terrain-v", GpuBufferUsage.Vertex); using IGpuBuffer terrainIndices = Buffer(device, "terrain-i", GpuBufferUsage.Index); var worldGeometry = new DirectionalShadowMeshGeometry( worldVertices, worldIndices); var terrainGeometry = new DirectionalShadowTerrainGeometry( terrainVertices, terrainIndices); DirectionalShadowEnvironmentState environment = EnabledEnvironment(); using (IGpuFrame frame = device.BeginFrame()) { renderer.RenderPrepared( frame, environment, Matrix4x4.Identity, Matrix4x4.CreatePerspectiveFieldOfView(1f, 1f, 0.1f, 500f), 0.1f, 48f, world, terrain, worldGeometry, terrainGeometry, PublishSharedTransforms(frame, world.Transforms)); } RecordingGpuBuffer[] retained = device.CreatedBuffers .Where(buffer => buffer.Name.StartsWith( "directional-shadow-", StringComparison.Ordinal)) .ToArray(); Assert.Equal(3, retained.Length); Assert.Equal(3, renderer.RetainedCommandBufferCount); Assert.Equal(retained.Sum(buffer => buffer.SizeBytes), renderer.RetainedCommandBufferBytes); device.Clear(); int createdBefore = device.CreatedBuffers.Count; using (IGpuFrame frame = device.BeginFrame()) { renderer.RenderPrepared( frame, environment, Matrix4x4.Identity, Matrix4x4.CreatePerspectiveFieldOfView(1f, 1f, 0.1f, 500f), 0.1f, 48f, world, terrain, worldGeometry, terrainGeometry, PublishSharedTransforms(frame, world.Transforms)); } Assert.Equal(createdBefore, device.CreatedBuffers.Count); Assert.All(retained, buffer => Assert.False(buffer.IsDisposed)); Assert.Equal(2, device.OfKind().Count()); Assert.DoesNotContain( device.OfKind(), allocation => allocation.Usage == GpuRingUsage.Indirect); } [Fact] public void TopologyRebuild_SwapsRetainedBuffersAndDisposalReleasesTheCurrentSet() { using var device = new RecordingGpuDevice(); var renderer = new DirectionalSunShadowRenderer( device, DirectionalShadowPreset.Low); DirectionalShadowPreparedDraws world = CreateWorldDraws( device.DefaultTextureSlot); DirectionalShadowTerrainPreparedDraws terrain = CreateTerrainDraws(); using IGpuBuffer worldVertices = Buffer(device, "world-v", GpuBufferUsage.Vertex); using IGpuBuffer worldIndices = Buffer(device, "world-i", GpuBufferUsage.Index); using IGpuBuffer terrainVertices = Buffer(device, "terrain-v", GpuBufferUsage.Vertex); using IGpuBuffer terrainIndices = Buffer(device, "terrain-i", GpuBufferUsage.Index); var worldGeometry = new DirectionalShadowMeshGeometry( worldVertices, worldIndices); var terrainGeometry = new DirectionalShadowTerrainGeometry( terrainVertices, terrainIndices); DirectionalShadowEnvironmentState environment = EnabledEnvironment(); using (IGpuFrame frame = device.BeginFrame()) { renderer.RenderPrepared( frame, environment, Matrix4x4.Identity, Matrix4x4.CreatePerspectiveFieldOfView(1f, 1f, 0.1f, 500f), 0.1f, 48f, world, terrain, worldGeometry, terrainGeometry, PublishSharedTransforms(frame, world.Transforms)); } RecordingGpuBuffer[] firstSet = device.CreatedBuffers .Where(buffer => buffer.Name.StartsWith( "directional-shadow-", StringComparison.Ordinal)) .ToArray(); RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(3); Assert.True(world.TryBegin(generation, 8, 1)); Matrix4x4 moved = Matrix4x4.CreateTranslation(20f, 30f, 40f); world.Add( 30, 2, 9, GpuTextureSlot.Unassigned, 0, CullMode.Clockwise, DirectionalShadowCasterMaterial.Opaque, in moved); DirectionalShadowPreparationStats stats = default; world.Complete(generation, 8, in stats); Assert.True(terrain.TryBegin(2, 1)); var terrainRange = new DirectionalShadowTerrainRange(80, 90); terrain.Add(in terrainRange); terrain.Complete(2); using (IGpuFrame frame = device.BeginFrame()) { renderer.RenderPrepared( frame, environment, Matrix4x4.Identity, Matrix4x4.CreatePerspectiveFieldOfView(1f, 1f, 0.1f, 500f), 0.1f, 48f, world, terrain, worldGeometry, terrainGeometry, PublishSharedTransforms(frame, world.Transforms)); } Assert.All(firstSet, buffer => Assert.True(buffer.IsDisposed)); RecordingGpuBuffer[] currentSet = device.CreatedBuffers .Where(buffer => buffer.Name.EndsWith("-2", StringComparison.Ordinal)) .ToArray(); Assert.Equal(3, currentSet.Length); Assert.All(currentSet, buffer => Assert.False(buffer.IsDisposed)); renderer.Dispose(); Assert.All(currentSet, buffer => Assert.True(buffer.IsDisposed)); } [Theory] [InlineData(DirectionalShadowGateReason.Indoor)] [InlineData(DirectionalShadowGateReason.SelectedLightBelowHorizon)] [InlineData(DirectionalShadowGateReason.SelectedLightHasNoEnergy)] [InlineData(DirectionalShadowGateReason.NoVisibleCelestial)] [InlineData(DirectionalShadowGateReason.PackDisabled)] internal void DisabledEnvironment_RecordsNoPassOrUpload(DirectionalShadowGateReason reason) { using var device = new RecordingGpuDevice(); using var renderer = new DirectionalSunShadowRenderer(device, DirectionalShadowPreset.Low); device.Clear(); using IGpuFrame frame = device.BeginFrame(); DirectionalSunShadowDiagnostics diagnostics = renderer.RenderPrepared( frame, new DirectionalShadowEnvironmentState(reason, Vector3.UnitZ, 0f, 0f, 1f), Matrix4x4.Identity, Matrix4x4.Identity, 0.1f, 48f, new DirectionalShadowPreparedDraws(), new DirectionalShadowTerrainPreparedDraws(), null, null, default); Assert.Equal(reason, diagnostics.GateReason); Assert.Empty(device.OfKind()); Assert.Empty(device.OfKind()); } [Fact] public void Disposal_ReleasesTextureSlotAndEveryOwnedResource() { using var device = new RecordingGpuDevice(); int baselineSlots = device.LiveTextureSlotCount; var renderer = new DirectionalSunShadowRenderer(device, DirectionalShadowPreset.High); RecordingGpuDirectionalDepthTarget target = Assert.Single(device.CreatedDirectionalDepthTargets); RecordingGpuPipeline[] pipelines = device.CreatedPipelines.ToArray(); RecordingGpuSampler sampler = device.CreatedSamplers[^1]; Assert.Equal(GpuSamplerDescription.ShadowNearestClamp, sampler.Description); Assert.Equal(GpuFilter.Nearest, sampler.Description.MinFilter); Assert.Equal(GpuFilter.Nearest, sampler.Description.MagFilter); Assert.Equal(GpuAddressMode.ClampToEdge, sampler.Description.AddressU); Assert.Equal(GpuAddressMode.ClampToEdge, sampler.Description.AddressV); renderer.Dispose(); Assert.Equal(baselineSlots, device.LiveTextureSlotCount); Assert.True(target.IsDisposed); Assert.True(sampler.IsDisposed); Assert.All(pipelines, pipeline => Assert.True(pipeline.IsDisposed)); } [Fact] public void ConstructionFailure_RollsBackTargetSlotSamplerAndEarlierPipelines() { using var device = new RecordingGpuDevice(); int baselineSlots = device.LiveTextureSlotCount; device.PipelineFailure = description => description.Name == "directional-shadow-world-opaque" ? new InvalidOperationException("injected pipeline failure") : null; InvalidOperationException failure = Assert.Throws(() => new DirectionalSunShadowRenderer(device, DirectionalShadowPreset.Medium)); Assert.Equal("injected pipeline failure", failure.Message); Assert.Equal(baselineSlots, device.LiveTextureSlotCount); Assert.True(Assert.Single(device.CreatedDirectionalDepthTargets).IsDisposed); Assert.True(device.CreatedSamplers[^1].IsDisposed); Assert.True(Assert.Single(device.CreatedPipelines).IsDisposed); } [Fact] public void MultiviewConstructionFailure_RetiresAllOrdinaryAndLayeredCandidates() { using var device = new RecordingGpuDevice(); int baselineSlots = device.LiveTextureSlotCount; device.PipelineFailure = description => description.Name == "directional-shadow-world-cutout-multiview" ? new InvalidOperationException("injected multiview failure") : null; Assert.Throws(() => new DirectionalSunShadowRenderer( device, DirectionalShadowPreset.Low, multiviewCascades: true)); Assert.Equal(baselineSlots, device.LiveTextureSlotCount); Assert.True(Assert.Single(device.CreatedDirectionalDepthTargets).IsDisposed); Assert.True(device.CreatedSamplers[^1].IsDisposed); Assert.Equal(5, device.CreatedPipelines.Count); Assert.All(device.CreatedPipelines, pipeline => Assert.True(pipeline.IsDisposed)); } [Fact] public void RebuildAfterDisposal_UsesANewLiveShadowSampler() { using var device = new RecordingGpuDevice(); var first = new DirectionalSunShadowRenderer(device, DirectionalShadowPreset.Low); RecordingGpuSampler firstSampler = device.CreatedSamplers[^1]; first.Dispose(); using var second = new DirectionalSunShadowRenderer(device, DirectionalShadowPreset.Low); RecordingGpuSampler secondSampler = device.CreatedSamplers[^1]; Assert.NotSame(firstSampler, secondSampler); Assert.True(firstSampler.IsDisposed); Assert.False(secondSampler.IsDisposed); Assert.Equal(GpuSamplerDescription.ShadowNearestClamp, secondSampler.Description); } [Fact] public void ReceiverBinding_IsValidOnlyForTheProducingFrame() { using var device = new RecordingGpuDevice(); using var renderer = new DirectionalSunShadowRenderer( device, DirectionalShadowPreset.Low); using (IGpuFrame frame = device.BeginFrame()) { WorldTransformFrameSlice sharedTransforms = PublishSharedTransforms( frame, ReadOnlySpan.Empty); renderer.RenderPrepared( frame, new DirectionalShadowEnvironmentState( DirectionalShadowGateReason.Enabled, Vector3.UnitZ, 1f, 1f, 1f, AuthoredCelestialShadowSourceKind.Sun, SourceObjectIndex: 0, SourceGfxObjId: AuthoredCelestialShadowSourceResolver.SunGfxObjId), Matrix4x4.Identity, Matrix4x4.CreatePerspectiveFieldOfView(1f, 1f, 0.1f, 100f), 0.1f, 48f, new DirectionalShadowPreparedDraws(), new DirectionalShadowTerrainPreparedDraws(), null, null, sharedTransforms); Assert.True(renderer.TryGetCurrentFrameBinding(frame, out var binding)); Assert.Equal(frame.Serial, binding.FrameSerial); Assert.Equal((uint)DirectionalShadowUniforms.SizeInBytes, binding.SizeBytes); Assert.Equal(2, binding.CascadeCount); } using IGpuFrame later = device.BeginFrame(); Assert.False(renderer.TryGetCurrentFrameBinding(later, out _)); } private static DirectionalShadowPreparedDraws CreateWorldDraws(GpuTextureSlot cutoutSlot) { var draws = new DirectionalShadowPreparedDraws(); RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(3); Assert.True(draws.TryBegin(generation, 7, 2)); Matrix4x4 opaque = Matrix4x4.CreateTranslation(1f, 2f, 3f); Matrix4x4 cutout = Matrix4x4.CreateRotationZ(0.3f) * Matrix4x4.CreateTranslation(4f, 5f, 6f); draws.Add(0, 0, 6, GpuTextureSlot.Unassigned, 0, CullMode.CounterClockwise, DirectionalShadowCasterMaterial.Opaque, in opaque); draws.Add(6, 4, 12, cutoutSlot, 2, CullMode.None, DirectionalShadowCasterMaterial.AlphaCutout, in cutout); DirectionalShadowPreparationStats stats = default; draws.Complete(generation, 7, in stats); return draws; } private static DirectionalShadowCascade Cascade( int index, float splitFarMeters, DirectionalShadowWorldBias bias) => new( index, index == 0 ? 0.1f : 20f, splitFarMeters, Matrix4x4.Identity, Matrix4x4.Identity, Matrix4x4.Identity, Vector2.Zero, 10f, 0.1f, 48f, bias); private static DirectionalShadowEnvironmentState EnabledEnvironment() => new( DirectionalShadowGateReason.Enabled, Vector3.Normalize(new Vector3(0.2f, 0.3f, 1f)), 0.94f, 0.8f, 1.25f, AuthoredCelestialShadowSourceKind.Sun, SourceObjectIndex: 0, SourceGfxObjId: AuthoredCelestialShadowSourceResolver.SunGfxObjId); private static DirectionalShadowTerrainPreparedDraws CreateTerrainDraws() { var draws = new DirectionalShadowTerrainPreparedDraws(); Assert.True(draws.TryBegin(1, 1)); var range = new DirectionalShadowTerrainRange(20, 60); draws.Add(in range); draws.Complete(1); return draws; } private static IGpuBuffer Buffer(RecordingGpuDevice device, string name, GpuBufferUsage usage) => device.CreateBuffer(new GpuBufferDescription( name, 4096, usage | GpuBufferUsage.TransferDestination, GpuMemoryResidency.DeviceLocal)); private static WorldTransformFrameSlice PublishSharedTransforms( IGpuFrame frame, ReadOnlySpan transforms) { GpuRingAllocation allocation = frame.AllocateRing( checked((int)WorldTransformCapacityPolicy.InitialBindingSizeBytes), GpuRingUsage.Storage); if (!transforms.IsEmpty) MemoryMarshal.AsBytes(transforms).CopyTo(allocation.Data); return new WorldTransformFrameSlice( frame.Serial, allocation.Buffer, allocation.OffsetBytes, WorldTransformCapacityPolicy.InitialBindingSizeBytes, FirstInstance: 0, checked((uint)transforms.Length)); } private static int Offset(string field) => checked((int)Marshal.OffsetOf(field)); private static string RepositoryRoot() { var directory = new DirectoryInfo(AppContext.BaseDirectory); while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "AcDream.slnx"))) directory = directory.Parent; return directory?.FullName ?? throw new InvalidOperationException("Could not locate repository root."); } }