Hi all,
We’re seeing laggy mouse interaction in the Direct2D renderer on low-end GPUs when there’s a decent amount of painting going on, with large UI and animated components. Dragging a knob or clicking feels delayed, the UI visibly trails the cursor. It was first reported by one of our beta testers, we then reproduced it on our end, and we managed to put together a reproducible example (a slight modification of a previous Windows graphics bug repro we had):
GitHub - DamRsn/juce-opengl-crash at laggy_interaction · GitHub (laggy_interaction branch)
It happens on latest develop, and on 8.0.13 which we’re currently on. Also, it only happens on D2D (things behave correctly with OpenGL and the software renderer).
Here is a video showing the interaction lag:
(filmed with a phone, sorry — screen recording kills the graphics performance of my low-end Windows machine it reproduces on)
What seems to happen is that when the GPU can’t keep up, instead of dropping frames, the renderer blocks the message thread inside Present1 — so processing of user interactions like mouse clicks and drags gets delayed. We think the right behaviour here is to drop frames and keep the message thread responsive: the UI degrades in frame rate, not in input latency.
We analyzed the issue with Claude, which found the cause and came up with a fix — apologies for the AI-assisted report, but we’ve tested the fix under different conditions (slow iGPU, standalone, in DAW, many plugin instances at once) and it seems solid. The PR on our JUCE fork is here:
We’ll likely merge it on our fork soon, but we think this should really be addressed in JUCE directly.
Short summary of what’s going on: since d3a1f0f1 (which reinstated a maximum frame latency of 1, discussed in this thread), Present1 blocks the message thread whenever the previous frame hasn’t been displayed yet. That commit fixed a real multi-instance regression, so simply reverting it isn’t an option. But older versions of the renderer didn’t have this problem: before 22ae5baa removed the SwapChainThread, painting was gated on the swap chain’s frame latency waitable object, so frames were deferred when no present slot was free instead of blocking. That gate was never reinstated along with the latency cap — the PR above basically restores it, with a non-blocking check in checkPaintReady() instead of a dedicated thread.
There might be context we’re missing on why it was done this way, so happy to be corrected, and happy to provide what we can to help track this down. This thread also looks related: Sluggish and Glitchy UI on Windows with JUCE 8.0.13.
Thanks!
