Strange full-window Quartz repaints on PPC?

while hunting down a nasty GUI performance problem on a mac PPC AU plugin I observed the following strange phenomenon:
-on Intel, control repaints are limited to their boundaries (as expected)
-on PPC, even the slightest repaint of a child control seems to give rise to the repaint of the entire juce plugin window (not expected at all!)

I’m using the Quartz Debug utility which can do realtime highlighting of updated screen areas (from /Developer/Applications/Performance Tools)

to reproduce, run the AU version of Juce Audio Plugin Demo within any host (eg Logic or Live) and start the transport. Then enable Quartz Debug and check ‘flash screen updates’. On Intel you’ll see that only the plugin’s songpointer label control is updated (yellow flash). On PPC however it’s the entire plugin window that is shown as updated.

what could possibly explain this? Is there anything in Juce that causes full window repaints/blits on PPC? Or is it something in the PPC Quartz layer? I’m at my wit’s end here…

btw I tested on 10.4.x, with XCode 2.5 and both with with PPC build target set to 10.3 and 10.4. Behaviour remains identical though

found out some more:
-it seems to be an AU-only thing. Standalone apps & VSTs are fine
-Logic8 seems to be the most affected : complex plugins become almost unusable there, with visible lags in GUI message processing that extend even to the L8 UI itself. My guess is because in L8 there is less CPU headroom for them?

I can only guess that it’s a PPC/Logic thing - if you’re building for 10.4, then all the juce code is exactly the same on intel/ppc, so if it behaves differently, it must be something deeper inside the apple libs that’s different.

Are you using the tip? There’s a repaint optimisation that I added recently that might be related to this.

hi jules, yeah almost at the tip : 424

it’s not just Logic tho, Ableton Live 7 does it too

maybe indeed something in the apple AU SDK code?

it would be nice if you or one of the other devs could confirm this behaviour on their PPC machines - it just takes 10 seconds - and it would help me restore faith in my mental sanity :slight_smile:

that being said, the non-juce plugs I tested seem to be unaffected by this problem, if that can be of help

I don’t have a ppc machine any more, so can’t try it myself…

One difference I see during debugging is that on Intel the AU plugin window is a compositing window, whereas on PPC it’s a non-compositing window. This causes different code paths to be taken in the paint functions in juce_mac_Windowing.cpp

But I fail to understand why this is. The plugin window is created by the host right? Why do all AU hosts (Logic7, 8, Ableton, AUlab) provide a non-compositing window on a PPC an a compositing one on Intel? There must be some clue in there…

the saga continues… and now we’re getting somewhere!

I can now repro the problem also on an intel mac. It’s likely an issue with how juce interacts with a non-compositing plugin window

to repro:
-start AULab on an intel machine
-select preferences, expert settings
-uncheck “Use composited carbon windows with carbon au views”
-insert the juce au demo as effect
-start Quartz Debug (from /Developer/Applications/Performance Tools)
-in Quartz Debug main window, check “flash screen updates”, uncheck all others
-back to AUlab, move the gain slider in the jucedemo au plugin
-observe how the entire plugin window flashes yellow

now repeat the above steps with Aulab preferences set to enabled composited windows: observe how only the slider area flashes yellow

on Intel this is obviously not a real problem since hosts seem to provide composited windows to the plugin

however, on a PPC mac (tested on tiger) both Ableton and Live7/8 behave like Aulab with the compositing turned off and there seems to be no way to fix this on the host level (if you’re not Ableton or Apple that is)

Jules I realise PPC is probably not a big priority for you any more, but with you knowing the ComponentPeer stuff inside out, it would be great if you’d have any pointers on what I could change to avoid this whole-window repainting behaviour with non-compositing windows?

thx in advance!

Very interesting. Try this at around line 803:

[code]#if MACOS_10_2_OR_EARLIER
if (! isCompositingWindow)
Rect w;
GetWindowBounds (windowRef, windowRegionToUse, &w);

            RgnHandle rgn = NewRgn();
            //SetRectRgn (rgn, 0, 0, w.right - w.left, w.bottom -;
            for (RectangleList::Iterator i (repainter->regionsNeedingRepaint);;)
                const Rectangle& r = *i.getRectangle();

                RgnHandle r2 = NewRgn();
                SetRectRgn (r2, r.getX(), r.getY(), r.getRight(), r.getBottom());
                UnionRgn (rgn, r2, rgn);
                DisposeRgn (r2);

            UpdateControls (windowRef, rgn);
            DisposeRgn (rgn);


tried it, making sure build target was set to 10.2
your code certainly makes sense, but alas no difference in behaviour observed…

fwiw, if I set target >10.2 (forcing HiViews to be used) the problem is the same. Is there something that applies to both code forks that could be the root cause?

PS fyi, building with target = 10.2 breaks juce_Socket.cpp (at least when compiling with XCode 2.5 & SDK 10.4) This is easy to fix by changing the wrapping #ifdef

ok, well if it’s doing it in a 10.3 build, then the code paths are all the same, and the different behaviour must be something internal to the OS… It’s quite possible that this is just an apple bug/feature to force old code to work properly.

understand, but otoh plugins from other vendors (non-juce based) do not exhibit this behaviour (eg I tried with the freely available Fire from mdsp, and the CM version of u-he Zebra).

If it was the apple stack only that controls this single handedly, it should do full-window updates too when running these other plugs in a non-composited window, right? Or why would it treat juce plugins differently?

imho a conclusion could be that a juce plugin works slightly different (but identical in all code paths) than non-juce plugins in the way it handles screen updates. This difference then causes the apple stack to treat the repaints differently in the case of non-composited windows. But what could this difference be?

sorry for bugging you with this Jules - I’m not implying in any way juce is a problem - still love this lib to death! I’m just not ready yet to kiss PPC AUs goodbye forever

PS if you test on Intel (with aulab in noncompositing mode running juce demo au) the performAnyPendingRepaintsNow() function is never hit (it does hit on PPC though). Still the full-window redraw behaviour is the same, so if whatever it is it must be something outside that function

Yes, that sounds like a fair summary. I’m really not 100% sure what the other plugins are doing differently, but quite often in older apple code you get objects that redraw themselves synchronously (e.g. during the actual mouse-event callback), rather than posting invalid regions to the window manager. That’d work ok in this situation, but isn’t something I could retro-fit to juce.

YES!! finally pwn’ed that $*!# clipping region :smiley:

The ancient InvalWindowRect() seems to do the trick. I tested GUI performance with my plug-in on ppc and it really makes the difference between unusable and acceptable.

It’s rather easy. Change the following code in juce_mac_Windowing.cpp. At the kEventControlDraw event the invalidated region will then be exactly what we intended (instead of full-window) :

void performAnyPendingRepaintsNow() { stopTimer(); if (component->isVisible()) { #if MACOS_10_2_OR_EARLIER if (! isCompositingWindow) { Rect w; for (RectangleList::Iterator i (repainter->getRegionsNeedingRepaint());;) { const Rectangle& r = *i.getRectangle(); w.left = r.getX(); = r.getY(); w.right = r.getRight(); w.bottom = r.getBottom(); InvalWindowRect(windowRef, &w); } } else { ...

BTW, make sure your ppc build target is set to 10.2 in xcode (MACOSX_DEPLOYMENT_TARGET_ppc)

I compiled with XCode 2.5 and SDK 10.4u. I’m not sure how badly deprecated this API is, but it sure works here on a mac mini G4, both with tiger and leopard.

and here is a variation if you’d rather let juce do the merging of the clipping rectangles before telling OSX to invalidate the region
(i can’t really detect a perf difference with the previous one) :

void performAnyPendingRepaintsNow() { stopTimer(); if (component->isVisible()) { #if MACOS_10_2_OR_EARLIER if (! isCompositingWindow) { RgnHandle rgn = NewRgn(); for (RectangleList::Iterator i (repainter->getRegionsNeedingRepaint());;) { const Rectangle& r = *i.getRectangle(); RgnHandle r2 = NewRgn(); SetRectRgn (r2, r.getX(), r.getY(), r.getRight(), r.getBottom()); UnionRgn (rgn, r2, rgn); DisposeRgn (r2); } //UpdateControls (windowRef, rgn); // this degenerates to full-window redraws with non-compositing windows InvalWindowRgn(windowRef, rgn); // this gives more optimal, partial window redraws DisposeRgn (rgn); } else { ...

That sounds good, but I’m sure there’s a reason why I stopped using InvalWindowRect a long time ago. Can’t remember it, though, so I’m happy to go with your idea on this one! Will check in something soon!

No, hang on, surely if you just call invalwindowrect, then it won’t redraw synchronously, which is the whole point of the performAnyPendingRepaintsNow() call. That means that if you’re doing something like dragging a control in the host, then the plugin’s GUI will freeze?

if dragging means operating a slider or a rotary : those work fine apparently, even under heavy load

i think it still generates the kEventControlDraw event and the paint is only done at that moment as far as I can see. The difference is just the size of the clipping rectangle passed to that event. Of course I could miss something, more tests & more eyeballs would surely help…

a plugin config option is fine with me too. In any case whatever risk there is will slowly decrease proportionally to PPC phase-out. Intel code path is not affected.

It might just be in VST hosts that it’d cause a problem.