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));