Skip to content

MMCE art ordering: 1-frame COV head start replaces covPreempts drop#8

Merged
NathanNeurotic merged 10 commits intoincebtion-round-fourfrom
copilot/fix-mmce-art-loading-issue
Mar 19, 2026
Merged

MMCE art ordering: 1-frame COV head start replaces covPreempts drop#8
NathanNeurotic merged 10 commits intoincebtion-round-fourfrom
copilot/fix-mmce-art-loading-issue

Conversation

Copy link
Copy Markdown

Copilot AI commented Mar 18, 2026

  • Explore and understand MMCE art loading / navigation code
  • Fix menuInvalidateArtSelection and menuAdvanceArtSelectionOnMove to preserve READY/PRIMED art for MMCE mode on navigation (matching APP mode behaviour)
  • Code review (comments are on pre-existing unrelated code only)
  • CodeQL security scan: 0 alerts

📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.

…g slash in prefix, allow queueing while aborting

Co-authored-by: NathanNeurotic <109461996+NathanNeurotic@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix MMCE art loading issues on device page Fix MMCE art loading: remove blocking stat I/O, normalize prefix slash, allow queue during abort Mar 18, 2026
Copilot AI requested a review from NathanNeurotic March 18, 2026 01:40
… SPI channel

Co-authored-by: NathanNeurotic <109461996+NathanNeurotic@users.noreply.github.com>
Copilot AI changed the title Fix MMCE art loading: remove blocking stat I/O, normalize prefix slash, allow queue during abort Fix MMCE art loading stall: remove double-attempt fallback, normalize prefix slash, allow queue-while-aborting Mar 18, 2026
… wait loops

Co-authored-by: NathanNeurotic <109461996+NathanNeurotic@users.noreply.github.com>
Copilot stopped work on behalf of NathanNeurotic due to an error March 18, 2026 05:08
Copilot AI changed the title Fix MMCE art loading stall: remove double-attempt fallback, normalize prefix slash, allow queue-while-aborting texcache: yield to MMCE art thread during abort/wait loops to prevent fileXio corruption Mar 18, 2026
After cacheEnd(1) calls TerminateThread on the art worker, the cache
entry for the in-flight MMCE request stayed in CACHE_ENTRY_LOADING with
entry->qr pointing to the just-freed request struct.  Two problems
followed:

1. The slot was never eligible for LRU eviction (LOADING is excluded
   from the victim selector), so the cache shrank by one usable slot per
   force-terminate cycle.  After enough browsing, slots ran out and art
   stopped loading.

2. Future cacheInvalidateEntryLocked calls dereferenced the dangling
   entry->qr when calling cacheIsAbortableMmceRequest — use-after-free
   that produced random results, explaining art "loading randomly between
   titles".

Fix: call cacheClearItem(req->entry, 0) before cacheFreeRequest(req) in
the cacheEnd cleanup block.  This returns the slot to CACHE_ENTRY_FREE,
nulls entry->qr, and makes it available to the LRU pool again.
freeTxt=0 is correct because entry->texture.Mem is always NULL during
loading (the displaced texture lives in req->displacedTexture and is
freed by cacheFreeRequest).

Co-authored-by: NathanNeurotic <109461996+NathanNeurotic@users.noreply.github.com>
Copilot AI changed the title texcache: yield to MMCE art thread during abort/wait loops to prevent fileXio corruption Fix stale LOADING cache entry / use-after-free after TerminateThread in cacheEnd Mar 18, 2026
Two bugs caused "navigation clogged consistently / art barely loaded":

1. menuLoadConfigDirectInternal (menusys.c)
   cacheAbortMmceImageLoadsTimed(60) blocked the main thread for up to
   60 ms on every CROSS/CIRCLE press (game launch / settings).  When the
   abort timed out, cacheEnd(1)+cacheInit() added another ~240 ms AND
   called TerminateThread on the art worker mid-fileXio read, corrupting
   the fileXio RPC channel for the rest of the session.

   Fix: call cacheAbortMmceImageLoadsTimed(0) instead – this signals the
   abort (sets abortRequested, drops queued MMCE requests, wakes the
   worker) without any blocking wait or cacheEnd.  The MMCE config read
   that follows is simply serialised behind the in-flight art chunk in
   the IOP queue (typically ≤ 20 ms extra at normal card speeds).

2. mmceLoadGame (mmcesupport.c)
   MMCE_ART_ABORT_WAIT_TICKS = 60 ms was barely one read-chunk's worth
   of time on a slow card (~40 ms/chunk at 100 KB/s), so TerminateThread
   was called frequently, corrupting fileXio and preventing all
   subsequent art loads.

   Fix: raise the timeout to 500 ms – 30+ chunk-reads of headroom even
   on very slow cards – so TerminateThread becomes a true last resort.

Co-authored-by: NathanNeurotic <109461996+NathanNeurotic@users.noreply.github.com>
Copilot AI changed the title Fix stale LOADING cache entry / use-after-free after TerminateThread in cacheEnd Fix MMCE navigation freeze and art-loading degradation over session Mar 18, 2026
…gate

gMmceMainPageBrowseOnly caused drawGameImage to call cacheGetTextureIfReady
(no queue) for all non-COV art on the MMCE main list page, so Background,
Screenshot and other textures were only ever loaded when the user visited
the game info page (where thmSetMmceMainPageBrowseOnly(0) cleared the flag).

The texcache already limits MMCE interactive requests to one in flight at a
time (texcache.c lines 1404-1416): if a request for a different value/cache
is queued it is dropped and replaced; if one is already active or queued for
the same game, cacheGetTexture returns NULL and retries on the next frame.
This natural serialisation is sufficient -- the themes-layer restriction is
not needed and causes the observed symptom.

Remove gMmceMainPageBrowseOnly entirely:
- src/themes.c: drop static flag, drop thmSetMmceMainPageBrowseOnly(),
  simplify drawGameImage to always use getGameImageTexture()
- src/menusys.c: remove both thmSetMmceMainPageBrowseOnly() call-sites
- include/themes.h: remove function declaration

After this change, Cover loads first (first drawGameImage call per frame),
then Background, Screenshot, etc. load sequentially in subsequent frames as
the worker finishes each piece -- all without visiting the info page.

Co-authored-by: NathanNeurotic <109461996+NathanNeurotic@users.noreply.github.com>
Copilot AI changed the title Fix MMCE navigation freeze and art-loading degradation over session Fix MMCE art load regression: navigation freeze, session-wide art failure, and non-Cover art never loading on main page Mar 18, 2026
… art-type change

The MMCE throttle drop condition in cacheGetTextureInternal was:
  (queuedMmceReq->cache != cache || strcmp(queuedMmceReq->value, value) != 0)

COV, BG, SCR each use a different cache pointer but share the same value
(game startup ID). In a single render frame for an uncached game:
  1. COV queues its request
  2. BG: different cache -> DROP COV, queue BG
  3. SCR: different cache -> DROP BG, queue SCR

Only SCR ever loaded. Each drop also evicted an LRU slot, freeing its
displaced texture via the cleanup queue. On second pass this cascade of
evictions trashed the entire cache built on the first pass: art stopped
loading and the IOP was overloaded with constant eviction/cleanup work,
causing the observed "navigation clogged, art not loading" regression.

Fix: drop the queued request only when the GAME changes (different value),
not when only the ART TYPE changes (different cache, same value):
  strcmp(queuedMmceReq->value, value) != 0

With this fix, same-game art types serialize correctly: COV queues first;
BG and SCR see the queued request (same value -> no drop) and wait. After
COV loads, BG queues; SCR waits. After BG loads, SCR queues. No evictions,
no cache thrash. On a different-game request the old game's pending art is
still properly dropped, and cacheAbortMmceImageLoadsTimed continues to
flush all queued MMCE requests on context changes (info page, game launch).

Co-authored-by: NathanNeurotic <109461996+NathanNeurotic@users.noreply.github.com>
Copilot AI changed the title Fix MMCE art load regression: navigation freeze, session-wide art failure, and non-Cover art never loading on main page Fix MMCE art: all types load on main page; fix cascading-drop cache thrash on second pass Mar 18, 2026
Copilot stopped work on behalf of NathanNeurotic due to an error March 18, 2026 18:38
Background (main0) draws before ItemCover (main5) each frame, so BG
always queued first under the MMCE serial throttle.  When BG art does
not exist on the card, open() can take 100-500 ms to fail, serialising
Cover art behind every slow BG miss.  After ~7 such navigations the
user perceives navigation clogs and art not loading.

Extend the drop condition in cacheGetTextureInternal: a COV request now
also drops a same-game queued non-COV (BG/SCR) request so Cover art
always loads first.  The existing cacheHasQueuedInteractiveModeLocked
guard prevents BG/SCR from re-queuing while COV is queued or active,
avoiding the cascade eviction that the original cache!=cache condition
caused.  Load order: COV -> BG -> SCR.

Co-authored-by: NathanNeurotic <109461996+NathanNeurotic@users.noreply.github.com>
Copilot AI changed the title Fix MMCE art: all types load on main page; fix cascading-drop cache thrash on second pass Fix MMCE art clog: COV preempts queued non-COV for same game Mar 18, 2026
The covPreempts fix (c6f8bbe) caused "mostly missing" art: it dropped
the queued BG request every time COV came in, so when COV didn't exist
on the card its slow open() failure (100-500 ms) blocked all other art
types.  Users with BG/SCR but no COV saw art constantly reset before
it could appear.

Instead, give Cover art a 1-frame inactivity head start over non-COV
art types in MMCE mode.  The default theme draws Background (main0)
before Cover (main5) each frame; after each navigation guiInactiveFrames
resets to 0 and counts up, so:
  - frame 8: COV's delay (8) satisfied  -> COV queues
  - frame 9: BG/SCR delay (8+1=9)       -> MMCE throttle blocks them
                                            (COV is already in-flight)

BG/SCR are never dropped and never have to wait for a COV failure.
If COV loads fast (file exists), users see it immediately; BG/SCR follow
naturally afterwards.  If COV is missing, BG queues as soon as COV
fails, with no extra penalty beyond one frame.

Also revert the MMCE throttle drop condition to the simpler game-change-
only rule from 0b93ffc: drop only when the queued request is for a
different game (value mismatch), not when the art type changes.  The
new delay makes the covPreempts eviction unnecessary.

Co-authored-by: NathanNeurotic <109461996+NathanNeurotic@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants