Platform: macOS
System: PlayStation 2
Player: Native (PCSX2, embedded)
Workaround: the software renderer, offered per game on the launch screen and in the pause panel
What happens
Some PS2 titles show a black screen, or a black screen with an occasional flash, while everything else reports healthy: audio plays, the frame counter is a locked 60, PCSX2's log is clean, and GS snapshots of the render target are correct complete images.
Confirmed affected: God Hand (SLUS-20624) and Mobile Light Force 2 (SLUS-20608), both 512x448 interlaced per PCSX2's own OSD.
DaiOuJou and Burnout 3 look fine, but that is weak evidence: a PS2 game changes video mode between menus, gameplay and FMV, so this may well be per scene and those titles have only been observed in the scenes that happened to be played.
Why this is ours and not upstream's
The same PCSX2 fork, built from the same tree on the same Mac with the same Metal renderer, plays God Hand correctly as its own Qt app. The difference is the embedding: Cabinet supplies the whole Host namespace, its own audio backend, and a Mac Catalyst replacement for common/CocoaTools.mm, because the AppKit original cannot compile for macabi.
What has been ruled out
- The CAMetalLayer. A/B tested: the
UIView's own backing layer, and a fresh Cabinet-owned CAMetalLayer at Metal defaults added as a sublayer. Both black, byte-identically.
- Interlace and merge shaders are present in the metallib, built for
air64-apple-ios18.0-macabi.
- Framebuffer fetch, primitive ID and Metal 2.3 all report supported.
deinterlace_mode 1, 6 and 8 behave identically.
- Internal resolution: 1x renders correctly in snapshots, so this is not PCSX2 issue 12424.
MTL_USE_PRESENT_DRAWABLE forced to Always: no change.
- A real
drawableSize ownership conflict was found and fixed. Not the cause.
Found and fixed, and it did not cure it
GSConfig.SkipDuplicateFrames defaults on and its duplicate detection does not fire reliably here, so BeginPresent was being called with frame_skip on nearly every frame: no drawable acquired, nothing presented. That is exactly a black picture with an occasional flash, so it looked like the whole answer. It is now forced off, and the present trace is clean afterwards, skip=false with a PresentRect every frame carrying a correct 512x448 source and a full-window destination.
It is still black. The present path now reports presenting a correct-looking frame every frame and nothing appears.
Two signals that cannot see this bug
GS snapshots read the render target and never pass the present guard, and the frame counter reads 60 throughout. At one point six independent signals all said healthy while the screen was black. Screen captures are also unreliable here: a capture of the running game came back solid black on a day when a capture of the empty desktop did too, because the display had slept.
Next steps
- Trace a working game and diff it against a failing one. Every conclusion so far comes from the failing path alone.
- Instrument Metal command buffer completion and error status; nothing has checked whether the buffers complete or come back with an error.
- Confirm the window is genuinely on screen during a test.
CABINET_PRESENT_TRACE=1 in the environment logs the whole present path and is what found the SkipDuplicateFrames bug in a single run.
Platform: macOS
System: PlayStation 2
Player: Native (PCSX2, embedded)
Workaround: the software renderer, offered per game on the launch screen and in the pause panel
What happens
Some PS2 titles show a black screen, or a black screen with an occasional flash, while everything else reports healthy: audio plays, the frame counter is a locked 60, PCSX2's log is clean, and GS snapshots of the render target are correct complete images.
Confirmed affected: God Hand (SLUS-20624) and Mobile Light Force 2 (SLUS-20608), both 512x448 interlaced per PCSX2's own OSD.
DaiOuJou and Burnout 3 look fine, but that is weak evidence: a PS2 game changes video mode between menus, gameplay and FMV, so this may well be per scene and those titles have only been observed in the scenes that happened to be played.
Why this is ours and not upstream's
The same PCSX2 fork, built from the same tree on the same Mac with the same Metal renderer, plays God Hand correctly as its own Qt app. The difference is the embedding: Cabinet supplies the whole
Hostnamespace, its own audio backend, and a Mac Catalyst replacement forcommon/CocoaTools.mm, because the AppKit original cannot compile for macabi.What has been ruled out
UIView's own backing layer, and a fresh Cabinet-ownedCAMetalLayerat Metal defaults added as a sublayer. Both black, byte-identically.air64-apple-ios18.0-macabi.deinterlace_mode1, 6 and 8 behave identically.MTL_USE_PRESENT_DRAWABLEforced to Always: no change.drawableSizeownership conflict was found and fixed. Not the cause.Found and fixed, and it did not cure it
GSConfig.SkipDuplicateFramesdefaults on and its duplicate detection does not fire reliably here, soBeginPresentwas being called withframe_skipon nearly every frame: no drawable acquired, nothing presented. That is exactly a black picture with an occasional flash, so it looked like the whole answer. It is now forced off, and the present trace is clean afterwards,skip=falsewith aPresentRectevery frame carrying a correct 512x448 source and a full-window destination.It is still black. The present path now reports presenting a correct-looking frame every frame and nothing appears.
Two signals that cannot see this bug
GS snapshots read the render target and never pass the present guard, and the frame counter reads 60 throughout. At one point six independent signals all said healthy while the screen was black. Screen captures are also unreliable here: a capture of the running game came back solid black on a day when a capture of the empty desktop did too, because the display had slept.
Next steps
CABINET_PRESENT_TRACE=1in the environment logs the whole present path and is what found the SkipDuplicateFrames bug in a single run.