Direct2D: laggy mouse interaction under heavy paint load on low-end GPUs (8.0.13 / develop)

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!

2 Likes

Thanks for the detailed report and example code. So far I haven’t been able to reproduce the issue, but I can understand where the problem might arise.

Please could you test out this patch and check whether it improves things for you? Thanks!

direct2d.patch (3.5 KB)

Thanks for the patch, I just tested it both on our plugins and on my repro example, and it fixes the issue. I’ll let you know if anything else comes up.

We’ll probably merge your patch in our fork then (seems like the two are very similar). Let me know when it gets in develop, or if you improve it further. Thanks!