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:
Erik 2026-07-29 02:19:53 +02:00
parent b70b9832ff
commit 8a7a0837e1
121 changed files with 1243 additions and 19840 deletions

View file

@ -12,7 +12,6 @@ using DatReaderWriter;
using AcDream.Content;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Types;
using Silk.NET.OpenGL;
namespace AcDream.App.Rendering;
@ -24,14 +23,12 @@ namespace AcDream.App.Rendering;
/// retained gameplay UI.
///
/// <para><b>Campaign V slice V6m.</b> This was the last raw-GL world-adjacent
/// renderer; it now draws on both arms. Nothing about the scene changed — the
/// same synthetic Setup, the same 40 fps sequence, the same rotation and the
/// same distant light, through the same (already dual-arm)
/// <see cref="WbDrawDispatcher"/>. What forked is where the draw is recorded:
/// GL sets ambient capability state under a <see cref="GLStateScope"/> and draws
/// into whatever framebuffer is bound, while the RHI arm opens a pass of its own
/// against the backbuffer and publishes it on <see cref="IWorldPassScope"/> for
/// the span of the draw, exactly as the two offscreen viewports do
/// renderer, and its GL arm was deleted at slice V11. Nothing about the scene
/// changed — the same synthetic Setup, the same 40 fps sequence, the same
/// rotation and the same distant light, through the same
/// <see cref="WbDrawDispatcher"/>. The RHI arm opens a pass of its own against
/// the backbuffer and publishes it on <see cref="IWorldPassScope"/> for the
/// span of the draw, exactly as the two offscreen viewports do
/// (<see cref="PrivateEntityViewportRenderer"/>, slice V6l).</para>
/// </summary>
public sealed class PortalTunnelPresentation : IDisposable
@ -40,13 +37,6 @@ public sealed class PortalTunnelPresentation : IDisposable
public const uint AnimationClientEnum = 0x10000002u;
public const uint ClientEnumCategory = 7u;
// UIViewportObject::DrawContent @ 0x006950A5 calls
// RenderDeviceD3D::Clear(4, RGBAColor_Black, 1). Clear @ 0x0059FD30
// maps retail flag 4 to D3DCLEAR_ZBUFFER only: portal space deliberately
// preserves the black color target established by SceneTool::BeginScene's
// whole-frame Clear(7) while replacing the hidden world viewport.
internal const ClearBufferMask RetailViewportClearMask = ClearBufferMask.DepthBufferBit;
/// <summary>
/// The colour the RHI arm's pass loads with, and the one value that makes
/// its <c>Clear</c> load-op equivalent to GL's depth-only clear.
@ -74,22 +64,17 @@ public sealed class PortalTunnelPresentation : IDisposable
private static readonly HashSet<uint> AnimatedIds = new() { SyntheticEntityId };
/// <summary>The GL arm's context, or null on a backend that has none.</summary>
private readonly GL? _glContext;
/// <summary>
/// The world pass scope, or null on the GL arm.
///
/// <para><see cref="WbDrawDispatcher"/>'s RHI arm borrows its pass from the
/// scope rather than opening one, so a presentation that opens a pass of its
/// own has to publish it there for the duration of the draw. On the GL arm
/// the dispatcher records against whatever framebuffer is bound, so nothing
/// is published.</para>
/// The world pass scope this presentation's own pass publishes into for
/// the span of the draw. <see cref="WbDrawDispatcher"/> borrows its pass
/// from the scope rather than opening one, so a presentation that opens a
/// pass of its own has to publish it there. The raw-GL arm this used to be
/// optional for was deleted at Campaign V slice V11.
/// </summary>
private readonly IWorldPassScope? _scope;
private readonly IWorldPassScope _scope;
/// <summary>The frame the RHI arm's pass is opened on; null on the GL arm.</summary>
private readonly ICurrentGpuFrameSource? _frames;
/// <summary>The frame this presentation's own pass is opened on.</summary>
private readonly ICurrentGpuFrameSource _frames;
private readonly WbDrawDispatcher _dispatcher;
private readonly SceneLightingUboBinding _lightUbo;
@ -117,9 +102,8 @@ public sealed class PortalTunnelPresentation : IDisposable
private bool _disposed;
private PortalTunnelPresentation(
GL? gl,
IWorldPassScope? scope,
ICurrentGpuFrameSource? frames,
IWorldPassScope scope,
ICurrentGpuFrameSource frames,
WbDrawDispatcher dispatcher,
SceneLightingUboBinding lightUbo,
IWbMeshAdapter meshAdapter,
@ -132,7 +116,6 @@ public sealed class PortalTunnelPresentation : IDisposable
Action<string?>? displayNotice,
IDisposable? displayNoticeLifetime)
{
_glContext = gl;
_scope = scope;
_frames = frames;
_dispatcher = dispatcher;
@ -173,9 +156,8 @@ public sealed class PortalTunnelPresentation : IDisposable
/// Composition is the only caller.</para>
/// </summary>
internal static PortalTunnelPresentation CreateRequired(
GL? gl,
IWorldPassScope? scope,
ICurrentGpuFrameSource? frames,
IWorldPassScope scope,
ICurrentGpuFrameSource frames,
IDatReaderWriter dats,
IAnimationLoader animationLoader,
IAnimationHookSink hookSink,
@ -186,14 +168,8 @@ public sealed class PortalTunnelPresentation : IDisposable
IDisposable? displayNoticeLifetime = null,
Random? random = null)
{
if (gl is null && (scope is null || frames is null))
{
throw new ArgumentNullException(
nameof(scope),
"A backend without a GL context must supply a world pass scope and a frame source "
+ "for portal space to open its pass on.");
}
ArgumentNullException.ThrowIfNull(scope);
ArgumentNullException.ThrowIfNull(frames);
ArgumentNullException.ThrowIfNull(dats);
ArgumentNullException.ThrowIfNull(animationLoader);
ArgumentNullException.ThrowIfNull(hookSink);
@ -215,7 +191,6 @@ public sealed class PortalTunnelPresentation : IDisposable
animation is not null);
return new PortalTunnelPresentation(
gl,
scope,
frames,
dispatcher,
@ -335,51 +310,21 @@ public sealed class PortalTunnelPresentation : IDisposable
_camera.Aspect = width / (float)height;
_camera.UseSmartBoxFov(smartBoxProjection);
if (_glContext is { } gl)
{
DrawGl(gl, width, height);
return;
}
DrawRhi();
}
private void DrawGl(GL gl, int width, int height)
{
using var scope = new GLStateScope(gl);
gl.Viewport(0, 0, (uint)width, (uint)height);
gl.Disable(EnableCap.ScissorTest);
gl.ClearDepth(1.0);
gl.DepthMask(true);
gl.Clear(RetailViewportClearMask);
gl.Enable(EnableCap.DepthTest);
gl.DepthFunc(DepthFunction.Less);
gl.Enable(EnableCap.CullFace);
gl.CullFace(TriangleFace.Back);
gl.FrontFace(FrontFaceDirection.Ccw);
gl.Disable(EnableCap.Blend);
DrawScene();
}
/// <summary>
/// The RHI arm. Every capability the GL arm sets by hand is baked into the
/// dispatcher's pipelines, and the pass sets its own full-attachment
/// viewport, so what remains is the pass itself: a backbuffer pass at the
/// world's sample count that clears colour and depth (see
/// <see cref="RetailPortalSpaceClearColor"/> for why re-clearing colour is
/// exact) and is published as the scope's for the span of the draw.
/// Every capability the raw-GL arm used to set by hand (deleted at
/// Campaign V slice V11) is baked into the dispatcher's pipelines, and the
/// pass sets its own full-attachment viewport, so what remains is the
/// pass itself: a backbuffer pass at the world's sample count that clears
/// colour and depth (see <see cref="RetailPortalSpaceClearColor"/> for why
/// re-clearing colour is exact) and is published as the scope's for the
/// span of the draw.
/// </summary>
private void DrawRhi()
{
IWorldPassScope scope = _scope
?? throw new InvalidOperationException(
"Portal space reached its RHI arm with no world pass scope.");
IGpuFrame frame = (_frames ?? throw new InvalidOperationException(
"Portal space reached its RHI arm with no frame source."))
.CurrentFrame
IGpuFrame frame = _frames.CurrentFrame
?? throw new InvalidOperationException(
"Portal space requires an open IGpuFrame (see GpuDeviceFrameLifetime).");
@ -387,11 +332,11 @@ public sealed class PortalTunnelPresentation : IDisposable
GpuPassDescription.BackbufferClear(
"portal-space",
RetailPortalSpaceClearColor,
scope.SampleCount));
_scope.SampleCount));
// Published AFTER the pass opens and BEFORE the light upload: publishing
// resets the frame-global sections, and the distant light installed below
// is the one this scene wants rather than the world's.
using IDisposable publication = scope.Publish(encoder);
using IDisposable publication = _scope.Publish(encoder);
DrawScene();
}