D2D: Power Throttling Woes

I’ve found something rather interesting (at least to me). This is a hard one to show, for reasons I’ll get into below.

I’ve made a visualizer, the code is rock solid (let’s just assume this is true). It updates through a VBlankAttachment. It has jitter. It is updating in perfect unison with the screen refresh, and moving 1 pixel, but it still has jitter.

If I open another plugin that has any kind of gpu workload, the issue is gone, and my visualiser looks as perfect as the code implies it will be. In fact, if any gpu workload spins up, the issue resolves. This is what makes it hard to show you. The moment I open OBS studio to screen record, it fixes the issue.

After some digging, I’ve narrowed this down to a windows power throttling setting. Basically, it sees one instance of my plugin as not enough work to warrant trying too hard, and it lets the VBlanks be a little loose.

The following has fixed the issue on my machine:

Windows 10/11’s “Power Throttling” explicitly limits background/low-load processes, including GPU tasks like Direct2D vsync. Disabling it forces consistent clocks.

  • Press Win + R, type regedit, and hit Enter (run as admin if prompted).

  • Navigate to: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power.

  • Right-click Power > New > Key, name it PowerThrottling.

  • Right-click the new key > New > DWORD (32-bit) Value, name it PowerThrottlingOff.

  • Double-click it, set Value data to 1, and hit OK.

  • Restart your PC.

  • Revert: Delete the PowerThrottlingOff value or set to 0.

Warnings: This increases power draw and heat slightly—fine for desktops, monitor on laptops. It won’t overclock anything, just prevents downclocking.

Now, this isn’t a solution for user machines. So I ask: Is there anything that can be done from the JUCE side?

thanks!, I am getting complaints about this but I did not know why it happens.

This does sound like an interesting one. Just to make sure I’m understanding this right. The power throttling is causing a visual jitter (framerate drops), and it’s not effecting how often you get the vBlank calls but add another instance and it’s fine (Windows has deemed you worthy)? Apologies, if I’m misunderstanding this. Just wondering if it’s the vblank dropping frames, because of the power throttling setting combined with how much work you are doing? Might be me making an assumption. Do you have some demo code to demonstrate this in like the simplest way? Just wondering causing I’m using Direct2D and vBlanks quite heavily at the moment, and don’t want to have this bouncing back.

The power throttling is causing visual jitter. It’s not dropping frames though, it’s just not timed perfectly, so there can be a slight gallop in the frame rate. So the jitter isn’t missed frames, it’s non-uniformly spaced vblanks. This is also not something you can measure on the JUCE side, any numbers you print out look fine, so I assume it’s a timing issue further down stream.

EDIT: I guess it’s possible it is dropping frames, but it would be dropping them down stream from my code. All I can see so far is the vblank is triggering my code at the expected frame rate, but jitter appears on the screen.

I can think of a few things to check. Apologies, if you have already done any.

  • Is there anything suspicious in profiling it? I had allocations causing frame drops with the vBlank before, timing was fine from the vBlank but the repaint call was taking too long so the callback would just be dropped.
  • Does it still happen if you cap the refresh rate? Say you cap it to 30 or 40, or even if you change your monitor refresh rate does it still happen.

Nothing odd in profiling. I am running some custom code to target 60fps internally, by finding the nearest rate to target that allows an even frame skip cadence. You can see it here in perfetto it’s going as planned: render, skip, repeat.

Paint time is short. The sequence you see here is the vblank copies the most recent path so paint() has it, then calls repaint and signals a worker thread to make a new path for the next go around.

Setting the display to 60hz via windows settings does seem to solve the issue, too.

So it seems to be some kind of power saving variable refresh rate that happens after vblank calls? This is the weird part. You get VBlankAttachment calls at 120hz, but what actually renders seems to differ.

EDIT: You actually can see some wonkiness in the clock of those profile blocks, though. I’m still unsure if the frame is dropped, or if it’s just a less precise clock.

I can think of some other little things:

  • Unit test the timing blocks to be sure (If you haven’t).
  • Not ideal, but could you factor an amount of timing error in your drawing code (just an idea).
  • Could you test for the timing error or if you’re are dropping frames?

I will check up on this thread later.

Timing seems to be pretty stable on the vblank side either way. Testing with the timestamp being passed through that chain shows no real difference. Max error from the median in both cases is around 0.17ms. This seems to be the error in the clock on the VBlank timestamp under normal circumstances, and not a factor here.

I’m not sure how you’d account for this unknown future error in the render pipeline from my code.

Every conceivable test I run on my side checks out. As far as I can tell, the issue comes down stream, either in some deep nested JUCE API code, or with the OS itself. It appears that JUCE hands off it’s drawing code at perfectly spaced intervals, but then the time to screen from the OS side is where things go wrong (while it deems power saving efforts are appropriate). Best case, I’d hope there’s some D2D API flag to let the OS know we mean business, and we don’t want any power saving enabled while our window is open.

Update: So to really illustrate what seems to be the issue, take the following example…

Native FPS = 240hz. Desired FPS = 60hz. Plan render 1 frame, skip 3.

Scenario A:
VBlank callback 1: Update path and call repaint()
VBlank callback 2-4: Do not update path. Do not call repaint().
Result: Jitter.

Scenario B:
VBlank callback 1: Update path and call repaint()
VBlank callback 2-4: Do not update path. Call repaint() with old path data.
Result: No jitter.

In both scenarios, the path is only updated 1 of the 4 frames.

This sounds like it could be aliasing? @matt any ideas?

The more I look into this, the less it looks like something we have control over and the more it looks like an OS quirk. Keen to hear Matt’s take if he has time to read through, but for now my plan is to just trigger repaint every frame, and run the update logic at a lower frequency.

1 Like

There are also forum posts about similar issues. Probably this will help:

1 Like

Hi-

I got a new job and I’ve been out of town so my attention has been elsewhere. I’ll give it some thought.

Matt

2 Likes