KytyPlus v3.8 Release Notes
Tag: v3.8 | License: GPL-2.0 | Platforms: Windows, macOS, Linux
Overview
Two releases' worth of work on one title, and it pays off: Crash Bandicoot 4 now plays its
cutscenes. The intro video chain runs start to finish instead of stranding the game on the
fourth bumper, and the black window that used to follow the logo is gone.
For everyone else this is a batch of correctness fixes — a fence wait that could never be
re-evaluated, geometry draws being silently skipped, three atomics misclassified as plain
reads, a depth-stencil aspect being demoted while still in use — plus the retirement of a
crashy default and some hang diagnostics that land in your log for any title.
Crash Bandicoot 4: the cutscene chain completes
The v3.7 retest log showed the real deadlock in video 4 (BumperUnreal): the demuxer hit EOF
and both decoders finished, but Active() stayed true for 116 seconds because decoded frames
were still sitting in the queue. The game stops polling GetVideoData near the end of a video,
so the leftovers never drained and the state machine never reached the branch that fires
STATE_STOP. Videos 1-3 escaped only because the game happened to consume their queues fully
before it stopped polling.
End-of-stream is now "demuxer at EOF and both decoders finished" — queued leftovers no
longer keep a player alive. The moment a drained video's workers finish, the natural-EOF gate
fires STATE_STOP exactly once, whether or not the game keeps consuming. That is what unsticks
the fourth bumper, and the whole chain (Intro_delay → BumperAtviNextGen →
BumperTfbNextGen → BumperUnreal) now runs to completion.
The black screen after the logo
Two independent bugs, both fixed.
A fence wait that could never be re-evaluated
CB4 wedged on a black screen while the present loop kept flipping empty frames at ~3 fps and
zero draws reached the GPU. The UE4 RHI issues a release_mem + wait_reg_mem fence
ping-pong on the compute queue forever. A submission blocked on wait_reg_mem sits at the
front of its queue, and the GPU thread only cleared blocked flags once every queue was idle
— so the graphics queue front was never re-polled while the compute queue kept producing work.
The wait was evaluated once, failed once, and was never retried.
- Blocked queue fronts are now re-pollable every 10 ms even while other queues have work, so
blocked waits actually get re-evaluated. - New per-address stalled-wait watchdog: after 10 s of consecutive unsatisfied polls the wait is
released and the awaited value written, so guest bookkeeping stays consistent and the run
self-heals instead of wedging forever. Ten seconds is comfortably longer than an iGPU
shader-compile burst; normal fences resolve in microseconds and never trip it. write32/write64andCpOpReleaseMemnow guard a null destination — an unpatched
release_memlabel address used to fall through to amemcpyto nullptr.
This one change alone moved CB4 past the splash screen — further than the previously documented
results for this title (upstream v0.0.5.5 crashes after splash, #88).
FSR upscaling on integrated GPUs
The 0xc0 fault — an access violation inside the AMD driver at CB4's first menu present, right
when the display-mode switch fires resize churn — took four commits to pin down honestly, and
ended in "don't do this by default":
- A surface rebuild swaps the Vulkan device handle, so FSR pipelines and descriptor sets built
on the old handle were dispatched to a dead one. Dispatch now binds to the creating device
handle and survives context rebuilds. Destroy()never took the mutex its own header documents as serialisingDispatchagainst
Destroy, so a resize could free pipelines and UBOs out from under a recording thread. Now
locked.- The first round of guards was wrong in an instructive way: it required resources to exist at
Dispatch()entry, but those are created lazily insideDispatch(). The guard could never
pass — FSR became impossible, every present silently fell back to blit, andreset()churned
the upscaler four times per run. The guard now only checks whatCreate()actually produces.
That correction is what exposed the real answer: FSR 1.0 faults deterministically in the
current AMD driver, independent of our lifetime handling. The plain blit path displays the
same content without the compute pass (verified: the splash renders through blit on the same
machine), so the iGPU performance floor now keeps only the texture LOD bias. FSR remains
available for explicit opt-in from the launcher; it is no longer auto-enabled on integrated GPUs.
If you have been running with FSR on an AMD iGPU and seeing a driver reset, this release
removes that path from your defaults.
Presentation fixes on integrated GPUs
- Present mode now floors to FIFO on iGPUs. The launcher's default is Immediate, and
Immediate on the Radeon 840M presents nothing at all — the only launcher-vs-command-line
difference the logs turned up was exactly this (presentMode=Fifoon the run that worked,
Immediateon every run that didn't). The floor is applied in both the config defaults and the
swapchain, so a config still asking for Immediate or Mailbox can't reintroduce black output. - Zero-extent swapchains are refused. The window could self-minimise mid-run; SDL reported a
0×0 surface, the swapchain was rebuilt at 0×0, and it stayed broken for tens of millions of log
lines before the next successful resize healed it. A zero extent now keeps the last known size
and falls back to the configured screen size. - The launcher no longer wraps the emulator in
cmd /KwithCREATE_NEW_CONSOLE. It spawns
the emulator directly, which also stops the exit code being swallowed.
Rendering correctness fixes
Three bugs that were quietly dropping or corrupting work, all found while chasing the above:
- Geometry-stage draws were being skipped. CB4 transitions from ES+GS to NGG primgen
passthrough without clearing the old GS address — disabled stages keep their base registers.
The skip test rejected any draw carrying a non-zero GS address alongside a non-zero ES address,
which is precisely the sequence CB4 emits, so its vertex draws all landed in
Skipping unsupported GE shader draw. Now gated on the GS stage-enable bit, which is what
actually determines whether a shader address names a live shader. IsAtomic()dropped three atomics. It enumeratedAtomicUMin..Orand then fell into
default: return false, soAtomicXorU32,AtomicFMinF32andAtomicFMaxF32were classified
as plain reads and the IR never marked those image resources atomic. Because of that, the
compute watchdog had to compensate bluntly and dropped every storage image whose format lacks
STORAGE_IMAGE_ATOMIC— silently discarding CB4's color clears on RGBA8_UNORM and friends. With
the flag fixed, the guard can ask the question it actually meant to ask.- Depth aspect demotion. Sampling a depth image that had been acquired
read-only-stencil transited both aspects to read-only, demoting the aspect that was still
the writable stencil attachment. The transit now adds shader-read access to the existing state
instead of re-selecting a read-only layout for the whole image.
Movie playback
file://URIs are stripped before the path reaches the filesystem. UE movies use URI form while
startup movies use plain paths, and the URI form was failing to open silently — the only
log line named the path without saying the path was the problem. All three early-return
failures (open,find_stream_info, codec init) now log the reason.
Hang diagnostics
Both land in the log, and both work for any title, not just CB4:
- Infinite
KernelWaitEventFlag/KernelWaitSemawaits (timeout == nullptr) now log the object
pointer, bit pattern / need count, and wait mode. - Any single
PthreadCondWaitexceeding 10 seconds reports the cond/mutex/thread pointers, once
per second per stuck wait.
These exist because CB4's wall was a silent block — shaders compiled, then no log activity, two
threads pegged, black screen. They turn "it hung" into a named object in the log.
Compatibility table
- Crash Bandicoot 4: It's About Time (PPSA02433) — the Native (PS5) row still reads
"Boots / logos only" at v3.6, which this release supersedes. The new verified depth is the
full intro cutscene chain playing to completion. The row updates through the normal
For testers — please retest
The FSR retirement is a behaviour change on integrated GPUs, and the GPU command-path changes
(watchdog, anti-starvation re-poll) touch every title. If you can, run a build from this release
on your usual title and report anything that changes — a log via Share Log is ideal. The stalls
that previously just looked like "it hangs" should now name themselves in the log, so if you
still see a hang, that log line tells us exactly which object to look at.