Repository navigation
Sync with upstream Lichii nx2 (d549411) - #17
Merged
Merged
Conversation
…os293#287) * frontend: fix Linux AppImage switch discovery and package defaults Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * frontend: constrain adjacent switch files and reject relative config roots Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* qt: add consent-first desktop compatibility reporting Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * tests: compare HTTP header names case-insensitively Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * qt: preserve report state and prefill active GPU identity Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * qt: prefill report title and enabled add-ons Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * qt: report the loaded NUP package version Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * qt: preserve literal characters in catalog searches Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…3#397) * core: add the run walker the pending-write port uses Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit eb6a2de) * buffer_cache: don't download GPU data over newer CPU writes Ported from Duck912/Eglantine-ducky e27fbc6b03 (Duck912). CPU writes into GPU-written memory are collected and applied later by the GPU thread; until then two paths wrote the GPU's older copy over them: the fencing thread's async download pops, and applying the collected writes one by one (WriteMemory downloads the GPU bytes elsewhere on the pages it touches). In Ocarina this made the Z-target reticle use values from two frames before on about half the frames. Every download write into guest memory now skips the collected, unapplied CPU writes (GPUDirtyMemoryManager::ForEachPending, which here also covers writes a Gather is handing out), and InvalidateGPUCache hands the collected writes to the rasterizer as one batch that is taken out of the GPU-written ranges before any is applied. This tree has more download paths than the original (DownloadMemoryRanges, DownloadRangesMemory); all eight writes go through the same check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 0c82282) * tests: downloads keep CPU writes collected and not yet applied Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: Frizti <FriztiMouche@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* shader: gather from a second descriptor buffer as TLD4.B From MateusAugustoBrito/Lichii a04f756 (MateusAugustoBrito). A gather whose handle comes from another descriptor buffer than the program's loads it with MOV and emits Maxwell TLD4.B, as TEX already does, instead of rejecting the program. Programs with one descriptor buffer lower as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 794233f) * tests: gathers from a second descriptor buffer lower to TLD4.B Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: Frizti <FriztiMouche@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…tos293#185) * texture_cache: land deferred clears before non-pass image writes A deferred clear is parked until its framebuffer's next render pass, but texture cache copies bypass render passes: the clear then realizes after the copy and wipes what the copy wrote. On hardware the clear executes at its stream position, before the copy, so the copy wins. That is the Ocarina of Time terrain layer (zortos293#101): the game clears the occlusion depth, blits the half-res scene depth over it, then the layer draw samples it. The parked clear landed after the blit from the second frame on (GetFramebufferId only flushes when it creates a framebuffer), the map read 1.0 everywhere, and the layer drew over the scene. It matches the workaround that dropping the Z-only clears before the blit removes the blotches. BlitImage, CopyImage and PrepareDmaImage now realize a parked clear before writing; draws keep the load-op fusion. * texture_cache: only land deferred clears when the write proceeds A rejected copy writes nothing and must not realize a parked clear ahead of the next draw that consumes it as its load-op. Move the flush in BlitImage after GetBlitImages (safe: framebuffer removal flushes first), after CopyImage's early returns, and gate PrepareDmaImage's on the DMA write actually happening (mark_as_modified). Review note from RomainROCH on zortos293#185.
…SURED register shadowing (zortos293#149) * vulkan: submit deferred work before waiting on pending fences On the split GPU thread, Scheduler::DeferFlush holds fence submissions back to batch them; the back submits them only when idle or asked between entries. WaitPendingFences(force) on the back queued a fence and blocked on the fencing thread, which waited for that fence's submission: Ocarina of Time froze on a black screen as gameplay began. Submit deferred work before waiting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * present: interpolate between the previous and current frame in FSR3 frame gen Process() copied the new frame into both the history and current images after the flow, so every generated frame blended the newest frame with itself; the interpolation also applied the motion vectors with the wrong sign and as full-resolution texels. Text over moving backgrounds was mixed with its own drop shadow (black title menu options in Ocarina of Time). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * shader: shadow the data registers of RED and SURED The register shadowing check reads bits 32-39 only for register-form opcodes and a short exception list. zortos293#100 added ISBEWR (0x927) for Ocarina's door NaN. RED (0x98e) and SURED (0x59f) take their data register from bits 32-39 as well, and RegisterPressure already lists operand 32 for both, but they were never asked: a data register at or above the uniform bank read Lichii's UR mirror instead of the guest value. Add them alongside ISBEWR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…e rejecting (zortos293#241) A native program whose own registers reach into the high uniform mirrors (R(216 - n) and up) has those registers shadowed by PrepareRegisterPressure, which works in aligned groups of four. n counts in pairs, so the base could be misaligned (R202 for n = 14) and the program was rejected with "native program uses lowering scratch registers" even though shadowing supports its instructions. Request one more lowering pass with n rounded up to a multiple of four instead; programs that lowered before are unchanged. Also shadow the register form of SHF (0x219): its destination, low word, shift amount and high word, like 0x210-0x212. Programs with it there were rejected with "native register shadowing mode". Shadow LOP3.LUT with a constant buffer or uniform B (0xa12, 0xc12) like its immediate form (0x812) too: Rd, A and C are its only registers. ampere_ported_uniform_vote expected the old rejection for a native R214 inside the UR28 mirrors; it now checks the realigned result instead: UR28 read at R212 and the native move written to its shadow, R210. In Ocarina of Time both rejected pixel shaders of the Gerudo Valley rock draws, so the rocks were skipped and the sky showed through. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…rray index against the innermost array (zortos293#203) * fix(file_sys): apply LayeredFS mods to NUP titles AppLoader_NUP reports the RomFS as not updatable (update already merged), so RomFSFactory returned the raw file and user/load/<title>/<mod>/romfs layers were never applied. Run PatchRomFS without an NCA: the update step is skipped and LayeredFS still runs. No mods present = same file as before. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(config): assert nested array index against the innermost array SetArrayIndex compared the index with the outermost array's size, so a nested array (DisabledAddOns/<n>/disabled with more than one entry) hit the assert on every entry past the outer size. Reading still worked; only the log filled with Critical asserts. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…arina white flashes) (zortos293#215) * video_core: flush the query cache before macro DMA reads under the GPU split Macro DMA reads of report regions flush the query cache so occlusion reports and semaphore writes land before the read, but the flush was gated on the split GPU thread being inactive. The default configuration (EDEN_NX2_SPLIT=2) therefore served stale counts to macros whose own comment warns that stale values make 'occlusion predicates read stale values' -- the guest reads a zero visibility count and culls visible objects, which matches Ocarina of Time's missing vegetation (zortos293#101) and the count=0 draws traced there. The native macro path now flushes under the same owns_caches condition as its stale-read trace, and the split front path (FrontDmaRead) flushes on the back next to its buffer cache flush. SemiFlushQueryDirty writes out finalized values directly and waits out the rest through the fence manager (WaitPendingFences, hardened in zortos293#149). Build-checked and unit-tested on Linux; gameplay validation needs the EDEN_NX2_SPLIT=0 vs 2 vegetation A/B described in zortos293#101. * video_core: wait for pending query results before macro DMA reads The query cache flush before macro DMA reads asks the fence manager to wait out pending reports, but below High GPU accuracy a fence runs its callback at once, so the wait returned before the report writes landed. The macro still read the old occlusion counts. With the GPU split's EDEN_NX2_QUERY_DMA_TRACE on, Ocarina of Time (0400486024DEC000) showed 976 such reads in 2.5 minutes, each reading a different value than the one that landed later, and the scene flashed overexposed for a few frames whenever the camera moved (cutscenes or the stick). The macro paths now flush through FlushQueryResults: when the region holds pending reports, Vulkan queues an operation behind them, signals a fence, submits work the split's back deferred, and waits until the operation has run. Other ReleaseFences callers are unchanged. Build-checked on Linux (clang, Release). Gameplay tested on NVIDIA, Low GPU accuracy, GPU split active: the flashes on camera movement are gone. Not run: unit tests, AMD, High accuracy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Gavin Schiller <gavinschiller51@gmail.com> Co-authored-by: WeslieDE <weslie@weslie.tk> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ortos293#197) * shader_cache: skip the cached rejection path for compute dispatches A rejected program fails the same way on every dispatch, but ComputeShader() rethrows the recorded exception and then records it again: two C++ throws, a mutex-guarded insert and a current_exception() each time the program is dispatched. GetGraphicsEnvironments already skips the equivalent draws for exactly this reason ("two C++ throws each time cost ~1-2% of the GPU thread"), so give the compute path the same treatment. Return a missing shader instead, which CurrentComputePipeline() in both renderers already handles, and keep the first exception recorded for an address rather than capturing a new one on every rejection. Reported against build-75 (c9ebb5c): Ocarina of Time rejects compute programs with "native texture gather mode" and "native per-tap gather control-flow proof" and then dispatches them repeatedly, so the compute path paid this cost per dispatch rather than once. * renderer_opengl: report a null native compute pipeline as a failed dispatch Native mode is enabled from the command stream (ChannelState::Init, gated by g_nx2_ampere), not by the renderer, so the OpenGL rasterizer can reach the cached-rejection path that now returns a missing ComputePipeline. Returning silently there makes KeplerCompute::DispatchNativeCompute treat the dispatch as successful, which skips its rejection bookkeeping (including dlss_chain.rejected) and reports no QMD failure. Match Vulkan and throw instead, as both backends already do for a native compute pipeline that cannot be built. Reported by CodeRabbit on zortos293#197. --------- Co-authored-by: axsddlr <axsddlr@users.noreply.github.com>
…GPU back thread (zortos293#412) * vulkan: look the draw's pipeline up once UpdateDynamicStates looked the current graphics pipeline up again for each draw (~2.5% of the split's back thread in Ocarina); PrepareDraw now passes the one it found. Ported from Frizti/Lichii 23f40d5 without its tools/nx2/sample_report.py change (nx2 has no sampling profiler report). (cherry picked from commit 23f40d5) * common, query_cache: skip range subtractions and query erases that touch nothing RangeSet::Subtract looks the range up first: the buffer cache clears several sets on every write, and most hold nothing there. IterateCache only erases a page's queries when one was in the range. About 3% of the split's back thread in Ocarina of Time (Lake Hylia). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit da215e8) * video_core: skip resetting untouched buffer usage trackers Port the tracked-state guard from sylvieistaken/selevet-eglantine d8c95fe. Keep page and recent-range clears after every Track while avoiding repeated clears for idle buffers. Preserve existing tracker ownership and reset semantics. (cherry picked from commit b16ba4f) * tests: cover RangeSet subtraction and skipped usage tracker resets RangeSet::Subtract now returns early when nothing intersects the range, and UsageTracker::Reset skips trackers nothing tracked since the last reset. Check both against reference models: a per-address bitmap for the range set, and the always-clearing tracker for resets, including back-to-back resets. --------- Co-authored-by: Frizti <FriztiMouche@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…r-GPU-written pages (zortos293#413) * vulkan: skip the buffer cache lock for dirty checks on pages the GPU never wrote The split's front asks MustFlushRegion before each macro DMA read; it waited for the buffer cache mutex the back holds through each draw (8% of the front in Ocarina). Every GPU-modified range also marks its pages GPU-written (bits that never clear), so pages without the bit are answered without the lock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 6247fd5) * video_core: answer the lock-free flush check from nx2.h and test it Move the GPU-written page walk behind MustFlushRegion's lock-free answer into Tegra::Nx2MayBeGpuWritten next to the bitmap it reads. A range past the bitmap, or one that wraps, now answers true and takes the lock: Nx2MarkGpuWritten clamps to the bitmap, so those pages were never marked. Test the page boundaries, the bitmap end and clearing. --------- Co-authored-by: Frizti <FriztiMouche@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…93#414) From Hydro74000/Edlws 034668a14e (Hydro74000). The old buffer is kept until the GPU is done with the current tick instead of finishing the GPU, and the buffer grows by at least 1.5x. (cherry picked from commit a20c9a9) Co-authored-by: Frizti <FriztiMouche@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…(OoT near foliage) (zortos293#410) * shader: bind loaded pointers that compete with a paired cbuf pointer A native global access whose address is a select or phi of a constant buffer pointer and a pointer loaded from that buffer was paired with the constant buffer pointer alone, so the native candidate search never ran. Accesses through the loaded pointer were then rebased on the constant buffer's binding and read out of bounds as zeros. Ocarina of Time reads its foliage unit placement records from c0[12] or from *c0[12]. The near-leaf pass (539ffb3f650a34ac) read every record as zero, normalised a zero vector and wrote NaN positions, so the near ShootLeaf meshes (BancLeafMesh, FoliageLeafNear) were never built and trees lost their leaves inside about 20 m. Run the candidate search for paired accesses too. If a loaded pointer competes with the pair, bind every candidate plus the paired pointer and let the backend pick the buffer at run time; otherwise keep the pair. Signed-off-by: Duck912 <350528965+Duck912@users.noreply.github.com> (cherry picked from commit a51a69f) * tests: bind both a cbuf pointer and the pointer loaded from it A native access whose address is a select or phi of c0[0x30] and a pointer loaded through c0[0x30] binds both buffers and stays global, as Ocarina of Time's near-leaf pass 539ffb3f650a34ac needs. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * shader: keep the pair when competing loaded pointers cannot be bound Binding the loaded pointers that compete with a paired cbuf pointer leaves the access global. The SPIR-V backend cannot route global subword accesses or most global atomics, so those accesses keep their pair. The loaded pointers are bound without the table budget, so a program already at 32 storage buffers overflowed the descriptor vector. When they leave too few bindings, collect again keeping the pairs. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * shader: keep the pair without 64-bit integers The SPIR-V backend routes no global access among bound buffers without shaderInt64, so a paired access left global would read zeros or fail to translate. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Signed-off-by: Duck912 <350528965+Duck912@users.noreply.github.com> Co-authored-by: Duck912 <350528965+Duck912@users.noreply.github.com> Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
… CPUs on Windows (zortos293#232) SetCurrentThreadToPerformanceCores(), SetCurrentThreadToEfficiencyCores() and the other placement calls only did something on Android. On Windows a hybrid CPU (Intel P and E cores) could run the CPU core threads, the GPU front and back threads, the Vulkan worker, HostTiming and the fencing thread on an efficiency core, where a frame takes noticeably longer, while the texture decoder and pipeline builders competed for the fast cores. Read the CPU sets with GetSystemCpuSetInformation, take the highest EfficiencyClass as the performance group, and assign each thread its group with SetThreadSelectedCpuSets (a soft affinity the power manager respects, unlike affinity masks). Non-hybrid CPUs (one efficiency class) are unchanged; EDEN_HYBRID_CORES=0 turns it off. Both APIs are looked up at run time so older Windows builds still start. On an i9-13900K (8 P + 16 E cores) the time-critical threads now show the 16 P-core CPU sets selected and the texture decoder the 16 E-core sets (verified from outside with GetThreadSelectedCpuSets). Ocarina of Time in Kokiri Forest measured within noise of the previous placement (Windows already favoured the P-cores for those threads in that scene), so this is parity with the Android placement and a guard against the scheduler moving a hot thread, not a measured speed-up.
* video_core: fix GCC shadow error in pipeline cache The deferred shader pool local reused the name of the CreateGraphicsPipeline parameter. GCC treats this as a shadow warning, and Linux builds with ENABLE_WERROR=ON fail because -Werror=shadow promotes it to an error. Rename the local while keeping pool release and reuse unchanged. * tests: fix GCC shadow errors in Linux build Recent config, audio, and video tests introduced local names that shadow inherited members or the global Result type. With ENABLE_WERROR=ON, GCC treats those warnings as errors and stops the full build. Rename the test locals without changing their behavior.
MSVC builds with warnings as errors stop on C4388/C4389 in nx2_debug_server.cpp (std::optional<u64> == int) and buffer_cache_ports.cpp (u32 == int). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…93#167) swscale defines only the visible width of each output row: the bytes up to the stride are spill from its vector stores and can differ between two identical conversions. The scaler-path tests compared full pitch rows, so two of them fail on FFmpeg 9.0 with 24 differing bytes that are all in the chroma row padding past the output width (the visible pixels are bit-identical). Compare the visible pixels instead. The hand-built copy-path expectations stay byte-exact. Build-checked and tested on Linux with FFmpeg 9.0.2: all 674 test cases pass, including the two that failed before.
) The comment said =2 paces submission two frames ahead. PresentClientBuffer reads only whether the value starts with '1': 1 waits for the previous frame's fence, anything else for this frame's. So =2 removes the overlap the comment promised, and in Ocarina of Time it cost about a fifth of the frame rate. Claude-Session: https://claude.ai/code/session_01RqE9yWsiiDmjnKWMhij7HM Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
In NPad::OnUpdate the JoyconDual case built the attribute bit by bit: is_connected, then is_left_connected and is_right_connected under an if each. GCC 13 at -O3 (the Linux release build, Ubuntu 24.04) keeps only the last of the two conditional assigns: with both Joy-Cons it writes 0x11 instead of 0x15, so the game sees the left Joy-Con missing. Ocarina of Time (0400486024DEC000) then opens the controller applet (raw id 0x100000c) about 40 times a second and never takes input, while the controller settings show the pad working. Build 93's binary has the two constant stores (5, then 0x11) and a lone g++ 13.3 -O3 compile of npad.cpp reproduces them. Build the value once and copy it to the libnx state, as Orchid did for the same loop in Drag x Drive (MythosOfs/Orchid 3c0ca62). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…3#159) OpenDataStorageWithProgramIndex only looks in the content provider, which has no entry for a NUP container or a loose dump, so games using it abort at boot. Return the RomFS the loader prepared when the lookup finds nothing. Co-authored-by: Walnutoast830 <Walnutoast830@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…os293#279) Ocarina of Time rejected two native pixel programs at the Kakariko graveyard with "native register shadowing mode" on IMAD R229, R29, UR10, RZ and IMAD R225, R49, UR10, RZ (opcode 0xc24), and their draws were skipped. The destinations sit in the uniform bank mirror range, but PrepareRegisterPressure had no operand list for 0xc24. Its register operands match 0xa24 (Rd, Ra and Rc; bits 32 to 39 name a uniform register), so it now shares that case.
…ortos293#329) The required-subgroup-size create info was built for graphics stages but never chained, so drivers chose the subgroup size themselves. Mesa's Intel driver runs mesh shaders 16 wide (it reports 16 to 32, with task and mesh in requiredSubgroupSizeStages), which splits the guest's 32-thread warp and breaks the votes, ballots, shuffles and broadcasts the translated programs use. Ocarina of Time draws its vegetation with task/mesh programs: one pair for the depth prepass and another for the colour pass, which tests depth EQUAL against a 2D-engine copy of the prepass depth. With split warps the two programs placed the leaves differently, so the colour pass left leaf-shaped holes that showed the sky behind them, moving every frame with the wind ("white flashing leaves"). Chain the create info for every graphics stage that uses warp-level operations when the device can force the guest warp size for that stage, as compute pipelines already do. Quad shuffles and FSWZADD are left out: they only span four lanes, and forcing SIMD32 on Intel can spill. Kokiri Forest by the ladder, Arc B390 (Mesa), Low accuracy: leaf holes went from 7-10% of the lower screen in every frame to 0 in 40 frames. Assisted-by: Claude Opus 5.5 (diagnosis, fix)
zortos293#353) * vulkan: don't write render target components the fragment shader doesn't output A fragment shader writes the render target components its header lists. The guest leaves the other components of a bound target untouched, but a Vulkan pipeline writes undefined values to attachment components that have no shader output, and AMD writes whatever its export registers hold. In Ocarina of Time a depth-only visibility draw with RT0 still bound left a band of NaN and saturated red/blue pixels above the horizon, which the bloom spread into violet halos. The pipeline's write mask, and the dynamic one with extended dynamic state 3, now leave out the components the fragment shader doesn't write. The dynamic mask takes the pipeline PrepareDraw binds, and is recorded again when the bound pipeline outputs a different set of components. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * vulkan: invalidate the scheduler state after a texture blit to a framebuffer BlitColor from a bare image view binds its own pipeline in a render pass of its own but, unlike the other blits, left the scheduler's state as it was. The next draw with the pipeline bound before it skipped binding that pipeline again, and every draw skipped recording the dynamic state the blit's pipeline replaced, such as the color write mask, until something else marked it dirty. DrawTexture and the DLSS output use this blit. It now invalidates the state as the other blits do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: jpkto <255363096+jpkto@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…#520) * ci: build and test with clang-cl on Windows in build-check build-check had no Windows job, so MSVC and Windows-only breakage went unnoticed until a release run. The new job builds the production targets and tests with the release compiler (clang-cl from LLVM 22.1.8, without ThinLTO) and runs ctest, with sccache and the CPM cache. The pinned LLVM install moves to tools/windows/install-llvm.ps1 so both workflows use one version and checksum. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * ci: spell the build-check clang-cl flags with dashes Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * build-check: make the Windows clang-cl tests pass and keep their cache Bundled zlib registered its example tests, which cannot find an installed package, so turn ZLIB_BUILD_TESTING off. The NUP metadata-edit test held the archive open while the edit renamed over it, which Windows refuses. Save the sccache cache even when tests fail. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * tests: keep the microphone capture stream off the stack SinkStream holds several megabytes of inline queues, which overflowed the 1 MiB Windows main stack. Allocate it on the heap like the sysmix tests. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…ts were null, bind up to 64 storage buffers (OoT Hyrule Field grass; imports zortos293#395, zortos293#421, zortos293#434) (zortos293#551) * shader: charge pointer table entries only for bindings the first pass left free Pointer table entries are bound in the storage buffer bindings that the pass without them leaves free. The second pass charged a table entry's holder to that budget whenever it was not in the set yet, but the holder is usually a buffer the first pass bound, and so already left out of the budget: charged twice, it shut out other tables. Ocarina of Time's foliage culling compute 331e607152ce78d5 bound 28 of 32 storage buffers and 4 of its 8 per-type instance tables, and a type whose table is not bound is culled whole. Only buffers the first pass did not bind take from the budget now: it binds 32 with all 8 tables (541ba22d44898196: 7 tables, was 6). Should the second pass still exceed the 32 bindings, the program keeps the first pass's buffers rather than failing to build. Two tests cover the charge and the fallback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 4ff805a) * shader, buffer_cache: bind up to 64 storage buffers per stage A native (Switch 2) program takes a storage buffer for every raw pointer it reads, and 32 bindings are not enough for all of them. Ocarina of Time's foliage culling computes need 33 in Hyrule Field and 37 with Castle Town's leaf lists bound too. Past the cap the pass leaves pointer table entries out, the per-type instance tables, and every instance of a type whose table is unbound is culled. Raise the cap to 64, which takes the buffer cache's storage binding masks to 64 bits, and keep each program within the device's per-stage storage buffer limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit f25c551) * vulkan: rebuild native compute pipelines whose pointer slot was null at translation Native programs only bind a constant buffer pointer as a storage buffer when it pointed to mapped memory while the program was translated, and that answer is stored in the pipeline cache. Ocarina of Time's foliage culling compute was first translated while two list pointers (c4[0x90], c4[0x130]) were still null; its list writes were dropped for good and the near hedges in Castle Town kept only their bare branches (Lichii zortos293#201). Remember the slots that were null at translation and rebuild the pipeline once such a slot holds a mapped pointer. Replaced pipelines stay alive for work in flight. When the cache holds both translations, keep the later one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 30e43ca) * vulkan: keep the cached compute translation that binds more pointers Which of a native program's pointer slots are filled depends on the area (Castle Town and Hyrule Field fill different list slots of Ocarina's foliage culling compute), so the later cache entry is not always the better one. Prefer the translation that bound more pointers and keep the earlier entry on a tie, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 15ff6cb) * vulkan: keep the old compute pipeline when its rebuild fails A rebuild can fail where the stale translation worked, for example when a narrow access now stays global across two mapped buffers. Keep the working pipeline and stop retrying instead of losing the dispatches. A cache reload loads entries while runtime entries exist; keep those and only choose between duplicates read from the file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 1ba80f0) * vulkan: choose between cached compute translations before building them Replacing an already loaded entry could destroy a pipeline that a dispatch was using, when loading overlaps guest work (cache reload, CLI startup). Read all compute entries first and order the duplicates of a key: more bound pointers first, the earlier entry on a tie. One worker per key builds the first candidate that translates and only adds it, so an existing entry is never replaced. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 1e22e5c) * vulkan: rebuild compute translations without dropping the pointers they bind A rebuilt translation binds the pointer slots that are filled when it is translated, so a rebuild in one area dropped the slots another area fills: Ocarina of Time's foliage culling fills different list slots in Hyrule Field and Castle Town. The load then preferred whichever cached translation bound the most pointers, which need not bind those the first translation did. A rebuild now keeps binding every pointer the old translation binds (a null pointer binds nothing), and the load tries first the cached translations that bind every pointer the first one binds, those binding the most first. With all of them bound, Castle Town's hedges keep their near leaves and Hyrule Field's grass keeps its per-type tables. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit dfc7211) * tests: hold 64 storage bindings in the binding capacity test The stages bind up to 64 storage buffers now, past what a 32-bit mask holds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 16551ff) * shader: reject programs that read more pointers than a stage binds Only the retries for competing loaded pointers and for pointer table entries kept a program within the stage's storage buffer limit. A program reading more direct pointers than that got a pipeline layout past the device's limit, or, past 64, a bad_alloc from the descriptor list, which the pipeline caches don't catch. Reject such a program with a translation error instead: binding only some of its pointers would drop the accesses through the others. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit a695c14) * vulkan: free replaced compute pipelines once the GPU is done with them A rebuilt compute pipeline's predecessor was kept until the cache was destroyed. Retire it with the current tick and destroy it in TickFrame once the GPU has passed that tick, as evicted graphics pipelines are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 4d62d81) * shader: count NVN buffers and the descriptor set limit against the limit The check that a program stays within the storage buffers a stage binds ran before AddNVNStorageBuffers appends the NVN fallback descriptors, and it took only the per-stage limit. StorageBufferLimit now also caps by the device's descriptor set limit, and AddNVNStorageBuffers rejects a program the same way when an NVN descriptor it adds would pass it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit a676f10) * vulkan: keep a replaced compute pipeline until its queued build has run A runtime compute pipeline queues its build on a worker, and a dispatch may build it first on the scheduler thread; the queued task still runs later and reads the pipeline. Destroying a replaced pipeline once the GPU passed its tick could free it before that task ran. The task now marks the pipeline when it is done with it, and a retired pipeline is destroyed only after both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit bec25b4) * shader: keep a dual vertex program's storage buffers within the limit MergeDualVertexPrograms joined Vertex B's storage buffers onto Vertex A's without a limit, so the merged stage could pass the device's limit or, past 64, overflow the descriptor list. JoinStorageInfo now takes the limit and rejects the program as the storage buffer pass does; the Vulkan and OpenGL pipeline caches pass their host's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 89f72df) * shader_environment: keep the native shader hash comment on its function Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * vulkan: keep a graphics pipeline's storage buffers within the descriptor set limit Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * tests: give the table loop helper internal linkage (-Wmissing-declarations) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: jpkto <255363096+jpkto@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: win <atk3001@gmail.com> Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…#221) * file_sys: ignore IPSwitch lines that are not patch records Text after a patch block, such as a mod's credits, was parsed as records and written into the module. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * file_sys: require the IPSwitch value right after the offset A quote or hex word later in free text still passed as a record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * file_sys: require a closing quote at the end of IPSwitch strings A line such as A "thank you was still written as a string record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * file_sys: keep the opening quote active when stripping IPSwitch comments An apostrophe inside a double-quoted value changed the quote state, so a later comment was kept and a slash could cut the value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
zortos293#160) A ZBIC segment that failed to decompress only logged `assert r > 0`; the loader then copied the previous segment's bytes into the code set and booted a corrupt image that never presents a frame. - DecompressDataZBIC logs zstd's error name instead of discarding it. - LoadModule returns nullopt on failure, which the callers already report as ErrorLoadingNSO, and zero-fills a short segment instead of leaving stale bytes in it. - zbic_compression.cpp is built with -fno-profile-instr-use: it runs once per module at load, and the aarch64 PGO build is the one that fails. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* dist: install the MIME package under the Lichii app id The Linux install still shipped dist/dev.eden_emu.eden.xml to share/mime/packages. That path collides with the file an installed Eden package owns, so package managers refuse to install both. Each MIME type also pointed at the dev.eden_emu.eden icon, which Lichii never installs, so NRO, NSO, NSP and XCI files showed no icon. Rename the package to io.github.zortos293.lichii.xml and point the icons at io.github.zortos293.lichii, matching the installed desktop file, metainfo and icon. The application/x-nx-* type names stay unchanged: the desktop file's MimeType= line uses them, and shared-mime-info merges duplicate definitions from several packages. * dist: register NUP, NSZ and XCZ MIME types The loader and the game list open .nup, .nsz and .xcz files, but the MIME package only defined NRO, NSO, NSP and XCI. Desktops showed those files without a type or icon and did not offer Lichii to open them. Add application/x-nx-nup (matched by its "NUP\x01" header), application/x-nx-nsz (a PFS0 container like NSP) and application/x-nx-xcz, and list them in the desktop file's MimeType=.
* qt: don't resize a maximized window when a game boots Resizing leaves the window marked as maximized at the new size, stuck in the top-left corner until it is dragged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * qt: keep a hidden render window hidden when resetting its size showNormal() showed the render window of a stopped game again. Clear the maximized state without changing visibility instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…s293#365) * vulkan: disable installed Overwolf hooks before loading Vulkan * docs: keep Overwolf startup notes limited to implementation and checks
A Switch 2 game paces itself on the vblank word in its present call: Lichii holds each present until the next vblank of that word times the swap interval (PaceToVblank), so the game cannot run faster than the rate it declares. Games that declare 60 Hz therefore cap at 60 whatever the host display does, and EDEN_NX2_VSYNC_HZ, which only drives the synthetic vblank event some titles start frames on, does not change it (OoT ignores it: its word stays 0x1003c). Add "FPS Limit" to the renderer settings (0 = off, up to 480): the paced interval becomes the longer of the game's vblank interval and 1/fps_limit, so the game itself runs at the requested rate instead of only the display. EDEN_NX2_FPS_LIMIT=<n> overrides it for tests without touching a config. The interval maths moves into PacedInterval so it is testable; the pacer tests cover limits above, at and below the vblank rate.
The experimental pin_gpu_threads setting (zortos293#418) only applied Windows CPU sets and was skipped elsewhere. On Linux, read each allowed logical processor's package and core id (and cpu_capacity on hybrid CPUs) from sysfs, plan the same way (the front and back on a physical core each, the guest cores on the other performance cores) and apply it with pthread_setaffinity_np.
…tos293#495) * vulkan: grow descriptor payload storage on demand after draining its worker * docs: clarify bounded descriptor growth and format its tests
…ffer (zortos293#427) Draws with no colour and no depth target take their framebuffer size from the surface clip, which Ocarina of Time leaves at 32768x32768 after the intro video. Apple GPUs allow 16384 (maxFramebufferWidth/Height), and Metal loses the device on the larger attachment-less render pass (kIOGPUCommandBufferCallbackErrorPageFault, or Internal Error 0000010d on M1). Clamp the framebuffer width, height and layers to the device limits. Constant buffers are bound from the stream buffer and shaders declare each at 64 KiB, so a binding near the end of the buffer reads past it; the Metal debug layer reports "argument c3[0] ... has space for 26624 bytes, but argument has a length(65536)". Create the stream buffer 64 KiB larger than the part that is handed out. Based on the patch balderos posted in zortos293#237, which was tested on an M1 and an M6. Adapted to current nx2: the layer clamp now applies to the layer count the framebuffer is created with, and the clamp is a small header so it can be unit tested. Refs zortos293#237
…owing (zortos293#440) * vulkan, texture_cache: fail an image that does not fit instead of throwing MemoryAllocator::CreateImage threw vk::Exception once every fallback was out of memory. On the texture cache path nothing catches it (the cached image is built inside SlotVector::insert, which is noexcept), so an 8 GB RX 9060 XT running Ocarina of Time died when the 1024x1024 RGBA8 decode image of an ASTC to BC3 transcode did not fit (zortos293#342). TryCreateImage returns a null image on VK_ERROR_OUT_OF_DEVICE_MEMORY or VK_ERROR_OUT_OF_HOST_MEMORY instead; CreateImage still throws for every other caller. On the texture cache path: - A cached image without memory is left out of the cache: JoinImages erases it and returns no image, so lookups get a null view (blits fall back) and make it again later. - TranscodeAstcToBcn frees its idle decode images and retries once; if that fails too it leaves the image to be uploaded again on its next use. - The allocator counts images it could not make, and the collector runs at the critical threshold the frame after one, whatever the usage counts say. Refs zortos293#342 * texture_cache: retry a texture whose image did not fit on its next read VisitImageView kept the null view of an image that had no memory in the channel's descriptor map, so the texture never came back once memory was freed. Leave it out of the map and invalidate the table so the descriptor is read and the image made again. Refs zortos293#342 * texture_cache: retry only images that did not fit, after the collector ran 6a53fbd dropped every null view from the descriptor map and invalidated the table on each visit. Drop it only when the allocator's failed image count moved during CreateImageView, and leave the table alone: the collector's deletions invalidate it, so the image is made again once memory was freed rather than on every draw. Refs zortos293#342 * texture_cache: re-read texture tables the frame after an image did not fit A texture left out for lack of memory was only retried once some image was deleted, which the out-of-memory collection does not guarantee. Invalidate the channels' image tables after that collection, so such textures are made again once a frame while images keep failing. Refs zortos293#342
… (Ocarina ~11% faster in Kokiri village) (zortos293#446) * gpu_thread: poll suspended channels every 50 us instead of every 1 ms A channel suspended on a semaphore its CPU releases is polled with yields for 500 us, then with 1 ms sleeps. Ocarina of Time suspends ~330 times a second and its present waits for the GPU work behind those acquires, so a release seen up to 1 ms late delayed the frame: in Kokiri village its present waited ~17 ms a frame on the GPU fence. Sleep 50 us instead, with the GPU thread's timer slack lowered on Linux so the sleep stays short. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * gpu_thread: back to 1 ms polls for a channel still waiting after 2 s Long waits (menus, loading) don't need 50 us polls. The waits behind the frame time measured in Kokiri village last ~1 ms. --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…base (zortos293#245) Handle programs record storage buffer offsets relative to the buffer address aligned down to the host storage buffer offset alignment (StorageOffset), and the buffer cache binds the buffer at that same aligned base. The CPU-side evaluator read from the unaligned address instead, so when a storage buffer address is not a multiple of the host alignment (16 on NVIDIA, up to 256 elsewhere) a storage-sourced handle is read from the wrong word. Pass the device alignment through from both the Vulkan and OpenGL pipelines and mask the buffer address the same way the shader does. For indirect buffers the table entry is still read at its exact address and only the address it holds is masked, as in the SPIR-V base_address. Adds a unit test that reads the same program at alignments 16, 4 and 0, directly and through an unaligned table. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Lets the shuffles of a group overlap; unrolling all of K16 raised register pressure and slowed large dispatches. Ocarina's DLSS menu frame went from ~18 to ~12.6 ms of GPU time on a 7900 XT.
…ose pages (zortos293#250) * video_core: skip GPU-write invalidations of caches with nothing on those pages The GPU's own cached guest writes (WriteBlockCached: constant buffer data, every draw) are invalidated in the texture, buffer, query and pipeline caches at the next draw (FlushCaching -> InnerInvalidation). The texture, query and shader caches only visit the objects registered on those pages, and constant buffer memory holds none: each call still took the cache's lock and walked its page tables, about 88k times a second in Ocarina of Time. Each of the three now keeps a PageFilter: one bit per 4 KiB device page it ever registered an object on (images, cached queries, shaders), never cleared. A write whose pages have no bit set can't concern that cache, so InnerInvalidation skips it. The buffer cache is still always told. Ocarina of Time, Kokiri Forest village, GPU accuracy Low, RTX 4090 + i9-13900K: nx2 52.0-52.3 fps -> 53.6-54.2 fps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * shader_cache: mark rejected native programs in the page filter A program rejected before Register is never marked, so InnerInvalidation skipped the shader cache for GPU writes over it and its rejection was never cleared. Mark its start address, which is all the erase predicate matches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * video_core: leave ranges past the page filter's space untracked Marking one (a stand-in address of an image without CPU backing) set an overflow flag that made the filter report every range as possibly holding objects. Any already reports ranges past its space that way, so Mark now ignores them and the flag goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The buffer cache makes about 30,000 RangeSet lookups and 18,000 subtractions a frame in Ocarina of Time, 97% of the subtractions missing every range, against 3,400 additions. The sets stay small: 35 ranges on average at an add, 198 at a subtract that hits, 763 at most. A boost interval_set with pooled tree nodes behind a mutex spent 5.0% of the GPU back thread on that. Sorted, joined ranges in one array answer the same questions with a binary search over contiguous memory and no allocation per range. Add and Subtract keep the interval set's semantics, including the joining of touching ranges. OverlapRangeSet needs counts per segment and stays as it is.
…3#333) * nvdrv: size Switch 2 GPU page tables to the VA NVN is given nx2:gpu reports [0x8000000, 0xC000000000) to NVN, but AllocAsEx built the GMMU from NvRm's 128 GiB va_range_end. Nintendo GameCube - Nintendo Classics mapped 0x2109ec0000 and PageTableOp indexed off the end of entries[]. Size the tables to the advertised end on Switch 2, and refuse a map that is still outside the address space. * nvdrv: refuse a sparse destroy that runs past the GPU address space PageTableOp leaves a range that crosses the address-space end mapped. DestroySparseRegion still released that range's backing, so the pages could be freed while the GMMU pointed at them. Reject the destroy before the release. A map or unmap of the last valid page that crosses the end leaves that page's translation alone.
…3#465) Scenario: Switch 2 (native Turing) draws whose graphics pipeline is still building are never skipped, so the GPU waits for the driver build. On an RX 6700 XT (Zelda OoT Switch 2) the log showed up to 14656 ms of draws held in a 10 s window and single builds of 16.7 s. Add native_async_draws (Wait, Bounded, Skip; default Wait keeps the current behavior) and native_pipeline_wait_ms (per-frame budget, default 8 ms), both in the Qt advanced graphics settings. Bounded does a timed condition-variable wait on the pipeline build up to the per-frame budget, reset in PipelineCache::TickFrame, then follows Skip. EDEN_NX2_ASYNC_DISCARD=1 still forces Skip. The decision is a pure function covered by a Catch2 test. Measured with Bounded at 8 ms on the same machine: at most 229 ms of draws held per 10 s window, 10 to 614 ms of bounded wait per window, and a one-frame flash when the budget runs out. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…293#550) * vulkan: don't evict disk-cache pipelines that were never used A pipeline prebuilt from the disk cache at boot keeps last_use_frame 0 until a draw uses it, so the first eviction pass after 9000 frames destroyed nearly all of them and the game translated them again on the GPU thread when it reached that content. * vulkan: track first use separately from the frame number frame_count starts at 0, so a pipeline used only on the first frame also had LastUse() 0 and was never evicted. Mark first use with a flag instead. * vulkan: cap the never-used prebuilt pipelines kept from eviction The used list spans 16 sessions, so a large cache could keep thousands of never-used pipelines resident for the whole session. Keep at most 2000 (~0.9 GB of driver memory); past that they are evicted like the rest.
…ter sizes (imports zortos293#478 by @mstampfli) (zortos293#580) Imports zortos293#478 by @mstampfli.\n\nCo-authored-by: mstampfli <marc.kurt.stampfli@icloud.com>
… queued (imports zortos293#170 by @janvitos) (zortos293#585) Imports zortos293#170 by @janvitos.\n\nCo-authored-by: janvitos <10037804+janvitos@users.noreply.github.com>
…tos293#299 by @ctgl1987) (zortos293#584) Imports zortos293#299 by @ctgl1987. Co-authored-by: CTGL <ctgl1987@gmail.com>
…OMG (imports zortos293#189 by @vouion) (zortos293#588) Imports zortos293#189 by @vouion. Co-authored-by: Gavin Schiller <gavinschiller51@gmail.com>
…12% standing, +24% walking) (imports zortos293#475 by @mstampfli) (zortos293#582) Imports zortos293#475 by @mstampfli. Co-authored-by: mstampfli <marc.kurt.stampfli@icloud.com>
…eneration loads (imports zortos293#230 by @PantACRO4life) (zortos293#587) Imports zortos293#230 by @PantACRO4life. Co-authored-by: PantACRO4life <6643071+PantACRO4life@users.noreply.github.com>
…a of Time 41 → 60 fps) (imports zortos293#521 by @estevanbs) (zortos293#583) Imports zortos293#521 by @estevanbs. Co-authored-by: estevanbs <estevanbartmann@gmail.com>
Brings in 101 upstream commits (including zortos293#412/zortos293#413/zortos293#414, zortos293#178, zortos293#215, zortos293#397, zortos293#485). Conflicts were branding and text only: README, desktop file, main_window log line. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RmvPX2tqfoEsDjDfWTAmSf
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RmvPX2tqfoEsDjDfWTAmSf
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.
Merges 101 upstream commits, including the CPU-side performance work (zortos293#412, zortos293#413, zortos293#414, zortos293#178), the white-flash fix (zortos293#215), the stale reticle fix (zortos293#397) and the SparseLargeVector crash fix (zortos293#485).
Three conflicts, all branding or text: README.md, the desktop file (kept Chata name and keywords, took upstream's new MIME types), and the startup log line in main_window.cpp. No conflicts in the Vulkan or GPU code.
Updates UPSTREAM_BASE_SHA in the chata-linux workflow.
Generated by Claude Code