feat(render): Vulkan campaign V11 step 2 — delete the OpenGL backend
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>
This commit is contained in:
parent
b70b9832ff
commit
8a7a0837e1
121 changed files with 1243 additions and 19840 deletions
|
|
@ -1,8 +1,5 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
|
|
@ -42,196 +39,42 @@ namespace AcDream.App.Rendering;
|
|||
/// depth write lands only inside the slice region, matching retail's clipped
|
||||
/// fan.</para>
|
||||
///
|
||||
/// <para>Self-contained GL state (feedback_render_self_contained_gl_state):
|
||||
/// sets everything it depends on, restores the frame-global convention on
|
||||
/// exit, no early-outs between set and restore.</para>
|
||||
/// <para>The GL arm's inline program (rehomed clip planes, raw uniform
|
||||
/// locations) and this class's own dynamic VAO/VBO ring were deleted at
|
||||
/// Campaign V slice V11; the RHI arm compiles
|
||||
/// <c>Rendering/Shaders/portal_depth.{vert,frag}</c> from committed SPIR-V,
|
||||
/// and its per-frame fan vertices come from the frame ring instead.</para>
|
||||
/// </summary>
|
||||
public sealed partial class PortalDepthMaskRenderer : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The GL arm's inline program. Campaign V slice V6l added a SECOND arm that
|
||||
/// compiles <c>Rendering/Shaders/portal_depth.{vert,frag}</c> — the same body,
|
||||
/// with its loose uniforms rehomed onto the shared push block and the clip
|
||||
/// planes onto the <c>TerrainClip</c> UBO — because a Vulkan pipeline needs a
|
||||
/// named, SPIR-V-compiled pair. This string stays because the GL arm keeps
|
||||
/// its raw path through to V10 (plan §5.5.6) and because rehoming its clip
|
||||
/// planes onto UBO binding 2 would clobber the terrain clip block that
|
||||
/// <c>ClipFrame</c> binds there globally on GL. The two are kept in step by
|
||||
/// <c>PortalDepthShaderParityTests</c>, and this one is deleted at V11.
|
||||
/// </summary>
|
||||
internal const string VertSrc = @"#version 430 core
|
||||
layout(location = 0) in vec3 aPos;
|
||||
uniform mat4 uViewProjection;
|
||||
uniform int uPlaneCount;
|
||||
uniform vec4 uPlanes[8];
|
||||
uniform int uForceFarZ;
|
||||
uniform float uDepthBias; // NDC bias toward the viewer (mark pass only)
|
||||
uniform float uDepthBiasEyeCapN; // eye-span cap x near plane (#129; see MarkBiasNdc)
|
||||
out float gl_ClipDistance[8];
|
||||
void main()
|
||||
{
|
||||
vec4 clipPos = uViewProjection * vec4(aPos, 1.0);
|
||||
for (int i = 0; i < 8; i++)
|
||||
gl_ClipDistance[i] = (i < uPlaneCount) ? dot(uPlanes[i], clipPos) : 1.0;
|
||||
if (uForceFarZ == 1)
|
||||
clipPos.z = clipPos.w * 0.99999988; // retail far-z punch constant (0x0059bc90 tail)
|
||||
else if (uDepthBias > 0.0)
|
||||
{
|
||||
// #117 mark-pass bias, #129 eye-space cap. clipPos.w = eye depth d;
|
||||
// an NDC bias b spans ~b*d*d/near meters of eye depth, so the
|
||||
// constant-NDC form alone reached METERS at distance (door-shaped
|
||||
// leaks through hills/houses). Keep in sync with MarkBiasNdc.
|
||||
float biasNdc = min(uDepthBias, uDepthBiasEyeCapN / max(clipPos.w * clipPos.w, 1e-6));
|
||||
clipPos.z -= biasNdc * clipPos.w;
|
||||
}
|
||||
gl_Position = clipPos;
|
||||
}";
|
||||
|
||||
internal const string FragSrc = @"#version 430 core
|
||||
void main() { } // depth-only: color writes are masked off by the caller state
|
||||
";
|
||||
|
||||
private readonly GL? _glContext;
|
||||
private GL _gl => _glContext
|
||||
?? throw new InvalidOperationException(
|
||||
"PortalDepthMaskRenderer's GL arm was reached on a backend with no GL context "
|
||||
+ "(campaign plan slice V6l; the RHI arm lives in PortalDepthMaskRenderer.Rhi.cs).");
|
||||
|
||||
private readonly uint _program;
|
||||
private readonly int _locViewProjection;
|
||||
private readonly int _locPlaneCount;
|
||||
private readonly int _locPlanes;
|
||||
private readonly int _locForceFarZ;
|
||||
private readonly int _locDepthBias;
|
||||
private readonly int _locDepthBiasEyeCapN;
|
||||
private readonly ResourceCleanupGroup _resources;
|
||||
|
||||
/// <summary>Shared by both arms: the largest fan <see cref="DrawDepthFan"/> accepts.</summary>
|
||||
private const int MaxFanVerts = 32;
|
||||
private readonly float[] _scratch = new float[MaxFanVerts * 3];
|
||||
|
||||
private sealed class FrameBufferSet
|
||||
{
|
||||
public uint Vao;
|
||||
public uint Vbo;
|
||||
public int CapacityBytes;
|
||||
public int UsedVertices;
|
||||
}
|
||||
|
||||
private readonly FrameBufferSet?[] _frameBuffers = new FrameBufferSet[3];
|
||||
private FrameBufferSet? _activeFrameBuffer;
|
||||
|
||||
internal long DynamicBufferCapacityBytes =>
|
||||
_frameBuffers.Sum(set => (long)(set?.CapacityBytes ?? 0));
|
||||
|
||||
public PortalDepthMaskRenderer(GL gl)
|
||||
{
|
||||
_glContext = gl ?? throw new ArgumentNullException(nameof(gl));
|
||||
var resources = new ResourceCleanupGroup();
|
||||
try
|
||||
{
|
||||
_program = ShaderProgramConstruction.Build(
|
||||
new GlShaderProgramBuildApi(gl),
|
||||
VertSrc,
|
||||
FragSrc);
|
||||
resources.Add(
|
||||
"portal-depth program",
|
||||
() => GlResourceCommand.DeleteProgram(
|
||||
gl,
|
||||
_program,
|
||||
$"delete PortalDepthMask program {_program}"));
|
||||
|
||||
(
|
||||
_locViewProjection,
|
||||
_locPlaneCount,
|
||||
_locPlanes,
|
||||
_locForceFarZ,
|
||||
_locDepthBias,
|
||||
_locDepthBiasEyeCapN) = GlResourceCommand.Execute(
|
||||
_gl,
|
||||
"resolve PortalDepthMask uniforms",
|
||||
() => (
|
||||
_gl.GetUniformLocation(_program, "uViewProjection"),
|
||||
_gl.GetUniformLocation(_program, "uPlaneCount"),
|
||||
_gl.GetUniformLocation(_program, "uPlanes"),
|
||||
_gl.GetUniformLocation(_program, "uForceFarZ"),
|
||||
_gl.GetUniformLocation(_program, "uDepthBias"),
|
||||
_gl.GetUniformLocation(_program, "uDepthBiasEyeCapN")));
|
||||
|
||||
for (int i = 0; i < _frameBuffers.Length; i++)
|
||||
{
|
||||
FrameBufferSet set = CreateFrameBufferSet(resources, i);
|
||||
_frameBuffers[i] = set;
|
||||
}
|
||||
}
|
||||
catch (Exception constructionFailure)
|
||||
{
|
||||
resources.RollbackConstructionAndThrow(
|
||||
"PortalDepthMaskRenderer construction failed and its GL prefix did not cleanly roll back.",
|
||||
constructionFailure);
|
||||
throw new System.Diagnostics.UnreachableException();
|
||||
}
|
||||
|
||||
_resources = resources;
|
||||
}
|
||||
/// <summary>
|
||||
/// The GL arm's per-flight VAO/VBO ring this used to report on was deleted
|
||||
/// at Campaign V slice V11: the RHI arm draws every fan from a ring
|
||||
/// allocation that lives until its frame retires, so there is no
|
||||
/// persistent dynamic-buffer pool left to size.
|
||||
/// </summary>
|
||||
internal long DynamicBufferCapacityBytes => 0;
|
||||
|
||||
/// <summary>
|
||||
/// Selects the GPU-fenced frame slot and resets its append cursor. Portal
|
||||
/// fans written during one frame occupy distinct ranges, so later portal
|
||||
/// slices never overwrite vertices still referenced by earlier draws.
|
||||
///
|
||||
/// <para>The GL arm's per-flight VAO/VBO ring this used to activate was
|
||||
/// deleted at Campaign V slice V11 — the RHI arm owns no per-flight ring
|
||||
/// at all, because every frame ring allocation is already distinct memory
|
||||
/// that lives until the frame retires — so all this edge carries now is
|
||||
/// the started latch.</para>
|
||||
/// </summary>
|
||||
public void BeginFrame(int frameSlot)
|
||||
{
|
||||
if ((uint)frameSlot >= (uint)_frameBuffers.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(frameSlot));
|
||||
// Slice V6l: the RHI arm owns no per-flight VBO ring — every frame ring
|
||||
// allocation is already distinct memory that lives until the frame
|
||||
// retires — so all this edge carries there is the started latch.
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(frameSlot);
|
||||
_rhiFrameStarted = true;
|
||||
if (_glContext is null)
|
||||
return;
|
||||
_activeFrameBuffer = _frameBuffers[frameSlot];
|
||||
_activeFrameBuffer!.UsedVertices = 0;
|
||||
}
|
||||
|
||||
private FrameBufferSet CreateFrameBufferSet(
|
||||
ResourceCleanupGroup resources,
|
||||
int frameIndex)
|
||||
{
|
||||
uint vao = TrackedGlResource.CreateVertexArray(
|
||||
_gl,
|
||||
"PortalDepthMask frame VAO creation");
|
||||
RetryableGpuResourceRelease vaoRelease =
|
||||
TrackedGlResource.CreateRetryableVertexArrayDeletion(
|
||||
_gl,
|
||||
vao,
|
||||
"PortalDepthMask frame VAO disposal");
|
||||
resources.Add($"portal-depth frame {frameIndex} VAO", vaoRelease.Run);
|
||||
|
||||
uint vbo = TrackedGlResource.CreateBuffer(
|
||||
_gl,
|
||||
"PortalDepthMask frame VBO creation");
|
||||
var set = new FrameBufferSet { Vao = vao, Vbo = vbo };
|
||||
RetryableGpuResourceRelease vboRelease =
|
||||
TrackedGlResource.CreateRetryableBufferDeletion(
|
||||
_gl,
|
||||
vbo,
|
||||
() => set.CapacityBytes,
|
||||
"PortalDepthMask frame VBO disposal");
|
||||
resources.Add($"portal-depth frame {frameIndex} VBO", vboRelease.Run);
|
||||
|
||||
GlResourceCommand.Execute(_gl, "configure PortalDepthMask frame buffers", () =>
|
||||
{
|
||||
_gl.BindVertexArray(set.Vao);
|
||||
_gl.BindBuffer(BufferTargetARB.ArrayBuffer, set.Vbo);
|
||||
unsafe
|
||||
{
|
||||
_gl.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), (void*)0);
|
||||
}
|
||||
_gl.EnableVertexAttribArray(0);
|
||||
_gl.BindVertexArray(0);
|
||||
_gl.BindBuffer(BufferTargetARB.ArrayBuffer, 0);
|
||||
});
|
||||
return set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -303,143 +146,8 @@ void main() { } // depth-only: color writes are masked off by the caller state
|
|||
{
|
||||
if (worldVerts.Length < 3)
|
||||
return;
|
||||
if (_glContext is null)
|
||||
{
|
||||
DrawDepthFanRhi(worldVerts, in viewProjection, planes, forceFarZ);
|
||||
return;
|
||||
}
|
||||
|
||||
FrameBufferSet frameBuffer = _activeFrameBuffer
|
||||
?? throw new InvalidOperationException("BeginFrame must be called before drawing portal depth masks.");
|
||||
int n = Math.Min(worldVerts.Length, MaxFanVerts);
|
||||
int planeCount = Math.Min(planes.Length, 8);
|
||||
int firstVertex = frameBuffer.UsedVertices;
|
||||
int requiredBytes = checked((firstVertex + n) * 3 * sizeof(float));
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
_scratch[i * 3 + 0] = worldVerts[i].X;
|
||||
_scratch[i * 3 + 1] = worldVerts[i].Y;
|
||||
_scratch[i * 3 + 2] = worldVerts[i].Z;
|
||||
}
|
||||
|
||||
// ---- set state (everything this draw depends on) ----
|
||||
_gl.UseProgram(_program);
|
||||
_gl.Disable(EnableCap.Blend);
|
||||
_gl.Disable(EnableCap.CullFace); // portal fans face either way
|
||||
_gl.Disable(EnableCap.ScissorTest);
|
||||
_gl.Enable(EnableCap.DepthTest);
|
||||
_gl.ColorMask(false, false, false, false); // alpha-0 fan ≙ no color
|
||||
for (int i = 0; i < planeCount; i++)
|
||||
_gl.Enable(EnableCap.ClipDistance0 + i);
|
||||
|
||||
unsafe
|
||||
{
|
||||
var m = viewProjection;
|
||||
_gl.UniformMatrix4(_locViewProjection, 1, false, (float*)&m);
|
||||
_gl.Uniform1(_locPlaneCount, planeCount);
|
||||
if (planeCount > 0)
|
||||
{
|
||||
Span<float> p = stackalloc float[planeCount * 4];
|
||||
for (int i = 0; i < planeCount; i++)
|
||||
{
|
||||
p[i * 4 + 0] = planes[i].X;
|
||||
p[i * 4 + 1] = planes[i].Y;
|
||||
p[i * 4 + 2] = planes[i].Z;
|
||||
p[i * 4 + 3] = planes[i].W;
|
||||
}
|
||||
fixed (float* pp = p)
|
||||
_gl.Uniform4(_locPlanes, (uint)planeCount, pp);
|
||||
}
|
||||
|
||||
_gl.BindVertexArray(frameBuffer.Vao);
|
||||
_gl.BindBuffer(BufferTargetARB.ArrayBuffer, frameBuffer.Vbo);
|
||||
if (frameBuffer.CapacityBytes < requiredBytes)
|
||||
{
|
||||
int newCapacity = DynamicBufferCapacity.Grow(
|
||||
frameBuffer.CapacityBytes,
|
||||
requiredBytes);
|
||||
TrackedGlResource.AllocateBufferStorage(
|
||||
_gl,
|
||||
GLEnum.ArrayBuffer,
|
||||
frameBuffer.Vbo,
|
||||
frameBuffer.CapacityBytes,
|
||||
newCapacity,
|
||||
GLEnum.DynamicDraw,
|
||||
"PortalDepthMask frame VBO growth");
|
||||
frameBuffer.CapacityBytes = newCapacity;
|
||||
}
|
||||
fixed (float* v = _scratch)
|
||||
_gl.BufferSubData(
|
||||
BufferTargetARB.ArrayBuffer,
|
||||
(nint)(firstVertex * 3 * sizeof(float)),
|
||||
(nuint)(n * 3 * sizeof(float)),
|
||||
v);
|
||||
frameBuffer.UsedVertices += n;
|
||||
|
||||
if (!forceFarZ)
|
||||
{
|
||||
// ── SEAL: retail-verbatim single pass ──
|
||||
_gl.DepthFunc(DepthFunction.Always);
|
||||
_gl.DepthMask(true);
|
||||
_gl.Uniform1(_locForceFarZ, 0);
|
||||
_gl.Uniform1(_locDepthBias, 0f);
|
||||
_gl.DrawArrays(PrimitiveType.TriangleFan, firstVertex, (uint)n);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ── PUNCH pass A: stencil-mark visible aperture pixels ──
|
||||
_gl.Enable(EnableCap.StencilTest);
|
||||
_gl.StencilFunc(StencilFunction.Always, 1, 0xFF);
|
||||
_gl.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Replace);
|
||||
_gl.StencilMask(0xFF);
|
||||
_gl.DepthFunc(DepthFunction.Lequal);
|
||||
_gl.DepthMask(false);
|
||||
_gl.Uniform1(_locForceFarZ, 0);
|
||||
_gl.Uniform1(_locDepthBias, PunchMarkDepthBias);
|
||||
_gl.Uniform1(_locDepthBiasEyeCapN,
|
||||
PunchMarkBiasEyeCapMeters * CameraNearPlaneMeters);
|
||||
_gl.DrawArrays(PrimitiveType.TriangleFan, firstVertex, (uint)n);
|
||||
|
||||
// ── PUNCH pass B: far-Z write on marked pixels only;
|
||||
// zero the stencil as we go (self-cleaning) ──
|
||||
_gl.StencilFunc(StencilFunction.Equal, 1, 0xFF);
|
||||
_gl.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Zero);
|
||||
_gl.DepthFunc(DepthFunction.Always);
|
||||
_gl.DepthMask(true);
|
||||
_gl.Uniform1(_locForceFarZ, 1);
|
||||
_gl.Uniform1(_locDepthBias, 0f);
|
||||
_gl.DrawArrays(PrimitiveType.TriangleFan, firstVertex, (uint)n);
|
||||
|
||||
_gl.Disable(EnableCap.StencilTest);
|
||||
}
|
||||
|
||||
_gl.BindVertexArray(0);
|
||||
_gl.BindBuffer(BufferTargetARB.ArrayBuffer, 0);
|
||||
}
|
||||
|
||||
// ---- restore the frame-global convention ----
|
||||
for (int i = 0; i < planeCount; i++)
|
||||
_gl.Disable(EnableCap.ClipDistance0 + i);
|
||||
_gl.ColorMask(true, true, true, true);
|
||||
_gl.DepthMask(true);
|
||||
_gl.DepthFunc(DepthFunction.Less);
|
||||
// Renderers opt into culling locally; the shared frame convention is
|
||||
// cull-off. Keep this success exit identical to frame-abort recovery.
|
||||
_gl.Disable(EnableCap.CullFace);
|
||||
_gl.CullFace(TriangleFace.Back);
|
||||
_gl.FrontFace(FrontFaceDirection.CW);
|
||||
_gl.UseProgram(0);
|
||||
DrawDepthFanRhi(worldVerts, in viewProjection, planes, forceFarZ);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_glContext is null)
|
||||
{
|
||||
DisposeRhiResources();
|
||||
return;
|
||||
}
|
||||
|
||||
_resources.RetryCleanup();
|
||||
}
|
||||
public void Dispose() => DisposeRhiResources();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue