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
|
|
@ -9,7 +9,6 @@ using DatReaderWriter;
|
|||
using AcDream.Content;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Enums;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Rendering.Sky;
|
||||
|
||||
|
|
@ -43,46 +42,11 @@ namespace AcDream.App.Rendering.Sky;
|
|||
/// measured clockwise from north.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed unsafe partial class SkyRenderer : IDisposable
|
||||
public sealed partial class SkyRenderer : IDisposable
|
||||
{
|
||||
private readonly GL? _gl;
|
||||
private readonly IDatReaderWriter _dats;
|
||||
private readonly Shader? _shader;
|
||||
private readonly TextureCache _textures;
|
||||
private readonly SamplerCache? _samplers;
|
||||
|
||||
// Campaign V slice V6e. Two GL objects replace what used to be a run of
|
||||
// glUniform* calls and a texture/sampler binding on unit 0:
|
||||
//
|
||||
// _paramsUbo — the std140 SkyParams block both stages declare.
|
||||
// _uTextureIndexALoc — the push-constant-named uniform carrying the slot.
|
||||
//
|
||||
// The wrap mode is what makes the (texture, wrap) pairing necessary. A
|
||||
// bindless handle BAKES its sampler state, so the per-submesh
|
||||
// Repeat-vs-ClampToEdge choice that used to be a glBindSampler call has to be
|
||||
// a different handle — which is also exactly how the Vulkan table works,
|
||||
// where an entry is a combined image sampler. ManagedGLTextureArray has
|
||||
// interned wrap/clamp handle pairs the same way since the world path went
|
||||
// bindless.
|
||||
//
|
||||
// Campaign V slice V6k retired the interim per-renderer GlBindlessHandleTable
|
||||
// — the last one in the tree — for the device's own retirement-gated table,
|
||||
// exactly as V4t did for the other four world renderers. The handles are
|
||||
// still minted here, because the sky is the one world path whose textures it
|
||||
// produces itself; only the slot allocator moved.
|
||||
private readonly Wb.BindlessSupport? _bindless;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign V slice V6k: the GL arm's slot allocator. The sky mints its own
|
||||
/// resident handles — it is the one world path that does — so it registers
|
||||
/// them into the device's table through V4t's world-handle seam instead of
|
||||
/// keeping the last private <c>GlBindlessHandleTable</c> in the tree.
|
||||
/// </summary>
|
||||
private readonly AcDream.App.Rendering.Gpu.Gl.GlGpuDevice? _glDevice;
|
||||
|
||||
private readonly Dictionary<(uint Texture, bool Repeat), ulong> _handleByTextureAndWrap = new();
|
||||
private uint _paramsUbo;
|
||||
private int _uTextureIndexALoc = -1;
|
||||
private SkyParams _params;
|
||||
|
||||
// Lazily-built GPU resources per sky-GfxObj.
|
||||
|
|
@ -125,40 +89,6 @@ public sealed unsafe partial class SkyRenderer : IDisposable
|
|||
public float Near { get; set; } = 0.1f;
|
||||
public float Far { get; set; } = 1_000_000f;
|
||||
|
||||
internal SkyRenderer(
|
||||
GL gl,
|
||||
IDatReaderWriter dats,
|
||||
Shader shader,
|
||||
TextureCache textures,
|
||||
SamplerCache samplers,
|
||||
Wb.BindlessSupport bindless,
|
||||
AcDream.App.Rendering.Gpu.Gl.GlGpuDevice device)
|
||||
{
|
||||
_gl = gl ?? throw new ArgumentNullException(nameof(gl));
|
||||
_dats = dats ?? throw new ArgumentNullException(nameof(dats));
|
||||
_shader = shader ?? throw new ArgumentNullException(nameof(shader));
|
||||
_textures = textures ?? throw new ArgumentNullException(nameof(textures));
|
||||
_samplers = samplers ?? throw new ArgumentNullException(nameof(samplers));
|
||||
_bindless = bindless ?? throw new ArgumentNullException(nameof(bindless));
|
||||
_glDevice = device ?? throw new ArgumentNullException(nameof(device));
|
||||
|
||||
_uTextureIndexALoc = _gl.GetUniformLocation(_shader.Program, "uTextureIndexA");
|
||||
|
||||
_paramsUbo = Wb.TrackedGlResource.CreateBuffer(_gl, "sky params UBO creation");
|
||||
// Fixed size: SkyParams is a 256-byte std140 struct, so the buffer never
|
||||
// grows and every write is one whole-struct BufferSubData.
|
||||
_gl.BindBuffer(BufferTargetARB.UniformBuffer, _paramsUbo);
|
||||
Wb.TrackedGlResource.AllocateBufferStorage(
|
||||
_gl,
|
||||
BufferTargetARB.UniformBuffer,
|
||||
_paramsUbo,
|
||||
0,
|
||||
SkyParams.SizeInBytes,
|
||||
BufferUsageARB.DynamicDraw,
|
||||
"allocating sky params UBO");
|
||||
_gl.BindBuffer(BufferTargetARB.UniformBuffer, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw all NON-WEATHER sky objects (dome, sun, moon, stars, clouds —
|
||||
/// every <c>SkyObject</c> with <c>Properties & 0x04 == 0</c>).
|
||||
|
|
@ -265,7 +195,6 @@ public sealed unsafe partial class SkyRenderer : IDisposable
|
|||
skyView.M42 = 0f;
|
||||
skyView.M43 = 0f;
|
||||
|
||||
_shader?.Use();
|
||||
// Campaign V slice V6e: the values below used to be individual
|
||||
// glUniform* calls. They now populate the std140 SkyParams block, which
|
||||
// is uploaded once per submesh right before its draw — the same cadence
|
||||
|
|
@ -283,34 +212,6 @@ public sealed unsafe partial class SkyRenderer : IDisposable
|
|||
_params.SunDir =
|
||||
AcDream.Core.World.SkyStateProvider.SunDirectionFromKeyframe(keyframe);
|
||||
|
||||
bool wasCullFace = false;
|
||||
if (_gl is { } gl)
|
||||
{
|
||||
gl.BindBufferBase(
|
||||
BufferTargetARB.UniformBuffer,
|
||||
AcDream.App.Rendering.Gpu.GpuBindingModel.UniformSkyParams,
|
||||
_paramsUbo);
|
||||
|
||||
// Save + override GL state.
|
||||
gl.DepthMask(false);
|
||||
gl.Disable(EnableCap.DepthTest);
|
||||
// Save + disable CullFace for the sky pass; restore at the end.
|
||||
// Mirrors TextRenderer.cs's save/restore pattern. Without this the
|
||||
// sky pass left CullFace disabled regardless of its prior state,
|
||||
// which is benign today (the global convention in this codebase is
|
||||
// off and subsequent renderers manage their own CullFace) but
|
||||
// would break the moment any future caller assumes back-face
|
||||
// culling stays on across the sky pass.
|
||||
wasCullFace = gl.IsEnabled(EnableCap.CullFace);
|
||||
gl.Disable(EnableCap.CullFace);
|
||||
gl.Enable(EnableCap.Blend);
|
||||
// Default blend — overridden per-submesh inside the inner loop.
|
||||
// Additive surfaces (sun/moon/stars via SurfaceType.Additive =
|
||||
// 0x10000) get GL_SRC_ALPHA / GL_ONE; alpha-blended (clouds, dome
|
||||
// with Alpha flag) get GL_SRC_ALPHA / GL_ONE_MINUS_SRC_ALPHA.
|
||||
gl.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||
}
|
||||
|
||||
// Look up the keyframe's override list so we can apply
|
||||
// SkyObjReplace (r12 §2.3): per-keyframe GfxObj swaps + rotation
|
||||
// override + transparency fade + luminosity cap.
|
||||
|
|
@ -434,14 +335,6 @@ public sealed unsafe partial class SkyRenderer : IDisposable
|
|||
// mesh pipeline where Surface flags dictate state. On the RHI
|
||||
// arm the same two blend functions are two pipelines, because
|
||||
// Vulkan bakes blend rather than making it dynamic.
|
||||
if (_gl is { } blendGl)
|
||||
{
|
||||
if (sub.IsAdditive)
|
||||
blendGl.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.One);
|
||||
else
|
||||
blendGl.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||
}
|
||||
|
||||
// Emissive source picks the surface's authored Luminosity by
|
||||
// default; the per-keyframe replace data can OVERRIDE
|
||||
// (rep.Luminosity > 0) or CAP (rep.MaxBright). This matches
|
||||
|
|
@ -532,46 +425,9 @@ public sealed unsafe partial class SkyRenderer : IDisposable
|
|||
|| obj.TexVelocityX != 0f
|
||||
|| obj.TexVelocityY != 0f;
|
||||
uint slot = TextureTableSlot(sub.SurfaceId, needsRepeat);
|
||||
|
||||
if (_gl is { } drawGl)
|
||||
{
|
||||
drawGl.ProgramUniform1(
|
||||
_shader!.Program,
|
||||
_uTextureIndexALoc,
|
||||
slot);
|
||||
|
||||
UploadParams();
|
||||
FlushAndBindTextureTable();
|
||||
|
||||
drawGl.BindVertexArray(sub.Vao);
|
||||
drawGl.DrawElements(PrimitiveType.Triangles,
|
||||
(uint)sub.IndexCount,
|
||||
DrawElementsType.UnsignedInt,
|
||||
(void*)0);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawSubMeshRhi(sub, slot);
|
||||
}
|
||||
DrawSubMeshRhi(sub, slot);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore GL state expected by the rest of the pipeline.
|
||||
//
|
||||
// Slice V6e removed the glBindSampler(0, …) that used to happen here
|
||||
// and its matching unbind. The sky no longer touches texture unit 0 at
|
||||
// all — its wrap mode rides inside the bindless handle — so there is
|
||||
// nothing left to leak onto the next renderer's unit 0. That unbind was
|
||||
// load-bearing precisely because the binding was global state; a table
|
||||
// slot is not.
|
||||
if (_gl is { } restoreGl)
|
||||
{
|
||||
restoreGl.Disable(EnableCap.Blend);
|
||||
restoreGl.DepthMask(true);
|
||||
restoreGl.Enable(EnableCap.DepthTest);
|
||||
if (wasCullFace) restoreGl.Enable(EnableCap.CullFace);
|
||||
restoreGl.BindVertexArray(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -588,57 +444,8 @@ public sealed unsafe partial class SkyRenderer : IDisposable
|
|||
/// handle table in the codebase — the sky's texture set is a fixed handful
|
||||
/// per day group and does not churn.</para>
|
||||
/// </summary>
|
||||
private uint TextureTableSlot(uint surfaceId, bool repeat)
|
||||
{
|
||||
if (_gl is null)
|
||||
return RhiTextureTableSlot(surfaceId, repeat);
|
||||
|
||||
uint textureName = _textures.GetOrUpload(surfaceId);
|
||||
var key = (textureName, repeat);
|
||||
if (!_handleByTextureAndWrap.TryGetValue(key, out ulong handle))
|
||||
{
|
||||
handle = _bindless!.GetResidentHandle(
|
||||
textureName,
|
||||
repeat ? _samplers!.Wrap : _samplers!.Clamp);
|
||||
_handleByTextureAndWrap.Add(key, handle);
|
||||
}
|
||||
|
||||
return _glDevice!.RegisterWorldTextureHandle(handle).Index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the current <see cref="SkyParams"/> to its uniform buffer. Called
|
||||
/// immediately before each draw, which is the cadence the per-submesh
|
||||
/// glUniform* calls it replaces already had.
|
||||
/// </summary>
|
||||
private void UploadParams()
|
||||
{
|
||||
GL gl = _gl!;
|
||||
fixed (void* p = &_params)
|
||||
{
|
||||
gl.BindBuffer(BufferTargetARB.UniformBuffer, _paramsUbo);
|
||||
gl.BufferSubData(
|
||||
BufferTargetARB.UniformBuffer, 0, (nuint)SkyParams.SizeInBytes, p);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign V slice V6k: drains the device texture table's dirty runs and
|
||||
/// (re)binds it at
|
||||
/// <see cref="AcDream.App.Rendering.Gpu.GpuBindingModel.StorageTextureTable"/>.
|
||||
/// Mirrors <c>ParticleRenderer.FlushAndBindTextureTable</c>, including its
|
||||
/// reason for running per draw rather than per pass: a submesh partway
|
||||
/// through the sky stack can be the first to ask for a wrap mode.
|
||||
/// </summary>
|
||||
private void FlushAndBindTextureTable()
|
||||
{
|
||||
AcDream.App.Rendering.Gpu.Gl.GlGpuDevice device = _glDevice!;
|
||||
device.FlushTextureTable();
|
||||
_gl!.BindBufferBase(
|
||||
BufferTargetARB.ShaderStorageBuffer,
|
||||
AcDream.App.Rendering.Gpu.GpuBindingModel.StorageTextureTable,
|
||||
device.TextureTableGlName);
|
||||
}
|
||||
private uint TextureTableSlot(uint surfaceId, bool repeat) =>
|
||||
RhiTextureTableSlot(surfaceId, repeat);
|
||||
|
||||
/// <summary>
|
||||
/// Find the <see cref="SkyObjectReplaceData"/> entries for the
|
||||
|
|
@ -865,106 +672,9 @@ public sealed unsafe partial class SkyRenderer : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
private SubMeshGpu UploadSubMesh(GfxObjSubMesh sm)
|
||||
{
|
||||
if (_gl is null)
|
||||
return UploadSubMeshRhi(sm);
|
||||
private SubMeshGpu UploadSubMesh(GfxObjSubMesh sm) => UploadSubMeshRhi(sm);
|
||||
|
||||
uint vao = _gl.GenVertexArray();
|
||||
_gl.BindVertexArray(vao);
|
||||
|
||||
uint vbo = _gl.GenBuffer();
|
||||
_gl.BindBuffer(BufferTargetARB.ArrayBuffer, vbo);
|
||||
fixed (void* p = sm.Vertices)
|
||||
_gl.BufferData(BufferTargetARB.ArrayBuffer,
|
||||
(nuint)(sm.Vertices.Length * sizeof(Vertex)), p, BufferUsageARB.StaticDraw);
|
||||
|
||||
uint ebo = _gl.GenBuffer();
|
||||
_gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, ebo);
|
||||
fixed (void* p = sm.Indices)
|
||||
_gl.BufferData(BufferTargetARB.ElementArrayBuffer,
|
||||
(nuint)(sm.Indices.Length * sizeof(uint)), p, BufferUsageARB.StaticDraw);
|
||||
|
||||
uint stride = (uint)sizeof(Vertex);
|
||||
_gl.EnableVertexAttribArray(0);
|
||||
_gl.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, (void*)0);
|
||||
_gl.EnableVertexAttribArray(1);
|
||||
_gl.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, stride, (void*)(3 * sizeof(float)));
|
||||
_gl.EnableVertexAttribArray(2);
|
||||
_gl.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, stride, (void*)(6 * sizeof(float)));
|
||||
|
||||
_gl.BindVertexArray(0);
|
||||
|
||||
// Classify blend mode from the Surface's flags. Sun/moon/stars with
|
||||
// `SurfaceType.Additive = 0x10000` get GL_ONE / GL_ONE (their texture
|
||||
// has a black background and a bright body; additive makes the
|
||||
// background contribute nothing and the body glow on top of the sky).
|
||||
//
|
||||
// NOTE: earlier revision also treated `SurfaceType.Luminous = 0x40`
|
||||
// as additive, but that flag is present on the sky DOME itself and
|
||||
// on cloud sheets — turning those additive blew the whole sky to
|
||||
// white. `Luminous` means "self-illuminated / unshaded" in retail's
|
||||
// render pipeline, not "additive blend". Only the Additive bit
|
||||
// toggles the blend mode.
|
||||
bool isAdditive = sm.Translucency == TranslucencyKind.Additive;
|
||||
|
||||
return new SubMeshGpu
|
||||
{
|
||||
Vao = vao,
|
||||
Vbo = vbo,
|
||||
Ebo = ebo,
|
||||
IndexCount = sm.Indices.Length,
|
||||
SurfaceId = sm.SurfaceId,
|
||||
IsAdditive = isAdditive,
|
||||
SurfLuminosity = sm.Luminosity,
|
||||
SurfDiffuse = sm.Diffuse,
|
||||
NeedsUvRepeat = sm.NeedsUvRepeat,
|
||||
SurfOpacity = sm.SurfOpacity,
|
||||
DisableFog = sm.DisableFog,
|
||||
};
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_gl is null)
|
||||
{
|
||||
DisposeRhi();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var subs in _gpuByGfxObj.Values)
|
||||
{
|
||||
foreach (var sub in subs)
|
||||
{
|
||||
_gl.DeleteBuffer(sub.Vbo);
|
||||
_gl.DeleteBuffer(sub.Ebo);
|
||||
_gl.DeleteVertexArray(sub.Vao);
|
||||
}
|
||||
}
|
||||
_gpuByGfxObj.Clear();
|
||||
|
||||
// Campaign V slice V6e. Residency first: a handle made resident pins
|
||||
// its texture's GPU address, and the textures themselves belong to
|
||||
// TextureCache, which outlives this renderer and disposes them itself.
|
||||
//
|
||||
// Slice V6k: the device's table entry is retired alongside it, in the
|
||||
// order V4t established — the table entry goes first, because the slot
|
||||
// is only recycled once the frames that could still read it retire,
|
||||
// whereas the handle stops being valid the instant it is non-resident.
|
||||
foreach (ulong handle in _handleByTextureAndWrap.Values)
|
||||
{
|
||||
_glDevice!.ReleaseWorldTextureHandle(handle);
|
||||
_bindless!.MakeNonResident(handle);
|
||||
}
|
||||
_handleByTextureAndWrap.Clear();
|
||||
|
||||
if (_paramsUbo != 0)
|
||||
{
|
||||
Wb.TrackedGlResource.DeleteBuffer(
|
||||
_gl, _paramsUbo, SkyParams.SizeInBytes, "sky params UBO disposal");
|
||||
_paramsUbo = 0;
|
||||
}
|
||||
}
|
||||
public void Dispose() => DisposeRhi();
|
||||
|
||||
/// <summary>
|
||||
/// Campaign V slice V6e: the CPU mirror of sky.{vert,frag}'s <c>SkyParams</c>
|
||||
|
|
@ -999,14 +709,11 @@ public sealed unsafe partial class SkyRenderer : IDisposable
|
|||
|
||||
private sealed class SubMeshGpu
|
||||
{
|
||||
public uint Vao;
|
||||
public uint Vbo;
|
||||
public uint Ebo;
|
||||
/// <summary>
|
||||
/// Campaign V slice V6k: the RHI arm's vertex source. The sky's meshes are
|
||||
/// built once per GfxObj and never change, so each submesh owns a
|
||||
/// device-local buffer pair rather than taking a ring slice per frame.
|
||||
/// Null on the GL arm, which uses <see cref="Vao"/>.
|
||||
/// The raw-GL arm's VAO/VBO/EBO names were deleted at slice V11.
|
||||
/// </summary>
|
||||
public AcDream.App.Rendering.Gpu.IGpuBuffer? VertexBuffer;
|
||||
public AcDream.App.Rendering.Gpu.IGpuBuffer? IndexBuffer;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue