Repository navigation
video_core: keep karts on the water and restore waves (Mario Kart World) - #690
Merged
Merged
Conversation
Port the fingerprinted wave and collision-map fixes from #553 and adapt #729 movie pacing without opt-in switches. Limit movie activity to MKW and the current graphics session, track completed frame outputs independently of release fences, and retain current experimental decoding and shader diagnostics. Assisted by Codex. (cherry picked from commit 39a33c4) Lichii port (Eglantine-Community/Eglantine#737, merged to upstream nx2 through #755): - Only the water half is taken: the wave displacement fallback for empty depth inputs (upstream #553, 9530cecfc4) and the collision height map write-back (upstream #553, 13c88a9599). The movie pacing half (upstream #729, klova5's 447c9244a9) is left out: nx2 serves the gsv/vi2/vdec sessions on three host threads (am.cpp, Lichii #659), the root cause of the game asking for decoded frames ~34 times a second while vi2 blocked its only thread. - The mkw_zero_depth_v1 pipeline cache directory is left out. nx2 keys a native program's pipeline by the hash of its lowered code (GenericEnvironment::Analyze), so the patched shader gets a new key and the cached unpatched one is never used; moving the directory would only throw away every Mario Kart World cache entry and break the Qt "remove shader cache" paths for it. - The dispatch hook is placed after nx2's DispatchCompute setup, where qmd is defined, and the include follows nx2's list. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…GPU wait The ported write-back downloaded the 256x256 collision height map and called runtime.Finish() after every dispatch of the collision program, so the GPU thread waited for the GPU to go idle at least once a frame on any course with water. Queue the image on the existing preemptive download path instead (the one BindRenderTarget uses): the next fence records the copy, and the fencing thread writes it to guest memory when the GPU reaches that fence. nx2 signals a fence per compute dispatch, so the map lands within the GPU's own latency, without the stall. The wave fallback inserts three instructions into the lowered program and moves everything after them. Upstream checked that its lowering of this shader has no branches; nx2's lowering differs, so skip the fallback (with a warning) if the lowered program has any branch, call or SSY/PBK/PCNT push, instead of corrupting its targets. Follows up Eglantine-Community/Eglantine@39a33c4a78 (upstream #737). Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Contributor
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 22 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
jpkto
added a commit
to jpkto/Lichii
that referenced
this pull request
Nov 8, 2026
Brings in the 23 nx2 commits from zortos293#792 to zortos293#621 (bf48fe9). Thirteen files conflicted. Two changes of this branch are now upstream and are dropped here: - "cmake: reuse a cached CI package when its checksum can't be downloaded" (f3702f3). zortos293#792 records the hash an archive was verified against next to the cached package and uses it without downloading the sha512sum. CPMUtil.cmake is upstream's. Ours covered one more case: a cache made before zortos293#792, at the first configure after it, with the release host down. That configure fails as before zortos293#792; the first one that reaches the host records the hash. - "vulkan: keep a deferred clear's render pass for the draws after it" (1457bd0). zortos293#699 continues the active pass for every draw on the same framebuffer and render area; ours did that only for a clear variant without MSAA discards. Without tiler resolves (desktop GPUs) every deferred clear's variant is such a variant, so the two keep the same passes. On tilers zortos293#699 also stays in a variant that discards its MSAA attachments, where ending it, as ours did, loaded attachments that pass had just discarded. RequestRenderpass is upstream's, and Scheduler's clear_variant member is gone. The other conflicts keep both sides: - vk_rasterizer.h: this branch's read entry points (FlushRegionForRead, FinishRead, TraceRead) next to zortos293#752's pending_tick parameter and IsGpuTickDone. - vk_rasterizer.cpp, DispatchCompute: zortos293#690 adds publish_collision_map next to record_read_barrier, which this branch replaced ("vulkan: record a dispatch's barrier in the dispatch's own command"). publish_collision_map stays, record_read_barrier stays removed. - vk_rasterizer.cpp, GetFlushArea: zortos293#264 forgets the read traps over a texture cache area, this branch tags the area's source for the read statistics. Both. - buffer_cache.h: zortos293#816 marks native compute bindings only as far as their stores reach, this branch passes the write site to MarkWrittenBuffer. Both, in BindMultiRangeStorage and BindHostComputeStorageBuffers. - buffer_cache_base.h: Nx2ReadListCopy's comment keeps this branch's word-exact description and adds zortos293#752's Pending. - dma_pusher.h, dma_pusher.cpp: zortos293#752 suspends a channel on a list copy the host GPU hasn't finished and skips the writer wait when the entry is parsed again. This branch takes GPU-written macro parameters as placeholders (TakeGpuWrittenListWords), waits only for the writers before the last sync (WaitForSyncedWriters) and counts list reads (EDEN_NX2_LIST_STATS). Step tries the placeholders first and then zortos293#752's read, which may suspend. A resumed read skips WaitForSyncedWriters: it ran before the suspension. The read statistics wrap the pending-aware read. - control/scheduler.h, scheduler.cpp, gpu_thread.cpp: zortos293#621 makes ResumePending return whether a channel moved, to reset the poll timer, as this branch's "video_core: decide Switch 2 acquires from the split's semaphore view" (188baa1) did. Upstream's names are taken. The poll's profile zone, PublishSoon and the QMD view takeover after the poll stay. - puller.cpp: zortos293#687 releases fences again, at most once a millisecond, while an acquire waits with delayed fence operations. This branch split CanResumeAcquire into the semaphore view (PollWithoutDrain) and the drained path, and moved the long-wait log into ReportLongWait. zortos293#687's release goes into the drained path before that log, where upstream has it. The view needs none: it applies only while releases land at once (ReleasesLandAtOnce), never with delayed fence operations. - maxwell_3d.cpp: zortos293#488 tracks whether the back's method sink holds state writes (sink_pending), this branch sends the same writes with Writes::Noted and notes what they write. Both. A back macro consumes the sink before it runs, as ProcessMacro does, so sending one clears sink_pending too. Interactions checked: - zortos293#816 and the GPU-written list words: a dispatch from list words (Nx2ListIndirect) has its slot's placeholders as its grid, small numbers in y and z. BindComputeStorageBuffer resolved the store extent with them, which marks too few bytes when a store's offset grows with the group id in y or z: list words and read downloads over the rest would have gone stale. Such a dispatch's grid now counts as unknown, as an indirect dispatch's does. Everywhere else the mark leaves out only bytes no store reaches, which no list word or read download depends on. - zortos293#752 and the word-exact list reads: Nx2ReadListCopy returns Pending only after its word checks, so lists on words no GPU work wrote still read guest memory without waiting. EDEN_NX2_LIST_VERIFY reads its reference as before (all writers, waiting for the copy). EDEN_NX2_ENTRY_CHECK and EDEN_NX2_DRAIN_CHECK look at entries on pages no GPU work wrote, which never suspend on a copy. - zortos293#264 and the read downloads: every GetFlushArea path a Switch 2 title takes (read downloads in flight, early downloads, flushes) forgets the read's traps, so the next GPU write arms them again; the texture cache's area now does too. - zortos293#337 and "land read downloads under query results": a fence-flagged report's operation still lands the read downloads under it first, now from the fence thread, which holds no cache lock while it runs operations. zortos293#337 also makes such reports land at their fence with Normal accuracy. The semaphore view values the payload reports the front sends itself without reading memory. A word it reads from memory once the back ran the release may still lack a report waiting for its fence: the channel then waits until that report's operation latches it. - zortos293#488 and "engines: write 3D state registers in one call": the back still puts state writes outside macros in the method sink and consumes it before macro writes, executable methods and method runs. The fast path only changes how CallMethod writes a register, so sink_pending matches the back. - zortos293#688 and the back macros: native MME no longer reads its parameters again from guest memory. The back's macro engine runs on the parameters the front fetched, as the front's does. - zortos293#621 and "release threads waiting for sync operations once the GPU threads stop": the poll only resets its timer and skips the sleep when no channel is left; NotifyShutdown is unchanged. - zortos293#769 pins the split GPU threads to their own cores by default (8 physical cores on a 7800X3D): the threads this branch measured unpinned are pinned now. - zortos293#818, zortos293#817, zortos293#296 and zortos293#690 change image uploads and the texture cache. None of them uses a buffer cache buffer on the GPU, so the upload reordering's use marks still cover every buffer use. zortos293#815, zortos293#781 and zortos293#820 change only the pipeline cache and shaders, zortos293#655 the game patches and the vsync rate (60 Hz by default), zortos293#689 decoder frames. Tested: - Compile checks of 23 files: the conflicted ones, the auto-merged ones both sides changed and users of the changed headers, without RTTI. The tests that include the changed headers compile (buffer_cache_ports, command_snapshot_suspensions, command_writer, maxwell_fast_method, gpu_written_pages). - clang strict warnings (shadowing, narrowing, sign, switch, reorder) on the lines changed against bf48fe9 and against f9f3bc6: none. - The harnesses, rebuilt against this tree (their settings objects too, since zortos293#655, zortos293#769 and zortos293#816 add settings): - split: 20 of 20 runs, and 10 of 10 pinned to one CPU; - QMD view, with EDEN_NX2_DRAIN_CHECK=1 EDEN_NX2_ACQUIRE_STATS=1: no failures, every drain check released otherwise 0, read otherwise 0, differing 0; - read downloads (with buffer_cache_ports' download, read and GPU write cases): 14,311 checks, no failures; - DMA writes: no failures; - list writers: entry (EDEN_NX2_DRAIN_CHECK=1 EDEN_NX2_ENTRY_CHECK=1) 17 checks, tagged 212,633, front 158, vk 288 (lavapipe with synchronization validation: nothing outside the controls, each control reports its hazard), no failures. The macro harness needs nx2_list_dump.h, which neither this tree nor the PR head has, and was not run; - upload reordering: hreorder and hshutdown, no failures; - gpu_hbatch (lavapipe with synchronization validation): nothing outside the controls, each control reports its hazard, no failures. The output of every harness that ran matches its run on the PR head. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pavelgajdo
pushed a commit
to pavelgajdo/Lichii-Android
that referenced
this pull request
Nov 8, 2026
…ld) (zortos293#690) * mkw: enable targeted water feedback (Mario Kart World) Port the fingerprinted wave and collision-map fixes from zortos293#553 and adapt zortos293#729 movie pacing without opt-in switches. Limit movie activity to MKW and the current graphics session, track completed frame outputs independently of release fences, and retain current experimental decoding and shader diagnostics. Assisted by Codex. (cherry picked from commit 39a33c4) Lichii port (Eglantine-Community/Eglantine#737, merged to upstream nx2 through zortos293#755): - Only the water half is taken: the wave displacement fallback for empty depth inputs (upstream zortos293#553, 9530cecfc4) and the collision height map write-back (upstream zortos293#553, 13c88a9599). The movie pacing half (upstream zortos293#729, klova5's 447c9244a9) is left out: nx2 serves the gsv/vi2/vdec sessions on three host threads (am.cpp, Lichii zortos293#659), the root cause of the game asking for decoded frames ~34 times a second while vi2 blocked its only thread. - The mkw_zero_depth_v1 pipeline cache directory is left out. nx2 keys a native program's pipeline by the hash of its lowered code (GenericEnvironment::Analyze), so the patched shader gets a new key and the cached unpatched one is never used; moving the directory would only throw away every Mario Kart World cache entry and break the Qt "remove shader cache" paths for it. - The dispatch hook is placed after nx2's DispatchCompute setup, where qmd is defined, and the include follows nx2's list. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * texture_cache: write Mario Kart World's collision map back without a GPU wait The ported write-back downloaded the 256x256 collision height map and called runtime.Finish() after every dispatch of the collision program, so the GPU thread waited for the GPU to go idle at least once a frame on any course with water. Queue the image on the existing preemptive download path instead (the one BindRenderTarget uses): the next fence records the copy, and the fencing thread writes it to guest memory when the GPU reaches that fence. nx2 signals a fence per compute dispatch, so the map lands within the GPU's own latency, without the stall. The wave fallback inserts three instructions into the lowered program and moves everything after them. Upstream checked that its lowering of this shader has no branches; nx2's lowering differs, so skip the fallback (with a warning) if the lowered program has any branch, call or SSY/PBK/PCNT push, instead of corrupting its targets. Follows up Eglantine-Community/Eglantine@39a33c4a78 (upstream zortos293#737). Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: snowstormxd <214507868+snowstormxd@users.noreply.github.com> Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> (cherry picked from commit 5acbea8)
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.
Ports the water half of upstream Eglantine's Mario Kart World water/movie fix and makes its collision write-back stop stalling the GPU thread.
What it fixes in game
Mario Kart World (0400C3F00006E000). Two related water bugs:
6e0ae200ee9aa220) writes, but nothing writes that image back to guest memory: the texture cache only writes back GPU-modified images on request, and nx2's read traps cover buffers, not images.5a4f3726590efb17) gets an empty water/ground depth pair, its depth factor is 0 and the displacement disappears. The fallback sets the factor to 1.0 only when both inputs are exactly zero, so real shallows and dry ground keep their behaviour.Source
39a33c4a78by @snowstormxd (Fix MKW water feedback and cup intro movie pacing by default Eglantine-Community/Eglantine#737, merged to upstream nx2 via Yoshi and the Mysterious Book #755). It ports upstream Android Reworked + Performance #553 (9530cecfc4wave fallback,13c88a9599collision write-back). It is cherry-picked with-x, so snowstormxd stays the author.What I adapted (commit 1, cherry-pick)
447c9244a9, which skips present pacing while a large MKW movie plays). klova5 diagnosed the slow cup-intro movie: the game requested decoded frames only ~34 times a second while vi2's present blocked. That is the same mechanism Lichii nx2: keep movie video in sync with its audio (imports #571, ports upstream 1f52ac7e2e) #659 fixed at the root:am.cppnow serves the gsv/vi2/vdec sessions on three host threads, and upstream doesn't have that fix. A second, game-specific pacing bypass isn't needed unless the intro is still slow on current nx2. See the test steps below.mkw_zero_depth_v1pipeline cache directory. nx2 keys a native program's pipeline by the hash of its lowered code (GenericEnvironment::Analyze→NativeShaderHash). The patched shader gets a new key, so the cached unpatched pipeline is never used. Moving the directory would discard every MKW user's shader cache and break Qt's "remove shader cache" paths for MKW.DispatchComputedefinesqmd. The include was adjusted to nx2's list.What I changed on top (commit 2)
runtime.Finish()after every dispatch of the collision program, so on water courses the GPU thread waits for the GPU to go idle at least once per frame. This version queues the image on the existing preemptive download path, the sameuncommitted_downloadspathBindRenderTargetuses. The next fence records the copy, and the fencing thread writes it to guest memory when the GPU reaches that fence. nx2 signals a fence per compute dispatch, so the copy is committed right away. Trade-off: with the default (fast) GPU fence behaviour the guest's semaphore release happens immediately, so the game may read a map that is up to one GPU latency (about a frame) old. Upstream'sFinish()gives it the same-frame map. The bug being fixed is a map that is never updated, so a one-frame lag should not matter, but it is not proven in game.lower_to_maxwell.hdiffers a lot, so the fallback now skips (and logs a warning once per lowering) if the lowered program has any branch, call, or SSY/PBK/PCNT push (Maxwell 0xe20–0xe2b), instead of corrupting their targets.How I verified it
-DENABLE_WERROR=ON(the CI configuration, Qt off):teststarget, which also buildsvideo_coreandcore, built with 0 warnings. Full./bin/tests: all 889 test cases passed.clang++ --target=aarch64-linux-gnu -fsyntax-onlywith each TU's compile_commands flags passed forshader_environment.cpp,vk_pipeline_cache.cpp,vk_rasterizer.cppandvk_texture_cache.cpp(which instantiatestexture_cache.h). Forvk_rasterizer.cppthe host's x86-64 ffmpeg headers were put on the include path.git diff --checkclean.Not verified
Nothing ran in the game. I couldn't check whether nx2's lowering of the wave shader has branches (if it does, the log shows the warning below and waves stay flat), whether the async write-back is fresh enough for the game's physics, or the fps change.
How to test in game
Mario Kart World wave fallback skipped. If it's there, the wave half did nothing on this build; report it so it can be redone for nx2's lowering.*:Info,nx2:vdecsvc frame 1 (2560x1440andframe 1452should be about 24 s apart.Co-authored-by: Codex draft codex-draft@users.noreply.github.com