(SOLVED) Repaint() ignores opacity and repaints parent Component

We’re currently experimenting with some different approaches to rendering. If all goes well then the solution could be easy - just use JUCE’s new Metal renderer. It would be supported on Macbooks dating back to 2012 or so, and the iPhone 5s and newer. However, we still need to have a think about how to handle layers and not having to redraw everything all the time.

Before that lands (no promises on any timeline) I’ll have a look if there’s anything we can do to improve the current renderers. Using an imaged based context is one approach, but you might end up sacrificing a lot of the speed benefits of using the CoreGraphics API in a more abstract way.

5 Likes

Just a quick update: I got a proper caching implementation for iOS now. Works nicely. PR will follow as soon as I find the time.

1 Like

@t0m, @basteln
PR: https://github.com/WeAreROLI/JUCE/pull/572

As this is indeed to be deemed somewhat risky and may even slowdown drawing for some, I made it a Projucer option of the juce_gui_basics module called JUCE_ENABLE_IOS_REPAINT_CACHE.

implementation details:

  • At first I implemented this by employing a CGLayer - the docs sound like it is exactly what one would want An offscreen context for reusing content drawn with Core Graphics . But it turned out to be really slow. Your guess is as good as mine why that might be.
  • the implementation is entirely in the JuceUIViewController. This allowed me to override setNeedsDisplayInRect and friends so that the UIViewComponentPeer can be ignorant of the dirtyRects and it will be easier to maintain.
  • I suppose one could adopt this “technique” for macOS as well.
1 Like

@pixelpacker That looks interesting, thanks for letting me know! I haven’t had a chance to try it yet…have you been using this for a couple of days without issues? @t0m I would be interested in your opinion on this as well :slight_smile:

So far, it works great - no issues. Would also be interested in @t0m’s opinion on this.

Tom’s on holiday until next week so it might be a little while until he can look at this.

thanks for the heads-up.

@pixelpacker Just a heads-up: I tried this in our project, turning one knob very quickly. Without the changes from your PR, the CPU is at 45-55%. With the changes, it goes to 100% and the animation stutters. You pointed out that drawing might become slower for some, and for my project it seems to be the case. But still, I very much appreciate your efforts :+1:

Yes, as you pointed out, the success of this method of course heavily depends on the workload. It’s will always be slower to first draw to the cache and then display the cached output than to draw directly. So it will only be faster if the painting of the other components that is prevented by the cache is slower than the added overhead caused by the caching. In my scenario it was well worth the overhead - perhaps in yours it isn’t (or something else is wrong) - hard to tell from a distance.

Despite that, CPU load being 50% or 100% is not a very good indicator for how well something performs. Just as an example: variant A could draw 1 frame per second and 50% CPU load and variant B could draw 1000 frames per second at 100% CPU load. (Not saying that this is the case here as you also mentioned, that it stutters)

Have you verified, that the paint() of the other components is NOT called when the UIImage cache is active (perhaps by adding a print)? What are the heaviest items when profiling an optimised build? Perhaps you can post (or send) an inverted call tree of the heaviest thread.

Any updates on this? This seems like a pretty high priority issue, especially for ios, Mojave, and now Catalina coming out.

2 Likes

We’re still experimenting. The only concrete outcome so far is that using Skia to render graphics doesn’t provide any significant benefits.

2 Likes

Thanks for the reply and thanks for staying on this :+1:t4:

1 Like

+1 on having a way that prevents a full UI redraw whenever a few pixels need to change (iOS and macOS).

Just as an example: A simple and small VU bar, drawing a filled rect and set to opaque, and an update rate of about 20 Hz triggers a full UI redraw every time, causing the CPU load to be around 50% on an iPad without even processing audio. We’ve also started to receive customer complaints that projects don’t run smoothly anymore with builds on Xcode 11.

As a temporary fix for iOS, would going back to Xcode 9 and defining JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS solve it for now, or did I get that wrong?

You can keep using Xcode 11, but build against an older SDK:

2 Likes

Hm, why would the Base SDK in Xcode make a difference? My understanding is that the version of getRectsBeingDrawn:count: that gets dynamically linked and called at runtime is from the user’s /System/Library/Frameworks/AppKit.framework. So the user’s macOS version determines the behaviour of that method.

Hi,
Just wondering, what’s the latest on this.
Is the repaint problem going to be solved in the upcoming JUCE 6?

1 Like

@pixelpacker, thanks a lot for sharing your solution! It seems to me like this condition CGBitmapContextGetWidth(bitmapContext) != self.frame.size.width || CGBitmapContextGetHeight(bitmapContext) != self.frame.size.height is missing multiplication by scale for the frame dimensions. And perhaps it could also use detail::ColorSpacePtr and detail::ContextPtr - a more C++/Jucy way.

I was going back and forth between your solution and setting setBufferedToImage(true) on the editor, so far the latter options seems good enough in my case, but I know where to go if I find it lacking in performance :slight_smile:

And yeah, any updates on this issue? @t0m

After further testing: no, setBufferedToImage() isn’t good enough once there are components updated frequently enough by timer, so I went back to @pixelpacker’s caching thing and tweaked it a bit. Here’s the latest commit if anyone is interested.

Works OK so far. (Note: I had to set JUCE_DISABLE_COREGRAPHICS_FONT_SMOOTHING=1 for the cached image to look the same as normal rendering.)
Still I think an official solution would be beneficial for quite a lot of end-users - sometimes I can tell an iOS app is built with JUCE just by how meh the knobs feel.

@amethystdeceiver

Thanks for your efforts. Unfortunately, this slowed down our graphics and drawing is much slower. This is especially seen, when implementing playing video with an interface, because the the problem here is that JUCE always redraws the entire app GUI on every video frame. So ~25 times per second. The optimizations here: Slow Frame Rates on 2017+ Macs do not fix the drawing issue. It only helps to mask it, because not Metal is being used.

I guess for regular UI stuff that a user is only moving knobs periodically would suffice, but when you add video into the mix, then things just go back to the same.

@t0m

Can you confirm this: (SOLVED) Repaint() ignores opacity and repaints parent Component