Commit graph

3 commits

Author SHA1 Message Date
Erik
7a0227c12e feat(render): Vulkan campaign V11 step 3 — drop the GL packages and shaders
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>
2026-07-29 02:58:15 +02:00
Erik
24834a6478 fix(render): Campaign V slice V6g — the Vulkan frame stops lying to the driver
V6f ran the bring-up host once under VK_LAYER_KHRONOS_validation and found
seven VUIDs, every one of them on the path any world frame takes (plan
§5.5.7). This closes all of them, plus a fourth defect in the same log that
§5.5.7 did not call out. The host now runs validation-clean: zero errors and
zero warnings over 39,855 frames.

Nothing outside Gpu/Vk/ is touched, so the GL backend executes not one changed
statement. The offline pixel gate says so too — 4.08e-05 differing fraction
against f8dbe2ee, which is exactly the value the campaign recorded as its own
same-commit control (§5.1's 15–23 pixel band).

The dynamic-descriptor limit was a decision, not a patch. V6b declared all ten
of set 0's bindings STORAGE_BUFFER_DYNAMIC on the reasoning that the contract
lets a renderer bind any range per draw. That is true and still cost nothing to
honour for four of them: a dynamic descriptor buys exactly one thing, the
ability to address the SAME buffer at a DIFFERENT offset without a descriptor
write, which is the shape of a ring allocation and of nothing else. So the
ring-fed bindings — instances, batches, clip slots, instance light sets — stay
dynamic, and the ones pointing at a long-lived buffer written whole and bound
once per pass carry their offset in the descriptor instead. Binding 9 is the
clearest of those: it is the GL-only uvec2 handle table, which the Vulkan
backend never binds at all.

That lands on four dynamic storage descriptors. The RX 9070 XT allows eight, so
eight would have worked here — but four is Vulkan's GUARANTEED minimum, which
means no conformant device can fail this layout, and V9's lavapipe row and the
deferred physical Linux row both depend on that. The count is asserted against
maxDescriptorSetStorageBuffersDynamic in the capability record, so a device that
cannot serve it is rejected at startup in the report under the same exit-code-4
contract as every other requirement, rather than failing silently at
vkCreatePipelineLayout the way this one did.

Depth-off pipelines were malformed in any pass that has depth. Dynamic rendering
bakes the depth/stencil attachment format into the pipeline and requires it to
equal the pass's; V6c set it only when the pipeline itself tested or wrote
depth. Debug lines, the retained UI and the sky are all depth-off and all
composite over the main pass, so this was not an edge case. The same
GpuPipelineDescription is legitimately used both ways — ui-text opens its own
depth-less pass — so the description cannot answer the question and the backend
builds both variants, binding whichever matches what vkCmdBeginRendering was
actually handed rather than what the pass asked for. Both are built at startup
against the persisted cache, so no frame compiles one. A slice entitled to
change the contract should add a depth-format field the way V6d added
ColorFormat; this is the honest expression of the gap until then.

vk-backbuffer-depth and vk-backbuffer-msaa-color were created UNDEFINED and
never moved. Both now barrier on every backbuffer pass — from UNDEFINED on the
first use after Configure, from attachment-optimal with a write-after-write
dependency thereafter. The dependency matters on its own account, not just the
layout: two passes in one frame write both images and so does the next frame,
and Vulkan orders nothing between render-pass instances.

The fourth defect is the one worth reading twice. CaptureBackbuffer transitioned
the LAST PRESENTED swapchain image to TRANSFER_SRC and copied out of it. After
vkQueuePresentKHR that image belongs to the presentation engine and its contents
are not ours to read — and the pixels were usually right, which is precisely the
problem. This campaign spent three sections of its own plan (§5.5.1–§5.5.3)
discovering how much a capture instrument that is "usually right" can cost, and
shipping that shape on the new backend would have made every Vulkan PNG, and the
V7 differential built on them, formally undefined. The frame now copies its own
output into a host-readable buffer while it still owns the image, and the
capture reads that. Retention is opt-in, armed when an artifact directory
exists: one full-resolution copy per frame is worth nothing to a player and is
the entire instrument to a gate. The old one-shot command pool, device-idle wait
and per-capture readback buffer go with it.

Two gaps found and recorded in §5.5.8 rather than fixed, both outside this
slice's brief. UniformSkyParams (set 1, binding 4) is not in the uniform set
layout, so whoever first draws sky on Vulkan must add it. And a binding pointed
at two different buffers within one frame silently corrupts the earlier draws,
on dynamic and plain descriptors alike, because descriptor contents are read at
execution time — no consumer does that today, but WbDrawDispatcher and
EnvCellRenderer each own their own instance and batch buffers and both bind
bindings 0, 1, 3, 4 and 5 in one frame, so the Vulkan world arm has to know
before it is written.

Gates: Release build; App tests 4,075 passed / 3 skipped (baseline 4,073 + the
two new capability cases); GL offline pixel gate PASS at 4.08e-05; one
validation-layer Vulkan run, clean, with the captured PNG inspected and correct
in orientation, colour and glyph coverage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 10:47:52 +02:00
Erik
e8a4c1af3f feat(render): Campaign V slice V5 - Vulkan bring-up, dark
Instance, physical-device selection, logical device, queues, swapchain, and the
three-layer capability gate, behind ACDREAM_RENDER_BACKEND=vulkan. Nothing of
the game renders through it. OpenGL stays the default and the only live backend
until V10, and with the variable unset or set to gl the GL path executes not one
new statement.

The shape of the slice. Plan §4.11 asks the Vulkan gate to mirror the GL one
exactly - passive record, active probes, an Evaluate producing operator-facing
sentences, NotSupportedException into Program.cs's exit-code-4 contract, and an
atomic JSON report. The harder question was where to put the seam, because a
capability gate is precisely the code you cannot exercise on the machine that
already passes it: this box has one discrete GPU, so device ranking, the split-
queue path, an sRGB-only surface, a minimised window and a device missing
descriptorBindingVariableDescriptorCount are all unreachable by running the
client. So every decision the gate makes is a pure function over plain records,
and the Silk interop layer only has to be right about which Vulkan field feeds
which property. VulkanPhysicalDeviceSelection ranks candidates,
VulkanExtensionSelection does the required-versus-optional set arithmetic,
VulkanSwapchainConfigurationFactory chooses format, present mode, image count,
extent, usage, transform and composite alpha, VulkanSwapchainRecreationPolicy
classifies every acquire and present result, and
VulkanCapabilityRequirements.Evaluate turns a captured record into failure
sentences. All of it is unit-tested with no driver, no device and no window.

This commit is the integration of that work onto the post-revert tree. The V5
branch was written on b064668b, before V4c/V4d were reverted, so GameWindow.cs
had to be merged rather than taken: the file here is eb2ba4e5's GameWindow plus
V5's fifteen-line backend branch, and it keeps _terrainModernShader, which the
revert restored and which the V5 branch never had. Every other file is byte-
identical to the branch - git diff e1ef4313 over Rendering/Gpu/Vk,
tests/.../Gpu/Vk and RenderBackendKind.cs is empty, no BOM was introduced, and
CRLF is uniform across all seventeen files.

Gate results, recorded verbatim.

Release build: succeeded, 0 warnings, 0 errors.

App tests, Release: Failed 0, Passed 3981, Skipped 3, Total 3984 - the 3,866
baseline plus V5's 115 new tests, exactly.

Offline pixel gate against eb2ba4e5: PASS world-offline.png, differing fraction
1.06534090909091E-05, which is 6 differing pixels out of the 563,200 compared
after the top 280 sky rows are masked. §5.1's re-measured same-commit control
band is 15-23 pixels at fraction <= 4.1e-05, so this sits below the noise floor
rather than merely inside it - the expected result for a slice that adds no
statement to the GL path.

Vulkan check (a), ACDREAM_RENDER_BACKEND=vulkan on the RX 9070 XT with an
automation artifact directory:

  vulkan: capability gate passed (Windows, AMD Radeon RX 9070 XT, Vulkan
  1.4.349, vendor 0x1002, device 0x7550, driver 2.0.395 (raw 0x0080018B));
  swapchain B8G8R8A8Unorm/PresentModeImmediateKhr 1280x720 x3
  vulkan: device selection - automatic: 'AMD Radeon RX 9070 XT' (DiscreteGpu,
  15.92 GiB device-local) ranked first of 2 enumerated device(s).
  [world-gate] screenshot-complete name=vulkan-bringup path=...
  artifacts\vk-bringup\vulkan-bringup.png size=1280x720
  vulkan: presented 64609 clear-colour frame(s); shutting down.

Exit code 0 on CloseMainWindow. The PNG is 5,238 bytes, 1280x720, and uniformly
RGBA(11,19,39,255) - exactly ClearColor [0.043, 0.075, 0.153, 1] scaled to
UNORM. Orientation is right-side-up by construction rather than by inspection,
which a uniform clear could not show: VulkanBackbufferSwizzle.ToGlOriginRgba
writes source row y into destination row height-1-y precisely because
FrameScreenshotController flips again on the way to the PNG, so the two
cancel. That double-flip is unit-tested.

Vulkan check (b), ACDREAM_VULKAN_FORCE_UNSUPPORTED=timelineSemaphore:

  [ERR] acdream's Vulkan renderer is unsupported by the selected device.
  Platform: win-x64, Windows, AMD Radeon RX 9070 XT (DiscreteGpu), Vulkan
  1.4.349, vendor 0x1002, device 0x7550, driver 2.0.395 (raw 0x0080018B)
   - timelineSemaphore is required; the frame serial is the semaphore value.
  Full capability report: ...\diagnostics\graphical-capabilities-vulkan.json

Exit code 4. The report records ForcedUnsupportedFeature timelineSemaphore,
TimelineSemaphore false against an otherwise complete feature set, and the
matching SupportFailures sentence, so the injected rejection is distinguishable
from a genuinely absent feature. Both enumerated devices, all five surface
formats, all four present modes and a clean FunctionProbe with no failures are
recorded beside it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 07:11:50 +02:00