From 7a0227c12e3584e08e74f1bc2026a5ae0a6048bb Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 29 Jul 2026 02:58:15 +0200 Subject: [PATCH] =?UTF-8?q?feat(render):=20Vulkan=20campaign=20V11=20step?= =?UTF-8?q?=203=20=E2=80=94=20drop=20the=20GL=20packages=20and=20shaders?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 2 deleted the GL rendering backend's implementations; this step removes the package references and shader vocabulary they leave behind, so nothing in the App project still spells Silk.NET.OpenGL. Silk.NET.OpenGL and Silk.NET.OpenGL.Extensions.ARB are dropped from AcDream.App.csproj. Chorizite.Core stays — the audit is NOT clean: its Render.Enums (TextureFormat, BufferUsage) and Lib.BoundingBox types are used directly and extensively across the Wb texture/mesh pipeline, independent of the deleted GL IUniformBuffer implementers the package comment used to cite. The stale comment is corrected in place. IMeshPipelineDevice.Gl is removed along with the GL? gl parameter threaded through WbMeshAdapter's four constructors, WorldRenderComposition's CreateMeshAdapter, and VulkanMeshPipelineDevice's Gl => null implementation — nothing read any of them once the legacy per-mesh upload bodies were gone (confirmed by grep: the sole non-doc-comment hit was a test assertion). While in WbMeshAdapter.Dispose(), found and fixed a real bug along the way: its teardown still pattern-matched the deleted GL GpuFrameFlightController to decide whether to wait for submitted work, which VulkanFrameFlightController replaced at slice V6a without this site being updated — so the wait had been silently dead on every Vulkan run since then. Retargeted to VulkanFrameFlightController, which carries the same WaitForSubmittedWork(). The GL pixel-format vocabulary (Silk.NET.OpenGL.PixelFormat/PixelType) that WorldTextureArray/TextureFormatExtensions/TextureAtlasManager used for upload validation is replaced by AcDream.Content's existing Silk.NET-free UploadPixelFormat/UploadPixelType enums (added at MP1a to keep the bake tool GL-free); two new members (Rgb, Red, Float) extend that enum with their GL ABI constants to cover the full vocabulary WorldTextureArray needs, since MP1a's original set only covered what the extractor itself emits. ObjectMeshManager's App-boundary cast `(Silk.NET.OpenGL.PixelFormat?)batch.UploadPixelFormat` becomes a direct pass-through now that both sides share the type. GpuBindingModel.StorageTextureTable (the GL-only binding=9 emulation of the Vulkan texture table) is deleted and StorageBindingCount drops from 10 to 9; the descriptor-set-layout code that builds from that count (VulkanPipelineLayouts, VulkanFrameBindings) is untouched and just allocates one fewer always-dummy-seeded, always-unused binding. Several fully dead GL-only classes came along for the ride, confirmed by zero construction sites: SilkFramebufferViewportTarget (NullFramebufferViewportTarget is the sole production IFramebufferViewportTarget), SilkRenderGlStateReader (NullRenderGlStateReader.Instance is the sole IRenderGlStateReader), RuntimeRenderFrameClearPhase (VulkanRenderFrameClearPhase is the sole IRenderFrameClearPhase, expressing the same atmosphere-clear logic as a pass load-op instead), and GpuFrameTimer plus FrameProfiler's GL-owning FrameBoundary(GL) overload and BeginGpuFrame/EndGpuFrame bracket (RecordGpuSample is the only GPU-timing path any backend uses now — the ACDREAM_WB_DIAG nested-query exclusion these existed for no longer applies, since WbDrawDispatcher's own diagnostic GPU sampling already moved to the device's Vulkan timer pool). GpuFrameFlightController itself stays (never constructed with a real fence API in production, but its retirement-ledger/serial-ring logic is backend-neutral and still covered by its own unit tests) — only its GL-specific parts (the public GL constructor overload, SilkGpuFenceApi) are deleted, since removing the whole class would mean restructuring the frozen Slice-8 composition shape's GpuFrameFlightController? threading, which is out of this commit's scope. TextureParameters.cs and BufferUsageExtensions.cs (zero callers each) are deleted outright. common.glsl is deleted: nothing in the actual Vulkan .spv build reads it. tools/ShaderCompiler/Program.cs compiles each .vert/.frag pair directly and tools/ShaderCompiler/VulkanGlslPreamble.cs injects its own complete self-contained preamble per file; common.glsl's textual concatenation was exclusively Shader.cs's GL-only mechanism, deleted at Commit 2. The five shader files that named it in comments (mesh_modern.vert, particle.vert, particle.frag, sky.frag, terrain_modern.frag) are corrected to point at VulkanGlslPreamble.cs instead. mesh.vert/mesh.frag — the pre-N.5 legacy shader pair the mandatory modern path already made unreachable, with zero C# consumers and no compiled .spv — are deleted too. Regenerated via tools/compile-shaders.ps1: 9/9 remaining shader pairs compile (previously 9/10, with mesh the sole failure — the VulkanShaderManifestTests doc comment's "nine of ten are not Vulkan-expressible" was already stale before this commit). Test fallout: dead-subject test methods/files are deleted rather than patched (TextRendererFailureSafetyTests.cs, ClipFrameUploadTests.cs, GpuResourceRetirementTransactionTests.cs's GL queue tests, one WorldRenderDiagnosticsTests source-order test, one RenderFrameResourceControllerTests clear-phase-order test); tests whose subject moved or was renamed are updated in place rather than deleted (GpuContractTests, VulkanCapabilityGateTests, MeshPipelineDeviceSeamTests' pinned seven-member surface now reads six, ParticleBindlessInstanceTests' cross-dialect check now covers the one surviving dialect, WbMeshAdapterTests' misleadingly-named null-gl test — gpuDevice was always the parameter that actually threw). Build: `dotnet build AcDream.slnx -c Release` — 0 warnings, 0 errors, with the Silk.NET.OpenGL/.Extensions.ARB package references physically removed from the csproj (not just unreferenced in code). Tests: full-solution `dotnet test` green across every project. Zero remaining `using Silk.NET.OpenGL` anywhere in src/ or tests/. Co-Authored-By: Claude Fable 5 --- src/AcDream.App/AcDream.App.csproj | 17 ++- .../Composition/WorldRenderComposition.cs | 11 +- src/AcDream.App/Diagnostics/FrameProfiler.cs | 111 ++++----------- src/AcDream.App/Diagnostics/GpuFrameTimer.cs | 123 ---------------- .../Rendering/FramebufferResizeController.cs | 14 +- .../Rendering/Gpu/GpuBindingModel.cs | 26 ++-- .../Gpu/Vk/VulkanCapabilityRecord.cs | 10 +- .../Gpu/Vk/VulkanCompositionFramePhases.cs | 12 +- .../Gpu/Vk/VulkanFrameGpuMeasurement.cs | 21 +-- .../Gpu/Vk/VulkanMeshPipelineDevice.cs | 16 ++- .../Rendering/Gpu/Vk/VulkanPipelineLayouts.cs | 4 +- .../Rendering/GpuFrameFlightController.cs | 44 ++---- src/AcDream.App/Rendering/ParticleRenderer.cs | 1 - .../RenderFrameResourceController.cs | 75 +--------- src/AcDream.App/Rendering/Shaders/common.glsl | 84 ----------- src/AcDream.App/Rendering/Shaders/mesh.frag | 132 ------------------ src/AcDream.App/Rendering/Shaders/mesh.vert | 24 ---- .../Rendering/Shaders/mesh_modern.vert | 14 +- .../Rendering/Shaders/particle.frag | 3 +- .../Rendering/Shaders/particle.vert | 5 +- src/AcDream.App/Rendering/Shaders/sky.frag | 7 +- .../Shaders/spv/shaders.manifest.json | 28 +--- .../Rendering/Shaders/terrain_modern.frag | 28 ++-- .../Rendering/Wb/BufferUsageExtensions.cs | 27 ---- .../Rendering/Wb/IMeshPipelineDevice.cs | 27 ++-- .../Rendering/Wb/ObjectMeshManager.cs | 13 +- .../Rendering/Wb/TextureAtlasManager.cs | 4 +- .../Rendering/Wb/TextureFormatExtensions.cs | 54 ++----- .../Rendering/Wb/TextureParameters.cs | 35 ----- src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs | 21 ++- .../Rendering/Wb/WorldTextureArray.cs | 14 +- .../Rendering/WorldRenderDiagnostics.cs | 58 -------- src/AcDream.Content/UploadFormats.cs | 39 ++++-- .../WorldRenderCompositionTests.cs | 2 - .../Rendering/Gpu/GpuContractTests.cs | 9 +- .../Gpu/Vk/VulkanCapabilityGateTests.cs | 9 +- .../Gpu/Vk/VulkanShaderManifestTests.cs | 7 +- .../ParticleBindlessInstanceTests.cs | 26 ++-- .../RenderFrameResourceControllerTests.cs | 24 ++-- .../Wb/MeshPipelineDeviceSeamTests.cs | 25 ++-- .../Rendering/Wb/TextureAtlasCapacityTests.cs | 14 +- .../Rendering/WorldRenderDiagnosticsTests.cs | 42 +----- .../WbDrawDispatcherIndirectBuilderTests.cs | 5 +- .../Rendering/Wb/WbMeshAdapterTests.cs | 12 +- 44 files changed, 300 insertions(+), 977 deletions(-) delete mode 100644 src/AcDream.App/Diagnostics/GpuFrameTimer.cs delete mode 100644 src/AcDream.App/Rendering/Shaders/common.glsl delete mode 100644 src/AcDream.App/Rendering/Shaders/mesh.frag delete mode 100644 src/AcDream.App/Rendering/Shaders/mesh.vert delete mode 100644 src/AcDream.App/Rendering/Wb/BufferUsageExtensions.cs delete mode 100644 src/AcDream.App/Rendering/Wb/TextureParameters.cs diff --git a/src/AcDream.App/AcDream.App.csproj b/src/AcDream.App/AcDream.App.csproj index 5a04d5a4..c79ba665 100644 --- a/src/AcDream.App/AcDream.App.csproj +++ b/src/AcDream.App/AcDream.App.csproj @@ -14,9 +14,18 @@ - + - - diff --git a/src/AcDream.App/Composition/WorldRenderComposition.cs b/src/AcDream.App/Composition/WorldRenderComposition.cs index 47513e25..8ecdbd0c 100644 --- a/src/AcDream.App/Composition/WorldRenderComposition.cs +++ b/src/AcDream.App/Composition/WorldRenderComposition.cs @@ -13,7 +13,6 @@ using DatReaderWriter; using DatReaderWriter.DBObjs; using Microsoft.Extensions.Logging.Abstractions; using Silk.NET.Input; -using Silk.NET.OpenGL; namespace AcDream.App.Composition; @@ -148,7 +147,6 @@ internal interface IWorldRenderCompositionFactory float[] heightTable, TerrainAtlas? atlas); WbMeshAdapter CreateMeshAdapter( - GL? gl, AcDream.App.Rendering.Gpu.IGpuDevice device, IDatReaderWriter dats, IPreparedAssetSource preparedAssets, @@ -299,14 +297,12 @@ internal sealed class RetailWorldRenderCompositionFactory } public WbMeshAdapter CreateMeshAdapter( - GL? gl, AcDream.App.Rendering.Gpu.IGpuDevice device, IDatReaderWriter dats, IPreparedAssetSource preparedAssets, IGpuResourceRetirementQueue retirement, ResidencyBudgetOptions budgets) => new( - gl, device, dats, preparedAssets, @@ -542,14 +538,13 @@ internal sealed class WorldRenderCompositionPhase // Campaign V slice V6i-3: the mesh pipeline builds the same arena, // atlases and render data on every remaining backend, which is what // makes streaming's publication into GPU state real rather than a - // no-op. gl is always null here — the raw-GL arm was deleted at - // slice V11, and the constructor's GL? gl parameter is Commit 3's - // (IMeshPipelineDevice.Gl) to remove. + // no-op. The raw-GL arm was deleted at slice V11, and its + // constructor's GL? gl parameter (always null here) was removed in + // the same slice's package/shader cleanup. WbMeshAdapter meshAdapter = AcquireAndPublish( scope, "WB mesh adapter", () => _factory.CreateMeshAdapter( - gl: null, _dependencies.GpuDevice, content.Dats, content.PreparedAssets, diff --git a/src/AcDream.App/Diagnostics/FrameProfiler.cs b/src/AcDream.App/Diagnostics/FrameProfiler.cs index ab083aff..e2448900 100644 --- a/src/AcDream.App/Diagnostics/FrameProfiler.cs +++ b/src/AcDream.App/Diagnostics/FrameProfiler.cs @@ -5,7 +5,6 @@ using System.Globalization; using System.IO; using System.Text; using AcDream.Core.Rendering; -using Silk.NET.OpenGL; namespace AcDream.App.Diagnostics; @@ -30,7 +29,8 @@ public enum FrameStage /// names array) — if grows, extend this /// record, , and the CSV header /// together. GpuUs is -1 for a frame with no available GPU -/// sample (warm-up, or ACDREAM_WB_DIAG=1 self-disable). +/// sample yet (warm-up: the Vulkan arm's timestamp samples resolve two or +/// three frames late). /// internal readonly record struct FrameHistoryRecord( int FrameIndex, @@ -48,17 +48,22 @@ internal readonly record struct FrameHistoryRecord( /// FrameBoundary call at the top of the accepted render transaction /// measures CPU frame time as the delta between consecutive boundaries /// (captures the FULL frame including present) and samples per-frame allocated -/// bytes + GC collection counts. and -/// separately bracket only submitted render work. -/// Stage scopes +/// bytes + GC collection counts. separately +/// reports each backend-timed GPU sample as it resolves. Stage scopes /// () attribute CPU time to Update / Upload / /// ImGui. Emits one [frame-prof] line every ~5 s while /// is true; costs one /// bool check per frame when off. /// /// Permanent apparatus — every MP-track gate reads it; do not strip. -/// Whole-frame GPU timing self-disables under ACDREAM_WB_DIAG=1 -/// (nested TimeElapsed is illegal GL; see GpuFrameTimer). +/// Campaign V slice V11 deleted the GL arm's own TimeElapsed query ring +/// (GpuFrameTimer), the GL-owning FrameBoundary overload it fed, +/// and the ACDREAM_WB_DIAG=1 self-disable that existed only to avoid +/// two simultaneously active GL queries (that env var no longer touches this +/// profiler at all — the WB diagnostic's own GPU samples come from the +/// device's Vulkan timer pool now, not a nested raw-GL query, so the two were +/// never in conflict here to begin with). Every backend reports GPU time +/// through instead. /// /// 2026-07-24 measurement-tooling review — the aggregated report /// resets its ring buffers every ~5 s (), @@ -91,14 +96,11 @@ public sealed class FrameProfiler : IDisposable private readonly FrameStatsBuffer[] _stageUs; private readonly long[] _stageAccumTicks; private readonly long[] _lastStageUs; - private readonly bool _wbDiagActive = - Environment.GetEnvironmentVariable("ACDREAM_WB_DIAG") == "1"; private readonly List? _history; private readonly long _profilerStartTimestamp; private readonly DateTime _profilerStartUtc; private int _currentFrameIndex = -1; - private GpuFrameTimer? _gpuTimer; private bool _externalGpuActive; private long _lastBoundaryTimestamp; private long _lastAllocBytes; @@ -107,7 +109,6 @@ public sealed class FrameProfiler : IDisposable private int _framesInWindow; private int _ownerThreadId; private bool _threadWarned; - private bool _wbDiagNoticePrinted; private bool _wasEnabled; /// Most recent immutable report line, for explicit automation checkpoints. @@ -115,9 +116,10 @@ public sealed class FrameProfiler : IDisposable /// /// Index of the frame currently being measured, or -1 before the first - /// enabled boundary. Campaign V slice V8: a backend whose GPU timer is not - /// GL's query ring pairs its delayed samples with this index, exactly as - /// does internally on the GL arm. + /// enabled boundary. Campaign V slice V8: a backend whose GPU timer + /// resolves samples late (Vulkan's timestamp queries) pairs its delayed + /// samples with this index — the same pairing the deleted GL query ring + /// (GpuFrameTimer) used to do internally. /// public int CurrentFrameIndex => _currentFrameIndex; @@ -134,36 +136,21 @@ public sealed class FrameProfiler : IDisposable } /// - /// Call once at the accepted render-transaction boundary, before - /// . + /// Call once at the accepted render-transaction boundary. Campaign V + /// slice V11 deleted the GL-owning overload this used to have alongside + /// it (GL is gone; there is no other backend that measures GPU time by + /// owning a query ring from inside this call) — every backend now drives + /// this same no-argument boundary and feeds GPU time in separately through + /// , so a perf gate that compared two + /// differently-measured numbers never had anything to compare. /// - public void FrameBoundary(GL gl) => FrameBoundary(gl, ownsGpuTimer: true); - - /// - /// Campaign V slice V8: the same CPU/allocation boundary with no GL context. - /// The Vulkan arm has no TimeElapsed query ring to own, so it drives - /// this overload and feeds GPU time in through - /// . Everything else — the CPU delta, the - /// per-thread allocation delta, the stage buffers, the history row and the - /// five-second report — is the identical code path GL uses, because a - /// perf gate that compared two different instruments would compare nothing. - /// - public void FrameBoundary() => FrameBoundary(null, ownsGpuTimer: false); - - private void FrameBoundary(GL? gl, bool ownsGpuTimer) + public void FrameBoundary() { bool enabled = RenderingDiagnostics.FrameProfEnabled; if (!enabled) { if (_wasEnabled) { - // Dispose (not just Stop) so a later re-enable rebuilds the - // query ring fresh — a kept instance would poll slots left - // pending from BEFORE the pause and report temporally stale - // GPU samples. Safe here: this runs at the top of OnRender - // with the GL context current. - _gpuTimer?.Dispose(); - _gpuTimer = null; _wasEnabled = false; _lastBoundaryTimestamp = 0; _currentFrameIndex = -1; @@ -193,14 +180,7 @@ public sealed class FrameProfiler : IDisposable _lastReportTicks = DateTime.UtcNow.Ticks; Array.Clear(_stageAccumTicks); _gc0Base = GC.CollectionCount(0); _gc1Base = GC.CollectionCount(1); _gc2Base = GC.CollectionCount(2); - if (ownsGpuTimer && gl is not null && _gpuTimer is null && !_wbDiagActive) - _gpuTimer = new GpuFrameTimer(gl); _currentFrameIndex = 0; - if (_wbDiagActive && !_wbDiagNoticePrinted) - { - _wbDiagNoticePrinted = true; - Console.WriteLine("[frame-prof] GPU frame timing OFF: ACDREAM_WB_DIAG=1 owns TimeElapsed queries (nested queries are illegal GL)"); - } } else { @@ -242,7 +222,7 @@ public sealed class FrameProfiler : IDisposable int gc1 = GC.CollectionCount(1) - _gc1Base; int gc2 = GC.CollectionCount(2) - _gc2Base; LastReport = FormatReport(_framesInWindow, _cpuUs, _gpuUs, - gpuActive: _gpuTimer is not null || _externalGpuActive, + gpuActive: _externalGpuActive, _allocBytes, gc0, gc1, gc2, _stageUs); Console.WriteLine(LastReport); _lastReportTicks = nowTicks; @@ -253,45 +233,15 @@ public sealed class FrameProfiler : IDisposable } } - /// - /// Begin GPU timing immediately before render-resource, world, and private - /// presentation submission. Completed delayed samples are associated with - /// their owning history row rather than the frame that happened to poll - /// them. - /// - public void BeginGpuFrame() - { - if (!_wasEnabled || _gpuTimer is null || _currentFrameIndex < 0) - return; - - Span completed = stackalloc GpuFrameSample[4]; - int completedCount = _gpuTimer.BeginFrame(_currentFrameIndex, completed); - for (int index = 0; index < completedCount; index++) - { - GpuFrameSample sample = completed[index]; - _gpuUs.Push(sample.ElapsedUs); - if (_history is not null - && (uint)sample.FrameIndex < (uint)_history.Count) - { - FrameHistoryRecord row = _history[sample.FrameIndex]; - _history[sample.FrameIndex] = row with { GpuUs = sample.ElapsedUs }; - } - } - } - - /// End GPU timing immediately after render submission. - public void EndGpuFrame() - { - _gpuTimer?.EndFrame(); - } - /// /// Campaign V slice V8: publish one delayed GPU sample measured by a backend - /// that owns its own timer (Vulkan timestamp queries). Identical bookkeeping - /// to 's promotion loop — the sample joins the + /// that owns its own timer (Vulkan timestamp queries). The sample joins the /// five-second window and back-fills the history row of the frame that /// issued it, so gpu_us in the CSV is never attributed to a frame - /// that merely happened to poll it. + /// that merely happened to poll it. Campaign V slice V11 deleted the GL + /// arm's own BeginGpuFrame/EndGpuFrame bracket (the query-ring + /// promotion loop this comment used to describe) along with the GL query + /// ring itself — this is now the only way any backend reports GPU time. /// public void RecordGpuSample(int frameIndex, long elapsedUs) { @@ -383,7 +333,6 @@ public sealed class FrameProfiler : IDisposable /// public void Dispose() { - _gpuTimer?.Dispose(); if (_history is { Count: > 0 } && RenderingDiagnostics.FrameHistoryPath is { } path) { try diff --git a/src/AcDream.App/Diagnostics/GpuFrameTimer.cs b/src/AcDream.App/Diagnostics/GpuFrameTimer.cs deleted file mode 100644 index b000c789..00000000 --- a/src/AcDream.App/Diagnostics/GpuFrameTimer.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System; -using Silk.NET.OpenGL; - -namespace AcDream.App.Diagnostics; - -internal readonly record struct GpuFrameSample(int FrameIndex, long ElapsedUs); - -/// -/// MP0 (2026-07-05) — render-transaction GPU time via a ring of -/// queries (depth 4, so results are -/// read several frames late and never stall). Mirrors WbDrawDispatcher's query -/// idiom including the #125 lesson: a glGenQueries name is not a query -/// OBJECT until first glBeginQuery, so never-begun slots are skipped via -/// the pending flags. -/// -/// MUST NOT be active while ACDREAM_WB_DIAG=1: GL forbids two -/// simultaneously active TimeElapsed queries and WbDrawDispatcher brackets -/// its passes with them under that flag. The caller (FrameProfiler) -/// enforces the exclusion; this class just does the ring. -/// -/// The query begins immediately before render-resource/world/private -/// presentation submission and ends immediately after it. It is deliberately -/// NOT left active until the next render callback: doing so includes display -/// pacing and an arbitrary CPU idle interval in the GPU clock. Completed -/// samples retain the frame index that owned the query so the delayed result -/// can be paired with the correct CPU/history row. -/// -internal sealed class GpuFrameTimer : IDisposable -{ - private const int RingDepth = 4; - - private readonly GL _gl; - private readonly uint[] _queries = new uint[RingDepth]; - private readonly bool[] _pending = new bool[RingDepth]; - private readonly int[] _frameIndices = new int[RingDepth]; - private int _nextSlot; - private int _activeSlot = -1; - - public GpuFrameTimer(GL gl) - { - _gl = gl ?? throw new ArgumentNullException(nameof(gl)); - for (int i = 0; i < RingDepth; i++) - _queries[i] = _gl.GenQuery(); - } - - /// - /// Poll every ended query non-blocking, then begin this frame's render - /// transaction in the first free ring slot. If all slots are still in - /// flight the current frame is intentionally left unmeasured rather than - /// stalling or overwriting a pending result. - /// - public int BeginFrame(int frameIndex, Span completed) - { - if (_activeSlot >= 0) - throw new InvalidOperationException("GPU frame timing was begun twice without an end."); - if (completed.Length < RingDepth) - throw new ArgumentException( - $"Completed-sample storage must hold at least {RingDepth} entries.", - nameof(completed)); - - int completedCount = 0; - for (int slot = 0; slot < RingDepth; slot++) - { - if (!_pending[slot]) - continue; - - _gl.GetQueryObject( - _queries[slot], - QueryObjectParameterName.ResultAvailable, - out int available); - if (available == 0) - continue; - - _gl.GetQueryObject( - _queries[slot], - QueryObjectParameterName.Result, - out ulong elapsedNanoseconds); - completed[completedCount++] = new GpuFrameSample( - _frameIndices[slot], - (long)(elapsedNanoseconds / 1000UL)); - _pending[slot] = false; - } - - for (int offset = 0; offset < RingDepth; offset++) - { - int slot = (_nextSlot + offset) % RingDepth; - if (_pending[slot]) - continue; - - _gl.BeginQuery(QueryTarget.TimeElapsed, _queries[slot]); - _frameIndices[slot] = frameIndex; - _activeSlot = slot; - _nextSlot = (slot + 1) % RingDepth; - break; - } - - return completedCount; - } - - /// End the query around the current render transaction. - public void EndFrame() - { - if (_activeSlot < 0) - return; - - _gl.EndQuery(QueryTarget.TimeElapsed); - _pending[_activeSlot] = true; - _activeSlot = -1; - } - - /// End an active query without beginning another. - public void Stop() - { - EndFrame(); - } - - public void Dispose() - { - Stop(); - for (int i = 0; i < RingDepth; i++) - _gl.DeleteQuery(_queries[i]); - } -} diff --git a/src/AcDream.App/Rendering/FramebufferResizeController.cs b/src/AcDream.App/Rendering/FramebufferResizeController.cs index fe71880c..868370f3 100644 --- a/src/AcDream.App/Rendering/FramebufferResizeController.cs +++ b/src/AcDream.App/Rendering/FramebufferResizeController.cs @@ -1,6 +1,5 @@ using AcDream.App.Input; using Silk.NET.Maths; -using Silk.NET.OpenGL; namespace AcDream.App.Rendering; @@ -9,14 +8,11 @@ internal interface IFramebufferViewportTarget void ResizeViewport(int width, int height); } -internal sealed class SilkFramebufferViewportTarget(GL gl) - : IFramebufferViewportTarget -{ - private readonly GL _gl = gl ?? throw new ArgumentNullException(nameof(gl)); - - public void ResizeViewport(int width, int height) => - _gl.Viewport(0, 0, (uint)width, (uint)height); -} +// Campaign V slice V11 deleted SilkFramebufferViewportTarget, the GL +// implementation of IFramebufferViewportTarget: VulkanHostInputCameraComposition +// Factory's NullFramebufferViewportTarget is the sole surviving implementation +// (Vulkan's swapchain recreation owns the actual viewport-equivalent resize, +// which this seam never drove). internal interface IFramebufferCameraTarget { diff --git a/src/AcDream.App/Rendering/Gpu/GpuBindingModel.cs b/src/AcDream.App/Rendering/Gpu/GpuBindingModel.cs index cdb0adf4..76ff4c02 100644 --- a/src/AcDream.App/Rendering/Gpu/GpuBindingModel.cs +++ b/src/AcDream.App/Rendering/Gpu/GpuBindingModel.cs @@ -18,9 +18,11 @@ namespace AcDream.App.Rendering.Gpu; /// to their own set preserves both numbers and removes the collision. /// set 2 — the global sampled-texture table that replaces ARB_bindless_texture. /// Vulkan binds it as one variable-count, partially-bound, -/// update-after-bind descriptor array. GL emulates it with a storage -/// buffer of uvec2 handles at set 0 binding 9 (), -/// which is why both a set index and a storage binding exist here. +/// update-after-bind descriptor array. GL used to emulate it with a +/// storage buffer of uvec2 handles at set 0 binding 9 +/// (StorageTextureTable); Campaign V slice V11 deleted that +/// binding along with the rest of the raw-GL arm, so set 2 is the +/// only texture table left. /// /// A batch no longer carries a 64-bit bindless handle; it carries a /// index into the table. That single change is what @@ -58,16 +60,16 @@ internal static class GpuBindingModel /// Retail SmartBox selection lighting: one vec2 (luminosity, diffuse) per instance. public const uint StorageInstanceSelectionLighting = 8; - /// - /// GL-only emulation of the Vulkan texture table: a storage buffer of uvec2 - /// bindless handles indexed by . The Vulkan - /// backend binds instead and never uses this - /// binding; it is deleted with the GL backend at slice V11. - /// - public const uint StorageTextureTable = 9; + // Campaign V slice V11 deleted StorageTextureTable (binding 9): the GL-only + // emulation of the Vulkan texture table via a storage buffer of uvec2 + // bindless handles indexed by GpuTextureSlot.Index. The Vulkan backend + // always bound TextureTableSet instead and never used this binding — every + // Vulkan descriptor set layout declared it anyway (seeded with a dummy + // buffer, like every other unused-by-a-given-shader binding), purely + // because it counted toward StorageBindingCount. - /// One past the highest storage binding — the count both backends must support. - public const uint StorageBindingCount = 10; + /// One past the highest storage binding — the count the backend must support. + public const uint StorageBindingCount = 9; // ---- set 1: uniform buffers ---- diff --git a/src/AcDream.App/Rendering/Gpu/Vk/VulkanCapabilityRecord.cs b/src/AcDream.App/Rendering/Gpu/Vk/VulkanCapabilityRecord.cs index d5e218f6..ad689e33 100644 --- a/src/AcDream.App/Rendering/Gpu/Vk/VulkanCapabilityRecord.cs +++ b/src/AcDream.App/Rendering/Gpu/Vk/VulkanCapabilityRecord.cs @@ -368,10 +368,12 @@ internal sealed record VulkanCapabilityRecord( Math.Min( Limits.MaxDescriptorSetUpdateAfterBindSampledImages, Limits.MaxPerStageDescriptorUpdateAfterBindSampledImages), - // Sets 0..2 give each binding its own namespace, so the ten storage - // bindings the model declares are always all available once the set - // count requirement passes. There is no per-set binding-count limit in - // Vulkan below maxPerStageDescriptorStorageBuffers, which is far higher. + // Sets 0..2 give each binding its own namespace, so the storage + // bindings the model declares (nine, since Campaign V slice V11 + // deleted the GL-only StorageTextureTable binding) are always all + // available once the set count requirement passes. There is no + // per-set binding-count limit in Vulkan below + // maxPerStageDescriptorStorageBuffers, which is far higher. MaxStorageBufferBindings = GpuBindingModel.StorageBindingCount, MaxPushConstantBytes = Limits.MaxPushConstantsSize, MinStorageBufferOffsetAlignment = Limits.MinStorageBufferOffsetAlignment, diff --git a/src/AcDream.App/Rendering/Gpu/Vk/VulkanCompositionFramePhases.cs b/src/AcDream.App/Rendering/Gpu/Vk/VulkanCompositionFramePhases.cs index 97da1fb8..8aad6b1a 100644 --- a/src/AcDream.App/Rendering/Gpu/Vk/VulkanCompositionFramePhases.cs +++ b/src/AcDream.App/Rendering/Gpu/Vk/VulkanCompositionFramePhases.cs @@ -172,11 +172,13 @@ internal sealed class VulkanBackbufferClearState /// /// Campaign V slice V6h: no GPU-timer bracket on the Vulkan arm. /// -/// drives -/// FrameProfiler's GL query ring, which is a GL-only instrument. -/// is its backend-neutral replacement and the -/// frame spine adopts it at slice V4h; until then the Vulkan arm reports no GPU -/// samples rather than reporting wrong ones. +/// FrameProfilerGpuMeasurement (deleted at Campaign V slice V11 +/// along with the rest of the GL arm) used to drive FrameProfiler's GL +/// query ring, which was a GL-only instrument. +/// is its backend-neutral replacement, adopted at slice V4h by +/// — this null object is the fallback +/// for a graphics handle with no Vulkan context, which reports no GPU samples +/// rather than reporting wrong ones. /// internal sealed class NullRenderFrameGpuMeasurement : IRenderFrameGpuMeasurement { diff --git a/src/AcDream.App/Rendering/Gpu/Vk/VulkanFrameGpuMeasurement.cs b/src/AcDream.App/Rendering/Gpu/Vk/VulkanFrameGpuMeasurement.cs index 634f797f..b946975d 100644 --- a/src/AcDream.App/Rendering/Gpu/Vk/VulkanFrameGpuMeasurement.cs +++ b/src/AcDream.App/Rendering/Gpu/Vk/VulkanFrameGpuMeasurement.cs @@ -14,20 +14,21 @@ namespace AcDream.App.Rendering.Gpu.Vk; /// meant to judge. V8 cannot measure what it cannot instrument, so the /// instrument lands first. /// -/// The bracket is deliberately the SAME one GL uses. On GL, -/// begins a TimeElapsed query -/// at and ends it at , spanning -/// resource preparation, the world scene and private presentation but not the -/// swapchain present. This adapter opens and closes a Vulkan timestamp scope at -/// exactly those two points, so gpu_ms means the same thing in both -/// columns of the V8 table. Comparing two differently-bracketed numbers would -/// have been worse than reporting none. +/// The bracket was deliberately the SAME one GL used. On GL, +/// FrameProfilerGpuMeasurement (deleted at Campaign V slice V11) began a +/// TimeElapsed query at and ended it at +/// , spanning resource preparation, the world scene and +/// private presentation but not the swapchain present. This adapter opens and +/// closes a Vulkan timestamp scope at exactly those two points, so +/// gpu_ms means the same thing in both columns of the V8 table. +/// Comparing two differently-bracketed numbers would have been worse than +/// reporting none. /// /// Timestamps resolve two or three frames late, which is why the sample /// carries the profiler frame index that ISSUED it /// () rather than being -/// credited to the frame that happened to read it — the same pairing -/// GpuFrameTimer performs internally on the GL arm. +/// credited to the frame that happened to read it — the same pairing the +/// deleted GpuFrameTimer used to perform internally on the GL arm. /// internal sealed class VulkanFrameGpuMeasurement : IRenderFrameGpuMeasurement { diff --git a/src/AcDream.App/Rendering/Gpu/Vk/VulkanMeshPipelineDevice.cs b/src/AcDream.App/Rendering/Gpu/Vk/VulkanMeshPipelineDevice.cs index ac53ac5f..365c946a 100644 --- a/src/AcDream.App/Rendering/Gpu/Vk/VulkanMeshPipelineDevice.cs +++ b/src/AcDream.App/Rendering/Gpu/Vk/VulkanMeshPipelineDevice.cs @@ -1,5 +1,4 @@ using AcDream.App.Rendering.Wb; -using Silk.NET.OpenGL; namespace AcDream.App.Rendering.Gpu.Vk; @@ -12,9 +11,10 @@ namespace AcDream.App.Rendering.Gpu.Vk; /// shared instance VBO, and two capability flags — but there was nothing to /// select between, because the pipeline's upload bodies still spoke GL. This /// slice moved those bodies: the mesh arena is IGpuBuffer work on both -/// arms, and the only raw-GL upload left is the per-mesh vertex-array -/// construction the N.5 ship amendment made unreachable. So the second -/// implementation is this, and it is four properties and two no-ops. +/// arms, and the only raw-GL upload left was the per-mesh vertex-array +/// construction the N.5 ship amendment had already made unreachable. So the +/// second implementation was this, and it was five properties and two +/// no-ops. /// /// Why the two capability flags answer true. Their names are /// GL-shaped because the seam was cut from a GL device, but what they gate is @@ -27,6 +27,11 @@ namespace AcDream.App.Rendering.Gpu.Vk; /// attribute buffer the pre-modern draw path bound, which the modern path never /// reads. Publishing 0 is the same value GlobalMeshBuffer publishes for /// its own raw names here, and for the same reason. +/// +/// Campaign V slice V11 deleted the Gl property this class +/// used to answer null for: the legacy per-mesh upload bodies it existed to +/// support were already gone by the time this class was cut, so nothing ever +/// read it. /// internal sealed class VulkanMeshPipelineDevice : IMeshPipelineDevice { @@ -36,9 +41,6 @@ internal sealed class VulkanMeshPipelineDevice : IMeshPipelineDevice ?? throw new ArgumentNullException(nameof(resourceRetirement)); } - /// - public GL? Gl => null; - /// public IGpuResourceRetirementQueue ResourceRetirement { get; } diff --git a/src/AcDream.App/Rendering/Gpu/Vk/VulkanPipelineLayouts.cs b/src/AcDream.App/Rendering/Gpu/Vk/VulkanPipelineLayouts.cs index 40c0c62c..b9503b13 100644 --- a/src/AcDream.App/Rendering/Gpu/Vk/VulkanPipelineLayouts.cs +++ b/src/AcDream.App/Rendering/Gpu/Vk/VulkanPipelineLayouts.cs @@ -152,7 +152,9 @@ internal static unsafe class VulkanPipelineLayouts } /// - /// Set 0 — the ten storage bindings pins, split + /// Set 0 — the storage + /// bindings pins (nine, since Campaign V + /// slice V11 deleted the GL-only StorageTextureTable binding), split /// between dynamic and plain by . /// internal static DescriptorSetLayout CreateStorageSetLayout(Silk.NET.Vulkan.Vk vk, Device device) diff --git a/src/AcDream.App/Rendering/GpuFrameFlightController.cs b/src/AcDream.App/Rendering/GpuFrameFlightController.cs index a0a6b78b..de5c9f38 100644 --- a/src/AcDream.App/Rendering/GpuFrameFlightController.cs +++ b/src/AcDream.App/Rendering/GpuFrameFlightController.cs @@ -1,5 +1,3 @@ -using Silk.NET.OpenGL; - namespace AcDream.App.Rendering; /// @@ -236,11 +234,14 @@ internal sealed class GpuFrameFlightController : public int SlotCount => _fences.Length; internal int PendingRetirementCount => _retirements.Sum(entry => entry.Value.Count); - public GpuFrameFlightController(GL gl, int maximumFramesInFlight = DefaultMaximumFramesInFlight) - : this(new SilkGpuFenceApi(gl), maximumFramesInFlight) - { - } - + // Campaign V slice V11 deleted the public GL gl overload constructor and + // SilkGpuFenceApi, its concrete IGpuFenceApi implementation — every + // production caller went through VulkanFrameFlightController / + // GpuDeviceFrameLifetime instead (this class is never constructed with a + // real fence API in production; only its own unit tests exercise it, via + // a fake IGpuFenceApi). The internal fenceApi-shaped constructor stays: the + // class's own retirement-ledger/serial-ring logic is backend-neutral and + // is what those tests protect. internal GpuFrameFlightController( IGpuFenceApi fenceApi, int maximumFramesInFlight = DefaultMaximumFramesInFlight) @@ -464,27 +465,8 @@ internal interface IGpuFenceApi void Delete(nint fence); } -internal sealed class SilkGpuFenceApi(GL gl) : IGpuFenceApi -{ - private readonly GL _gl = gl ?? throw new ArgumentNullException(nameof(gl)); - - public nint Insert() => - _gl.FenceSync(SyncCondition.SyncGpuCommandsComplete, SyncBehaviorFlags.None); - - public GpuFenceWaitResult Wait(nint fence, bool flushCommands, ulong timeoutNanoseconds) - { - SyncObjectMask flags = flushCommands - ? SyncObjectMask.Bit - : 0; - return _gl.ClientWaitSync(fence, flags, timeoutNanoseconds) switch - { - GLEnum.AlreadySignaled or GLEnum.ConditionSatisfied => GpuFenceWaitResult.Signaled, - GLEnum.TimeoutExpired => GpuFenceWaitResult.Timeout, - GLEnum.WaitFailed => GpuFenceWaitResult.Failed, - GLEnum value => throw new InvalidOperationException( - $"OpenGL returned unexpected fence wait status {value} (0x{(uint)value:X})."), - }; - } - - public void Delete(nint fence) => _gl.DeleteSync(fence); -} +// Campaign V slice V11 deleted SilkGpuFenceApi, the GL-backed IGpuFenceApi +// implementation (glFenceSync/glClientWaitSync/glDeleteSync) — it was the +// sole reason this file needed Silk.NET.OpenGL, and had no remaining +// production caller once GpuFrameFlightController's own GL constructor +// overload above was deleted alongside it. diff --git a/src/AcDream.App/Rendering/ParticleRenderer.cs b/src/AcDream.App/Rendering/ParticleRenderer.cs index a8cdc074..5e14a30a 100644 --- a/src/AcDream.App/Rendering/ParticleRenderer.cs +++ b/src/AcDream.App/Rendering/ParticleRenderer.cs @@ -11,7 +11,6 @@ using AcDream.Core.Vfx; using DatReaderWriter; using DatReaderWriter.DBObjs; using DatReaderWriter.Enums; -using Silk.NET.OpenGL; using RuntimeParticleEmitter = AcDream.Core.Vfx.ParticleEmitter; namespace AcDream.App.Rendering; diff --git a/src/AcDream.App/Rendering/RenderFrameResourceController.cs b/src/AcDream.App/Rendering/RenderFrameResourceController.cs index ef75b9d3..0d9eb916 100644 --- a/src/AcDream.App/Rendering/RenderFrameResourceController.cs +++ b/src/AcDream.App/Rendering/RenderFrameResourceController.cs @@ -5,7 +5,6 @@ using AcDream.App.Rendering.Wb; using AcDream.App.Streaming; using AcDream.App.World; using AcDream.Core.World; -using Silk.NET.OpenGL; namespace AcDream.App.Rendering; @@ -148,74 +147,12 @@ internal sealed class LocalPlayerTeleportRenderStateSource public uint ActiveDestinationCell => _teleport.ActiveDestinationCell; } -/// Atmosphere clear and frame-global GL state establishment. -internal sealed class RuntimeRenderFrameClearPhase : IRenderFrameClearPhase -{ - private readonly GL _gl; - private readonly WorldTimeService _worldTime; - private readonly WeatherSystem _weather; - private readonly IRenderFramePortalStateSource _portal; - private readonly ParticleVisibilityController _particleVisibility; - private readonly WorldRenderDiagnostics _diagnostics; - private readonly IRenderFrameGlState _frameGlState; - - public RuntimeRenderFrameClearPhase( - GL gl, - WorldTimeService worldTime, - WeatherSystem weather, - IRenderFramePortalStateSource portal, - ParticleVisibilityController particleVisibility, - WorldRenderDiagnostics diagnostics, - IRenderFrameGlState frameGlState) - { - _gl = gl ?? throw new ArgumentNullException(nameof(gl)); - _worldTime = worldTime ?? throw new ArgumentNullException(nameof(worldTime)); - _weather = weather ?? throw new ArgumentNullException(nameof(weather)); - _portal = portal ?? throw new ArgumentNullException(nameof(portal)); - _particleVisibility = particleVisibility - ?? throw new ArgumentNullException(nameof(particleVisibility)); - _diagnostics = diagnostics ?? throw new ArgumentNullException(nameof(diagnostics)); - _frameGlState = frameGlState - ?? throw new ArgumentNullException(nameof(frameGlState)); - } - - public RenderFrameFoundation Clear() - { - bool portalViewportVisible = _portal.IsPortalViewportVisible; - if (portalViewportVisible) - _particleVisibility.Reset(); - - SkyKeyframe sky = _worldTime.CurrentSky; - AtmosphereSnapshot atmosphere = _weather.Snapshot(in sky); - if (portalViewportVisible) - { - // SceneTool::BeginScene @ 0x0043DAD0 starts the replacement - // CreatureMode frame with an opaque black target. - _gl.ClearColor(0f, 0f, 0f, 1f); - } - else - { - _gl.ClearColor( - Math.Clamp(atmosphere.FogColor.X, 0f, 1f), - Math.Clamp(atmosphere.FogColor.Y, 0f, 1f), - Math.Clamp(atmosphere.FogColor.Z, 0f, 1f), - 1f); - } - - _frameGlState.RestoreFrameDefaults(); - _gl.Clear( - ClearBufferMask.ColorBufferBit - | ClearBufferMask.DepthBufferBit - | ClearBufferMask.StencilBufferBit); - _diagnostics.EmitGlStateTripwireIfChanged( - AcDream.Core.Rendering.RenderingDiagnostics.ProbeGlStateEnabled); - - return new RenderFrameFoundation( - portalViewportVisible, - sky, - atmosphere); - } -} +// Campaign V slice V11 deleted RuntimeRenderFrameClearPhase, the raw-GL +// IRenderFrameClearPhase implementation (glClearColor/glClear + +// IRenderFrameGlState.RestoreFrameDefaults): it had zero remaining +// construction sites — VulkanRenderFrameClearPhase (VulkanCompositionFramePhases.cs) +// is the sole production implementer, expressing the same atmosphere-clear +// and portal-viewport-black logic as a Vulkan pass load op instead. internal interface IRenderLoginStateSource { diff --git a/src/AcDream.App/Rendering/Shaders/common.glsl b/src/AcDream.App/Rendering/Shaders/common.glsl deleted file mode 100644 index ced0e599..00000000 --- a/src/AcDream.App/Rendering/Shaders/common.glsl +++ /dev/null @@ -1,84 +0,0 @@ -// Campaign V slice V2 shared preamble (docs/plans/2026-07-27-vulkan-campaign.md -// §3.4, §5.2). GL has no #include, so AcDream.App.Rendering.Shader -// concatenates this file's text into every shader source that opts in -// (Shader(gl, vertPath, fragPath, includeCommonPreamble: true)), inserted -// right after the leading #version / #extension block so it can declare new -// layout bindings and macros before the rest of the shader body runs. -// -// --- set 1 (uniform buffers) ------------------------------------------------ -// GL keeps the SSBO and UBO binding-number namespaces separate, so today's -// SceneLighting UBO (binding=1) never collides with BatchBuffer's SSBO -// (also binding=1). Vulkan has ONE binding namespace per set, so the Vulkan -// backend (from V6 on) moves every uniform buffer to its own set (1) to keep -// both binding numbers. ACDREAM_UBO_SET is a no-op under GL today and is -// redefined to `set = 1,` when the same source is compiled for Vulkan, so -// applying it to every UBO layout now costs nothing and needs no source edit -// at the call sites later. -#define ACDREAM_UBO_SET - -// --- set 0 binding 9 / set 2 (the global texture table) -------------------- -// GL emulates Vulkan's set 2 variable-count sampled-texture descriptor array -// (GpuBindingModel.TextureTableSet) with a plain storage buffer of packed -// GL_ARB_bindless_texture handles at set 0 binding 9 -// (GpuBindingModel.StorageTextureTable). A batch/pass no longer carries a -// 64-bit bindless handle directly into its GPU-visible struct; it carries a -// small integer slot index into this table instead, which is what makes the -// CPU-side data model backend-neutral (Campaign V slice V2). The table itself -// — and every per-renderer GL-side handle-slot allocator that fills it -// (WbDrawDispatcher, EnvCellRenderer, TerrainModernRenderer, ParticleRenderer) -// — is deleted once each renderer moves onto IGpuDevice's own retirement-gated -// table at V4c/V4d/V4e; see the campaign doc's §5.2 for why V2 cannot reach -// that table yet. -layout(std430, binding = 9) readonly buffer TextureTableBuf { - uvec2 gTextureTable[]; -}; - -// Looks up the packed bindless handle for table slot `idx`. Callers still -// wrap the result in `sampler2DArray(...)` themselves at the use site (kept -// explicit rather than folded into one sampler-returning macro) because every -// existing call site already follows that exact pattern and a function cannot -// return an opaque sampler type built from a runtime value in GLSL. -// -// Campaign V slice V6e: this macro is GL-shaped — under Vulkan it degenerates -// to the index itself, because there is no handle to look up. Every NEW call -// site should use ACDREAM_SAMPLE_ARRAY / ACDREAM_SAMPLE_2D below, which ask the -// dialect-neutral question ("sample table slot N") instead of the GL-only one -// ("what handle does slot N hold"). The remaining direct users are the shaders -// whose port slice has not run yet. -#define ACDREAM_TEXTURE_HANDLE(idx) gTextureTable[idx] - -// Campaign V slice V6e: samples a table slot that holds a 2-D ARRAY texture — -// the world/particle/terrain case, as opposed to the retained UI's plain 2-D -// entries that ACDREAM_SAMPLE_2D below covers. -// -// Expressed as a SAMPLING macro rather than a sampler-returning one on purpose. -// Under Vulkan the expansion carries a `nonuniformEXT` qualifier, and that -// qualifier belongs on the indexing expression at the point of use; binding the -// result to a local `sampler2DArray` variable first is where a driver is free to -// lose it. Both dialects therefore read the texture in one expression. -#define ACDREAM_SAMPLE_ARRAY(idx, uvw) texture(sampler2DArray(gTextureTable[idx]), uvw) - -// Campaign V slice V6e: the reserved "this draw has no texture" slot index. -// -// GL could ask the question directly — an unregistered slot holds the handle 0, -// so `gTextureTable[idx] == uvec2(0)` answered it. Vulkan cannot: set 2 is an -// opaque descriptor array with nothing to compare, and reading an unwritten -// element of a partially-bound array is undefined rather than zero. So the -// answer moves to the index itself, which both dialects can test identically, -// and the CPU writes this value instead of registering a null handle. -#define ACDREAM_TEXTURE_NONE 0xFFFFFFFFu - -// Campaign V slice V6d: samples a table slot that holds a plain 2-D texture. -// -// The two backends disagree about what a 2-D table entry IS, and this macro is -// the one place that difference lives. Under GL a bindless handle carries its -// own texture type, so a GL_TEXTURE_2D entry is reconstructed as a sampler2D -// and read with a 2-component UV. Under Vulkan the table is one descriptor -// array whose element type is fixed at sampler2DArray, so the same entry is a -// one-layer array read at layer 0 (see tools/ShaderCompiler/VulkanGlslPreamble.cs). -// -// That asymmetry is deliberate and is what keeps the retained UI's textures -// exactly as they are on GL — including the paperdoll/appraisal FBO colour -// texture, which is an externally-owned GL_TEXTURE_2D registered by the §7.1 -// transitional seam and cannot be made an array before V4g moves its renderer. -#define ACDREAM_SAMPLE_2D(idx, uv) texture(sampler2D(gTextureTable[idx]), uv) diff --git a/src/AcDream.App/Rendering/Shaders/mesh.frag b/src/AcDream.App/Rendering/Shaders/mesh.frag deleted file mode 100644 index f2e879ae..00000000 --- a/src/AcDream.App/Rendering/Shaders/mesh.frag +++ /dev/null @@ -1,132 +0,0 @@ -#version 430 core -in vec2 vTex; -in vec3 vWorldNormal; -in vec3 vWorldPos; -out vec4 fragColor; - -uniform sampler2D uDiffuse; - -// Phase 9.1: translucency kind — matches TranslucencyKind C# enum. -// 0 = Opaque — depth write+test, no blend; shader never discards -// 1 = ClipMap — alpha-key discard (doors, windows, vegetation) -// 2 = AlphaBlend — GL blending handles compositing; do NOT discard -// 3 = Additive — GL additive blending; do NOT discard -// 4 = InvAlpha — GL inverted-alpha blending; do NOT discard -uniform int uTranslucencyKind; - -// ───────────────────────────────────────────────────────────── -// Phase G.1+G.2: shared scene-lighting UBO (binding = 1). -// -// Layout mirrors SceneLightingUbo in C#: -// struct Light { -// vec4 posAndKind; xyz = world pos, w = kind (0=dir,1=point,2=spot) -// vec4 dirAndRange; xyz = forward, w = range (metres, hard cutoff) -// vec4 colorAndIntensity; xyz = RGB linear, w = intensity -// vec4 coneAngleEtc; x = cone (rad), yzw = reserved -// }; -// layout(std140, binding = 1) uniform SceneLighting { -// Light uLights[8]; -// vec4 uCellAmbient; xyz = ambient RGB, w = active count -// vec4 uFogParams; x = start, y = end, z = flash, w = mode -// vec4 uFogColor; xyz = color -// vec4 uCameraAndTime; xyz = camera pos, w = day fraction -// }; -// ───────────────────────────────────────────────────────────── -struct Light { - vec4 posAndKind; - vec4 dirAndRange; - vec4 colorAndIntensity; - vec4 coneAngleEtc; -}; -layout(std140, binding = 1) uniform SceneLighting { - Light uLights[8]; - vec4 uCellAmbient; - vec4 uFogParams; - vec4 uFogColor; - vec4 uCameraAndTime; -}; - -// Retail per-vertex point-light ramp (calc_point_light 0x0059c8b0): the -// contribution scales by (1 - dist/falloff_eff) — a LINEAR fade to exactly -// 0 at the edge, NOT a hard-cutoff bubble. (The prior "no attenuation inside -// Range / crisp boundaries" note was a misread; it is the literal cause of -// the #133 "spotlight" look. falloff_eff = Falloff * static_light_factor 1.3 -// is folded into Range by LightInfoLoader.) Spots add a binary cos-cone test. -vec3 accumulateLights(vec3 N, vec3 worldPos) { - vec3 lit = uCellAmbient.xyz; - int activeLights = int(uCellAmbient.w); - for (int i = 0; i < 8; ++i) { - if (i >= activeLights) break; - - int kind = int(uLights[i].posAndKind.w); - vec3 Lcol = uLights[i].colorAndIntensity.xyz * uLights[i].colorAndIntensity.w; - - if (kind == 0) { - // Directional: "forward" is the light's direction vector - // pointing INTO the scene. N·(-forward) = light-facing. - vec3 Ldir = -uLights[i].dirAndRange.xyz; - float ndl = max(0.0, dot(N, Ldir)); - lit += Lcol * ndl; - } else { - // Point / spot: falloff is a HARD bubble at Range. - vec3 toL = uLights[i].posAndKind.xyz - worldPos; - float d = length(toL); - float range = uLights[i].dirAndRange.w; - if (d < range && range > 1e-3) { - vec3 Ldir = toL / max(d, 1e-4); - float ndl = max(0.0, dot(N, Ldir)); - // calc_point_light (1 - dist/falloff_eff) linear ramp; Range already - // carries falloff_eff (Falloff * 1.3), so it fades to 0 at the cutoff. - float atten = clamp(1.0 - d / max(range, 1e-3), 0.0, 1.0); - if (kind == 2) { - // Spotlight: hard-edged cos-cone test. - float cos_edge = cos(uLights[i].coneAngleEtc.x * 0.5); - float cos_l = dot(-Ldir, uLights[i].dirAndRange.xyz); - atten *= (cos_l > cos_edge) ? 1.0 : 0.0; - } - // Retail per-channel "no-blowout" cap (calc_point_light 0x0059c8b0): a single - // point/spot light can't push a channel past its own colour, regardless of - // intensity (~100) — kills the close-torch overblow (#93). See mesh_modern.frag. - lit += min(Lcol * ndl * atten, uLights[i].colorAndIntensity.xyz); - } - } - } - return lit; -} - -// Linear fog (r12 §5.1): mode 1 = LINEAR, 0 = off, others reserved. -vec3 applyFog(vec3 lit, vec3 worldPos) { - int mode = int(uFogParams.w); - if (mode == 0) return lit; - float d = length(worldPos - uCameraAndTime.xyz); - float fogStart = uFogParams.x; - float fogEnd = uFogParams.y; - float span = max(1e-3, fogEnd - fogStart); - float fog = clamp((d - fogStart) / span, 0.0, 1.0); - return mix(lit, uFogColor.xyz, fog); -} - -void main() { - vec4 sampled = texture(uDiffuse, vTex); - - // Alpha cutout only for clip-map surfaces (doors, windows, vegetation). - if (uTranslucencyKind == 1 && sampled.a < 0.5) discard; - - vec3 N = normalize(vWorldNormal); - vec3 lit = accumulateLights(N, vWorldPos); - - // Lightning flash (r12 §9) — additive cold-white pulse layered on top - // of diffuse lighting. - float flash = uFogParams.z; - lit += flash * vec3(0.6, 0.6, 0.75); - - // Clamp per-channel to 1.0 — matches retail (r13 §13.1). - lit = min(lit, vec3(1.0)); - - vec3 rgb = sampled.rgb * lit; - - // Atmospheric fog — applied after lighting. - rgb = applyFog(rgb, vWorldPos); - - fragColor = vec4(rgb, sampled.a); -} diff --git a/src/AcDream.App/Rendering/Shaders/mesh.vert b/src/AcDream.App/Rendering/Shaders/mesh.vert deleted file mode 100644 index 8f9134fa..00000000 --- a/src/AcDream.App/Rendering/Shaders/mesh.vert +++ /dev/null @@ -1,24 +0,0 @@ -#version 430 core -layout(location = 0) in vec3 aPos; -layout(location = 1) in vec3 aNormal; -layout(location = 2) in vec2 aTex; - -uniform mat4 uModel; -uniform mat4 uView; -uniform mat4 uProjection; - -out vec2 vTex; -out vec3 vWorldNormal; -out vec3 vWorldPos; - -void main() { - vTex = aTex; - // Transform the mesh normal into world space. For uniform-scale transforms - // (the common case), the upper-left 3x3 of uModel is correct. Non-uniform - // scale would require the inverse transpose; we accept that as a Phase 3+ - // concern. - vWorldNormal = normalize(mat3(uModel) * aNormal); - vec4 world = uModel * vec4(aPos, 1.0); - vWorldPos = world.xyz; - gl_Position = uProjection * uView * world; -} diff --git a/src/AcDream.App/Rendering/Shaders/mesh_modern.vert b/src/AcDream.App/Rendering/Shaders/mesh_modern.vert index b2c58da2..b7fd426c 100644 --- a/src/AcDream.App/Rendering/Shaders/mesh_modern.vert +++ b/src/AcDream.App/Rendering/Shaders/mesh_modern.vert @@ -11,14 +11,14 @@ struct InstanceData { // Campaign V slice V2 (2026-07-27): textureHandle (uvec2, a 64-bit // GL_ARB_bindless_texture handle) became textureIndex (uint) plus an explicit -// pad word. textureIndex is a slot into the binding=9 handle table -// (common.glsl) which main() below forwards to the fragment stage, where slice -// V6e moved the lookup so the same source compiles for Vulkan. The pad word keeps -// textureLayer/flags at their original std430 offsets (8/12), so the struct -// is still 16 bytes and every existing CPU writer's layout is unchanged -// (GpuBindingModel.GpuBatchDataStrideBytes). +// pad word. textureIndex is a slot into the global texture table (set 2, +// injected by tools/ShaderCompiler/VulkanGlslPreamble.cs — see +// ACDREAM_TEXTURE_HANDLE/ACDREAM_SAMPLE_ARRAY) which main() below forwards to +// the fragment stage. The pad word keeps textureLayer/flags at their original +// std430 offsets (8/12), so the struct is still 16 bytes and every existing +// CPU writer's layout is unchanged (GpuBindingModel.GpuBatchDataStrideBytes). struct BatchData { - uint textureIndex; // slot into the binding=9 handle table + uint textureIndex; // slot into the global texture table uint _pad; // keeps textureLayer/flags at offsets 8/12 uint textureLayer; // layer in the shared WB or pooled composite array uint flags; // reserved — N.5 dispatcher owns all blend state diff --git a/src/AcDream.App/Rendering/Shaders/particle.frag b/src/AcDream.App/Rendering/Shaders/particle.frag index a4a571e3..6cd2b09d 100644 --- a/src/AcDream.App/Rendering/Shaders/particle.frag +++ b/src/AcDream.App/Rendering/Shaders/particle.frag @@ -6,7 +6,8 @@ in vec4 vColor; // Campaign V slice V6e: the texture-table SLOT, not the bindless handle — see // particle.vert. ACDREAM_TEXTURE_NONE is the untextured particle, which used to // be spelled "the slot whose handle is zero"; a Vulkan descriptor array cannot -// be asked that question, so the answer lives in the index (common.glsl). +// be asked that question, so the answer lives in the index (see +// tools/ShaderCompiler/VulkanGlslPreamble.cs). flat in uint vTextureIndex; out vec4 fragColor; diff --git a/src/AcDream.App/Rendering/Shaders/particle.vert b/src/AcDream.App/Rendering/Shaders/particle.vert index e9259d61..3da9560f 100644 --- a/src/AcDream.App/Rendering/Shaders/particle.vert +++ b/src/AcDream.App/Rendering/Shaders/particle.vert @@ -10,8 +10,9 @@ layout(location = 3) in vec4 aAxisX; layout(location = 4) in vec4 aAxisY; layout(location = 5) in vec4 aColor; // Campaign V slice V2c (2026-07-27): was uvec2 aTextureHandle (a raw -// ARB_bindless_texture handle); now a slot into the binding=9 handle table -// (ACDREAM_TEXTURE_HANDLE, common.glsl). +// ARB_bindless_texture handle); now a slot into the global texture table +// (ACDREAM_TEXTURE_HANDLE, injected by +// tools/ShaderCompiler/VulkanGlslPreamble.cs). layout(location = 6) in uint aTextureIndex; uniform mat4 uViewProjection; diff --git a/src/AcDream.App/Rendering/Shaders/sky.frag b/src/AcDream.App/Rendering/Shaders/sky.frag index 5a8f01b9..4eb4836a 100644 --- a/src/AcDream.App/Rendering/Shaders/sky.frag +++ b/src/AcDream.App/Rendering/Shaders/sky.frag @@ -7,9 +7,10 @@ in float vFogFactor; // 1 = no fog, 0 = full fog color out vec4 fragColor; // Campaign V slice V6e: the sky's texture is now read through the shared table -// (ACDREAM_SAMPLE_2D, common.glsl) rather than a `uniform sampler2D` bound to -// texture unit 0. Vulkan has no default uniform block to declare a loose -// sampler in, and set 2 is where every sampled texture lives. +// (ACDREAM_SAMPLE_2D, injected by tools/ShaderCompiler/VulkanGlslPreamble.cs) +// rather than a `uniform sampler2D` bound to texture unit 0. Vulkan has no +// default uniform block to declare a loose sampler in, and set 2 is where +// every sampled texture lives. // // The wrap mode travels WITH the slot: SkyRenderer registers a distinct table // entry per (texture, sampler) pair, so the per-submesh Repeat-vs-ClampToEdge diff --git a/src/AcDream.App/Rendering/Shaders/spv/shaders.manifest.json b/src/AcDream.App/Rendering/Shaders/spv/shaders.manifest.json index 2d4faaa9..3751771e 100644 --- a/src/AcDream.App/Rendering/Shaders/spv/shaders.manifest.json +++ b/src/AcDream.App/Rendering/Shaders/spv/shaders.manifest.json @@ -17,31 +17,13 @@ } ] }, - { - "name": "mesh", - "vulkanReady": false, - "stages": [ - { - "stage": "vert", - "sourceSha256": "c35f767ab07fa9df805f9e77f4851f517c153dd2ef2efa6d49d0c24b688e4f56", - "compiled": false, - "message": "mesh.vert:73: error: \u0027uModel\u0027 : undeclared identifier" - }, - { - "stage": "frag", - "sourceSha256": "4d6478543a9a903a3453581fa847e096aaecf01f38ebb2921572663bad8e24ea", - "compiled": false, - "message": "mesh.frag:160: error: \u0027uDiffuse\u0027 : undeclared identifier" - } - ] - }, { "name": "mesh_modern", "vulkanReady": true, "stages": [ { "stage": "vert", - "sourceSha256": "770e6300e023bd2600e8fed52768c624d8fff2ff8d6f997f076980502d3f675b", + "sourceSha256": "2c37aa0fd1ee4af5ee1f6541b00e6a7226d4173988e748b1fb1c271b0a9b91b2", "compiled": true }, { @@ -57,12 +39,12 @@ "stages": [ { "stage": "vert", - "sourceSha256": "9629271f8997853a3c78a3cb1ec7af02a13518d68bdaa5ae1378997da7e5ab62", + "sourceSha256": "921c32617708b3931a6304b4697ee96d728077d96f489d6c09eea4c5fe225b63", "compiled": true }, { "stage": "frag", - "sourceSha256": "bc08e4fb6f57da94d6c41d52aa81d73ab287c968214303a446c40a04d0495b40", + "sourceSha256": "6f2a5769670087bf58b0c1a7a007c1853952354dfd9106973656311bcbb855cd", "compiled": true } ] @@ -110,7 +92,7 @@ }, { "stage": "frag", - "sourceSha256": "bcb21fd47fc6f74a75edd349bc3dff6120b8995115b2a08269a22f98c0bedd6c", + "sourceSha256": "8de9a5d8f819d1abf893f134cf8ed9fa7edf937a353a74d2befab7b05a3ff700", "compiled": true } ] @@ -126,7 +108,7 @@ }, { "stage": "frag", - "sourceSha256": "21f41dcca4d4973ad9ee148c5433e0dae015489414c164c51bde958963f03598", + "sourceSha256": "a9f0ad2e679b68ee2de6c4877202f6f5e5089d0f7368fcedd6048891c6848915", "compiled": true } ] diff --git a/src/AcDream.App/Rendering/Shaders/terrain_modern.frag b/src/AcDream.App/Rendering/Shaders/terrain_modern.frag index 2d09c9f7..9bb8a01d 100644 --- a/src/AcDream.App/Rendering/Shaders/terrain_modern.frag +++ b/src/AcDream.App/Rendering/Shaders/terrain_modern.frag @@ -5,15 +5,16 @@ // Math identical to terrain.frag (Phase 3c per-cell maskBlend3 + // Phase G fog + lightning flash). // -// Texture reads go through ACDREAM_SAMPLE_ARRAY (common.glsl) since slice -// V6f-3, so this source compiles for both backends. Under GL that still expands -// to the uvec2-handle + sampler2DArray-constructor pattern this shader has -// always used — the documented "always works" form per the ARB_bindless_texture -// spec, and the one that avoids the GL_INVALID_OPERATION the alternative -// (`uniform sampler2DArray` set via glProgramUniformHandleARB) produces on at -// least one driver in practice. Under Vulkan it indexes the set-2 descriptor -// array instead. The extension requirement above is dropped for Vulkan by the -// compiler's preamble, where it would be an error rather than a no-op. +// Texture reads go through ACDREAM_SAMPLE_ARRAY, which +// tools/ShaderCompiler/VulkanGlslPreamble.cs injects to index the set-2 +// descriptor array. GL is deleted (Campaign V slice V11); before that this +// macro expanded (via the now-deleted common.glsl) to the uvec2-handle + +// sampler2DArray-constructor pattern this shader used on that arm — the +// documented "always works" form per the ARB_bindless_texture spec, and the +// one that avoided the GL_INVALID_OPERATION the alternative (`uniform +// sampler2DArray` set via glProgramUniformHandleARB) produced on at least one +// driver in practice. The extension requirement above is dropped for Vulkan +// by the compiler's preamble, where it would be an error rather than a no-op. in vec2 vBaseUV; in vec3 vWorldNormal; @@ -30,8 +31,9 @@ out vec4 fragColor; // Campaign V slice V2b (2026-07-27): uTerrainHandle/uAlphaHandle (uvec2, raw // ARB_bindless_texture handles) became uTextureIndexA/uTextureIndexB (slots -// into the binding=9 handle table, ACDREAM_TEXTURE_HANDLE in common.glsl). -// Named to match the pinned GpuPushConstants.TextureIndexA/B fields so V4d's +// into the global texture table, ACDREAM_TEXTURE_HANDLE injected by +// tools/ShaderCompiler/VulkanGlslPreamble.cs). Named to match the pinned +// GpuPushConstants.TextureIndexA/B fields so V4d's // move to push constants is a rename, not a redesign — there is no // push-constant plumbing yet, so these stay plain uniforms for now. uniform uint uTextureIndexA; @@ -45,8 +47,8 @@ uniform uint uTextureIndexB; // question both dialects can answer ("sample table slot N at these // coordinates") and expands to the right thing on each. // -// A SAMPLING macro, not a sampler-returning one, for the reason common.glsl -// records: under Vulkan the expansion carries `nonuniformEXT` on the indexing +// A SAMPLING macro, not a sampler-returning one, for the reason +// VulkanGlslPreamble.cs records: under Vulkan the expansion carries `nonuniformEXT` on the indexing // expression, and binding the result to a local sampler2DArray first is exactly // where an implementation may drop that qualifier. The old `#define uTerrain // sampler2DArray(...)` was that shape textually, so keeping it would have diff --git a/src/AcDream.App/Rendering/Wb/BufferUsageExtensions.cs b/src/AcDream.App/Rendering/Wb/BufferUsageExtensions.cs deleted file mode 100644 index 19788818..00000000 --- a/src/AcDream.App/Rendering/Wb/BufferUsageExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Chorizite.Core.Render.Enums; -using Silk.NET.OpenGL; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace AcDream.App.Rendering.Wb { - public static class BufferUsageExtensions { - /// - /// Converts a BufferUsage to a GL BufferUsageARB - /// - /// - /// - public static GLEnum ToGL(this BufferUsage usage) { - switch (usage) { - case BufferUsage.Static: - return GLEnum.StaticDraw; - case BufferUsage.Dynamic: - return GLEnum.DynamicDraw; - default: - return GLEnum.StaticDraw; - } - } - } -} diff --git a/src/AcDream.App/Rendering/Wb/IMeshPipelineDevice.cs b/src/AcDream.App/Rendering/Wb/IMeshPipelineDevice.cs index de20127c..343f5341 100644 --- a/src/AcDream.App/Rendering/Wb/IMeshPipelineDevice.cs +++ b/src/AcDream.App/Rendering/Wb/IMeshPipelineDevice.cs @@ -1,5 +1,4 @@ using AcDream.App.Rendering; -using Silk.NET.OpenGL; namespace AcDream.App.Rendering.Wb; @@ -10,13 +9,13 @@ namespace AcDream.App.Rendering.Wb; /// Plan §5.5.10 recorded the blocker plainly: "WbMeshAdapter owns an /// OpenGLGraphicsDevice, so it is not constructible on Vulkan" — which is /// why a null mesh adapter had to exist at all. §5.5.12 item 6 then MEASURED how -/// wide that dependency really is, and the answer is this: a GL context, the -/// retirement queue, the shared instance VBO, and two capability flags. Seven -/// members out of a 760-line class. +/// wide that dependency really is, and the answer at the time was seven members: +/// a GL context, the retirement queue, the shared instance VBO, and two +/// capability flags (the two process/queue members below). /// -/// So the coupling is expressed as an interface at exactly that surface, +/// So the coupling was expressed as an interface at exactly that surface, /// and OpenGLGraphicsDevice declared it — every member already existed, -/// so the GL arm executed not one changed statement. What this buys is that +/// so the GL arm executed not one changed statement. What this bought was that /// ObjectMeshManager and WbMeshAdapter no longer NAME a backend, /// which is the prerequisite for the slice that gives them a second /// implementation. @@ -32,21 +31,15 @@ namespace AcDream.App.Rendering.Wb; /// second implementation this interface was cut for. /// /// Campaign V slice V11 deleted OpenGLGraphicsDevice along -/// with the rest of the raw-GL arm it fronted, so +/// with the rest of the raw-GL arm it fronted (and the legacy per-mesh upload +/// bodies the modern path had already made unreachable), so /// is now -/// the interface's only implementation. always answers null -/// there; removing it (and the legacy per-mesh upload bodies it alone still -/// gated) is Campaign V's package/shader cleanup slice, not this one. +/// the interface's only implementation and its own package/shader cleanup slice +/// removed the Gl member this interface used to carry — nothing read it +/// any more once those bodies were gone. /// internal interface IMeshPipelineDevice : IDisposable { - /// - /// The GL context, or null on a backend that has none. Since slice V6i-3 the - /// only readers are the legacy per-mesh upload bodies the mandatory modern - /// path never reaches, plus the mesh arena's vertex array. - /// - GL? Gl { get; } - /// Frame-flight-gated release for everything the pipeline allocates. IGpuResourceRetirementQueue ResourceRetirement { get; } diff --git a/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs b/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs index c7c5a835..0b6d7cd9 100644 --- a/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs +++ b/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs @@ -6,7 +6,6 @@ using DatReaderWriter.Enums; using CullMode = DatReaderWriter.Enums.CullMode; using DatReaderWriter.Types; using Microsoft.Extensions.Logging; -using Silk.NET.OpenGL; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -18,7 +17,6 @@ using System.Threading.Tasks; using AcDream.Content; using AcDream.App.Rendering.Residency; using AcDream.Core.Rendering.Wb; -using PixelFormat = Silk.NET.OpenGL.PixelFormat; using BoundingBox = Chorizite.Core.Lib.BoundingBox; namespace AcDream.App.Rendering.Wb @@ -2041,15 +2039,16 @@ namespace AcDream.App.Rendering.Wb atlasManager.LastUseSequence = ++_atlasUseSequence; // MP1a: AcDream.Content is Silk.NET-free — the extraction records - // carry Content-owned UploadPixelFormat/UploadPixelType enums whose - // underlying values are the GL ABI constants (numerically identical - // to Silk.NET.OpenGL.PixelFormat/PixelType), so this lifted nullable - // cast is value- and null-preserving. + // carry Content-owned UploadPixelFormat/UploadPixelType enums. + // Campaign V slice V11 moved the atlas/array stack onto that same + // Content-owned vocabulary (the GL cast this used to need is gone + // along with Silk.NET.OpenGL.PixelFormat/PixelType themselves), so + // this is now a direct pass-through rather than a lifted cast. bool uploadsNewLayer = !atlasManager.HasTexture(batch.Key); try { textureIndex = atlasManager.AddTexture(batch.Key, batch.TextureData, - (PixelFormat?)batch.UploadPixelFormat, (PixelType?)batch.UploadPixelType); + batch.UploadPixelFormat, batch.UploadPixelType); } catch { diff --git a/src/AcDream.App/Rendering/Wb/TextureAtlasManager.cs b/src/AcDream.App/Rendering/Wb/TextureAtlasManager.cs index 00ceb795..079a465e 100644 --- a/src/AcDream.App/Rendering/Wb/TextureAtlasManager.cs +++ b/src/AcDream.App/Rendering/Wb/TextureAtlasManager.cs @@ -2,10 +2,8 @@ using AcDream.Content; using Chorizite.Core.Render; using Chorizite.Core.Render.Enums; using DatReaderWriter.Enums; -using Silk.NET.OpenGL; using System; using System.Collections.Generic; -using PixelFormat = Silk.NET.OpenGL.PixelFormat; using AcDream.App.Rendering; namespace AcDream.App.Rendering.Wb { @@ -144,7 +142,7 @@ namespace AcDream.App.Rendering.Wb { _ => throw new NotSupportedException($"Unsupported texture-atlas format {format}.") }; - public int AddTexture(TextureKey key, byte[] data, PixelFormat? uploadPixelFormat = null, PixelType? uploadPixelType = null) { + public int AddTexture(TextureKey key, byte[] data, UploadPixelFormat? uploadPixelFormat = null, UploadPixelType? uploadPixelType = null) { ObjectDisposedException.ThrowIf(_disposed || _disposeTransaction.IsRunning, this); _layerRetirement.RetryPendingPublications(); if (_textureIndices.TryGetValue(key, out var existingIndex)) { diff --git a/src/AcDream.App/Rendering/Wb/TextureFormatExtensions.cs b/src/AcDream.App/Rendering/Wb/TextureFormatExtensions.cs index d3b00bb3..17f008b0 100644 --- a/src/AcDream.App/Rendering/Wb/TextureFormatExtensions.cs +++ b/src/AcDream.App/Rendering/Wb/TextureFormatExtensions.cs @@ -1,55 +1,31 @@ +using AcDream.Content; using Chorizite.Core.Render.Enums; -using Silk.NET.OpenGL; using System; namespace AcDream.App.Rendering.Wb { public static class TextureFormatExtensions { - public static SizedInternalFormat ToGL(this Chorizite.Core.Render.Enums.TextureFormat format) { + // Campaign V slice V11 deleted ToGL()/ToCompressedGL()/IsCompressed(): + // each had zero remaining callers once ManagedGLTextureArray (their + // sole consumer) was deleted along with the rest of the raw-GL arm. + + public static UploadPixelFormat ToPixelFormat(this Chorizite.Core.Render.Enums.TextureFormat format) { return format switch { - TextureFormat.RGBA8 => SizedInternalFormat.Rgba8, - TextureFormat.RGB8 => SizedInternalFormat.Rgb8, - TextureFormat.A8 => SizedInternalFormat.R8, - TextureFormat.Rgba32f => SizedInternalFormat.Rgba32f, - TextureFormat.DXT1 => SizedInternalFormat.CompressedRgbaS3TCDxt1Ext, - TextureFormat.DXT3 => SizedInternalFormat.CompressedRgbaS3TCDxt3Ext, - TextureFormat.DXT5 => SizedInternalFormat.CompressedRgbaS3TCDxt5Ext, + Chorizite.Core.Render.Enums.TextureFormat.RGBA8 => UploadPixelFormat.Rgba, + Chorizite.Core.Render.Enums.TextureFormat.RGB8 => UploadPixelFormat.Rgb, + Chorizite.Core.Render.Enums.TextureFormat.A8 => UploadPixelFormat.Red, + Chorizite.Core.Render.Enums.TextureFormat.Rgba32f => UploadPixelFormat.Rgba, _ => throw new NotSupportedException($"Texture format {format} is not supported."), }; } - public static InternalFormat ToCompressedGL(this Chorizite.Core.Render.Enums.TextureFormat format) { + public static UploadPixelType ToPixelType(this Chorizite.Core.Render.Enums.TextureFormat format) { return format switch { - TextureFormat.DXT1 => InternalFormat.CompressedRgbaS3TCDxt1Ext, - TextureFormat.DXT3 => InternalFormat.CompressedRgbaS3TCDxt3Ext, - TextureFormat.DXT5 => InternalFormat.CompressedRgbaS3TCDxt5Ext, - _ => throw new NotSupportedException($"Texture format {format} does not support compression."), - }; - } - - public static PixelFormat ToPixelFormat(this Chorizite.Core.Render.Enums.TextureFormat format) { - return format switch { - Chorizite.Core.Render.Enums.TextureFormat.RGBA8 => PixelFormat.Rgba, - Chorizite.Core.Render.Enums.TextureFormat.RGB8 => PixelFormat.Rgb, - Chorizite.Core.Render.Enums.TextureFormat.A8 => PixelFormat.Red, - Chorizite.Core.Render.Enums.TextureFormat.Rgba32f => PixelFormat.Rgba, + TextureFormat.RGBA8 => UploadPixelType.UnsignedByte, + TextureFormat.RGB8 => UploadPixelType.UnsignedByte, + TextureFormat.A8 => UploadPixelType.UnsignedByte, + TextureFormat.Rgba32f => UploadPixelType.Float, _ => throw new NotSupportedException($"Texture format {format} is not supported."), }; } - - public static PixelType ToPixelType(this Chorizite.Core.Render.Enums.TextureFormat format) { - return format switch { - TextureFormat.RGBA8 => PixelType.UnsignedByte, - TextureFormat.RGB8 => PixelType.UnsignedByte, - TextureFormat.A8 => PixelType.UnsignedByte, - TextureFormat.Rgba32f => PixelType.Float, - _ => throw new NotSupportedException($"Texture format {format} is not supported."), - }; - } - - public static bool IsCompressed(this Chorizite.Core.Render.Enums.TextureFormat format) { - return format == Chorizite.Core.Render.Enums.TextureFormat.DXT1 || - format == Chorizite.Core.Render.Enums.TextureFormat.DXT3 || - format == Chorizite.Core.Render.Enums.TextureFormat.DXT5; - } } } diff --git a/src/AcDream.App/Rendering/Wb/TextureParameters.cs b/src/AcDream.App/Rendering/Wb/TextureParameters.cs deleted file mode 100644 index 30a7be14..00000000 --- a/src/AcDream.App/Rendering/Wb/TextureParameters.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Silk.NET.OpenGL; - -namespace AcDream.App.Rendering.Wb { - /// - /// Configurable OpenGL texture parameters for wrap mode, filtering, mipmaps, and anisotropic filtering. - /// - public struct TextureParameters { - public TextureWrapMode WrapS; - public TextureWrapMode WrapT; - public TextureMinFilter MinFilter; - public TextureMagFilter MagFilter; - public bool EnableMipmaps; - public bool EnableAnisotropicFiltering; - - /// Standard tiling textures — Repeat + trilinear + aniso. - public static readonly TextureParameters Default = new() { - WrapS = TextureWrapMode.Repeat, - WrapT = TextureWrapMode.Repeat, - MinFilter = TextureMinFilter.LinearMipmapLinear, - MagFilter = TextureMagFilter.Linear, - EnableMipmaps = true, - EnableAnisotropicFiltering = true, - }; - - /// Non-tiling textures (alpha maps, fonts, UI, object atlases) — ClampToEdge + trilinear + aniso. - public static readonly TextureParameters ClampToEdge = new() { - WrapS = TextureWrapMode.ClampToEdge, - WrapT = TextureWrapMode.ClampToEdge, - MinFilter = TextureMinFilter.LinearMipmapLinear, - MagFilter = TextureMagFilter.Linear, - EnableMipmaps = true, - EnableAnisotropicFiltering = true, - }; - } -} diff --git a/src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs b/src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs index abf93dba..e8f7351c 100644 --- a/src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs +++ b/src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs @@ -6,7 +6,6 @@ using AcDream.Core.Rendering; using DatReaderWriter; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Silk.NET.OpenGL; namespace AcDream.App.Rendering.Wb; @@ -115,8 +114,6 @@ public sealed class WbMeshAdapter /// type by the pinned RHI contract. Every caller already lives inside /// AcDream.App or its InternalsVisibleTo test assemblies. /// - /// Active Silk.NET GL context. Must be bound to the current - /// thread (construction runs GL queries; call from OnLoad). /// The one process RHI device. Supplies the mesh /// arena's vertex/index buffers. /// acdream's shared runtime DAT facade. Tooling uses it @@ -124,12 +121,10 @@ public sealed class WbMeshAdapter /// Logger for the adapter; ObjectMeshManager uses /// NullLogger internally. internal WbMeshAdapter( - GL? gl, AcDream.App.Rendering.Gpu.IGpuDevice gpuDevice, IDatReaderWriter dats, ILogger logger) : this( - gl, gpuDevice, dats, preparedAssets: null, @@ -141,13 +136,11 @@ public sealed class WbMeshAdapter } internal static WbMeshAdapter CreateWithLiveDatPreparedAssets( - GL? gl, AcDream.App.Rendering.Gpu.IGpuDevice gpuDevice, IDatReaderWriter dats, ILogger logger, AcDream.App.Rendering.IGpuResourceRetirementQueue resourceRetirement) => new( - gl, gpuDevice, dats, preparedAssets: null, @@ -157,7 +150,6 @@ public sealed class WbMeshAdapter ResidencyBudgetOptions.Default); internal WbMeshAdapter( - GL? gl, AcDream.App.Rendering.Gpu.IGpuDevice gpuDevice, IDatReaderWriter dats, IPreparedAssetSource preparedAssets, @@ -165,7 +157,6 @@ public sealed class WbMeshAdapter AcDream.App.Rendering.IGpuResourceRetirementQueue resourceRetirement, ResidencyBudgetOptions? budgets = null) : this( - gl, gpuDevice, dats, preparedAssets, @@ -177,7 +168,6 @@ public sealed class WbMeshAdapter } private WbMeshAdapter( - GL? gl, AcDream.App.Rendering.Gpu.IGpuDevice gpuDevice, IDatReaderWriter dats, IPreparedAssetSource? preparedAssets, @@ -714,7 +704,14 @@ public sealed class WbMeshAdapter // The current global arena is still directly owned by the mesh // manager. Fence every submitted draw before manager teardown // can delete that arena's VAO/VBO/IBO. - if (_resourceRetirement is AcDream.App.Rendering.GpuFrameFlightController frameFlights) + // + // Campaign V slice V11: this used to pattern-match the deleted + // GL GpuFrameFlightController, which VulkanFrameFlightController + // replaced at V6a without this site being updated — so the wait + // was dead on every Vulkan run since then. Fixed, not just + // renamed: VulkanFrameFlightController is IGpuResourceRetirementQueue + // AND exposes the same WaitForSubmittedWork() the GL type did. + if (_resourceRetirement is AcDream.App.Rendering.Gpu.Vk.VulkanFrameFlightController frameFlights) frameFlights.WaitForSubmittedWork(); }, () => _meshManager?.Dispose(), @@ -722,7 +719,7 @@ public sealed class WbMeshAdapter () => DrainGraphicsQueue("publishing mesh resource retirements"), () => { - if (_resourceRetirement is AcDream.App.Rendering.GpuFrameFlightController frameFlights) + if (_resourceRetirement is AcDream.App.Rendering.Gpu.Vk.VulkanFrameFlightController frameFlights) frameFlights.WaitForSubmittedWork(); }, () => DrainGraphicsQueue("releasing retired mesh resources"), diff --git a/src/AcDream.App/Rendering/Wb/WorldTextureArray.cs b/src/AcDream.App/Rendering/Wb/WorldTextureArray.cs index 9addda89..17bda9ac 100644 --- a/src/AcDream.App/Rendering/Wb/WorldTextureArray.cs +++ b/src/AcDream.App/Rendering/Wb/WorldTextureArray.cs @@ -1,9 +1,9 @@ using AcDream.App.Rendering.Gpu; using AcDream.App.Rendering.Gpu.Vk; +using AcDream.Content; using AcDream.Core.Rendering.Wb; using Chorizite.Core.Render.Enums; using Microsoft.Extensions.Logging; -using Silk.NET.OpenGL; namespace AcDream.App.Rendering.Wb; @@ -66,7 +66,7 @@ internal interface IWorldTextureArray : IDisposable /// so a burst of layer writes costs one /// GPU submission rather than one per layer. /// - void UpdateLayer(int layer, byte[] data, PixelFormat? uploadPixelFormat, PixelType? uploadPixelType); + void UpdateLayer(int layer, byte[] data, UploadPixelFormat? uploadPixelFormat, UploadPixelType? uploadPixelType); /// /// Flushes staged layers and refreshes the mip chain. Returns the bytes of @@ -317,7 +317,7 @@ internal sealed class RhiWorldTextureArray : IWorldTextureArray /// public bool IsPhysicalRetirementComplete => _disposed; - public void UpdateLayer(int layer, byte[] data, PixelFormat? uploadPixelFormat, PixelType? uploadPixelType) + public void UpdateLayer(int layer, byte[] data, UploadPixelFormat? uploadPixelFormat, UploadPixelType? uploadPixelType) { ObjectDisposedException.ThrowIf(_disposed, this); ArgumentNullException.ThrowIfNull(data); @@ -502,8 +502,8 @@ internal sealed class RhiWorldTextureArray : IWorldTextureArray int width, int height, int dataLength, - PixelFormat? uploadPixelFormat, - PixelType? uploadPixelType) + UploadPixelFormat? uploadPixelFormat, + UploadPixelType? uploadPixelType) { int expectedBytes = CalculateExpectedDataSize(format, width, height); if (dataLength != expectedBytes) @@ -521,8 +521,8 @@ internal sealed class RhiWorldTextureArray : IWorldTextureArray return; } - PixelFormat expectedFormat = format.ToPixelFormat(); - PixelType expectedType = format.ToPixelType(); + UploadPixelFormat expectedFormat = format.ToPixelFormat(); + UploadPixelType expectedType = format.ToPixelType(); if ((uploadPixelFormat ?? expectedFormat) != expectedFormat || (uploadPixelType ?? expectedType) != expectedType) { diff --git a/src/AcDream.App/Rendering/WorldRenderDiagnostics.cs b/src/AcDream.App/Rendering/WorldRenderDiagnostics.cs index 81660c22..1ae0191b 100644 --- a/src/AcDream.App/Rendering/WorldRenderDiagnostics.cs +++ b/src/AcDream.App/Rendering/WorldRenderDiagnostics.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.Text; using AcDream.Core.Vfx; using AcDream.Core.World; -using Silk.NET.OpenGL; namespace AcDream.App.Rendering; @@ -44,63 +43,6 @@ internal interface IRenderGlStateReader RenderGlScissorSnapshot CaptureScissor(); } -/// Render-thread GL state reader used only by explicitly enabled probes. -internal sealed class SilkRenderGlStateReader : IRenderGlStateReader -{ - private readonly GL _gl; - - public SilkRenderGlStateReader(GL gl) => - _gl = gl ?? throw new ArgumentNullException(nameof(gl)); - - public RenderGlStateSnapshot CaptureState() - { - Span scissor = stackalloc int[4]; - Span viewport = stackalloc int[4]; - _gl.GetInteger(GetPName.ScissorBox, scissor); - _gl.GetInteger(GetPName.Viewport, viewport); - - int clipBits = 0; - for (int index = 0; index < ClipFrame.MaxPlanes; index++) - { - if (_gl.IsEnabled(EnableCap.ClipDistance0 + index)) - clipBits |= 1 << index; - } - - // Preserve the old tripwire boundary: consume the error that existed - // after the scissor/viewport/clip reads, before any of the state reads - // below can produce a probe-owned error. - int error = (int)_gl.GetError(); - - return new RenderGlStateSnapshot( - _gl.IsEnabled(EnableCap.DepthTest), - _gl.GetBoolean(GetPName.DepthWritemask), - _gl.GetInteger(GetPName.DepthFunc), - _gl.IsEnabled(EnableCap.Blend), - _gl.GetInteger(GetPName.BlendSrcRgb), - _gl.GetInteger(GetPName.BlendDstRgb), - _gl.IsEnabled(EnableCap.CullFace), - _gl.GetInteger(GetPName.CullFaceMode), - _gl.GetInteger(GetPName.FrontFace), - _gl.IsEnabled(EnableCap.ScissorTest), - new IntRenderRectangle(scissor[0], scissor[1], scissor[2], scissor[3]), - new IntRenderRectangle(viewport[0], viewport[1], viewport[2], viewport[3]), - _gl.GetInteger(GetPName.DrawFramebufferBinding), - _gl.IsEnabled(EnableCap.SampleAlphaToCoverage), - _gl.IsEnabled(EnableCap.StencilTest), - clipBits, - error); - } - - public RenderGlScissorSnapshot CaptureScissor() - { - Span box = stackalloc int[4]; - _gl.GetInteger(GetPName.ScissorBox, box); - return new RenderGlScissorSnapshot( - _gl.IsEnabled(EnableCap.ScissorTest), - new IntRenderRectangle(box[0], box[1], box[2], box[3])); - } -} - /// /// Owns print-on-change world-render probes and their reusable scratch. Inputs /// are borrowed for one call; the owner retains only copied signatures and IDs. diff --git a/src/AcDream.Content/UploadFormats.cs b/src/AcDream.Content/UploadFormats.cs index b0f63612..cf22784b 100644 --- a/src/AcDream.Content/UploadFormats.cs +++ b/src/AcDream.Content/UploadFormats.cs @@ -3,17 +3,19 @@ namespace AcDream.Content; // MP1a follow-up (2026-07-05): Content-owned upload-format hint enums. // AcDream.Content must stay Silk.NET-free (the MP1b bake tool must not ship // GL binaries), so the moved MeshBatchData/TextureBatchData records carry -// these instead of Silk.NET.OpenGL.PixelFormat/PixelType. The underlying -// values are the OpenGL ABI constants, kept numerically identical to the -// corresponding Silk.NET.OpenGL members (verified against Silk.NET.OpenGL -// 2.23.0) so the App-boundary cast -// `(Silk.NET.OpenGL.PixelFormat?)batch.UploadPixelFormat` is -// value-preserving. GL enum constants are a stable specification ABI — -// they cannot drift between Silk.NET versions. +// these. The underlying values are the OpenGL ABI constants — a stable +// specification ABI that cannot drift between library versions — which is +// what let App's world-texture stack (WorldTextureArray.cs, +// TextureFormatExtensions.cs) adopt this same vocabulary directly at +// Campaign V slice V11, once Silk.NET.OpenGL.PixelFormat/PixelType were +// deleted along with the rest of the raw-GL arm. Before that slice, App held +// a separate Silk.NET-typed enum and cast across the boundary +// (`(Silk.NET.OpenGL.PixelFormat?)batch.UploadPixelFormat`); now it's the +// same type on both sides of AcDream.Content's assembly boundary. // -// Members are ONLY the values the extraction code actually assigns -// (see MeshExtractor.cs). Add new members with their GL constant if a -// future decode path needs them — never renumber. +// Members are ONLY the values a decode path actually assigns. Add new +// members with their GL constant if a future one needs them — never +// renumber. /// /// GL pixel-format upload hint computed at extraction time. @@ -22,6 +24,17 @@ namespace AcDream.Content; public enum UploadPixelFormat { /// GL_RGBA. Rgba = 0x1908, + + /// + /// GL_RGB. Added at Campaign V slice V11: App's + /// TextureFormatExtensions.ToPixelFormat needs the full expected-format + /// vocabulary once it stopped returning Silk.NET.OpenGL.PixelFormat + /// (the deleted GL backend's own enum), not just the values extraction emits. + /// + Rgb = 0x1907, + + /// GL_RED. See 's remark — same V11 motivation. + Red = 0x1903, } /// @@ -31,4 +44,10 @@ public enum UploadPixelFormat { public enum UploadPixelType { /// GL_UNSIGNED_BYTE. UnsignedByte = 0x1401, + + /// + /// GL_FLOAT. Added at Campaign V slice V11 for the same reason as + /// . + /// + Float = 0x1406, } diff --git a/tests/AcDream.App.Tests/Composition/WorldRenderCompositionTests.cs b/tests/AcDream.App.Tests/Composition/WorldRenderCompositionTests.cs index e2ee6328..c4059be7 100644 --- a/tests/AcDream.App.Tests/Composition/WorldRenderCompositionTests.cs +++ b/tests/AcDream.App.Tests/Composition/WorldRenderCompositionTests.cs @@ -13,7 +13,6 @@ using AcDream.Core.Terrain; using AcDream.UI.Abstractions.Settings; using DatReaderWriter.DBObjs; using Silk.NET.Input; -using Silk.NET.OpenGL; namespace AcDream.App.Tests.Composition; @@ -293,7 +292,6 @@ public sealed class WorldRenderCompositionTests new ConcurrentDictionary()); public WbMeshAdapter CreateMeshAdapter( - GL? gl, IGpuDevice device, IDatReaderWriter dats, IPreparedAssetSource preparedAssets, diff --git a/tests/AcDream.App.Tests/Rendering/Gpu/GpuContractTests.cs b/tests/AcDream.App.Tests/Rendering/Gpu/GpuContractTests.cs index e39a7d0a..ed7c1a90 100644 --- a/tests/AcDream.App.Tests/Rendering/Gpu/GpuContractTests.cs +++ b/tests/AcDream.App.Tests/Rendering/Gpu/GpuContractTests.cs @@ -36,8 +36,10 @@ public sealed class GpuContractTests [Fact] public void StorageBindingsMatchTheShaderSources() { - // mesh_modern.vert declares std430 bindings 0..8 in exactly this order; - // binding 9 is the GL-only texture handle table added by slice V2. + // mesh_modern.vert declares std430 bindings 0..8 in exactly this order. + // Binding 9 was the GL-only texture handle table added by slice V2; + // Campaign V slice V11 deleted it (StorageTextureTable) along with the + // rest of the raw-GL arm, so 9 is now one past the highest binding. Assert.Equal(0u, GpuBindingModel.StorageInstances); Assert.Equal(1u, GpuBindingModel.StorageBatches); Assert.Equal(2u, GpuBindingModel.StorageClipRegions); @@ -47,8 +49,7 @@ public sealed class GpuContractTests Assert.Equal(6u, GpuBindingModel.StorageInstanceIndoor); Assert.Equal(7u, GpuBindingModel.StorageInstanceAlpha); Assert.Equal(8u, GpuBindingModel.StorageInstanceSelectionLighting); - Assert.Equal(9u, GpuBindingModel.StorageTextureTable); - Assert.Equal(10u, GpuBindingModel.StorageBindingCount); + Assert.Equal(9u, GpuBindingModel.StorageBindingCount); } [Fact] diff --git a/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanCapabilityGateTests.cs b/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanCapabilityGateTests.cs index e6ca461b..0937eaf2 100644 --- a/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanCapabilityGateTests.cs +++ b/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanCapabilityGateTests.cs @@ -219,11 +219,10 @@ public sealed class VulkanCapabilityGateTests Assert.False(VulkanPipelineLayouts.IsDynamicStorageBinding(GpuBindingModel.StorageGlobalLights)); Assert.False(VulkanPipelineLayouts.IsDynamicStorageBinding(GpuBindingModel.StorageClipRegions)); - // Binding 9 is the GL-only uvec2 handle-table emulation. The Vulkan - // backend binds set 2 instead and never touches it, so spending a scarce - // dynamic descriptor on it would be spending one on a binding that is - // provably never bound. - Assert.False(VulkanPipelineLayouts.IsDynamicStorageBinding(GpuBindingModel.StorageTextureTable)); + // Binding 9 (the GL-only uvec2 handle-table emulation, StorageTextureTable) + // is deleted as of Campaign V slice V11 — the Vulkan backend always bound + // set 2 instead and never touched it, so there is no longer a ninth + // binding to assert never spends a scarce dynamic descriptor. } [Fact] diff --git a/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanShaderManifestTests.cs b/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanShaderManifestTests.cs index 111ff7bb..3e17e00a 100644 --- a/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanShaderManifestTests.cs +++ b/tests/AcDream.App.Tests/Rendering/Gpu/Vk/VulkanShaderManifestTests.cs @@ -20,8 +20,11 @@ namespace AcDream.App.Tests.Rendering.Gpu.Vk; /// backend silently keeps rendering the old one. This test is what turns that /// into a red build. /// -/// It also pins which production shaders are Vulkan-expressible TODAY. Nine -/// of the ten pairs are not, and each failure is a specific source-level fact +/// It also pins which production shaders are Vulkan-expressible TODAY. As +/// of Campaign V slice V11 (which deleted the one pair that never was — +/// mesh.vert/mesh.frag, the pre-modern-pipeline shader the N.5 ship +/// amendment's mandatory modern path made unreachable) every remaining pair +/// compiles. A future non-ready pair's failure is a specific source-level fact /// belonging to a renderer-port slice that has not landed — not a toolchain gap. /// Recording them here means the next slice inherits an inventory rather than a /// rediscovery. diff --git a/tests/AcDream.App.Tests/Rendering/ParticleBindlessInstanceTests.cs b/tests/AcDream.App.Tests/Rendering/ParticleBindlessInstanceTests.cs index aba04240..69aaabfb 100644 --- a/tests/AcDream.App.Tests/Rendering/ParticleBindlessInstanceTests.cs +++ b/tests/AcDream.App.Tests/Rendering/ParticleBindlessInstanceTests.cs @@ -50,15 +50,17 @@ public sealed class ParticleBindlessInstanceTests } /// - /// Campaign V slice V6e: "this particle has no texture" is now a reserved - /// index rather than a null handle, and that value is written in three - /// places — the CPU that produces it, the GL preamble that tests it, and the - /// Vulkan preamble that will. Three copies of a magic number is a drift - /// waiting to happen, and its failure mode is silent: a particle would - /// sample slot 0xFFFFFFFF instead of drawing the procedural blob. + /// Campaign V slice V6e: "this particle has no texture" is a reserved index + /// rather than a null handle, and that value is written in two places — the + /// CPU that produces it and the Vulkan preamble that tests it in the shader. + /// Before Campaign V slice V11 deleted the GL arm there was a third copy in + /// common.glsl, which this test also cross-checked; two copies of a magic + /// number is still a drift waiting to happen, and its failure mode is + /// silent: a particle would sample slot 0xFFFFFFFF instead of drawing the + /// procedural blob. /// [Fact] - public void TheReservedNoTextureSlotAgreesAcrossCpuAndBothDialects() + public void TheReservedNoTextureSlotAgreesBetweenCpuAndTheVulkanPreamble() { const string literal = "0xFFFFFFFF"; @@ -67,13 +69,9 @@ public sealed class ParticleBindlessInstanceTests ?.GetRawConstantValue(); Assert.Equal(0xFFFFFFFFu, Assert.IsType(cpuValue)); - string common = File.ReadAllText(Path.Combine( - AppContext.BaseDirectory, "Rendering", "Shaders", "common.glsl")); - Assert.Contains($"#define ACDREAM_TEXTURE_NONE {literal}u", common); - - // The Vulkan half is injected by the offline compiler, not by - // common.glsl, so it is a separate declaration that has to say the same - // thing. + // The Vulkan half is injected by the offline compiler at compile time, + // not carried in a shared GLSL source, so it is a separate declaration + // that has to say the same thing. string preamble = File.ReadAllText(Path.Combine( RepositoryRoot(), "tools", "ShaderCompiler", "VulkanGlslPreamble.cs")); Assert.Contains($"#define ACDREAM_TEXTURE_NONE {literal}u", preamble); diff --git a/tests/AcDream.App.Tests/Rendering/RenderFrameResourceControllerTests.cs b/tests/AcDream.App.Tests/Rendering/RenderFrameResourceControllerTests.cs index 34c60eb3..c19cff39 100644 --- a/tests/AcDream.App.Tests/Rendering/RenderFrameResourceControllerTests.cs +++ b/tests/AcDream.App.Tests/Rendering/RenderFrameResourceControllerTests.cs @@ -79,21 +79,15 @@ public sealed class RenderFrameResourceControllerTests "_particles?.BeginFrame(gpuSlot);"); } - [Fact] - public void Production_clear_phase_preserves_atmosphere_and_gl_state_order() - { - string source = ResourceSource(); - - AssertAppearsInOrder( - source, - "bool portalViewportVisible = _portal.IsPortalViewportVisible;", - "_particleVisibility.Reset();", - "SkyKeyframe sky = _worldTime.CurrentSky;", - "AtmosphereSnapshot atmosphere = _weather.Snapshot(in sky);", - "_frameGlState.RestoreFrameDefaults();", - "_gl.Clear(", - "_diagnostics.EmitGlStateTripwireIfChanged("); - } + // Campaign V slice V11 deleted RuntimeRenderFrameClearPhase (the raw-GL + // IRenderFrameClearPhase implementation this test pinned): its whole + // subject — the frame-global GL state restore, glClear, and GL-state + // tripwire, in that order — no longer exists anywhere. The ordering it + // shared with the surviving Vulkan implementation (portal check → + // particle reset → sky → atmosphere) has no GL-specific steps left to + // pin, since VulkanRenderFrameClearPhase (VulkanCompositionFramePhases.cs) + // hands the clear colour to a pass load-op instead of restoring ambient + // state and issuing glClear. [Fact] public void Weather_frame_clock_advances_only_after_the_weather_tick() diff --git a/tests/AcDream.App.Tests/Rendering/Wb/MeshPipelineDeviceSeamTests.cs b/tests/AcDream.App.Tests/Rendering/Wb/MeshPipelineDeviceSeamTests.cs index 0d1e70b8..ff9f00a6 100644 --- a/tests/AcDream.App.Tests/Rendering/Wb/MeshPipelineDeviceSeamTests.cs +++ b/tests/AcDream.App.Tests/Rendering/Wb/MeshPipelineDeviceSeamTests.cs @@ -7,7 +7,6 @@ using System.Threading; using AcDream.Content; using Chorizite.Core.Render.Enums; using Microsoft.Extensions.Logging.Abstractions; -using Silk.NET.OpenGL; namespace AcDream.App.Tests.Rendering.Wb; @@ -16,17 +15,18 @@ namespace AcDream.App.Tests.Rendering.Wb; /// /// Plan §5.5.10 recorded the blocker as a fact about types — "WbMeshAdapter /// owns an OpenGLGraphicsDevice, so it is not constructible on Vulkan" — which is -/// why NullWbMeshAdapter exists. §5.5.12 item 6 measured how wide the -/// dependency really is: a GL context, the retirement queue, the instance VBO, +/// why NullWbMeshAdapter existed. §5.5.12 item 6 measured how wide the +/// dependency really was: a GL context, the retirement queue, the instance VBO, /// and two capability flags. This suite proves the interface at that surface is /// load-bearing rather than cosmetic, by building the object graph against a /// device that has NO GL context at all. /// -/// It deliberately proves construction and nothing more. The upload bodies -/// are still raw GL and the world renderers still bind a GL handle table; both -/// belong to the slice that draws Dereth on Vulkan. What matters here is that -/// each of those now fails at the site that needs GL, naming why, instead of -/// throwing a cast before the constructor has run a statement. +/// It originally proved construction and nothing more, back when the +/// upload bodies were still raw GL and the world renderers still bound a GL +/// handle table. Campaign V slice V11 deleted both along with the rest of the +/// raw-GL arm (and the interface's own Gl member, which nothing read any +/// more once they were gone) — the arena-build and upload tests below now cover +/// what those slices only asserted would eventually fail loudly. /// public sealed class MeshPipelineDeviceSeamTests { @@ -36,8 +36,6 @@ public sealed class MeshPipelineDeviceSeamTests bool modernPath = false) : IMeshPipelineDevice { - public GL? Gl => null; - public IGpuResourceRetirementQueue ResourceRetirement { get; } = retirement; public uint InstanceVBO => 0; @@ -120,8 +118,9 @@ public sealed class MeshPipelineDeviceSeamTests } /// - /// The seam's whole value is that it is NARROW — seven members measured out - /// of a 760-line class. A later slice that quietly widens it back out would + /// The seam's whole value is that it is NARROW — six members measured out + /// of a 760-line class (seven until Campaign V slice V11 deleted the unread + /// Gl member). A later slice that quietly widens it back out would /// re-couple the mesh pipeline to a backend without any other gate noticing, /// so the member set is pinned rather than described. /// @@ -143,7 +142,6 @@ public sealed class MeshPipelineDeviceSeamTests Assert.Equal( [ - "Gl", "HasBindless", "HasOpenGL43", "HasPendingWork", @@ -244,7 +242,6 @@ public sealed class MeshPipelineDeviceSeamTests using var vulkanDevice = new AcDream.App.Rendering.Gpu.Vk.VulkanMeshPipelineDevice(device.Retirement); - Assert.Null(vulkanDevice.Gl); Assert.True(vulkanDevice.HasBindless); Assert.True(vulkanDevice.HasOpenGL43); Assert.False(vulkanDevice.HasPendingWork); diff --git a/tests/AcDream.App.Tests/Rendering/Wb/TextureAtlasCapacityTests.cs b/tests/AcDream.App.Tests/Rendering/Wb/TextureAtlasCapacityTests.cs index 9e618d9f..a7a3f62f 100644 --- a/tests/AcDream.App.Tests/Rendering/Wb/TextureAtlasCapacityTests.cs +++ b/tests/AcDream.App.Tests/Rendering/Wb/TextureAtlasCapacityTests.cs @@ -1,6 +1,6 @@ using AcDream.App.Rendering.Wb; +using AcDream.Content; using Chorizite.Core.Render.Enums; -using Silk.NET.OpenGL; namespace AcDream.App.Tests.Rendering.Wb; @@ -33,7 +33,7 @@ public sealed class TextureAtlasCapacityTests public void DirectUploadAcceptsExactRgbaPayload() { RhiWorldTextureArray.ValidateUploadPayload( - TextureFormat.RGBA8, 2, 2, 16, PixelFormat.Rgba, PixelType.UnsignedByte); + TextureFormat.RGBA8, 2, 2, 16, UploadPixelFormat.Rgba, UploadPixelType.UnsignedByte); } [Theory] @@ -43,7 +43,7 @@ public sealed class TextureAtlasCapacityTests { Assert.Throws(() => RhiWorldTextureArray.ValidateUploadPayload( - TextureFormat.RGBA8, 2, 2, bytes, PixelFormat.Rgba, PixelType.UnsignedByte)); + TextureFormat.RGBA8, 2, 2, bytes, UploadPixelFormat.Rgba, UploadPixelType.UnsignedByte)); } [Fact] @@ -51,10 +51,10 @@ public sealed class TextureAtlasCapacityTests { Assert.Throws(() => RhiWorldTextureArray.ValidateUploadPayload( - TextureFormat.RGBA8, 2, 2, 16, PixelFormat.Rgb, PixelType.UnsignedByte)); + TextureFormat.RGBA8, 2, 2, 16, UploadPixelFormat.Rgb, UploadPixelType.UnsignedByte)); Assert.Throws(() => RhiWorldTextureArray.ValidateUploadPayload( - TextureFormat.RGBA8, 2, 2, 16, PixelFormat.Rgba, PixelType.Float)); + TextureFormat.RGBA8, 2, 2, 16, UploadPixelFormat.Rgba, UploadPixelType.Float)); } [Fact] @@ -63,7 +63,7 @@ public sealed class TextureAtlasCapacityTests int bytes = RhiWorldTextureArray.CalculateExpectedDataSize(TextureFormat.DXT1, 4, 4); Assert.Throws(() => RhiWorldTextureArray.ValidateUploadPayload( - TextureFormat.DXT1, 4, 4, bytes, PixelFormat.Rgba, PixelType.UnsignedByte)); + TextureFormat.DXT1, 4, 4, bytes, UploadPixelFormat.Rgba, UploadPixelType.UnsignedByte)); } [Fact] @@ -71,6 +71,6 @@ public sealed class TextureAtlasCapacityTests { Assert.Equal(18, RhiWorldTextureArray.CalculateExpectedDataSize(TextureFormat.RGB8, 3, 2)); RhiWorldTextureArray.ValidateUploadPayload( - TextureFormat.RGB8, 3, 2, 18, PixelFormat.Rgb, PixelType.UnsignedByte); + TextureFormat.RGB8, 3, 2, 18, UploadPixelFormat.Rgb, UploadPixelType.UnsignedByte); } } diff --git a/tests/AcDream.App.Tests/Rendering/WorldRenderDiagnosticsTests.cs b/tests/AcDream.App.Tests/Rendering/WorldRenderDiagnosticsTests.cs index 319bd3d8..5c2b2a41 100644 --- a/tests/AcDream.App.Tests/Rendering/WorldRenderDiagnosticsTests.cs +++ b/tests/AcDream.App.Tests/Rendering/WorldRenderDiagnosticsTests.cs @@ -99,29 +99,12 @@ public sealed class WorldRenderDiagnosticsTests field => typeof(Delegate).IsAssignableFrom(field.FieldType)); } - [Fact] - public void SilkGlReader_ConsumesEnteringErrorBeforeStateQueries() - { - string source = File.ReadAllText(Path.Combine( - FindRepoRoot(), - "src", - "AcDream.App", - "Rendering", - "WorldRenderDiagnostics.cs")); - - int captureStart = source.IndexOf( - "public RenderGlStateSnapshot CaptureState()", - StringComparison.Ordinal); - int error = source.IndexOf("_gl.GetError()", captureStart, StringComparison.Ordinal); - int depth = source.IndexOf( - "_gl.IsEnabled(EnableCap.DepthTest)", - captureStart, - StringComparison.Ordinal); - - Assert.True(captureStart >= 0); - Assert.True(error > captureStart); - Assert.True(depth > error); - } + // Campaign V slice V11 deleted SilkRenderGlStateReader, the raw-GL + // IRenderGlStateReader implementation this test pinned (it was never + // constructed in production — NullRenderGlStateReader.Instance is the + // sole surviving implementer), so the "consume the entering GL error + // before other state reads" source-order tripwire it protected has + // nothing left to pin. [Fact] public void TerrainDiagnostics_RetryFailedPublicationWithoutLosingSamples() @@ -276,17 +259,4 @@ public sealed class WorldRenderDiagnosticsTests exteriorPartition: null, cameraPosition: Vector3.Zero, playerPosition: Vector3.Zero); - - private static string FindRepoRoot() - { - DirectoryInfo? directory = new(AppContext.BaseDirectory); - while (directory is not null) - { - if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx"))) - return directory.FullName; - directory = directory.Parent; - } - - throw new DirectoryNotFoundException("Could not find AcDream.slnx."); - } } diff --git a/tests/AcDream.Core.Tests/Rendering/Wb/WbDrawDispatcherIndirectBuilderTests.cs b/tests/AcDream.Core.Tests/Rendering/Wb/WbDrawDispatcherIndirectBuilderTests.cs index 61d4b366..50b947b5 100644 --- a/tests/AcDream.Core.Tests/Rendering/Wb/WbDrawDispatcherIndirectBuilderTests.cs +++ b/tests/AcDream.Core.Tests/Rendering/Wb/WbDrawDispatcherIndirectBuilderTests.cs @@ -134,8 +134,9 @@ public sealed class WbDrawDispatcherIndirectBuilderTests // // Campaign V slice V2 (2026-07-27): TextureHandle (ulong, an // ARB_bindless_texture handle) became TextureIndex (uint) plus an - // explicit Reserved pad word — a slot into the binding=9 handle table - // (GpuBindingModel.StorageTextureTable) instead of the raw handle. + // explicit Reserved pad word — a slot into the GL-only binding=9 handle + // table instead of the raw handle (that binding, GpuBindingModel's + // former StorageTextureTable, was deleted at Campaign V slice V11). // The struct stays 16 bytes and TextureLayer/Flags keep their offsets // (8/12), matching GpuBindingModel.GpuBatchDataStrideBytes and every // existing CPU writer, so both structs only need 4-byte packing now. diff --git a/tests/AcDream.Core.Tests/Rendering/Wb/WbMeshAdapterTests.cs b/tests/AcDream.Core.Tests/Rendering/Wb/WbMeshAdapterTests.cs index 9d2eb7ef..5604ff8c 100644 --- a/tests/AcDream.Core.Tests/Rendering/Wb/WbMeshAdapterTests.cs +++ b/tests/AcDream.Core.Tests/Rendering/Wb/WbMeshAdapterTests.cs @@ -1,21 +1,21 @@ using System; using AcDream.App.Rendering.Wb; using Microsoft.Extensions.Logging.Abstractions; -using Silk.NET.OpenGL; namespace AcDream.Core.Tests.Rendering.Wb; public sealed class WbMeshAdapterTests { [Fact] - public void Construct_WithNullGl_ThrowsArgumentNull() + public void Construct_WithNullGpuDevice_ThrowsArgumentNull() { - // GL is the first guarded parameter; verifies the constructor validates inputs. - // We can't pass a real GL (no context in tests), so we verify only the - // null-GL guard. The real pipeline is tested via integration. + // gpuDevice is the first guarded parameter (Campaign V slice V11 deleted + // the GL? gl parameter this test used to pass null through — the + // constructor never null-checked it; this assertion was always really + // exercising gpuDevice's guard). The real pipeline is tested via + // integration. Assert.Throws(() => new WbMeshAdapter( - gl: null!, gpuDevice: null!, dats: null!, logger: NullLogger.Instance));