Vulkan is the sole, user-signed-off backend (V10 landed) and step 1 already removed ImGui/Studio/DevTools. This step deletes the GL rendering backend itself: every Gpu/Gl/** implementation, the Wb ManagedGL*/GLHelpers/GLSLShader/GLStateScope/RenderStateCache/ BindlessSupport family, Shader/ShaderProgramConstruction/SamplerCache, RenderBootstrap, and RenderFrameGlStateController. GameWindow.cs's Run()/CreateGraphics()/CreateBackbufferReader()/ OnLoad() collapse to their Vulkan-only arm; GameWindowGraphics loses its OpenGlGameWindowGraphics subclass. RuntimeOptions.RenderBackend and RenderBackendKind (incl. the Gl member of GpuBackendKind) are gone — there is nothing left to select between. The five world-draw dual-arm renderers (WbDrawDispatcher, EnvCellRenderer, TerrainModernRenderer, ParticleRenderer, SkyRenderer) and the composition roots (WorldRenderComposition, HostInputCameraComposition, LivePresentationComposition, FrameRootComposition) collapse to their RHI-only arm. GL-only diagnostic properties with a live external reader (DynamicBufferCount and friends) simplify to a documented `=> 0`/no-op rather than disappearing, since the reader is out of this commit's scope. A few GL-flavored mechanisms turned out to be backend-neutral once isolated: GlConstructionCleanupLedger is renamed ResourceConstructionCleanupLedger (exception-chain walking has nothing to do with GL), and GlfwNativePlatformProbe moved out of the otherwise GL-only GraphicalCapabilityRecord.cs into GraphicalWindowBackendSelection.cs before the rest of that file was deleted. Test files with no surviving subject are deleted outright (GraphicalCapabilityRequirementsTests, ShaderProgramConstructionTests, PortalDepthShaderParityTests, TextureCacheBindlessTests, TextRendererFailureSafetyTests, ClipFrameUploadTests, every Gpu/Gl/*Tests, GlTextureOwnershipTests, RenderFrameGlStateControllerTests); others get their dead GL-only members trimmed while their live assertions stay (ClipFrameLayoutTests' MeshClipSsboBinding check now reads GpuBindingModel.StorageClipRegions, the same binding index under its new backend-neutral name; GpuResourceRetirementTransactionTests drops its OpenGLGraphicsDevice-subclassing test double and the two GL queue tests it existed for). EnvCellRendererTests' construction helper now builds a real ObjectMeshManager via VulkanMeshPipelineDevice instead of passing null through a null-forgiving operator, since the RHI constructor never tolerated a null mesh manager and the old GL constructor (which did) is gone. Deferred to the next two steps, deliberately not touched here: the Silk.NET.OpenGL/.Extensions.ARB package references, IMeshPipelineDevice.Gl (WbMeshAdapter's GL? threading stays in place), Chorizite.Core's stale csproj comment (the package itself is still load-bearing — TextureFormat and friends are used well beyond the deleted ManagedGLUniformBuffer), and the CI/gate scripts. Build: `dotnet build AcDream.slnx -c Release` — 0 warnings, 0 errors. Tests: full-solution `dotnet test` green across every project (App.Tests 3937/3940 + 3 skips, Core.Tests 3296/3298 + 2 skips, all others 100%); the 2 App.Tests names that flake under full-suite parallel execution (#250-family, documented pre-existing) pass in isolation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
186 lines
7.1 KiB
C#
186 lines
7.1 KiB
C#
using System.Numerics;
|
|
using AcDream.App.Rendering;
|
|
using AcDream.App.Rendering.Gpu;
|
|
using Xunit;
|
|
|
|
namespace AcDream.App.Tests.Rendering;
|
|
|
|
/// <summary>
|
|
/// Phase U.3: CPU-side proof that <see cref="ClipFrame"/> packs the shared clip
|
|
/// data in the EXACT std430 (mesh SSBO) / std140 (terrain UBO) byte layout the
|
|
/// shaders read. A silent layout drift here would mis-clip at U.4 with no build
|
|
/// error — these tests are the gate that catches it.
|
|
///
|
|
/// Layout under test (mesh CellClip, std430):
|
|
/// offset 0 : uint count
|
|
/// offset 4 : uint _p0 (pad)
|
|
/// offset 8 : uint _p1 (pad)
|
|
/// offset 12 : uint _p2 (pad)
|
|
/// offset 16 : vec4 planes[0] (16-byte vec4 stride)
|
|
/// ...
|
|
/// offset 16 + i*16 : vec4 planes[i]
|
|
/// stride 144 bytes per slot.
|
|
/// Terrain UBO (std140): int count at 0 (padded to 16), vec4 planes[8] at 16.
|
|
/// </summary>
|
|
public class ClipFrameLayoutTests
|
|
{
|
|
private static float ReadFloat(System.ReadOnlySpan<byte> b, int offset)
|
|
=> System.BitConverter.ToSingle(b.Slice(offset, 4));
|
|
|
|
private static uint ReadUInt(System.ReadOnlySpan<byte> b, int offset)
|
|
=> System.BitConverter.ToUInt32(b.Slice(offset, 4));
|
|
|
|
private static int ReadInt(System.ReadOnlySpan<byte> b, int offset)
|
|
=> System.BitConverter.ToInt32(b.Slice(offset, 4));
|
|
|
|
[Fact]
|
|
public void LayoutConstants_MatchShaderStruct()
|
|
{
|
|
// CellClip: 16 (count + 3 pad uints) + 8*16 (vec4 planes) = 144.
|
|
Assert.Equal(144, ClipFrame.CellClipStrideBytes);
|
|
Assert.Equal(16, ClipFrame.CellClipPlanesOffset);
|
|
Assert.Equal(8, ClipFrame.MaxPlanes);
|
|
Assert.Equal(144, ClipFrame.TerrainUboBytes);
|
|
// Binding contract: mesh clip regions on SSBO binding=2, terrain on UBO binding=2.
|
|
// The mesh side's binding index moved off ClipFrame at Campaign V slice
|
|
// V11 — the RHI arm addresses it through GpuBindingModel.StorageClipRegions
|
|
// instead of a raw GL binding constant (see ClipFrame's BeginFrame doc
|
|
// comment); the terrain UBO binding is still genuinely shared, so it stays.
|
|
Assert.Equal(2u, GpuBindingModel.StorageClipRegions);
|
|
Assert.Equal(2u, ClipFrame.TerrainClipUboBinding);
|
|
}
|
|
|
|
[Fact]
|
|
public void NoClip_HasExactlyOneSlot_AllZeros_Count0()
|
|
{
|
|
var frame = ClipFrame.NoClip();
|
|
Assert.Equal(1, frame.SlotCount);
|
|
|
|
var bytes = frame.RegionBytesForTest;
|
|
Assert.Equal(ClipFrame.CellClipStrideBytes, bytes.Length); // 144 — exactly one slot
|
|
|
|
// count == 0 ⇒ shader passes every plane (no-clip).
|
|
Assert.Equal(0u, ReadUInt(bytes, 0));
|
|
// Every byte of the reserved no-clip slot is zero.
|
|
foreach (var b in bytes)
|
|
Assert.Equal(0, b);
|
|
}
|
|
|
|
[Fact]
|
|
public void NoClip_TerrainBytes_Count0_AllZeros()
|
|
{
|
|
var frame = ClipFrame.NoClip();
|
|
var t = frame.TerrainBytesForTest;
|
|
Assert.Equal(ClipFrame.TerrainUboBytes, t.Length);
|
|
Assert.Equal(0, ReadInt(t, 0)); // count 0 ⇒ terrain ungated
|
|
foreach (var b in t)
|
|
Assert.Equal(0, b);
|
|
}
|
|
|
|
[Fact]
|
|
public void AppendSlot_WritesCountAndPlanes_AtStd430Offsets()
|
|
{
|
|
var frame = ClipFrame.NoClip();
|
|
|
|
// Three distinct planes so each lands at a verifiable offset.
|
|
var p0 = new Vector4(1f, 0f, 0f, 0.5f);
|
|
var p1 = new Vector4(0f, 1f, 0f, 0.25f);
|
|
var p2 = new Vector4(-1f, 0f, 0f, -0.75f);
|
|
|
|
int slot = frame.AppendSlot(new[] { p0, p1, p2 });
|
|
Assert.Equal(1, slot); // slot 0 is the reserved no-clip; this is slot 1
|
|
Assert.Equal(2, frame.SlotCount);
|
|
|
|
var bytes = frame.RegionBytesForTest;
|
|
Assert.Equal(2 * ClipFrame.CellClipStrideBytes, bytes.Length); // two slots now
|
|
|
|
int baseOff = slot * ClipFrame.CellClipStrideBytes; // 144
|
|
|
|
// count == 3 at offset 0 of the slot; the 3 pad uints stay zero.
|
|
Assert.Equal(3u, ReadUInt(bytes, baseOff + 0));
|
|
Assert.Equal(0u, ReadUInt(bytes, baseOff + 4));
|
|
Assert.Equal(0u, ReadUInt(bytes, baseOff + 8));
|
|
Assert.Equal(0u, ReadUInt(bytes, baseOff + 12));
|
|
|
|
// planes[0..2] at offset 16, 32, 48 (vec4 stride 16).
|
|
AssertPlaneAt(bytes, baseOff + 16, p0);
|
|
AssertPlaneAt(bytes, baseOff + 32, p1);
|
|
AssertPlaneAt(bytes, baseOff + 48, p2);
|
|
|
|
// Slot 0 (the reserved no-clip) is untouched: still count 0.
|
|
Assert.Equal(0u, ReadUInt(bytes, 0));
|
|
}
|
|
|
|
[Fact]
|
|
public void AppendSlot_EmptyPlaneList_PacksNoClipSlot_Count0()
|
|
{
|
|
var frame = ClipFrame.NoClip();
|
|
int slot = frame.AppendSlot(System.ReadOnlySpan<Vector4>.Empty);
|
|
Assert.Equal(1, slot);
|
|
|
|
var bytes = frame.RegionBytesForTest;
|
|
Assert.Equal(0u, ReadUInt(bytes, slot * ClipFrame.CellClipStrideBytes)); // count 0
|
|
}
|
|
|
|
[Fact]
|
|
public void AppendSlot_ClampsToEightPlanes()
|
|
{
|
|
var frame = ClipFrame.NoClip();
|
|
var planes = new Vector4[12];
|
|
for (int i = 0; i < planes.Length; i++)
|
|
planes[i] = new Vector4(i, 0f, 0f, 0f);
|
|
|
|
int slot = frame.AppendSlot(planes);
|
|
var bytes = frame.RegionBytesForTest;
|
|
// Only MaxPlanes (8) are recorded in the count.
|
|
Assert.Equal((uint)ClipFrame.MaxPlanes, ReadUInt(bytes, slot * ClipFrame.CellClipStrideBytes));
|
|
}
|
|
|
|
[Fact]
|
|
public void AppendSlot_FromClipPlaneSet_AxisAlignedSquare_PacksFourPlanes()
|
|
{
|
|
// A unit square in NDC → ClipPlaneSet with 4 convex planes.
|
|
var cv = new CellView();
|
|
cv.Add(new ViewPolygon(new[]
|
|
{
|
|
new Vector2(-0.5f, -0.5f), new Vector2(0.5f, -0.5f),
|
|
new Vector2(0.5f, 0.5f), new Vector2(-0.5f, 0.5f),
|
|
}));
|
|
var cps = ClipPlaneSet.From(cv);
|
|
Assert.Equal(4, cps.Count);
|
|
|
|
var frame = ClipFrame.NoClip();
|
|
int slot = frame.AppendSlot(cps);
|
|
|
|
var bytes = frame.RegionBytesForTest;
|
|
int baseOff = slot * ClipFrame.CellClipStrideBytes;
|
|
Assert.Equal(4u, ReadUInt(bytes, baseOff + 0));
|
|
|
|
// Each packed plane must match the ClipPlaneSet's plane bit-for-bit.
|
|
for (int i = 0; i < 4; i++)
|
|
AssertPlaneAt(bytes, baseOff + ClipFrame.CellClipPlanesOffset + i * 16, cps.Planes[i]);
|
|
}
|
|
|
|
[Fact]
|
|
public void SetTerrainClip_WritesCountAndPlanes_AtStd140Offsets()
|
|
{
|
|
var frame = ClipFrame.NoClip();
|
|
var p0 = new Vector4(0.3f, -0.4f, 0f, 0.1f);
|
|
var p1 = new Vector4(-0.6f, 0.8f, 0f, -0.2f);
|
|
|
|
frame.SetTerrainClip(new[] { p0, p1 });
|
|
|
|
var t = frame.TerrainBytesForTest;
|
|
Assert.Equal(2, ReadInt(t, 0)); // int count at offset 0
|
|
AssertPlaneAt(t, ClipFrame.CellClipPlanesOffset + 0, p0); // planes start at 16 under std140 too
|
|
AssertPlaneAt(t, ClipFrame.CellClipPlanesOffset + 16, p1);
|
|
}
|
|
|
|
private static void AssertPlaneAt(System.ReadOnlySpan<byte> bytes, int offset, Vector4 expected)
|
|
{
|
|
Assert.Equal(expected.X, ReadFloat(bytes, offset + 0), 6);
|
|
Assert.Equal(expected.Y, ReadFloat(bytes, offset + 4), 6);
|
|
Assert.Equal(expected.Z, ReadFloat(bytes, offset + 8), 6);
|
|
Assert.Equal(expected.W, ReadFloat(bytes, offset + 12), 6);
|
|
}
|
|
}
|