Why does my Opaque component continue to repaint when an LED it is completely covering blinks?

MacOS Sequoia, JUCE 8.0.10..

I have a blinking LED component that blinks according to the tempo.

I have certain floating components that can be called into existence by the user, that can completely cover the blinking LED, so that it is not visible.

And yet, when completely covered, the blinking LED continues to cause the overlapping component(s) to repaint() every time it blinks.

The overlapping component has setOpaque(true). Everything overlapping the LED has setOpaque(true).

I normally use JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS=1 but I tried recompiling with it set to 0 and no difference.

Is there a good way for the LED to tell that it is not visible at the moment, because isVisible() returns true even when it is covered up.

EDIT: I just tried isShowing() on the LED as well, and this doesn’t appear to block the repainting either.

In other words, should a component that is completely covered by another OPAQUE component continue to repaint itself, thereby propagating repaints to the component that is covering it?

when the overlaps are visible can you stop the led repaint and start it again when the overlaps disappear?

In the version of JUCE you are using not all opaque components are guaranteed to always prevent another component from drawing. There are a couple of reasons for this including where the components are in the component tree relative to each other, if a component is using setPaintingIsUnclipped (true), and even if a component has children.

However, if you check out the develop branch you should see that opaque components should now always prevent other components they obscure from drawing. That being said you need to weigh up the costs of drawing vs the costs of checking if an opaque component obscures another component.

In the version of JUCE you are using components always checked if they were being covered by an opaque component (with some caveats as mentioned above). This meant the price for checking for opaque components always existed so using setOpaque() was a no-brainer, it was unlikely to ever make performance noticeably worse.

In the latest version this cost will only be paid once a component is marked opaque, this cost is paid by all the components that need to check if they are covered by the opaque component(s). This means every component that is potentially* behind an opaque component will need to perform a check to see if they have been obscured. Therefore the savings of preventing a component being drawn need to be greater than the cost of all the combined checks, otherwise setOpaque() isn’t worth it.

As a rule of thumb if the component you are hiding is inexpensive to draw don’t use setOpaque (true). If it’s expensive to draw, first check you can’t implement some better/more informed logic to set the visibility of the component. If setOpaque() is the only option, and your UI does not consist of many components, it’s probably fine to use setOpaque(), otherwise measure that it is actually improving performance of the whole app as the cost of the checks will be paid for by potentially lots of other components. Another aspect to consider is how often the component(s) will be covered. If a component is only obscured occasionally you may still be paying for the checks on every paint call.

*A component is potentially behind another component if it has a lower z-order. This might be a sibling, or child of a sibling with a lower z-order, or a parent, sibling of a parent, or child of a sibling of a parent, with a lower z-order.

isVisible() returns a value mirroring that of setVisible().

isShowing() checks the value of isVisible() on this component and all it’s parents. Although a good indication of if a component is showing, actually checking all the conditions required to know if a component is on screen is significantly more complicated than that and could be a potentially time consuming task if there are enough components.

1 Like

@anthony-nichols Thank you for the detailed response! I really appreciate it.

So does this mean that the logic of “my component is completely filled with drawing; therefore, mark it opaque” will no longer be true moving forwards? Put another way: Even if your component is opaque, don’t mark it so if it’s not expensive to redraw?

I have lots of components marked setOpaque(true); I guess I would need to examine all of that in the next version?

Are you referring to the “under” or “over” component? In my case, the “under component” is a 10x10 pixel LED that is very cheap to draw. However, in some cases, it can be obscured by an “over component” that is very expensive to redraw when it gets repainted by the LED’s 10x10 clipRect being repainted underneath it.

However, having said that, the chances that this LED can be obscured by said expensive-to-paint objects is not high - so you are saying “don’t mark the expensive-to-paint object as Opaque? (Even though it is?)

“Some better more informed logic” I suppose might include the expensive-to-repaint object determining if it is covering up the LED and if so, setting the LED to isVisible(false)?

Wow, this seems like a lot of work to re-evaluate painting optimizations I thought I had already done.

Correct. Note that the performance of marking a component opaque is unlikely to be worse than it was before, but the idea is that you can potentially get better performance than before (in fact you should benefit from this better performance without doing anything).

Normally it’s only the “under” component that avoids being drawn, the “over” component (the opaque component) always draws (unless obscured by another opaque component).

It sounds like what you’re saying is the cheap led is being repainted often which in turn triggers the expensive component to redraw. I probably need to test that use case to give a more concrete answer.

Correct, although I will try to test your specific use case.

Correct, if you can do that it’s going to be a lot more effective than JUCE trying to figure that out as JUCE will need to traverse lots of components to test them.

I would not expect anyone to see a drop in performance compared to older JUCE versions, I would only expect improved performance, but it’s possible your performance could be improved further by removing calls to setOpaque (true).

To help with this evaluation there is some work in progress that aims to provide some drawing diagnostics you can obtain for components.

1 Like

Just to add, if your expensive to draw component doesn’t change much it might be worth using setBufferedToImage (true) or if some aspect of it does draw regularly and another part doesn’t, you could break it into multiple components and call setBufferedToImage (true) on the part that doesn’t change much.

1 Like

OK having had a quick look JUCE does not prevent the over component from drawing if the under components repaint is triggered, unless the under component is set to invisible via setVisible (false). As far as I know this has always been the case.

Implementing this for opaque checks could be an expensive traversal of components either on every call to repaint or the state would have to be cached and recalculated in a number of different situations such as when visibility, opacity, opaqueness, bounds, etc. change.

1 Like

I guess that was my original question. I sort of naively expected the under component to NOT repaint (and not trigger the over component) if you couldn’t actually see it.

Understood. I will attack it from the other direction (make the over component block the under component from repainting). Thank you!

1 Like