mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
master
25057
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dfef534426 |
UI: the Android internal-resolution set in the Qt and Big Picture pickers
Both desktop resolution pickers ran Native, 2x, 3x ... in whole steps. That is the wrong shape for a handheld at both ends: below native is unreachable even though rendering there is a large win and the GS accepts it -- only the top end is clamped, in GSClampUpscaleMultiplier -- and above native the jump from 1x to 2x is four times the pixels with nothing in between. Both pickers now carry the set the Android UI has offered since issue #207: 0.25x 0.5x 0.75x Native 1.25x 1.5x 1.75x 2x 2.25x 2.5x 2.75x 3x 3.5x 4x 5x 6x 7x 8x Everything above 8x moves behind Extended Upscaling Multipliers, which used to unlock 13x and up. That checkbox was dead on ordinary hardware: the GPU cap is max texture size / 1280, so a 16K-texture part reports exactly 12x and the "supports extended" test wanted more than 12x. It now unlocks anything past 8x, which is the first time it does something on a normal GPU, and 9x-25x still appear only as far as the GPU can actually go. Big Picture indexed two parallel arrays as "slot i means multiplier i+1", which a non-uniform list breaks. Replaced with one table carrying the label, the INI string and the multiplier, filtered against the cap. The quarter steps are all exact in binary, so matching a saved multiplier by equality is safe. Two things fixed in passing, both of which the new list would otherwise have broken. The Qt global tab keyed "nothing found" off index 0 being Native, and index 0 is now 0.25x. And Big Picture passed "1.000000" as its default while the INI is written by StringUtil::ToChars, which produces "1" -- so with no key set the picker read back a value matching no entry and displayed Unknown.nightly-20260820 |
||
|
|
7f0ae7a6c6 |
GameDB: internal FPS by DISPFB blit for NASCAR Thunder 2002 through NASCAR 06
The register-write detector reads these engines as producing a new image every vblank, so duplicate-frame skipping never fires and every repeat pays for a full present. Counting blits into the displayed framebuffer restores it. Reported-by: yuasasanightly-20260819 |
||
|
|
be72a8e1eb |
iOS: put Download Shaders under Preset, where it can be reached
It shipped as its own Section on the settings page, which put it below the parameter list. With crt-aperture selected that is twenty-two sliders and about thirty-nine swipes, on the one control a tester had asked for by name. Found by walking the screen in the simulator rather than by reading it, which is the only way this kind of thing turns up. It moves into ShaderChainSection, directly under Preset and above Install Shader Pack, so the three ways to get a preset sit together in the order you would try them: pick one you have, download one, install one from a file. That also puts it in the in-game pause panel, which is a gain rather than a side effect, and it works only because GameScreenView wraps the shared section in its own NavigationStack. Without that every NavigationLink in the section is dead on tap, Preset included, so the fence now checks for the stack in the text immediately around the mount. Immediately around, and not anywhere earlier in the file, because the first version of that check searched backwards from the mount through the whole of GameScreenView and any one of its several other NavigationStacks satisfied it. Deleting the one that matters left the suite green. That is the third time in this branch an assertion has been satisfied by a neighbour, and the only reason any of the three were caught is that each new check was run against a deliberately broken source before being trusted. The row sits outside the enabled gate, which the first attempt at this move got wrong. Every other row in that section is behind `if enabled`, and putting the download row there too hid it whenever the chain was off -- so a first run had nothing to select, no way to fetch anything, and no hint that the toggle came first. Caught by relaunching with the chain off and looking, one screenshot after the change built. |
||
|
|
4fa820435d |
iOS: decode a launch link's filename once, not twice
queryValue returns its answer already percent-decoded on both of its paths: URLComponents decodes for the ordinary case, and the raw-query fallback decodes by hand for callbacks that arrive unencoded. launchGame then decoded it again. A second pass reads a literal percent in the value as the start of a new escape. 100%.iso percent-encodes to 100%25.iso, the first decode gives back 100%.iso, and the second sees % followed by .i, which is not hex, so removingPercentEncoding returns nil. The guard falls through and tells the player the link is missing a game filename, which is the one thing it plainly carries. Only the launch route did this; exportLibrary reads the same helper and does not. The contract now sits on queryValue rather than being something each caller has to know, since knowing it is what went wrong. The fence pins more than the fix, because armsx2://launch?game= is not an internal detail. libraryPayload hands that string to other frontends, which store it and replay it much later, so the verb, the parameter name and the encoding are a contract with software this repository does not control. It also compares the schemes the handler accepts against the ones Info.plist registers, in both directions: a scheme in code but not in the plist fails silently, because iOS never routes the URL and the handler that would have accepted it is never reached. Six mutations run against the real source, all six caught, each restored byte for byte. The scheme check needed the second direction to catch the sixth; the first version iterated the known list and could not see an addition. |
||
|
|
05d94ed82d |
iOS: download the RetroArch shader collection from inside the app
867 presets over 27 categories, one manifest and one zip each, and no third request anywhere. A tester asked for what Manic EMU has: a button that fetches the collection instead of making people find a zip and side-load it. The closure resolution and the licence sign-off landed first; this is the phone half. Order is the whole safety argument, because a remote manifest is attacker- controlled if the host is. The stated size is refused before the transfer rather than after -- the manifest carries it, so the refusal costs nothing. The received byte count and the SHA-256 are both compared before the importer is called, and the hash is streamed rather than read whole. The relative path is validated before it becomes a URL, because .. and / both survive percent-encoding. Then the fenced extractor does the writing, unchanged, so there is no second containment guard to get wrong. The manifest is 8 MB raw and 312 KB gzipped, and 96% of those bytes are the per-file array. The entry type does not declare that key, so it is skipped: the zip carries its own hash and that covers every file inside it. What lands in the cache is this build's own projection rather than the served bytes, which is also what makes browsing work with no network -- a failed refresh ages the list instead of emptying it. Two things the import path never had. Staging files are swept at launch, because defer does not run when iOS kills a backgrounded app mid-download, which is the ordinary outcome and not an edge case. And cancelling removes the pack if the extract already began, which is not the same as stopping it; the comment says so rather than implying otherwise. The importer returns the name it installed instead of only publishing it. One property on a shared object is fine for one caller and wrong for a screen with 867 rows and no reason to install them one at a time: two installs overwrite each other's answer, which would write one entry's marker into the other's folder and make cancelling one delete the other. Three fixes in the code around it, from the same review. A loaded chain owned a render target and a pipeline per pass and nothing freed any of it when the player turned shaders off, because DestroyShaderChain had exactly two callers, a preset change and device teardown. The Metal frame path flushed on success and returned on failure, though a chain that failed partway has already encoded passes into the same command buffer and needs the submit for the same reason the success path does. And the pack extractor held every file's bytes resident to the 32 MB cap, because the autoreleased data was never drained inside the loop. The catalogue is not published yet. The base URL is one constant, and an INI key no UI writes can repoint it, accepting only https and file -- which is how a simulator reads a local emit, since ATS refuses plain HTTP and there is no reason to weaken it for a test. Eleven checks in the new fence, six mutations run against the real source and all six caught, each restored byte for byte. Still open and written down rather than left to be rediscovered: the extractor's per-entry decompressed cap is applied after the entry is fully inflated, so a crafted zip can spend up to that cap before the refusal. Bounding it earlier needs a streaming inflate. The extractor's own fence gains an ordering claim. It asserted that a canonical resolve appears somewhere in the method, which passes for a resolve whose answer is discarded; deleting the entire containment refusal left it green. It anchors on resolvedParent now, because the body carries several refusal sites naming the same constants and anything looser is satisfied by a neighbouring refusal that has nothing to do with containment -- which is the same trap the first attempt at this fix fell into. |
||
|
|
af30d18304 |
iOS: three defects the branch review found, two of them silent
Seven lenses over the branch, each finding then handed to a skeptic told to refute it rather than confirm it. Thirty-one were raised. These three survived and matter, and two of them fail without saying anything, which is why none of them turned up in a device pass over a green suite. Per-game write() set the enabled key straight from the picker, before the guard that needs the preset to resolve. Choose On, delete the pack the preset came from, then save any unrelated row on that game: the file keeps the chain enabled and loses both preset keys, and an absent key in the game layer falls through to the base layer. That game then renders the GLOBAL preset. The type's own first comment says this never happens and boot-time repair has always got it right; write did not. It is the worst kind of wrong because it is invisible -- one CRT shader looks like another, so the player sees a filter and assumes it is theirs. A per-game preset never received its saved parameter values at all. SettingsStore pushes the global tier's overrides at launch and on every change, but a per-game preset is chosen in a file SettingsStore never reads, so the game rendered the shader author's defaults and every value saved against that preset was ignored. The boot repair already resolves that token before bootISO, which is the one place that knows both the token and the timing; pushStored is nonisolated now so it can be called from there without hopping actors and losing the ordering. Save as New Preset could destroy the preset it was saving from. The reference it writes is relative to My Presets and the sheet pre-fills the base's own name, so selecting a saved preset, nudging a value and accepting the default replaced that file with one whose only reference is its own filename. Nothing resolves that, and the values it held are gone. It refuses now, in the write path, which is the only place that can see both the target and the base. |
||
|
|
30d9816eda |
iOS: resolve the shader catalogue off-device, and sign what it may ship
The half of the downloader that cannot run on a phone: a generator that turns any preset in a pinned slang-shaders tree into a complete, path-safe, licence-classified file closure, and refuses to emit anything until a person has signed the rules it would be built from. Resolving a closure means walking includes and references across a 5,000-file tree. Over the GitHub API that costs two to five requests per preset against a 60-per-hour limit keyed to the originating IP rather than to the app, so every user behind one carrier NAT shares one budget. On a local clone it costs fifteen seconds of CPU and no network at all. That asymmetry is the whole design. emit refuses without a signed rules file recording the pin, so the catalogue cannot physically exist before the nine class questions were answered. Six were confirmations of rules the bundled sixteen already ran under. Three had never been decided and were worth 577 presets between them, and the one that mattered was whether a LICENSE file governs the directory it sits in -- worth 552 on its own, and exactly the inference the standing rule exists to refuse. Admitted, with the reasoning in the signed document rather than here. Of 2,553 presets in the tree, 867 are offered: 13 dropped on upstream defects, 8 on an extension the extractor will not write, and 1,665 excluded by class. Fourteen presets the earlier hand audit had measured agree row for row on file count and on upstream bytes, which is the free correctness check on all of it. The whole-tree run found the divide-by-zero prescale in ten more files than the two bundled ones, refusing 98 presets. Twelve sites and not ten, because the scanner reports one per file and clamping the first in crt-potato and ultra_potato made a second visible in each; the scan was re-run until it came back empty. All twelve now carry a notice in the file itself saying it changed and when. ATTRIBUTION.md covers the bundle and covers nothing once the same file travels in a zip on its own, which is where GPL section 2(a) asks for the notice anyway. The first wording of that notice said the change was "one max() and nothing else", and the guard test looked for max() anywhere in the file -- so the comment describing the fix satisfied the test that checks the fix exists. Both were changed: the notice says clamp, and the test now requires the guard on a line that actually matches the prescale pattern. |
||
|
|
e2b1bcaea2 |
iOS: let one game keep its own shader preset
A preset was a single global value, so picking crt-geom for a 2D fighter also applied it to the next 3D game booted. The per-game subsystem is the right home; the blocker was that its bridge exposed Int, Bool and Float and no String, while a preset is a string token. Four String accessors added, in the forISO and the current-game shapes the twenty existing per-game settings already use. A Shaders section on the per- game Graphics tab, where the global Shader Chain section sits, on the same tri-state sentinel every other control there uses: use global, off, on. Scope is preset only, decided with both arms in front of the developer. Parameter values stay global and stay keyed by preset token, and the panel says so on screen rather than leaving it to be discovered. A preset exposes up to twenty-two values and per-game copies of those would multiply the storage and the UI. The identity is the same root token the global tier uses, re-rooted at boot before bootISO reads the file, so a per-game choice survives a reinstall for the same reason a global one does. ShaderPresetLibrary.resolve stays the only token-to-path resolver; nothing here reimplements containment. The rule that matters, and the one the fence exists for: a token that no longer names a file turns the chain off for that game rather than falling through to the global preset. A different CRT shader looks like a CRT shader, so a substitution is invisible -- the player sees a filter, assumes it is theirs, and never learns their choice is gone. Six source checks over the six files the selection lives in, and four mutations run against the real source with every restore byte identical. Also here, because it landed in the same wave: the prescale fence widens to .inc and .h. A .slangp names its stages, but a stage includes whatever it likes, so that bug can sit in a header and never appear in a .slang -- which is exactly where the whole-tree catalogue run found it. |
||
|
|
7e8f7f1955 |
iOS: give the shader controls their own page and a pause-menu route
Four things a tester asked for after playing the first build, and the two defects found while building them. Shaders are their own settings page rather than a section inside Graphics. The section already took its persistence from the caller, so this is a move and a root row. The same controls reach the in-game Quick Menu, under Game Tools rather than Quick Actions. Not a drop-in: the settings section is Section-shaped and embeds a push, while the Quick Menu is card-shaped with no navigation stack, so it routes out to a sheet the way the speed panel does. A test holds it to that shape, because the shape is the thing that works rather than an implementation detail. Parameter rows use NumberRow, the control eleven other settings files already use, so a value can be typed instead of only dragged. Detents, units and the reset affordance come with it. Every label is translated into the nine languages beside English. Then the two defects. A preset's saved values reached the core only when a shader screen was open, so a cold launch rendered the author's defaults until the player visited the page -- proven by measuring frame luminance across a launch rather than by reading the code. And the guard that keeps SettingsStore.init off SettingsStore.shared was recovered from an orphaned commit and turned out to be broken: a plus-or-minus 400 character window let an allowlist entry cover its neighbour, so the test would not have caught the crash it was written for. It requires the match to span the access now. |
||
|
|
d24e9d76ed |
iOS: clamp a shader's prescale so upscaling cannot blacken the frame
crt-aperture and sharp-bilinear each derive a whole-number prescale from output height over source height and then divide by it. RetroArch only ever feeds them a small console framebuffer being scaled up, so that ratio never falls below one. PCSX2 renders internally at up to 8x: past roughly 1.5x on a phone the source is taller than the screen, the ratio drops under one, floor() returns zero and the divide yields NaN. The whole frame goes black. Reported on an iPhone SE 2 with a 1334x750 window, where 1.5x rendered and 2x did not. Two hypotheses were wrong first -- push-constant placement, then parameter placement -- and both were refuted by tester data before the reporter supplied the actual trigger, which was the internal resolution and not the preset. Reproduced in the simulator at 3x and fixed there. The clamp is what the sibling sharp-bilinear-simple already carries as max(floor(...), vec2(1.0)) and what crt-geom carries as clamp(floor(...), 1.0, 2.0). Nine of the eleven bundled presets never divide by a derived scale and were unaffected. These files are otherwise byte-verbatim copies of a pinned upstream commit, so the divergence is a reversible patch beside the librashader one and a note in ATTRIBUTION.md. The test fails if either guard is dropped, which is what a re-sync from upstream would otherwise do silently. |
||
|
|
16b571cf72 |
iOS: put the shader chain and its parameters in settings
A section in Graphics after Shade Boost, matching pipeline order, a folder- at-a-time preset browser, and every parameter a preset declares on screen. The section is absent rather than disabled in a build without librashader, gated on a bridge capability, so a cargo-less build does not advertise a feature it cannot run. Every number in a preset's parameter block is the shader author's, so every number is treated as hostile. Absent, non-finite, inverted ranges and a zero step all occur in the published collection. A parameter whose range cannot be made sense of is dropped rather than rendered as a control that does nothing. Pushing a value sends the effective value of every parameter, not only the changed one. librashader has no unset call, so a name dropped from the override map would leave the chain on whatever was pushed last and a reset would never take. A tweaked preset can be saved as its own file: a #reference to the base plus the changed values, written into My Presets inside the scanned root so it becomes selectable with no extra plumbing. The reference is relative while the base is in Documents, so the pair survives the container moving; a bundled base gets a path instead, which a reinstall breaks, and the sheet says so. The naming sheet is .sheet(item:) rather than .sheet(isPresented:), because the parent's body invalidating tears the content down and takes keyboard focus with it, which is the failure this codebase has a rule about. Also here: the once-cached name lists are owned rather than read after free, and librashader builds for the simulator as well as the device, which is what makes any of this testable without hardware. |
||
|
|
f23ddabf1e |
iOS: run RetroArch shader chains on the Metal renderer
librashader built for arm64 and pinned, wired into GSDeviceMTL, and a preset library behind it that can name a file the same way twice across a reinstall. The chain runs from DoApplyShaderChain, after ShadeBoost and before present, on the same ping-pong the FXAA path uses. Two things about it are load- bearing rather than incidental. EndRenderPass comes first, because the chain opens its own passes and Metal aborts if ours is still encoding. And FlushEncoders comes last, because librashader recycles its per-frame objects over a ring shallower than our deferred-submit window, so a chain frame has to end the batch. A failure latches on the preset that caused it, or a preset that will not compile recompiles every frame forever. Static archive rather than dylib, decided by building both against a working tracer and measuring, and the loser was deleted rather than left as an option. The library underneath is where the reinstall problem lives. Both preset roots sit under a container UUID that changes on every sideload, so a selection stored as an absolute path is stale within days. A preset is stored as a marker plus a root-relative path -- bundle: or data: -- and re- rooted at launch. The separator is a colon because Files refuses one in a name and it is not a path separator, so the relative half never needs escaping. Packs come in as a picked zip or folder through an extractor that keeps the directory tree, because a .slangp names its stages by relative path and the tree is part of the pack rather than an arrangement of it. That is the opposite of the skin extractor's flattening policy, so a test fences the two apart. Sixteen presets ship in the app, each cleared against its own header rather than a blanket grant. librashader's own cache goes to Library/Caches through XDG_CACHE_HOME, set before anything loads it. Latent today because the Metal runtime never reaches that cache, but a pin bump that adds caching would otherwise put a disposable file somewhere iOS can neither purge nor keep out of a backup. |
||
|
|
5b616729f1 |
EE rec: stop raising TLB misses on unknown MMIO too
The _ext_mem* fallbacks raise a TLB exception when a registered region gets an access its device has no case for. Under a recompiler that is the defect just removed from vtlb_Miss by another route: nothing diverts the block, so the raise only latches Status.EXL. Raise on the interpreter alone. Recompilers report instead, which is new - MEM_LOG is devbuild-only, so the raise was all a release build left. |
||
|
|
040104142a |
Tests: pin the I immediate against a rewrite of micro memory
Both polarities of the gamefix. With it on, a rewritten immediate has to reach the block already compiled; with it off, the rewrite has to force a recompile. Each row asserts the compile count as well as the result, so neither can pass by recompiling behind the value it checks. Three things the harness needs handling for. The first re-entry through RunJitPreserveBlockCache compiles a second block variant, because it enters on the pipeline state the previous run left rather than the post-Reset one, so the block only goes warm on the re-entry after that. LoadProgram writes VU.Micro directly and so bypasses the vtlb path that calls mVUclear, which leaves the stale quick slot serving the old program unless the test runs the invalidation itself. And the pair that carries the immediate writes VI[REG_I] again on the way out, as does the delay slot LoadProgram appends, so the register reads back 0 and the assertion belongs on the VF result. |
||
|
|
19cb54586f |
microVU: read the I immediate at run time under IbitHack
x86's doIbit picks between folding the immediate into the block and loading it from micro memory; arm64 only ever folded. Scarface (SLUS-21111), the game the gamefix is listed for, writes a per-object transform into VU1 micro memory as I immediates. Under the recompiler its trees and much of the geometry around them took whichever object's transform compiled first. The interpreter re-reads the word on every dispatch and was unaffected. |
||
|
|
c5ad9ddd4d |
EE rec: stop raising TLB misses, matching x86
vtlb_Miss raised the exception under the arm64 recompiler and returned, on the assumption that the rec would pick cpuRegs.pc up at the next dispatch. Nothing picks it up: the block runs on and its tail writes its own branch target over the vector PC, while cpuException has already latched Status.EXL. cpuException leaves EPC alone whenever EXL is already set, so from the first swallowed miss onward every exception keeps its predecessor's EPC, and the guest kernel's syscall epilogue erets to an address belonging to the fault. Report the miss and continue instead, as the x86 rec already does. The guest's handler still does not run, so a title that needs demand paging still cannot work under the rec; a stray miss now stays local to the instruction. ee_rec_tlb_divert_tests.cpp describes the divert and stays disabled. With nothing left to set s_recTlbMissOccurred, the poll after every interpreter call goes too. It could not have covered the raise anyway: only the interpreter-call sites had it, so a flag set at an inline access sat there until an unrelated later recCall consumed it and diverted on a pc belonging to neither. This gives up the one case the rec did handle, a miss in a branch delay slot reaching the vector through the cpuRegs.branch bracket epilogue. Its recompiler half moves to the disabled file; the interpreter half stays live, beside a new test pinning the rec's behaviour. The bracket itself stays: the tlb_fallback_* handlers raise from a delay slot on their own. |
||
|
|
249aefdb9b |
Tests: pin the rec's TLB-miss divert, disabled
A TLB miss on an inline load or store leaves the arm64 EE rec at exception level in user code. Eight cases, all disabled, because the rec does not do this yet and the route to making it is staged: x86's behaviour first as a floor, then the divert built back up with these dropping their prefix one at a time. Found from the other end. `3D Pinball Space Cadet (PS2) (3.0) (RA)` hangs after "Parsing complete. Finalizing...", and the visible fault is a thread id of 0xff966c22 arriving at a caller whose syscall returned 1. That is strlen's `subu v0,v0,a0` on the correct v0, reached because the kernel's syscall epilogue eret'ed into the middle of strlen: 131072 of the run's next 162766 exceptions were taken with EXL already set, so none of them updated EPC. Upstream of all of it is one swallowed miss on a strlen(NULL). Two working hypotheses died on the way — that the pinned-GPR cache lost v0, and that the call-ret shadow stack mispopped — both refuted by reading the state at the moment of damage rather than by reasoning about the emitters. The tests are the chain in four instructions, plus the load and store halves of both inline emitter pairs, plus the flush the divert needs. Three separate defects fall out of the one missing poll. The block runs past the faulting load. EPC names the instruction after it, because cpuTlbMiss skips its `pc -= 4` for the rec while the rec's own cursor is already one instruction ahead outside a delay slot — the delay-slot case comes out right only because two errors cancel. And Cause is whatever exception came last, describing a different instruction than EPC does. Two findings shape the fix rather than the tests, so they are recorded here. Reaching the vector with guest state intact costs one writeback, not a general flush: iFlushCall(FLUSH_VTLB) already precedes every inline access and frees the caller-saved hosts, leaving only x28, the allocator's single callee-saved host. A boot with fastmem off puts a live dirty guest GPR there at 489 sites and nothing else anywhere. The fastmem backpatch thunk is the part with no clean answer. It is generated at fault time and cannot name the live guest values of the block around it, so it cannot divert. A census of the 12411 fastmem sites emitted during that boot says how much per-site state a precise one would need: 9355 have nothing live and dirty, 2480 have between one and seven GPRs, and 576 involve the NEON file. Lesson, from a guard test that was written wrong first. Three dirty registers before the faulting load is not enough to make the allocator reach x28, so that test passed with the writeback deleted — it guarded nothing. It takes sixteen live guest values at once. A test that guards a writeback has to create the pressure that puts something in the register the writeback exists for, and the way to find out is to delete the code and watch. |
||
|
|
83f2510134 |
Tests: pin the VU FMAC's range against the console
The VU's largest value is 0x7FFFFFFF, one binade above FLT_MAX, the same range the EE FPU has. So an exponent-255 word is an ordinary number on the way in and on the way out, and "overflow" starts above it rather than above FLT_MAX. Both engines put the boundary a binade lower. vuDouble() rewrites an exponent-255 operand as 0x7F7FFFFF and VU_MAC_UPDATE() calls every exponent-255 result an overflow; the arm64 COP2 macro emitters clamp the result to +/-FLT_MAX and raise neither O nor U. microVU's per-op operand clamps approximate the same thing from a list of games rather than a rule. 68 rows off an SCPH-90000 through VU0 macro mode, scored per engine and per column, with what each engine cannot yet reproduce recorded per case so a fix trips the test as loudly as a regression. Nothing is fixed here. Two of the rows are structural rather than about range. An overflowed product does not become 0x7FFFFFFF before the accumulate: an addend of -0x7FFFFFFF cannot cancel it. An underflowed product does become zero before it. And the multiplier is the EE's, with the same one-ULP deficit decided by ft's mantissa alone. The harness grows two things the rows need: a VADDA encoder, and an opt-out from Run()'s VU0 JIT-vs-interp auto-diff for tests that score each engine against a hardware capture instead of against the other engine. |
||
|
|
e1f8fb1c56 |
Settings: a knob missing from the key table should not be a crash
The tables are meant to be complete and the drift tests are what say so, but ForCoreKnob returns no section for an enumerator nobody mapped, and both callers walked straight into strcmp with it. |
||
|
|
a6d1e35748 |
Settings: copying global settings into a game writes only the real decisions
"Copy Global Settings" does not copy the settings you can see. It runs the whole configuration through a wrapper that writes every key unconditionally, so the file it leaves behind holds roughly seven hundred of them — network adapters, the debugger, trace logging, memory cards, sections no settings page ever shows. That was untidy and no worse, until a key present in a per-game file started meaning the player claimed it. Now one press of a button whose dialog promises only that "the configuration for this game will be replaced by the current global settings" turns off every automatic fix that game had, permanently and silently. A value is worth writing down only if it decides something, and there are two ways it can fail to. It can be the stock default, in which case the file carries it as noise. Or it can be what the game database is going to set anyway, in which case writing it can only become a claim that suppresses the fix it agrees with. So the copy now excludes both, and what lands is what the player actually chose. The comparison goes through the string form rather than the typed value, so a float or an enum name compares the way it will be stored rather than the way it happens to sit in memory. That is why the references are built with the same interface class: same formatting on both sides, exact comparison, one path for every type. The database reference is a default configuration with the entry applied, not this one with the entry applied. The question is what the database wants, not where it would leave the source. It matters for the handful of fixes that clamp rather than assign, and it errs towards writing the player's value — never towards dropping a fix, since a value is only skipped when it already equals what the fix would set. Working the reference out means running the apply functions for an outcome nobody is going to run with, so they take an apply mode. A hypothetical apply says nothing to the log, raises none of the recommendation messages, and does not allocate the four megabyte lookup table that the Goemon TLB fix asks for. The tests cover the precedence rule and the filter, but the ones that matter are the drift guards: they assert every gamefix, speedhack and clamp mode has a settings key, and that the only graphics fixes without one are the six that genuinely have no setting behind them — three renderer routine selectors and three that only raise a recommendation. A knob nobody maps is a setting that goes quietly back to being overridden, with no warning and no failure, and that is what these are here to catch. |
||
|
|
96932405ce |
Settings: a setting the player set for one game outranks the game database
Change a setting in the per-game settings screen and it does not take. The widget keeps the new value, the file on disk keeps it, and the emulator runs with something else — for every game the database has an entry for, which is most of the ones anyone plays. The layered read is not the problem; it already puts the per-game file above the global one. The inversion is that the database writes into EmuConfig afterwards, from ApplyGameFixes, with no idea where any value came from. So the real order is global < per-game < database, and the only ways out are two switches that are all or nothing: EnableGameFixes for the CPU side, Manual Hardware Fixes for graphics. Either one rescues the setting you wanted by throwing away every other fix the game had, which usually just trades one glitch for a different one. So tell the database which settings are not its to write. A key sitting in a per-game file is a deliberate act: every settings screen represents "use the global setting" by deleting the key, not by writing the global value. That makes presence the record we need, with nothing new to store and no frontend to change, and it works for files already on disk rather than only for edits made from here on. PerGameOverrides reads one per-game file — the game layer alone, since the layered stack cannot tell a per-game choice from a global one and only the per-game one wins — and reports what it claims. Both apply functions take it and leave those settings alone, down the road they already had for the global switches, so each one still gets named in the log and now also on screen. This generalises the pin that already existed for graphics hacks and had no writer outside iOS. That mask stays, because MaskUserHacks and any INI already carrying one speak it, and the derived bits merge into it. But the claim itself is keyed by hardware-fix id rather than by that mask: there are more fixes than its 32 bits can name, its width is a persisted format, and the settings players actually change — mipmapping, trilinear, deinterlacing, texture preloading, blend level, download mode — were never user hacks and so had no bit at all. That is also why the pin test moves out from under isUserHackHWFix: nested there, none of them could be claimed. The database keeps the last word where it should. The three renderer routine selectors have no setting and no UI, so nothing can claim them. The BIOS path is untouched — it forces instant DMA on for a hardware reason and strips hacks deliberately, and its own comment already says claims get no say there. The iOS bridge derived the same thing from its own copy of the key table; it now calls the shared one, so the two cannot drift. Android's live GS apply re-derives the claims after its reload, or masking would strip a hack the player set. Drive-by: the division rounding mode logged eeRoundMode's value under its own name, in both the applied and the skipped message. |
||
|
|
2a98726692 | Merge remote-tracking branch 'origin/master' into jit-android-catchup-gv7 2.6.6.7 nightly-20260817 | ||
|
|
5b790427dd |
LSFG/FSR: remove the debug instrumentation
The per-second LSFG branch counters and the FSR gate line were added to find two specific bugs and both did their job — the counters proved generated frames were reaching the screen uncounted (VK_SUBOPTIMAL_KHR treated as failure), and the gate proved all three FSR conditions passed while a misplaced log made the pass look dead. Neither belongs in a release: one printed every second, the other on every state change. What stays is event-driven and diagnostic in the ordinary sense: LSFG's initialise line, shader-cache hits and misses, load and ABI failures, and one FSR line per output-size change. |
||
|
|
da25cb84cc |
Android: call eeClampMode 4 Exact, the name the other frontends use
The desktop and Big Picture pickers landed the same tier as Exact, and GameIndex.md documents it under that name. Android was the only frontend calling it something else, which made the same setting look like two different ones depending on which screen the user was on. The translation key moves with the label rather than keeping the old name for a value it no longer matches, so the map stays alphabetical and there is nothing left to mislead the next reader. |
||
|
|
541b1abfaf |
Android: offer eeClampMode 4 as Ludicrous in the clamping pickers
The tier existed in the core but nowhere in the UI, on any frontend, so
reaching it meant hand-editing the settings file. Both Android pickers —
the Performance tab and the in-game pause menu — now carry a fifth
option, and the settings layer packs it.
The packing is the part that matters. emucore validates the four clamp
booleans as a cascade and silently resets an inconsistent set to the
defaults rather than rejecting it, so writing fpuExactMode without its
three predecessors would not fail loudly, it would quietly land the user
back on Normal. applyTo therefore writes all four cumulatively, and
readFromIni reads them back highest-first.
readFromIni treats a missing fpuExactMode as an older core rather than as
mode 3: a build without the key never wrote it, and inferring 3 there
would demote a Ludicrous setting every time the settings were reloaded
under a mixed pair of builds.
The chip row already scrolls horizontally, so a fifth option needs no
layout change.
⚠️ Not addressed here, and worth a decision: the GameDB overwrites the
whole tier for any title carrying an eeClampMode entry, and an entry
below 4 clears the exact bit outright. On those ~115 titles the new
option is inert unless game fixes are off — which is most of the titles
whose users would want it. The setting description says so; whether the
core should let a user's choice raise the database's is a separate call.
|