Skip to content

Fix Vulkan texture exponent bias, depth-only pipeline stalls, and WaitMultiple polling overhead#368

Open
Ufoex wants to merge 3 commits into
rexglue:mainfrom
Ufoex:fix/vulkan-tint-and-stutter
Open

Fix Vulkan texture exponent bias, depth-only pipeline stalls, and WaitMultiple polling overhead#368
Ufoex wants to merge 3 commits into
rexglue:mainfrom
Ufoex:fix/vulkan-tint-and-stutter

Conversation

@Ufoex

@Ufoex Ufoex commented Jul 5, 2026

Copy link
Copy Markdown

Summary

Three independent fixes found while debugging visual corruption and stuttering under the Vulkan backend, running Lollipop: Chainsaw via the Re-Cherry mod loader (Linux, NVIDIA proprietary driver):

  1. spirv_translator_fetch.cpp - the SPIR-V texture-fetch translator read fetch constant word 4 for the result exponent bias instead of word 3 (D3D12's DXBC translator already reads word 3 for this). Caused blown-out brightness on affected texture fetches, Vulkan-only.

  2. vulkan/pipeline_cache.cpp - async pipeline compilation required a pixel shader, so depth/shadow-only pipelines (no pixel shader) always compiled synchronously on the render thread. These are created continuously during gameplay (not just at load) whenever the camera reveals new shadow casters, causing render-thread stalls. Extended the existing "skip this draw, retry next frame" placeholder mechanism to cover this case (there's nothing cheaper to substitute for a depth-only pipeline, so it just queues the real one on the background thread pool and skips drawing with it until ready).

  3. core/threading_posix.cpp - PosixConditionBase::WaitMultiple (backing KeWaitForMultipleObjects-style waits) polled every handle via pthread_mutex_trylock on a fixed 1ms interval regardless of wait duration. Any guest thread waiting on several kernel objects at once pays this cost continuously - measured via perf+Tracy on AudioSystem's worker thread (waits on 9 handles), which dropped from ~68% to ~21% CPU after adding exponential backoff (1ms -> 16ms cap, resets per wait). Semantics/timeouts unchanged, only the poll interval.

Test plan

  • Rebuilt RelWithDebInfo, confirmed no new warnings from the changed files
  • Ran Lollipop: Chainsaw via Re-Cherry on Linux/NVIDIA for multiple sessions after each fix
  • Fix 1: confirmed visually via screenshot that the exponent-bias corruption was gone
  • Fix 2: confirmed via log analysis that ~50% of newly created pipelines in a real session have no pixel shader; no crashes/hangs across repeated sessions after the change
  • Fix 3: confirmed via perf record/perf report before and after (WaitMultiple self-time 6.43% -> 2.73%, pthread_mutex_trylock 5.13% -> 2.30%) and ps -T thread CPU (Audio Worker 68% -> 21%); no hangs or audio glitches observed
  • Not tested on D3D12/Windows or non-NVIDIA GPUs - would appreciate a sanity check there, especially for fix 3 since it's used well beyond the audio path

Ufoex added 3 commits July 5, 2026 03:26
The Vulkan SPIR-V texture-fetch translator read fetch constant word 4
for both LOD biasing and the result exponent bias, but exponent bias
lives in word 3 (matching the D3D12 DXBC translator, which already
reads word 3 for this). Reusing word 4 applied the wrong bias value,
producing blown-out/incorrect brightness on affected texture fetches
on the Vulkan backend only.

Adds a dedicated word 3 load for the exponent bias while leaving
word 4's LOD/stacked-texture-filtering usage untouched.
Async pipeline compilation previously required a pixel shader (since
the mechanism swaps in a fast placeholder pixel shader while the real
one compiles on a background thread). Pipelines with no pixel shader
at all (depth/shadow-only passes) were excluded from this check and
always compiled synchronously on the render thread, regardless of the
async_shader_compilation cvar.

Depth/shadow-only pipeline permutations are created continuously
during normal gameplay (not just at load), e.g. whenever the camera
reveals previously-unseen shadow casters. Because there's nothing
cheaper to substitute for a depth-only pipeline (no pixel shader to
swap out), skip drawing with it for a few frames instead of blocking:
queue the real pipeline on the background thread pool and mark it as
a placeholder immediately, reusing the same "skip this draw, retry
next frame" path already used for the pixel-shader placeholder-swap
case.

Measured on a real gameplay session: roughly half of all newly
created pipeline permutations had no pixel shader, so this removes a
frequent render-thread stall source tied to camera movement.
WaitMultiple (used to implement KeWaitForMultipleObjects-style waits
across more than one handle) polled every handle's mutex via
pthread_mutex_trylock on a fixed 1ms interval regardless of how long
the wait had been idle. Any guest thread waiting on several kernel
objects at once - e.g. AudioSystem's worker thread, which waits on up
to 9 handles (8 client semaphores + a shutdown event) - paid this
poll cost roughly 500 times/sec even when nothing was signaling.

Profiling a real gameplay session (perf + Tracy) showed this
self-time (WaitMultiple + pthread_mutex_trylock + clock_gettime)
was one of the largest CPU consumers on the audio worker thread,
ahead of most actual guest-code execution.

The poll interval now backs off exponentially (1ms -> 2 -> 4 -> 8 ->
16ms cap) the longer a single WaitMultiple call goes without any
handle becoming signaled, and resets on every new wait. Freshly
started waits stay responsive at 1ms; only sustained idle waits back
off. Semantics, return values, and timeout behavior are unchanged -
only the sleep duration between polls differs.

Measured effect on one affected thread (Audio Worker) in Lollipop:
Chainsaw via Re-Cherry: CPU usage dropped from ~68% to ~21%, with no
observed hangs or audio glitches across repeated test sessions.
@Ufoex Ufoex requested a review from tomcl7 as a code owner July 5, 2026 09:28
@ItsNotPaths

Copy link
Copy Markdown

i can attest to 1, its a bad issue for fh1
#334 (comment)

@Graine25

Graine25 commented Jul 6, 2026

Copy link
Copy Markdown
Member

Let's clean up the formatting errors

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.

3 participants