Commit 2 deleted the GL rendering backend's implementations; this step
removes the package references and shader vocabulary they leave behind,
so nothing in the App project still spells Silk.NET.OpenGL.
Silk.NET.OpenGL and Silk.NET.OpenGL.Extensions.ARB are dropped from
AcDream.App.csproj. Chorizite.Core stays — the audit is NOT clean: its
Render.Enums (TextureFormat, BufferUsage) and Lib.BoundingBox types are
used directly and extensively across the Wb texture/mesh pipeline,
independent of the deleted GL IUniformBuffer implementers the package
comment used to cite. The stale comment is corrected in place.
IMeshPipelineDevice.Gl is removed along with the GL? gl parameter
threaded through WbMeshAdapter's four constructors, WorldRenderComposition's
CreateMeshAdapter, and VulkanMeshPipelineDevice's Gl => null
implementation — nothing read any of them once the legacy per-mesh
upload bodies were gone (confirmed by grep: the sole non-doc-comment hit
was a test assertion). While in WbMeshAdapter.Dispose(), found and fixed
a real bug along the way: its teardown still pattern-matched the deleted
GL GpuFrameFlightController to decide whether to wait for submitted work,
which VulkanFrameFlightController replaced at slice V6a without this site
being updated — so the wait had been silently dead on every Vulkan run
since then. Retargeted to VulkanFrameFlightController, which carries the
same WaitForSubmittedWork().
The GL pixel-format vocabulary (Silk.NET.OpenGL.PixelFormat/PixelType) that
WorldTextureArray/TextureFormatExtensions/TextureAtlasManager used for
upload validation is replaced by AcDream.Content's existing Silk.NET-free
UploadPixelFormat/UploadPixelType enums (added at MP1a to keep the bake
tool GL-free); two new members (Rgb, Red, Float) extend that enum with
their GL ABI constants to cover the full vocabulary WorldTextureArray
needs, since MP1a's original set only covered what the extractor itself
emits. ObjectMeshManager's App-boundary cast
`(Silk.NET.OpenGL.PixelFormat?)batch.UploadPixelFormat` becomes a direct
pass-through now that both sides share the type.
GpuBindingModel.StorageTextureTable (the GL-only binding=9 emulation of
the Vulkan texture table) is deleted and StorageBindingCount drops from
10 to 9; the descriptor-set-layout code that builds from that count
(VulkanPipelineLayouts, VulkanFrameBindings) is untouched and just
allocates one fewer always-dummy-seeded, always-unused binding.
Several fully dead GL-only classes came along for the ride, confirmed by
zero construction sites: SilkFramebufferViewportTarget
(NullFramebufferViewportTarget is the sole production
IFramebufferViewportTarget), SilkRenderGlStateReader
(NullRenderGlStateReader.Instance is the sole IRenderGlStateReader),
RuntimeRenderFrameClearPhase (VulkanRenderFrameClearPhase is the sole
IRenderFrameClearPhase, expressing the same atmosphere-clear logic as a
pass load-op instead), and GpuFrameTimer plus FrameProfiler's
GL-owning FrameBoundary(GL) overload and BeginGpuFrame/EndGpuFrame
bracket (RecordGpuSample is the only GPU-timing path any backend uses
now — the ACDREAM_WB_DIAG nested-query exclusion these existed for no
longer applies, since WbDrawDispatcher's own diagnostic GPU sampling
already moved to the device's Vulkan timer pool). GpuFrameFlightController
itself stays (never constructed with a real fence API in production, but
its retirement-ledger/serial-ring logic is backend-neutral and still
covered by its own unit tests) — only its GL-specific parts (the public
GL constructor overload, SilkGpuFenceApi) are deleted, since removing the
whole class would mean restructuring the frozen Slice-8 composition
shape's GpuFrameFlightController? threading, which is out of this
commit's scope. TextureParameters.cs and BufferUsageExtensions.cs
(zero callers each) are deleted outright.
common.glsl is deleted: nothing in the actual Vulkan .spv build reads
it. tools/ShaderCompiler/Program.cs compiles each .vert/.frag pair
directly and tools/ShaderCompiler/VulkanGlslPreamble.cs injects its own
complete self-contained preamble per file; common.glsl's textual
concatenation was exclusively Shader.cs's GL-only mechanism, deleted at
Commit 2. The five shader files that named it in comments
(mesh_modern.vert, particle.vert, particle.frag, sky.frag,
terrain_modern.frag) are corrected to point at VulkanGlslPreamble.cs
instead. mesh.vert/mesh.frag — the pre-N.5 legacy shader pair the
mandatory modern path already made unreachable, with zero C# consumers
and no compiled .spv — are deleted too. Regenerated via
tools/compile-shaders.ps1: 9/9 remaining shader pairs compile
(previously 9/10, with mesh the sole failure — the VulkanShaderManifestTests
doc comment's "nine of ten are not Vulkan-expressible" was already
stale before this commit).
Test fallout: dead-subject test methods/files are deleted rather than
patched (TextRendererFailureSafetyTests.cs, ClipFrameUploadTests.cs,
GpuResourceRetirementTransactionTests.cs's GL queue tests, one
WorldRenderDiagnosticsTests source-order test, one
RenderFrameResourceControllerTests clear-phase-order test); tests whose
subject moved or was renamed are updated in place rather than deleted
(GpuContractTests, VulkanCapabilityGateTests, MeshPipelineDeviceSeamTests'
pinned seven-member surface now reads six, ParticleBindlessInstanceTests'
cross-dialect check now covers the one surviving dialect,
WbMeshAdapterTests' misleadingly-named null-gl test — gpuDevice was
always the parameter that actually threw).
Build: `dotnet build AcDream.slnx -c Release` — 0 warnings, 0 errors,
with the Silk.NET.OpenGL/.Extensions.ARB package references physically
removed from the csproj (not just unreferenced in code).
Tests: full-solution `dotnet test` green across every project.
Zero remaining `using Silk.NET.OpenGL` anywhere in src/ or tests/.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Campaign V slice V6e, second of three. Billboard particles and mesh particles
are the last two pairs blocked on the texture-table shape; sky follows.
particle takes the same treatment mesh_modern took: the `flat uvec2` handle
varying becomes a `flat uint` slot and the fragment stage samples through
ACDREAM_SAMPLE_ARRAY. What is different here is the untextured particle. The
shader used to ask "is the handle I was given zero", which GL can answer because
its emulated table stores handles; Vulkan cannot, because set 2 is an opaque
descriptor array and reading an element nobody wrote is undefined rather than
zero. So the question moves to the index: the CPU writes ACDREAM_TEXTURE_NONE
for a particle with no texture instead of interning the null handle as a table
slot, and both dialects test the same value. GL renders identically — the same
particles take the same branch to the same procedural blob — and the handle
table simply stops carrying an entry that never named a texture. A test pins the
sentinel across all three declarations of it, because a silent disagreement here
would sample slot 0xFFFFFFFF instead of drawing the blob.
particle_mesh needed no restructuring, only names. Vulkan GLSL has no default
uniform block, so `uniform uint uTextureIndex;` is not unsupported but
unspellable, and the two values are per-pass — one texture and one layer for a
whole sub-batch — which is exactly what the shared push-constant block is for.
uTextureIndex becomes uTextureIndexA; uTextureLayer becomes uParamA, which was
the spare scalar and is a natural fit because the shader converted the layer to
float anyway. The widening moved from the shader to the CPU; layers are small
integers, so the sampled value is bit-identical.
Gates: Release build clean; App tests 4,058 passed / 3 skipped (baseline 4,057
plus the sentinel drift guard). Offline pixel gate against 95f8c25f: two
captures, 29 px and 21 px of 563,200 compared (3.73e-05 and 5.15e-05), with a
same-commit control between them of 13 px and this commit measuring 14 px
against its own parent. The scene draws no particles, so this gate is a tripwire
that the world path is undisturbed, not evidence about particles.
Particles remain user-gate debt — the same debt V2c and V4e already carry, to be
paid by casting a spell in a connected session.
Manifest: 6/9 pairs compile. Remaining: mesh (legacy, no consumer), sky (next
commit), terrain_modern.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Completes Campaign V slice V2 by moving both particle render paths -
billboard (particle.vert/.frag) and mesh-emitter (particle_mesh.vert/.frag) -
from raw 64-bit ARB_bindless_texture handles to binding=9 handle-table
indices, matching V2a's mesh path and V2b's terrain path.
Particles differ from both prior sub-slices in HOW the handle reaches the
shader:
- Billboard particles carry it as a per-INSTANCE vertex attribute (not an
SSBO batch or a per-draw uniform), because each particle can use a
different texture within one instanced draw. aTextureHandle (location 6,
uvec2) became aTextureIndex (uint); particle.vert looks it up via
ACDREAM_TEXTURE_HANDLE and reconstructs the SAME uvec2 into vTextureHandle
exactly as before, so particle.frag - including its zero-handle check for
the procedural circle fallback - needed no change at all. The per-instance
ABI struct BillboardGpuInstance shrank by 4 bytes (one uint slot instead of
two uint handle halves); ParticleBindlessInstanceTests updated for the new
68-byte layout and the vertex attribute declaration text.
- Mesh-emitter particles carry it as a per-draw uniform (uTextureHandle,
uvec2) exactly like terrain's pattern from V2b: one texture per draw call,
set right before it. Became uTextureIndex (uint) + the same
ACDREAM_TEXTURE_HANDLE lookup.
ParticleRenderer owns its own GlBindlessHandleTable and binding=9 SSBO,
independent of the other three renderers' tables, created eagerly in the
constructor alongside the other GL resources it already creates there. Unlike
the other three renderers, flushing/binding the table happens immediately
before EVERY individual draw call (four call sites: immediate billboard,
immediate mesh, and both halves of the deferred/prepared RetailAlphaQueue
path) rather than once per pipeline-state switch - a run of consecutive
mesh-particle sub-batches can register a new handle partway through (each
sub-batch has its own texture), and the table must be current for each one,
not just the first.
TextureCache's particle-texture cache (AcquireParticleTexture,
StandaloneBindlessTextureCache) needed no change: it only ever hands back a
raw ulong handle, and both ParticleGfxInfo.TextureHandle and
ParticleInstance.TextureHandle keep carrying that raw value - the table
lookup is added exactly where each path already converts its handle into
GPU-visible state (WriteBillboardGpuInstance and the two ProgramUniform
call sites).
Coverage caveat (flagged per the campaign doc's slice table): the offline
pixel gate's fixed outdoor view has no particles in frame, so it does not
exercise this slice - it only confirms nothing else regressed. This change
is correspondingly kept strictly mechanical (indirection only, no logic
change), but it still needs a user visual check with live particle emitters
before being trusted as pixel-identical.
Gate: dotnet build -c Release green, dotnet test tests/AcDream.App.Tests
-c Release green (3843 passed / 3 skipped on a clean run - one unrelated
pre-existing flaky allocation test, UiDatFontTests, failed once and passed
on immediate re-run in isolation and in the full suite, confirmed unrelated
to this change), and tools/run-offline-pixel-gate.ps1 passed against the
V2b commit's build with a 4.62e-05 differing-pixel fraction (a tripwire
only, per the coverage caveat above). No divergence-register row: this
introduces no retail behavior deviation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Carry each billboard particle's resident texture handle in the instance ABI so stable retail alpha ordering can batch mixed textures without rebinding and redrawing each short texture run. Keep DAT blend transitions as the only billboard draw boundary.
The connected 0xC95B stress scene improved from 6 FPS / 171 ms to about 153 FPS / 6.6 ms at the same roughly 21,000 visible entities. Release build succeeds and all 5,916 tests pass with five intentional skips.
Co-authored-by: OpenAI Codex <codex@openai.com>