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