Slider Attachment UI Thread Performance issue with Automation OSX


#1

I have a plugin with > 100 parameters and i’m using the new parameter system with the standard attachment classes.
I have a lot of sliders and removed shadows and gradients for the tests. There is nothing fancy on the UI. I can’t use setOpaque(true) on my sliders to avoid a whole UI repaint. I’m using the AU (AudioUnit) for the tests.

When i constantly move a slider with the mouse the CPU goes up 3%. That’s something i expect.

When i automate sliders i see that the main thread uses much more CPU. With 4 automated sliders it already uses 30% CPU when the UI is open (UI Thread). And it seems to be more when i automate more parameters with slider attachments.

My guess is that all the attachments get the same events like the host and this triggers a lot of repaints.

Audio data is much more granular than frames (44100 samples / s to 24 frames / s). I also do not need 24 frames for sliders animations. I’m not sure how the internals work. Some throttling mechanism would be great. I used to have a timer in the component class and a dirty flag to control the slider updates. I need some control over the updates to avoid this.

Any ideas are welcome.


#2

It gets really tiring over the years to constantly answer hundreds of performance questions like this, but the answer is always the same:

USE A PROFILER!

The system CPU meter gives you almost no useful information, but profilers are designed to debug exactly this kind of problem by showing you which functions are causing the bottleneck.


#3

Hi Jules, thanks for the response. I did profile now and i see that the main thread is affected. I see JUCE low level operations like arg and font related entries that i need to draw the knobs and static texts as Labels in the profiler. For sure the performance is better if i do nothing in my paint() methods. But already drops when i draw a simple knob with fillArg and more when i add an image with pre calculated gradients and shadows. I also have a lot of labels that need to repaint because they overlap the knobs. Maybe an optimisation is to create the text in the paint method instead of create a label for each text… but base problem stays.

The performance of the UI looks ok to me. The problem only occurs with automation and with a lot of related repaint (setValue) calls, i think, i don’t need.

Because of this observations i come back to my question. Is there a way to modify something to skip some repaint() calls and throttle down the slider updates?

A nice way would be a “ms” option for the update interval somewhere. This way the system is more deterministic and the host can’t slow down a plugin with a lot of updates. It would be nice to have something like this also for other things…

Maybe someone already did something like this with the new parameter system?


#4

Well, instead of calling repaint, you could just add your own intermediate method where you check the time and throw away some of the requests if things are getting too busy. I guess you could even build a more sophisticated system using a custom CachedComponentImage class.

But TBH maybe you just need to not worry about it too much, because the GUI thread is always at a lower priority than other audio stuff, so it doesn’t really matter whether you hammer it or not. If the user has your UI on the screen then why not use all the CPU if that’s what it takes to animate it well.


#5

On OSX we throttle the UI by controlling the underlying NSWindow.
We first set the NSWindow to not auto display itself. We have a Timer at 20hz that calls windowCallDisplay, which will only cause a repaint if needed. Why burn CPU cycles if there is no real reason for it.

void OSXUIUtils::setWindowManualDisplay(void* view, bool manual)
{
	if (view == nullptr)
		return;
	
	NSView* nsView = (NSView*)view;
	NSWindow* nsWindow = [nsView window];
	[nsWindow setAutodisplay:!manual];
}

void OSXUIUtils::windowCallDisplay(void* view)
{
	if (view == nullptr)
		return;
	
	NSView* nsView = (NSView*)view;
	NSWindow* nsWindow = [nsView window];
	[nsWindow displayIfNeeded];
}

#6

Hi Rebbur

Thanks for the information. Great solution for the issue. I didn’t know that this is possible. Did you implement this in the plugin wrappers and is it possible to do that without modifying the JUCE code?

Edit: I have tried this, but it seems that i can’t get it to work within a plugin. Maybe the host controls the refresh rate… ?


#7

We only build desktop applications so i’m not sure wether this will work in a plugin or not. The view pointer needed for the method is retrieved by calling:
component->getPeer()->getNativeHandle().
so if you would call setWindowManualDisplay with that view the auto display will be disabled and nothing will be drawn/painted until windowCallDisplay is invoked. So just calling setWindowManualDisplay with manual to true should already give you a clue wether this will work for you or not.
As you mentioned this might indeed depend on the host.