Vulkan backend with TAA and GTAO - #110
Draft
NightHammer1000 wants to merge 293 commits into
Draft
NightHammer1000 wants to merge 293 commits into
NightHammer1000 wants to merge 293 commits into
Conversation
Conflict in patches/VintagestoryLib/Vintagestory.Client.NoObf/ClientPlatformWindows.cs.patch resolved by porting branch 3's DisposeFrameBuffers HashSet dedupe into build/VintagestoryLib (the source of truth, git-ignored) on top of the main session's TAA shader-reload work, then re-running scripts/extract-patches.sh.
Injected static fields get no initializer (vanilla's static ctor runs), so the per-renderer gate crashed the first entity frame with a NullReferenceException. Verified in game on Vulkan and OpenGL; coverage test guards the lazy form.
…unwritten outputs, precise barrier accesses, validation that cannot be missed - frame submit waits for the acquired swapchain image at all stages (first use is the present blit, a transfer) - render-finished semaphore owned by the acquired image, not a rolling counter - pipelines zero the colour write mask of every attachment the fragment shader never stores to (GL keeps contents, Vulkan wrote undefined values into the SSAO G-buffer under fullscreen passes) - barrier access masks follow the layouts; read-only depth still makes its storeOp write available - zero-instance draws are no-ops - OPTIMUM_VULKAN_VALIDATION=1 logs to $TMP/optimum-vulkan-validation.log; OPTIMUM_VULKAN_VALIDATION_FEATURES=sync,best,gpu via VK_EXT_validation_features - layer messages with braces no longer throw in the client logger Verified: synchronization + best-practices validation in game shows no backend hazards; 322+8 GPU tests, Optimum.Tests green.
… pass culling, decals window) ChunkRenderer.RenderLiquidMotion: useSSBOs, the matrix pop and the blend restore all ran inside the try after the pool draws, so a throwing draw left the renderer with SSBOs off, an unbalanced matrix stack and blending off. The useSSBOs snapshot is now taken before the try and every restore sits in the finally, guarded by a pushedMatrix flag. ClientPlatformWindows.RenderOptimumSkyMotion: GlDisableCullFace() runs before BeginMotionOnlyWrite(), so neither the early return nor the finally undid it. Both paths call GlEnableCullFace() now. SystemRenderDecals.OnRenderFrame3D: the GL setup, shader activation and uniform setup ran between BeginMotionWrite() and the try; they are inside the try now, so EndMotionWrite() always runs. Verified: dotnet build VintageStory.slnx -c Release, scripts/extract-patches.sh + scripts/check-patches.sh (157 patches, 0 conflicts, 22 runtime donors), dotnet test Optimum.Tests -c Release (923 passed) including three new source-coverage assertions that the restores sit in the finally blocks. Not verified in game (no launch in this worktree).
…all tests green
Adds the tooling P5 needs before the acceptance matrix can be run; no renderer
behaviour changes and nothing new runs on the GPU.
- scripts/dev/perf-capture.sh: launches through scripts/dev/run-client.sh with
RENDERER=<backend>, optionally rewrites "Taa" in optimum.json, waits for the
"[Client Chat] Welcome" line plus an 8 s warm-up, records a 30 s window, closes
through scripts/dev/kill-client.sh and prints mean / 1% low / worst frame time
plus a summary.csv. It re-reads the renderer out of the client log and refuses
to report numbers on a silent OpenGL fallback (rule 1). It never pattern-kills.
- build/VintagestoryLib ClientMain: OptimumLogFrameTime writes one line per second
("[Optimum] fps window= frames= mean= min= max= p99=") when OPTIMUM_FPS_LOG names
a file. Off otherwise - one null check per frame - so TAA off is unchanged.
Backend-neutral, unlike OPTIMUM_VULKAN_STATS, which only exists on Vulkan.
Members and the method are listed in Optimum.Patcher/Program.cs; the caller
MainRenderLoop is an existing transplant target.
- scripts/dev/luma-diff.py: the still-frame luminance diff from the parity skill
(centre 60% crop, --median over seven pairs), so the doc can name a real command.
- docs/taa-acceptance.md: the P5 matrix as a checklist - 17 visual rows plus
byte-identical-off, performance and memory, each with scene, exact commands,
pass criterion and the measurement to record; preconditions (creative, wind
stilled, storms off, noon, clear sky); the P4 items carried into P5. docs/ is
git-ignored, so .gitignore gains a docs/* + negation for this one deliverable.
- Optimum.Tests/taa-acceptance-harness-coverage-tests.cs: the doc names every
matrix row TAA-PLAN.md lists, each row carries its four parts, the script drives
the dev scripts and confirms the renderer, and the patcher ships the log members.
Verified: bash scripts/extract-patches.sh; bash scripts/check-patches.sh
(93 applied, 64 cecil, 0 pending/conflict; runtime patches 22 applied and exact
donors compiled); dotnet build VintageStory.slnx -c Release (0 errors);
dotnet test Optimum.Tests -c Release (929 passed, 34 skipped, 0 failed);
dotnet test Optimum.Render.Vulkan.Tests (330 passed, 0 failed).
NOT verified: the game was never launched (task prohibition), so the capture
script has not been run end to end against a live client.
Settings tab (GuiCompositeSettings): a TAA toggle plus sharpness and mip-bias sliders, in the style of the existing Optimum rows. The toggle rebuilds the frame buffers, reloads the shaders and requests a Toggle temporal reset (the history targets and the motion attachment only exist while TAA is on, and a history captured under the other configuration must never be reprojected); both sliders apply live and carry hundredths because the element is an int. The toggle guards on IsFeatureExplicitlyDisabled, like OptimumConfig. EffectiveTaa, so a missing launcher scan cannot veto TAA. The three handlers are listed in Optimum.Patcher/Program.cs; en.json carries the six strings; the feature-flag build drops the row interval to 24px so 26 rows still fit the fixed 740px main-menu dialog. Scanner: the Taa verdict now covers taa-resolve, taa-debug, taa-sharpen, the fsr pair the sharpen shares its maths with, any external file with the "taa-" prefix and any file in the shaderincludes directory, not only vertexwarp.vsh. Packaging: make deploy copies the whole sources/shaders directory (not *.fsh plus *.vsh), creates the shaderincludes destination, and every packager plus the deploy verifies that each source shader and include actually reached the stage; scripts/package.ps1's staged-tree assertion names every TAA shader. Verified: extract-patches + check-patches (93 applied, 64 cecil, 0 conflict; 22 runtime patches), dotnet build VintageStory.slnx -c Release, Optimum.Tests 970 (936 passed, 34 skipped, 0 failed), Optimum.Launcher.Tests 32 passed (11 new scanner-behaviour cases), Optimum.Render.Vulkan.Tests 330 passed. Not verified in game (launching is out of scope for this task).
…940 source + 333 GPU tests green New taa-sharpen.vsh/.fsh (RCAS with a sharpness uniform; sharpness 0 is an early-out bypass that returns the centre texel untouched, and the result keeps its HDR range instead of clamping to 1). Registered like taa-resolve, optional on compile failure. RenderOptimumTaaSharpen runs right after RenderOptimumTaaResolve into a dedicated RGBA16F target (index 21, allocated and released with the history slots on both backends) and reassigns postSceneTexture, so bloom, god rays and the Luma copy the final composition reads all see the sharpened image. It skips itself when FSR 1's RCAS blit is going to run (render scale below 1): both the pass and BlitPrimaryToDefault now ask the same OptimumFsrBlitActive(), so the same pixels are never sharpened twice. OptimumConfig.EffectiveTerrainLodBias adds TaaMipBias to the render-scale bias while TAA is on; ChunkRenderer (atlas textures, both backends) and ShaderRegistry (the chunkopaque/chunktopsoil sampler objects that override them) both read it. A total of 0 - TAA off at native scale - still makes no parameter call at all. Scanner vetoes Taa when a mod ships taa-sharpen, taa-resolve or taa-debug; the latter two were missing from that list. Patcher entries for the new members. Verified: extract-patches + check-patches (157 patches, 22 runtime donors), Release build clean, dotnet test Optimum.Tests (940 passed) and Optimum.Render.Vulkan.Tests (333 passed, validation layers on) - including a GPU readback test that sharpness 0 is bit-for-bit identical to the input and that a known edge gains local contrast monotonically with sharpness. Not verified in game (no launch in this worktree).
…reports 43 runtime patches applied and exact donors compiled The installed-launcher path decompiles the user's own VSEssentials.dll and VSSurvivalMod.dll, applies patches/runtime/**, compiles that, and lets Cecil transplant the bodies named in Optimum.Patcher/mod-patcher.cs. Every TAA P3/P4 mover had a mod-patcher Methods entry but no runtime donor, so installed players would have got vanilla bodies: no motion vectors, camera-fallback ghosting, with all fork tests green. 21 new donor patches, generated against a pristine .build/runtime-donors decompile and matching the fork sources member for member: - VSEssentials: EntityPlayerShapeRenderer (hand window), ModSystemFpHands (AnimationPrev UBO), ModSystemRenderFallingBlocksFast (per-entity previous matrix, one window around the loop). - VSSurvivalMod: Quern, HelveHammer, Fruitpress, Resonator, Bloomery, Forge, Firepit, PotInFirepit (pot + lid), EchoChamber. - Mechanics: MechBlockRenderer (NoteDevice, WriteInstance), MechNetworkRenderer (ApplyPassUniforms + window), AngledCageGear, AngledGears, GenericMechBlock, Transmission, Clutch, CreativeRotor, Pulverizer (OptimumInstanceMotion layout, instance counts). EntityItemRenderer and EntityShapeRenderer donors regenerated to carry their RenderItem/DoRender3DOpaque motion additions on top of what they already had. Tests: new Optimum.Tests/taa-runtime-donor-coverage-tests.cs pins every fork patch that adds a motion call to a runtime donor that adds the same calls, and fails when a new instrumented fork patch has no donor mapping. mod-patcher-manifest-consistency-tests.cs gains a Methods cross-check: every transplanted method's declaring type must have a runtime patch or a sources/** overlay, with the two FluffyClouds (Vulkan, not TAA) entries listed as known gaps so a new one fails. Verified: scripts/extract-patches.sh clean; scripts/check-patches.sh reports "Runtime patches: 43 applied and exact donors compiled" (was 22); both donor projects compile; dotnet build VintageStory.slnx -c Release; dotnet test Optimum.Tests -c Release 951 passed; dotnet test Optimum.Render.Vulkan.Tests 330 passed. Donor coverage guards checked negatively by removing one patch. No fork, lib, shader or manifest behaviour changed.
Merges the four P5 worktrees into feat/taa: sharpen pass + mip bias, settings rows / scanner rules / shader packaging, runtime donors for the P3/P4 mod movers, and the acceptance + performance harness. Conflict resolution: - Optimum.Launcher/ShaderCompatibilityScanner.cs: both branches extended the externalMotionShader veto. Kept the superset - taa-resolve, taa-debug, taa-skymotion and taa-sharpen named individually, plus the taa- prefix rule, the FSR pair the sharpen shares its lobe maths with, vertexwarp.vsh and the whole shaderinclude directory. The duplicate taa-resolve/taa-debug lines the two sides each added were folded into one. - Optimum.Patcher/Program.cs merged both sides (sharpen members and program, the three settings callbacks, the fps-log fields and OptimumLogFrameTime). - build/ and the API fork are per-worktree and git-ignored, so the winning sources were copied into the main tree before re-extracting: ChunkRenderer, ClientPlatformWindows, ShaderPrograms, ShaderRegistry and VintagestoryApi/Config/OptimumConfig.cs from the sharpen branch, GuiCompositeSettings from the settings branch, ClientMain from the harness branch. The mod-fork donor branch touches only patches/runtime/**, which extract does not regenerate. Merge gap fixed: the Windows packager's explicit "what a release contains" list was written before the sharpen shader existed, so taa-sharpen.vsh/.fsh shipped only through the wildcard overlay and nothing asserted them. Added to scripts/package.ps1 and to both lists in taa-settings-coverage-tests.cs. Verified: scripts/extract-patches.sh clean (157 patches, 0 stale); scripts/check-patches.sh 0 pending / 0 conflict, runtime patches 43 applied and exact donors compiled; dotnet build VintageStory.slnx -c Release 0 errors; Optimum.Tests 987 passed / 34 skipped; Optimum.Render.Vulkan.Tests 333 passed; Optimum.Launcher.Tests 32 passed. Game not launched.
…, NaN LOD-bias cache Adversarial review of the P5 range (a04f409..f1a6300). Two defects found and fixed; everything else in the range was checked and held (see below). 1. The TAA mip-bias row did not apply live to the passes it exists for. chunkopaque and chunktopsoil sample the block atlas through sampler OBJECTS, and a bound sampler object overrides the texture object's parameters on that unit - LOD bias included. The bias was written into those samplers only in ShaderRegistry.loadRegisteredShaderPrograms, i.e. once per shader load, while ChunkRenderer.ApplyOptimumTextureLodBias re-applied the atlas TexParameter every frame. So dragging the slider moved the mip selection of liquid, transparent and shadow terrain and left opaque terrain and topsoil on the bias they were compiled with - an inconsistent mip choice across passes of the same atlas, with the GUI tooltip and onOptimumTaaMipBiasChanged both promising "applies immediately". Fix: ShaderRegistry.ApplyOptimumTerrainSamplerLodBias(float) is now the single place those four samplers are written, called from the shader load as before and from ChunkRenderer.SetOptimumTextureLodBias, which already only fires when OptimumConfig.EffectiveTerrainLodBias actually moves. Both new members are in Optimum.Patcher/Program.cs. 2. ChunkRenderer.optimumTextureLodBias started at 0f, not NaN, so the first OnBeforeRenderOpaque of a TAA-off native-scale session read "0 wanted, cache not NaN" and wrote an explicit LOD bias of 0 over the driver default on every atlas - the one call the code comments and the coverage test say that configuration never makes. With fix 1 that would have reached every terrain sampler too. Initialised to float.NaN. Regression tests: - Optimum.Render.Vulkan.Tests/VulkanDeviceIntegrationTests ALodBiasWrittenToAnAlreadyBoundSamplerChangesTheMipTheGpuReads: a 4x4 mip chain with one flat colour per level, drawn at one texel per pixel so lambda is 0; SetSamplerParameter on the already-bound sampler moves the readback from level 0 to level 1 and back. This is the backend half of the claim - the descriptor set has to key on the resolved VkSampler, not on the sampler id. - Optimum.Tests/taa-sharpen-coverage-tests: ALiveMipBiasChangeReachesThe TerrainSamplerObjectsAsWell and TheCachedLodBiasStartsAtNanSoTaaOffTouchesNothing. - fsr-pipeline-coverage-tests updated for the extracted sampler helper. Checked and NOT changed: TAA-off byte-identity (the FXAA/Luma else branch is vanilla; index 21 and the sharpen pass are both gated); the no-double-sharpening rule (one shared OptimumFsrBlitActive(), term-for-term the old inline condition); sharpness 0 (early-out before the first ring tap, GPU-proven bit-identical); scanner verdicts (taa- prefix plus the whole shaderincludes directory, external sources only); packaging (all five packagers, the Makefile wildcard-plus-verify, package.ps1's required-file list); runtime donors (43 applied, exact donors compiled, marker parity per fork patch); patcher entries; and the translation gate - taa-sharpen is a program pair in sources/shaders, so ShaderCorpus.ProgramNames picks it up and EveryVanillaProgramTranslatesToSpirv covers every variant row. Verified: extract-patches + check-patches (93 applied, 64 cecil, 0 pending; 43 runtime patches, exact donors compiled), Release build, 989 source tests, 334 GPU tests, 32 launcher tests. Not verified in game - no deploy, no client run.
What P5 landed (sharpen pass, no-double-sharpening rule, mip bias, settings rows, scanner rules, packaging verification, the installed-runtime donors that close P3 finding (f) / P4 finding (t), and the acceptance + performance harness), the exact vs fallback table updated for it, findings (ab)-(af) from the adversarial review, and the full "still owed in the game" list. The acceptance matrix has not been run - no phase of P5 deployed or launched the client - so P5 is not done by rule 3 and the default-on decision is left to the user with docs/taa-acceptance.md as the gate. TAA stays opt-in.
perf-capture and rule 1 both key on the '[Optimum] <backend> renderer' line; an explicit OpenGL choice, and a Vulkan start without an advisory, printed nothing.
docs/temporal-frame-contract.md is now the frozen, versioned (v1) specification every temporal consumer is written against: the in-house TAA resolve today, FSR 3.1 / XeSS 2 / DLSS next, frame generation and ray reconstruction after that. It specifies the per-frame input record member by member (type, units, coordinate convention, the point in the frame after which each value is this frame's), every resource with format, resolution, sampler state and channel semantics - the motion attachment's rg/b/a including the writer-depth validity tolerance and its half-float rationale, the history colour/glow/linear-depth slots and why their filters differ, the sharpen target - the jitter definition and Halton sequence, the reset reasons with their triggers, the per-class exact/fallback/reactive status consolidated from P3-P5, and the adapter formulas for the three vendor upscalers (motion vector scale and sign, jitter sign, depth convention, reactive/transparency mask mapping, exposure, camera constants). Native handles, extension negotiation, presentation lifetime and ray-reconstruction guides are explicitly reserved for the vendor plan. Optimum.Tests/temporal-contract-tests.cs is the tripwire: it pins the public surface of IOptimumTemporalContext and OptimumTemporalFrame by reflection against a checked-in list, and pins the conventions the document states as fact - the shear formula in both implementations and numerically, the motion-vector scale and sign per adapter, the writer-depth tolerance expression in taa-resolve.fsh, the resolve's full input set, the history/sharpen slot indices and parity rule, and the attachment formats and sampler state on both backends. Every failure message names the document, and the surface assertion prints the actual list so the new one is the failure message. Verified: extract-patches + check-patches (93 applied, 64 cecil, 0 pending; 43 runtime donors exact), dotnet build VintageStory.slnx -c Release, dotnet test Optimum.Tests -c Release (1007 passed, 34 skipped). Negative control run: removing one member from the checked-in surface list fails TheTemporalContextSurfaceIsFrozen with the document named. No game run - this phase changes no rendering code.
…itch reset, ChunkRenderer motion windows exception-safe - ClientPlatformWindows: both frame-buffer setup paths (device and GL) now read `!optimumTaaDisabled && OptimumConfig.EffectiveTaa`, so a platform that already failed the TAA allocation never retries it on a later rebuild. EffectiveTaa alone already goes false (DisableOptimumTaa always calls DisableTaaAtRuntime), so this is a belt-and-braces guard on the per-instance flag; the stale doc comment on DisableOptimumTaa that claimed EffectiveTaa stays true was corrected. - GuiCompositeSettings.onOptimumTaaChanged: the explicit-disable bail-out now resets the already-flipped switch to EffectiveTaa before returning, so the UI cannot show TAA on while it is off for the session. - ChunkRenderer.RenderOpaque and RenderAfterOIT: the motion-vector windows are wrapped in try/finally (matching RenderLiquidMotion), so a throwing shader setup or pool draw can no longer leak the expanded draw-buffer mask into every later draw and block every later window. Draw order and state calls unchanged; GlPopMatrix left where it was. Verified: dotnet build VintageStory.slnx -c Release (0 warnings, 0 errors); scripts/extract-patches.sh + scripts/check-patches.sh (0 pending, 0 conflict, 157 patches, 43 runtime donors exact); dotnet test Optimum.Tests -c Release (1010 passed, 0 failed, 34 skipped), including the three new coverage tests. All changed methods were already listed in Optimum.Patcher/Program.cs.
…f pairing, --vsync validation, doc corrections
Applies the review findings scoped to the launcher scanner, the Makefile deploy,
the dev scripts and the TAA docs.
- ShaderCompatibilityScanner.NormalizeShaderPath: the stage-extension filter
(.fsh/.vsh/.gsh) now applies to shaders/ only. ShaderRegistry loads every
shaderinclude regardless of extension and vanilla ships five .ash includes, so
an external .ash override was invisible and TAA stayed on with a replaced
helper. Directory entries (no extension) are still rejected.
- Makefile deploy: shader and shaderinclude copies are per-file with
`cp -f ... || exit 1` instead of `find -exec ... \;` / a bare wildcard cp
(find returns 0 when an individual cp fails), and the completeness check
compares CONTENT with `cmp -s` instead of `[ -f ]`, which passed against an
unchanged vanilla file of the same name. Both the VANILLA_DIR and the
INSTALL_DIR block.
- scripts/dev/luma-diff.py: --median now pairs non-overlapping files
(files[::2] with files[1::2]) instead of every adjacent pair, errors on an odd
count, and the docstring plus the docs/taa-acceptance.md invocation say to pass
files in pair order (a flat `shots/*.png` glob sorted a1..a7 before b1..b7).
- scripts/dev/perf-capture.sh: --vsync is validated like --taa ("", on, off),
exit 2 to stderr; "--vsync 0" or a typo silently meant vsync ON.
- docs/taa-acceptance.md P2: the slot-21 sharpen target (OptimumTaaSharpenIndex
= 21, RGBA8 the size of Primary, ~15.8 MiB at 1080p) added to the memory
budget; total ~86.9 MiB.
- docs/temporal-frame-contract.md: escaped |CameraPosDelta| in the Teleport row
(it read as extra table cells); new section 6.1 declaring cloud pixels
UNSUPPORTED for external motion consumers (mv is camera-rotation-only, reject
via the reactive mask; a real vector needs prev cloudOffset + the ray-marched
hit from cloudvolumetric.fsh, future work). No shader changed.
- TAA-PLAN.md: finding (n) carries the same contract term; finding (t) rewritten
- the eight mover donors landed in P5 (c897e23, merged 0120422) under
patches/runtime/VSSurvivalMod/Vintagestory/GameContent/ (MechNetworkRenderer
under .../Mechanics/), guarded by Optimum.Tests/taa-runtime-donor-coverage-tests.cs
and mod-patcher-manifest-consistency-tests.cs; the two P4 status paragraphs no
longer claim "not verified in game on either backend" - the entity
motion-writer gate was verified on Vulkan and OpenGL (2830577) and P5's
in-game run (7b0168d, deployed c9758ce+5b952da) drove both backends, while the
per-class mover behaviour and the 18-row acceptance matrix stay unverified.
Verified: dotnet test Optimum.Launcher.Tests -c Release -> 34 passed, 1 failed
(SolutionIntegrityTests.EverySolutionProjectPathExists, pre-existing and only
because the git-ignored build/ tree is absent in this worktree); the scanner
filter alone is 18/18 green. `make -n deploy` exits 0 and the rewritten copy and
cmp snippets were run against a temp directory: a clean copy passes, a tampered
destination and a deleted destination both fail with the new message.
`bash -n scripts/dev/perf-capture.sh` clean and
`perf-capture.sh --renderer vulkan --vsync bogus` exits 2 before any config
write. luma-diff.py run on generated 64x64 PNGs: 2 pairs -> median 5.000, odd
count -> SystemExit. Not run here: extract-patches/check-patches and the game
(no ignored trees in this worktree).
…pply inside the window; runtime donors mirrored Verified: dotnet build VintageStory.slnx -c Release (0 errors); extract-patches + check-patches (93 applied, 0 pending, 0 conflict; 43 runtime patches applied and exact donors compiled); dotnet test Optimum.Tests -c Release (1011 passed, 0 failed, 34 skipped).
…t masking, dead UBO buffer - CreateInstance: enable VK_EXT_validation_features whenever the ValidationFeaturesEXT struct is chained into pNext; the feature list is now parsed (ParseValidationFeatures) before the extension array is marshalled so both decisions use the same condition. A chained struct with the extension off was silently ignored. - ProgramInterfaceLayout: fragment output arrays with only a constant element stored to now mark just that element's location written (TryGetWrittenFragmentOutputElements); dynamic indices, whole-array and swizzled stores keep the full span, and an index on a non-array output still means the whole variable. PipelineCache no longer leaves colour writes on for an attachment the shader never writes (undefined data on Vulkan). - VulkanDevice: ResolveValidationLogPath accepts a Windows path too, and the default-path field now precedes the field that reads it (static initialiser order made the bare OPTIMUM_VULKAN_VALIDATION=1 log path null). - MirrorValidationMessage also swallows UnauthorizedAccessException, NotSupportedException and ArgumentException; a diagnostic write must not abort Initialize. - ClientUniformBuffer: removed the per-UBO VulkanBuffer, SyncBuffer, the descriptor Release and the deferred disposal. Verified dead: no path binds ubo.Buffer - the ring-exhausted path allocates its own transient copy - and SyncBuffer had no caller anywhere in the tree. - TaaResolveTests: all eight LoadProgram sites are 'using' now, so the programs are destroyed before the context. Verified: dotnet build Optimum.Render.Vulkan.Tests -c Release (0 warnings, 0 errors) and dotnet test Optimum.Render.Vulkan.Tests -c Release on the GPU with validation layers on: 349 passed, 0 failed, 0 skipped (ShaderTranslationTests and the pipeline/attachment tests included). Not run here: the game, make deploy, extract/check-patches (ignored trees absent in this worktree).
Test-only changes, no production code touched. - fsr-pipeline / taa-sharpen: "if (textureLodBias == 0f)" now has to be the restore path it actually is - the float.NaN cache, the !IsNaN guard, SetOptimumTextureLodBias(0f) and the cache reset, all asserted inside that branch (brace-matched, so the nonzero path below cannot satisfy them). The sampler entry point is asserted to apply the raw value: no "!= 0f" inside ApplyOptimumTerrainSamplerLodBias, all four samplers written with `bias`. Read ShaderRegistry.cs:315-343 first: the sampler path does NOT skip zero, so there is no production gap here. - New PatchMethodScopes: locates a patch's added lines inside the tree it was applied to and attributes them to the enclosing method (brace scanner that skips comments and string literals). Covered by patch-method-scopes-tests.cs. - mod-patcher-manifest-consistency: EveryTransplantedMethodHasARuntimeDonor is per METHOD now, not per declaring type. A type whose donor patch touches only some other method is reported separately (KnownUnchangedTransplants, seeded with ChunkMapLayer::loadFromChunkPixels, whose body really is unchanged in both trees). KnownDonorGaps keeps its meaning. Degrades to the old per-type check when neither .build/runtime-donors nor the fork is on disk. - taa-runtime-donor-coverage: marker sets are compared per method as well as file-wide, so a donor that writes the same markers into a different body no longer passes. - taa-terrain-motion: the prepass test reads through ReadPatchedOrSource like the rest of the file (it would have thrown in a clean clone); the vanilla projection line, which sits outside every hunk, is asserted against the decompiled tree only when that is checked out. Verified: dotnet test Optimum.Tests -c Release - 1015 passed, 0 failed, 34 skipped. Negative check on real data: moving the Optimum block of QuernTopRenderer out of OnRenderFrame in .build/runtime-donors made both strengthened tests fail with the right messages (donor coverage listed the three misplaced markers; the manifest test named QuernTopRenderer::OnRenderFrame); donor restored afterwards.
…ged Makefile The scripts/docs stage replaced the deploy shader copy (find -exec cp, then a bare [ -f "$d" ] completeness check) with a per-file cp loop that aborts on failure and a cmp -s content comparison. MakeDeployCopiesEveryShaderAndFails- WhenOneDoesNotArrive still pinned the old literal, so the merge of the two branches broke it - a semantic conflict, no functional regression. The assertions now pin the stronger contract: both deploy paths loop over the whole sources/shaders and sources/shaderincludes directories (not *.fsh plus *.vsh), each cp is '|| exit 1', and both completeness checks compare content with cmp -s rather than existence, with a DoesNotContain on the old [ -f ] form. Verified: dotnet build VintageStory.slnx -c Release 0 errors; extract-patches wrote 157 patches with no tree change; check-patches 0 pending / 0 conflict, 43 runtime donors exact; Optimum.Tests 1015 passed 0 failed 34 skipped; Optimum.Launcher.Tests 35 passed 0 failed; Optimum.Render.Vulkan.Tests 349 passed 0 failed on a real GPU with validation layers on.
Resolved: patcher listings, platform seam manifest, coverage tests and the design doc keep every stage's entries (the GUI section is renumbered 3d). ClientPlatformAbstract's generated patch is regenerated from build/ at the end of the integration.
…d build/ tree The five stage branches each inserted their seams at the same point in ClientPlatformAbstract, so the generated patch conflicted on every merge. Resolved by rebuilding build/ from patches, adding the entity, sky/particle/decal and GUI seams to the source, and re-extracting, so the hunk offsets are the extractor's own. Verified on the merged state: build Release 0 errors; extract-patches 159 patches / 202 valid; check-patches 93 applied, 66 cecil, 0 pending, 0 conflict, 43 runtime patches exact; Optimum.Tests 1271 passed / 0 failed / 34 skipped; Optimum.Render.Vulkan.Tests 1107 passed / 0 failed with the implicit-layer disable set (only VK_LAYER_MESA_device_select inserted).
Owner, 2026-09-16: the workflow approach does not work for them - they cannot see what is happening. Rule 8 now says the session does every step itself, in order, visibly; subagents only for read-only search. Rules 14-16 rewritten accordingly. Rule 19 added: build and tests compile against the fork and miss a wrong transplant parameter count and a lib call to a fork-only API member, both of which shipped today as crashes; make patch-il and the fork diff catch them and run after every lib or fork change.
…member The shipped client threw MissingMethodException 'MeshRef MeshDataPool.get_ModelRef()' at SystemRenderDecals.OnRenderFrame3D on both backends: the particles-sky stage added a public ModelRef to the API fork and made the lib call game.Platform.RenderDecalPool(decalPool.ModelRef, ...). A new public member on a vanilla API type never ships - the shipped VintagestoryAPI-patched.dll is vanilla plus the hooks in api-patcher.cs - and modelRef is internal in vanilla, so the lib could not read it either. The decals now use the scope shape the chunk pools already use: BeginDecalPass(decalTextureId, blockTextureId) / EndDecalPass() on ClientPlatformAbstract with empty neutral bodies, the VANILLA decalPool.Draw(game.api, game.frustumCuller, CullInstant) between them, and the Vulkan RenderMesh(MeshRef, int[], int[], int, bool) override routing the pool's multi-draw to TryDrawDecalPoolNative while the scope is open. OpenGL is vanilla again. The scope closes in the same finally as the motion window, ahead of it. Also fixes a transplant tuple in the same merge that make patch-il caught: ClientMain::RenderTextureIntoFrameBuffer is 10 parameters, not 9, and the patcher aborted on it before reaching the end. Verified: dotnet build VintageStory.slnx -c Release clean; extract-patches + check-patches 159 patches, 0 pending, 0 conflict; Optimum.Tests 1271 passed, 0 failed, 34 skipped; Optimum.Render.Vulkan.Tests 1107 passed, 0 failed with the implicit layers disabled; make patch-il "207/207 required methods patched". Not verified: the game was not launched (agents do not).
…r; transplant tuple corrected
… opt-in; draw-state trace
The stage-2 merge shipped with the first-person hand drawn wrong on Vulkan
with TAA on: the native entity route shows the joints the OpenGL body hides.
Bisected in the real client with new per-route switches
(OPTIMUM_VK_NATIVE_{CHUNKS,ENTITIES,WORLD,SKY,GUI}): only the entity route,
only with TAA on; TAA off matches OpenGL. A new render-trace line in
BindStorageSet prints, per draw, the record offset, every named block's
buffer and offset and the projection/view/model matrices by name - the two
routes bind byte-identical state for that draw, so the defect is in the
TAA-on path (motion window, TAAMOTION outputs, or the motion-writer hooks the
neutral RenderMesh body ran and the native route bypasses). Entities are
opt-in (OPTIMUM_VK_NATIVE_ENTITIES=1) until that is fixed; the finding and
the repro are in the handoff.
Also: the stale test assertion for the corrected transplant tuple.
Verified on the deployed build, both backends headless, TAA on: 0 client
errors, validation 0 errors and 0 SYNC-, entity GPU tests 24 passed, scene
SSIM 0.9838/0.9736/0.9820 against a same-session GL-vs-GL floor of
0.9961/0.9853/0.9932.
…only; on by default again VSEssentials registers its own entityanimated for the first-person hands, with its own Animation block and, under TAA, its own AnimationPrev block. Through the native route that program drew the arm several times too large with TAA on. Bisected in the real client: vanilla entity programs native with the hand on the neutral body matches OpenGL (0.9825/0.9835/0.9810). The render trace shows every bound input of that draw equal on both routes, so the cause is still open; it is recorded in the handoff with a repro. Phase 3b decision 1 already puts mod renderers on the adapter, so the route now takes only ShaderPrograms.Entityanimated and Shadowmapentityanimated and is on by default again. OPTIMUM_VK_NATIVE_ENTITIES=0 turns it off, OPTIMUM_VK_NATIVE_ENTITIES=all admits mod programs for the investigation. The trace line in BindStorageSet now also prints block content hashes, the record and push hashes and the frame block version and hash. Tests: the entity fixture builds the vanilla program type and registers it where the client does; new AModRegisteredEntityProgramStaysOnTheNeutralBody. Coverage pins the per-route environment switches and the vanilla-only rule. Verified: entity GPU tests 25 passed, coverage 28 passed, make deploy 207/207, headless Vulkan with TAA on 0 client errors, validation 0 errors and 0 SYNC-; Vulkan vs OpenGL 0.9631/0.9617/0.9658 against a same-session OpenGL floor of 0.9853/0.9750/0.9785, frames equal on inspection, residual is run timing.
…o longer vacuous New seam ClientPlatformAbstract.RenderSunQuad for the sun disc in SystemRenderSunMoon.OnRenderFrame3D, neutral body the RenderMesh it replaced. The Vulkan override takes only the registered vanilla standard program and resolves every sampler the pipeline declares (standard reads frame textures too). The occlusion-query probe keeps RenderMesh: a Vulkan occlusion query has to begin and end inside one render pass, and a native draw opens its own. Every pixel comparison in NativeWorldSystemsTests was between two untouched attachments: RunFrame bound programs directly, so the frame block stayed zero (standard.vsh divides by viewDistance and discards everything) and the model and view matrices stayed zero (every vertex collapses). The fixture now seeds both, and the comparison helper fails if the scene slot is still the clear. That exposed a fixture asymmetry - the decal tests never bound the atlases to units as ShaderProgramDecals' setters do - fixed; decals match with real pixels. The particle tests remain vacuous (no per-instance data in the quad) and say so. Verified: world/sky/entity GPU tests 38 passed with real pixels (sun 122,118,18; decals 15,31,19), Optimum.Tests 1271 passed, patches 159 with 0 conflicts, make patch-il 207/207, headless Vulkan 33 native sun passes, 0 client errors both backends, validation 0 errors and 0 SYNC-. The residual Vulkan-vs-OpenGL SSIM is the same with every stage-2 route switched off (0.981/0.971/0.964), so stage 2 did not introduce it.
… the optimisation pass
The quad pool goes through the native route under the Transparent target's blend contract the chunk route already records (weighted accumulation, revealage, glow), with depth test on and depth writes off - the state LoadFrameBuffer(Transparent) sets. Vanilla ShaderPrograms.Particlesquad only; falls back while the contract is unrecorded or the target is not Transparent. particleTex resolves from the program's declaration; particlesquad.fsh never samples it. Checked: build 0 errors, coverage 29 passed, deploy 207/207; headless Vulkan with precipitation forced on: 129 native OIT particle passes, 0 emulated quad particle draws, 0 client errors, validation 0 errors and 0 SYNC-, snowfall renders.
…T passes dropped the accumulation slots GUI quads. New seam ClientPlatformAbstract.RenderGuiQuad for every Render2DTexture overload (four transplanted ClientMain methods, the two seven-parameter overloads told apart by parameter types). The native route takes the vanilla gui program and draws under the state the client last stated through the platform's own virtuals - blend on and mode, depth test, depth mask, depth function, scissor - recorded in VulkanClientPlatform.State.cs, never read off the tracker. Native passes gained an optional scissor. Headless: 992 native GUI quad passes per run, HUD renders; 360 gui draws remain emulated (the other GUI systems). OIT fix. BeginOitAccumulation selects six draw buffers and keeps the colour accumulation on slots 3-5, which the Transparent FrameBufferRef does not list. The native chunk OIT groups and the quad particles derived their slots from that texture count (0x7), so every native OIT draw darkened revealage and added no colour - black snowflakes were how it showed. The slot set is now recorded where the client selects it (0x7 in ApplyTransparentPassBlendState, 0x3F in BeginOitAccumulation) and used by both routes. Snow renders white again; chunk-oit-liquid, chunk-oit-transparent and the particle passes declare 0x3F. Checked: build 0 errors, Optimum.Tests 1274 passed, GUI/world/chunk GPU tests 26 passed, patches 159 with 0 conflicts, make patch-il 211/211, headless Vulkan 0 client errors and validation 0 errors / 0 SYNC-.
VulkanClientPlatform.RenderMesh takes any draw under ShaderPrograms.Gui natively - block highlights, the wireframe, gear and progress overlays, mods drawing with the gui shader through IRenderAPI.RenderMesh - under the state the client stated through the platform's virtuals, now including cull mode and line width, with the program's declared textures. It honours NativeGuiEnabled, so the seams' neutral bodies and the old-route switch still reach the emulated draw. Checked: build 0 errors, GUI GPU tests 7 passed, coverage 32 passed, deploy; headless Vulkan: 992 native GUI quads and 8367 other native gui draws per run, 0 emulated gui draws, 0 client errors, validation 0 errors / 0 SYNC-, HUD renders.
… native VulkanClientPlatform.RenderMesh takes any draw under ShaderPrograms.Standard on a world target natively - held and dropped items, block entity models - under the state the client stated (blend, depth test and mask, depth function, cull) with GlToggleBlend's exceptions applied per attachment: SSAO G-buffer slots and the open motion attachment replace, the Transparent target keeps its recorded OIT contract. Samplers resolve from the program's declared textures. Never inside an occlusion query (a Vulkan query must begin and end inside one render pass - the sun probe keeps the emulated draw) and never on the default framebuffer (GUI item icons stay emulated for now). Checked: build 0 errors, coverage 33 passed, world/GUI GPU tests passed, deploy; headless Vulkan: 360 native standard draws, 0 emulated, 0 client errors, validation 0 errors / 0 SYNC-, frame free of artefacts against the old route.
RenderMeshInstanced under the vanilla particlesquad2d program becomes an instanced native pass on the bound target (the default framebuffer in the menu) with the state the client stated. DrawNativeGuiMesh gains an instance count. Checked: build 0 errors, deploy; headless Vulkan: 1766 native 2D particle passes, 0 emulated, validation 0 errors / 0 SYNC-, no client errors; menu screenshot on Vulkan renders the background and particles.
… the sun probe included - the colour mask the client stated (GlColorMask) is recorded and applied per slot on the native standard route; - draws into the default framebuffer (GUI item icons) get their own native pass (TryRenderStandardMeshToDefault) with the stated state and scissor; - the sun's occlusion probe no longer falls back: a native pass opens its scope through the target manager, whose scope hooks suspend and resume the open query. - coverage assertion for the 2D particle route updated to the instanced draw. Checked: build 0 errors, coverage 33 passed, GPU world/GUI/query tests 21 passed, deploy; headless Vulkan: remaining emulated draws are guigear, cloudmap, cloudvolumetric, the first-person hand and the loading screen only; validation 0 errors / 0 SYNC-. The spawn view in the save currently renders white on both routes (all native routes off gives the same frame), so no visual comparison of the world was possible in that scene.
RenderMesh under the platform's hardcoded ShaderProgramMinimalGui (no pass name; MainMenuRenderAPI.Render2DTexture before the shader registry is up) goes through DrawNativeGuiMesh with one sampler and the stated state. The drawer compares a null pass name as "". Checked: build 0 errors, coverage 34 passed, GPU GUI tests 7 passed, deploy; headless Vulkan: 10 MinimalGui trace lines, emulated draws left are guigear, cloudmap, cloudvolumetric and the first-person hand only; validation 0 errors / 0 SYNC-. Not checked visually: the quads show for a few startup frames.
- guigear (HudHotbar's temporal stability gear, a plain RenderMesh of the GUI quad) takes the single-sampler GUI mesh route with MinimalGui; - a native draw that samples a colour attachment of its own target takes the pooled ReadSelf copy the emulated route takes (SnapshotColorAttachment) instead of being refused, and the bindless resolve samples the copy. This is the atlas composition: BlendedTextureManager and RenderTextureIntoFrameBuffer copy one atlas region into another of the same atlas. A non-colour attachment of the own target is still refused. Checked: build 0 errors; GPU: new self-blit differential test (left half copied into the right half, native equals emulated, 2 native draws, 0 emulation calls, validation clean), 298 native/feedback GPU tests passed; coverage 34 passed; deploy; headless Vulkan: emulated draws left are cloudmap, cloudvolumetric and the first-person hand only; validation 0 errors / 0 SYNC-; gear crop matches the old route's look.
- VulkanForkGraphics records the depth test, blend and raw framebuffer binds it forwards, on the platform, so a native route runs with the fork's state; - cloudmap draws into the fork's own framebuffer (formats from the attachments its SetDrawBuffers enabled), blending and depth off; - cloudvolumetric draws onto the Transparent target under the OIT contract, samples Primary's depth as the bound depth (no depth writes, as GL with the depth test off) and resolves liquidDepth to the LiquidDepth target's depth: resolved to 0 it replaced the frame's liquid depth with a placeholder, which drew no clouds and an orange halo around the sun; - OPTIMUM_VK_NATIVE_CLOUDS=0 keeps both on the emulated route; - NativeWorldPrepare takes samplesBoundDepth. Checked: build 0 errors, coverage 35 passed, GPU world tests 13 passed, deploy; headless Vulkan, cumulus at noon, AO debug view off for the run: native clouds against OpenGL SSIM 0.969 (cloud route off: 0.879), cloud patch and sun match OpenGL by eye; validation 0 errors / 0 SYNC-; only the first-person hand is still emulated.
…aw is native The entity route admits the program the shader registry holds under the entity pass names - VSEssentials' first-person hand program replaces the registry entry for entityanimated. The registry is only asked about a program it registered (PassId set), since its type initializer rewrites ShaderPrograms.*; the cloud route uses the same check. The 2026-09-16 fault (the arm drawn several times too large with TAA on) no longer reproduces with its repro (OPTIMUM_VK_NATIVE_ENTITIES=all, OPTIMUM_VK_NATIVE_SHADERS=force, TAA on): parity dump of the hand region at frame 300 against the neutral body - depth identical, motion within 0.0023, G-buffer within 0.0039. The cause was never named; recorded in the progress doc. Checked: build 0 errors, coverage 35 passed, GPU entity/world/GUI tests 27 passed, deploy; headless Vulkan with default route settings and native shaders forced: 0 emulated draws, 58 native Opaque entity draws, validation 0 errors / 0 SYNC-, no exceptions.
…CAS_DENOISE)
The GTAO term's TAA-converged residual on flat faces (0.24/255 per-pixel
temporal std, three times the no-AO baseline of 0.08) was multiplied 2.7x by
the sharpen at strength 1.0 and drawn as grain: an RCAS port without AMD's
noise limiter, which FSR 3 ships enabled. The lobe is now scaled by
1 - 0.5 * (the centre's deviation from the mean of its four neighbours over the
ring's range): a lone deviation gets half the lobe, an edge keeps 7/8.
Eliminated on the way, with numbers in the tests and the branch doc: the AO's
noise is Monte-Carlo (medium raw 10 % -> denoised 2.7 % per frame on flat
faces; ultra 4x less), the reconstruction, normals, depth mips and row order
match the G-buffer, and the resolve keeps 15 % of one frame's noise (new
TaaResolveTests.PerFrameNoiseOnAStaticSurfaceAveragesOut, which also records
the variance clip's ~3 % softening of a +-5 % per-texel texture).
Checked: headless Vulkan, flat-face grain with GTAO now equals the AO-off image
(spatial HF 2.05 vs 2.04, was 2.82), flicker 0.64 -> 0.44; GPU tests for the
sharpen (new lone-pixel test: 0.6 on 0.3 sharpens to 0.72, not 0.9; the edge
test still holds), the resolve and the post chain pass; sharpen coverage 20
passed; the owner judged it in game ("worlds better").
…blend-no-cull pool The chunk shader forced gNormal.w = 1 (the 0.05-block occluder class) for every draw with haxyFade set, meaning the entire blend-no-cull pool. That pool holds solid blocks too - snow layers - so in a snow-covered world 59 % of the visible pixels, 158 of 168 flat faces, were thin occluders. A sentinel written from a per-pool uniform proved the pool (not RenderAfterOIT, which is the water-plant pass). The override is removed on both shader routes; the class is what vanilla's vertex stage already writes from each shape face's WindMode. Not the grain (turning the class off made the AO darker and noisier), and in this world it changes little: the snow-covered soil tops carry a WindMode in their shapes, so vanilla's flag still marks most of them. A per-block class needs a tesselator flag; open. Checked: build 0 errors, 49 programs / 142 variants compiled, patches 0 conflicts, 211/211 methods patched, AO coverage 23 passed, chunk/AO GPU tests 22 passed, deploy; headless Vulkan: flat-face temporal std 0.45 / spatial HF 2.02 (before 0.44 / 2.05), validation 0 errors / 0 SYNC-.
…tory AGENTS.md, CLAUDE.md, .claude/, the Vulkan-native plan, the branch status notes and scripts/dev/harvest-maps.py are ignored and untracked; they stay beside the checkout. Code, tests, shaders, scripts and the tracked docs no longer point at them or at review sessions: each reference now states the rule it meant (a launch is not a verification; a clear on a masked attachment is a no-op on every path; the resolve's temporal invariants; the still-frame luminance diff). TAA-PLAN.md keeps its content and drops the review-session mentions.
The parity capture's reworded comment must not spell the process-kill command the test forbids, and the seam-deletion test names the fork bridge's current two-argument constructor (platform, device) from the cloud work. Checked: Optimum.Tests 1278 passed, 34 skipped.
…, mesh part pool, installer 51 upstream commits. The merge was done on the sources, not the generated files: every file upstream touched was three-way merged (ours, merge-base, upstream) on one common baseline and the patches regenerated from the result, because regenerating from a tree that had only some of upstream's changes silently dropped the rest (caught by upstream's own patch-reading tests). Baseline: upstream decompiles with ilspycmd 11.0.0.9375; 84 lib files differ from the 10.1 decompile, 18 of them carrying our patches. build/ was rebased onto the 11 snapshot (two files needed a hand merge, ShaderRegistry and EventManager: our edits against the decompiler's own reformulation of the same lines) and every patch is regenerated against it, so the maintainer's bootstrap applies them. Resolutions: the frame end keeps EndFrame() (the platform seam) followed by upstream's RecordChunkRenderFrame(); OptimumConfig takes both sides' new members; the patcher lists both sides' targets (224 methods now); the overlay packager ships the shader includes with the completeness check the other packagers have; the vanilla-regions test lists upstream's indirect-draw members. Checked: build 0 errors, patches 0 conflicts, 224/224 methods patched, Optimum.Tests 1343 passed (34 skipped), GPU suite 1116 passed, deploy; headless on both backends in the real save: no exceptions, Vulkan validation 0 errors / 0 SYNC-, frames render.
…on removal, step 1) Every draw the dedicated native routes do not take - mod renderers with their own programs, the vanilla programs without a dedicated route, the seams' neutral bodies behind the route switches - is now recorded natively before the emulated draw is reached: RenderMesh, RenderMeshInstanced, the pool multi-draw and the fullscreen triangle (VulkanClientPlatform.NativeStated.cs). It builds the pipeline, the pass and the textures from StatedRenderState, the fixed-function state the client stated through the platform's virtuals, with OpenGL's semantics: glBlendFunc sets every draw buffer, glBlendFunci one, glDisable(GL_BLEND) keeps the functions; the colour mask is global; draw buffers are per framebuffer (attachment 0 by default) and become write masks; one texture per unit; a framebuffer bind does not move the viewport. Every recording site forwards to the device as before (StateDrawBuffers, StateSlotBlend, StateBlend, the fork bridge's notes), so the emulated route is unchanged until step 2. Textures resolve per sampler through the program's unit mapping, as the emulated draw does; the pass declares every colour slot attached on the device. Found on the way: the OIT accumulation targets are attached to Transparent without being in its ColorTextureIds (water drew black until the slots came from the attachments); GL's thick-line probe leaves the line width at 1.5; GlToggleBlend(false) keeps the functions on GL while the device reset them. OPTIMUM_VK_NATIVE_STATED=0 keeps the emulated route; OPTIMUM_VK_STATED_CHECK=1 compares the record against the device's tracked state at every draw. Checked: build 0 errors; new NativeStatedTests (blend off, three modes, colour mask, scissor, an unselected draw buffer: pixels equal to the emulated route, one native draw, no emulated call inside the pass); the dedicated-route differential fixtures keep the emulated neutral body; GPU suite 1123 passed, Optimum.Tests 1338 passed; headless Vulkan with every dedicated route off: 24400 generic passes, 0 emulated draws, validation 0 errors / 0 SYNC-, the state check reports no difference, frame matches the dedicated and emulated routes within launch-to-launch variance (mean colour of repeated launches of one route differs by more than the routes do).
… 2 and 3) Every draw is native now. The device has no GL state machine, bound target, texture-unit tables or emulated draws; GlStateTracker is gone and its shared pipeline types live in Core/PipelineState.cs. The platform records what the client states (StatedRenderState, including glUseProgram) and draws everything without a dedicated route through Platform/StatedDraw.cs, keeping the scope open so consecutive stated draws coalesce. Clears name their target and honour the stated draw buffers, colour mask and depth mask. RenderTargetManager lost its draw-buffer mask and sampled-slot exclusion; a declared pass's slots are the only exclusion, with the frame graph on or off. Fixes found on the way: - native pipeline cache keyed on the described blend set, so draws differing only in dynamic blend/write mask no longer share one description - pipeline formats for a pass ignore the target's current pass exclusion - set-0 textures a draw does not name are made readable, or replaced by the placeholder (sky after the liquid depth pass: VUID-vkCmdDrawIndexed-imageLayout-00344) - a platform framebuffer bind cancels an earlier fork bind - a native pipeline request no longer counts a skipped draw GPU tests keep their GL-shaped calls through a test-only GlShapedDevice that records into StatedRenderState and draws through StatedDraw. Verified: solution builds; Optimum.Tests 1333 passed; Optimum.Render.Vulkan.Tests 1082 passed (sync,best, implicit layers off); headless "serene cave world" on Vulkan (0 validation errors, closed itself) and OpenGL (closed itself).
…n A) The frame-marking half of the latency seams, ported from feat/latency without its pacing backends. One latency frame id per rendered frame, allocated by the pre-input LatencySleep lib seam (S3) or at BeginFrame; InputSample and SimulationStart at the sleep, SimulationEnd and RenderSubmitStart at the first render stage, RenderSubmitEnd after Submit A, PresentStart/PresentEnd around vkQueuePresentKHR. Every frame submit is tagged through FrameSlot.Submit, every present gets a process-wide present id, chained as VkPresentIdKHR where the device supports it, and the swapchain tells the backend about each creation and retirement. Device requirements go through IDeviceRequirementContributor and one VkDeviceCreateInfo pNext chain. The stats sample gains stats.latency and the latency_sleep wait site. None is the only backend here: it never sleeps and owns no frame cap, so the lib keeps its limiter and pacing is unchanged. Native, NV, AMD, LatencyMode and OPTIMUM_VULKAN_LATENCY stay on feat/latency. Unlike there, VK_KHR_present_id is enabled on its own merit, not only for NV. Device-side code is in VulkanDevice.Latency.cs. Verified: solution builds; make patch-il 224/224; Optimum.Tests latency, integration and Cecil tests 109 passed; GPU latency, present identity, pacing stats, swapchain and colour-write tests passed with sync,best validation and the implicit layers off (RTX 4070, driver 615.71.09: present id ON, low_latency2 rev 2 detected only); headless "serene cave world" on Vulkan (0 validation errors, stats.latency ~59 reports/s, sleep_n 0, closed itself) and OpenGL (closed itself), frames identical by eye.
…on B) The frame is rendered HUD-less and the UI is composed onto it at the end, always on for Vulkan and with no switch: the structure an upscaler or a frame generator needs, because the UI must never enter the image a reconstruction consumes. OpenGL keeps drawing its GUI straight onto the window. Slot 23 holds the HUD-less scene, copied from Primary colour 0 at the end of RenderFinalComposition - the one moment the composited image holds the scene alone. Slot 24 holds the UI image at window size, colour plus its own depth, because the GUI depth-sorts itself over ScreenManager's 0..20000 range. The scope opens at the end of BlitPrimaryToDefault, on every route out of it, and the device resolves Default to the UI image while it is open (VulkanDevice.RedirectDefaultFramebuffer), so no render system has to know it moved - the atlas item renderer's LoadFrameBuffer(Default) included. Standard blending into that image takes ONE as its source alpha factor (AttachmentBlend.ForUiImage), so straight-alpha GUI accumulates the over-operator's coverage instead of squaring it. The compose closes the scope first and is the only draw that writes the window image after the blit; BeginFrame and every framebuffer rebuild close a scope nobody composed. The lib gains only neutral pieces: the OptimumComposeUiTarget virtual, its calls in ClientMain.RenderToDefaultFramebuffer (before the Done stage, so the with-HUD screenshot and the AVI writer still record the finished frame) and in ScreenManager.Render (the menu screens never reach ClientMain), the ui-compose program in both twins, and the two slot names for the parity dump. Verified: solution builds; make patch-il 225/225; Optimum.Tests 1355 passed; Optimum.Render.Vulkan.Tests 1132 passed with sync,best validation and the implicit layers off; headless "serene cave world" on Vulkan (0 validation errors) and OpenGL, both closed themselves; the parity dump at frame 300 shows the snapshot, the UI colour and its coverage behaving as the over-operator, and the composed frame matches what the GUI drawn directly gives.
Author
|
This approach fundamentally differs from the previous Vulkan implementation that was emulating OpenGL Calls. Its the proper way to do this to actually get Vulkans full potential here. The UI separation and frame marking were backported from my DLSS and DLSS-FG branch as foundational systems here, as I will need them later on for upscaling, frame generation and VR |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #69, reduced to the Vulkan backend and TAA as agreed there. Upscalers and frame generation are not in this branch.
The backend replaces the platform class rather than branching the GL calls:
VulkanClientPlatformoverrides the graphics members ofClientPlatformWindows, so OpenGL keeps running the unmodified base and stays the default. Vulkan is opt-in throughRendererinoptimum.json.Still experimental. I run it daily on an RTX 4070 (Linux, driver 615); nothing here has been verified on AMD or Intel yet.
Done
libshaderc_shared.solands in the application root,prime-runis only used when present,python3-numpyis in the prerequisite check, teardown drains the frame and transfer timelines before the device goes, the donor-drift and swapchain tests are green here, and there is no XeSS surface on this branchVkPresentIdKHRwhere the device offers it, and astats.latencyline with the frame's breakdown. The vendor latency backends are not in this branch, so nothing paces the frame differentlyVulkanDevice.cssplit by subject (3456 lines into the device plus ten partials), the platform's duplicated state fields folded into the one stated-state record, the GPU tests no longer leak shader trees into/tmpOPTIMUM_VULKAN_PASS_TIMES=1, astats.passesline per second) - no wait anywhere on the frame path. First table: at shadow quality 2 the shadow cascades are 60 % of GPU time; the far cascade is bound by draw count, not fillNoAPIwindow, so it routes to the swapchain's present modeLeft
ShadowCasterSplit, a second shadow program without the alpha test for the opaque groups): pixel-identical, 63 % faster on the near cascade, but 18 % slower on the far cascade at shadow quality 4 - net −1.5 FPS there, because that cascade is draw-count bound. Decide off-by-default or drop before mergeNot in this PR, on the roadmap after it: shadow draw batching (the far cascade is 2,400 draws at a median of 200 triangles), camera-aware shadow-caster culling and occlusion culling, and a sweep of every platform member that depends on something the Vulkan window lacks (the vsync crash was one).