Skip to content

Commit 961ff32

Browse files
Component: Improve opaque component checks
- Improve default performance when components check if they are opaque - Allows all components to take advantage of setPaintingIsUnclipped - Give more control to opt out of opaque checks separate from setPaintingIsUnclipped
1 parent 79dfa1d commit 961ff32

File tree

3 files changed

+204
-92
lines changed

3 files changed

+204
-92
lines changed

modules/juce_gui_basics/components/juce_Component.cpp

Lines changed: 181 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ class Component::EffectState
209209
return std::exchange (effect, &i) != &i;
210210
}
211211

212-
void paint (Graphics& g, Component& c, bool ignoreAlphaLevel)
212+
void paint (Graphics& g, Component& c, bool ignoreAlphaLevel, OpaqueLayer& opaqueLayer)
213213
{
214214
auto scale = g.getInternalContext().getPhysicalPixelScaleFactor();
215215
auto scaledBounds = c.getLocalBounds() * scale;
@@ -238,7 +238,7 @@ class Component::EffectState
238238
Graphics g2 (effectImage);
239239
g2.addTransform (AffineTransform::scale ((float) scaledBounds.getWidth() / (float) c.getWidth(),
240240
(float) scaledBounds.getHeight() / (float) c.getHeight()));
241-
c.paintComponentAndChildren (g2);
241+
c.paintComponentAndChildren (g2, opaqueLayer);
242242
}
243243

244244
Graphics::ScopedSaveState ss (g);
@@ -257,6 +257,133 @@ class Component::EffectState
257257
ImageEffectFilter* effect;
258258
};
259259

260+
//==============================================================================
261+
class Component::OpaqueLayer
262+
{
263+
public:
264+
explicit OpaqueLayer (const Component&& c) = delete;
265+
explicit OpaqueLayer (const Component& c)
266+
{
267+
appendOpaqueChildren (c);
268+
}
269+
270+
enum class ObscuredByKind
271+
{
272+
children,
273+
siblings
274+
};
275+
276+
void popComponent (Component& c)
277+
{
278+
// The most likely scenario is that a component isn't opaque
279+
if (! c.isOpaque())
280+
return;
281+
282+
// As the component is opaque chances are it's the next item in the list
283+
// of opaque components.
284+
// If not, it has been skipped, probably because it's covered by another opaque component.
285+
// Chances are that the component is only one or two steps away in the list.
286+
for (auto i = currentPosition; i < opaqueComponents.size(); ++i)
287+
{
288+
if (opaqueComponents[i] == &c)
289+
{
290+
currentPosition = i + 1;
291+
return;
292+
}
293+
}
294+
295+
// This suggests we've encountered an opaque component that should be
296+
// in the list but isn't! Please contact the JUCE team if you encounter
297+
// this assertion.
298+
jassert (c.getAlpha() < 1.0f);
299+
}
300+
301+
Rectangle<int> getNonObscuredBoundsFor (const Component& component,
302+
Rectangle<int> clipBounds,
303+
ObscuredByKind obscuredBy) const
304+
{
305+
auto visibleBounds = component.getBounds().getIntersection (clipBounds);
306+
307+
if (visibleBounds.isEmpty())
308+
return {};
309+
310+
RectangleList visibleRegions { visibleBounds };
311+
312+
const auto obscureWith = [&] (const Component& opaqueComponent)
313+
{
314+
const auto offset = positionOffsets[&opaqueComponent] - positionOffsets[&component];
315+
const auto opaqueBounds = opaqueComponent.getBounds() + offset;
316+
317+
if (! opaqueBounds.intersects (visibleBounds))
318+
return;
319+
320+
if (opaqueBounds.contains (visibleBounds))
321+
visibleRegions.clear();
322+
else
323+
visibleRegions.subtract (opaqueBounds);
324+
325+
visibleBounds = visibleRegions.getBounds();
326+
};
327+
328+
if (obscuredBy == ObscuredByKind::children)
329+
{
330+
for (auto i = currentPosition; i < opaqueComponents.size(); ++i)
331+
{
332+
const auto* opaqueComponent = opaqueComponents.getUnchecked (i);
333+
334+
if (! component.isParentOf (opaqueComponent))
335+
break;
336+
337+
obscureWith (*opaqueComponent);
338+
339+
if (visibleBounds.isEmpty())
340+
return {};
341+
}
342+
}
343+
else
344+
{
345+
for (int i = opaqueComponents.size(); --i >= currentPosition;)
346+
{
347+
const auto* opaqueComponent = opaqueComponents.getUnchecked (i);
348+
349+
if (component.isParentOf (opaqueComponent))
350+
break;
351+
352+
obscureWith (*opaqueComponent);
353+
354+
if (visibleBounds.isEmpty())
355+
return {};
356+
}
357+
}
358+
359+
return visibleBounds;
360+
}
361+
362+
private:
363+
void appendOpaqueChildren (const Component& parent, Point<int> offset = {})
364+
{
365+
for (auto* child : parent.getChildren())
366+
{
367+
positionOffsets.set (child, offset);
368+
369+
if (! detail::ComponentHelpers::isVisible (*child, false)
370+
|| child->isTransformed())
371+
{
372+
continue;
373+
}
374+
375+
if (child->isOpaque())
376+
opaqueComponents.add (child);
377+
378+
appendOpaqueChildren (*child, child->getPosition() + offset);
379+
}
380+
}
381+
382+
Array<Component*> opaqueComponents;
383+
int currentPosition = 0;
384+
HashMap<const Component*, Point<int>> positionOffsets;
385+
};
386+
260387
//==============================================================================
261388
Component::Component() noexcept
262389
: componentFlags (0)
@@ -1701,17 +1828,17 @@ void Component::paintOverChildren (Graphics&)
17011828
}
17021829

17031830
//==============================================================================
1704-
void Component::paintWithinParentContext (Graphics& g)
1831+
void Component::paintWithinParentContext (Graphics& g, OpaqueLayer& opaqueLayer)
17051832
{
17061833
g.setOrigin (getPosition());
17071834

17081835
if (cachedImage != nullptr)
17091836
cachedImage->paint (g);
17101837
else
1711-
paintEntireComponent (g, false);
1838+
paintEntireComponent (g, false, opaqueLayer);
17121839
}
17131840

1714-
void Component::paintComponentAndChildren (Graphics& g)
1841+
void Component::paintComponentAndChildren (Graphics& g, OpaqueLayer& opaqueLayer)
17151842
{
17161843
#if JUCE_ETW_TRACELOGGING
17171844
{
@@ -1727,70 +1854,69 @@ void Component::paintComponentAndChildren (Graphics& g)
17271854
}
17281855
#endif
17291856

1730-
auto clipBounds = g.getClipBounds();
1857+
using ObscuredByKind = OpaqueLayer::ObscuredByKind;
17311858

1732-
if (flags.dontClipGraphicsFlag && getNumChildComponents() == 0)
1733-
{
1734-
paint (g);
1735-
}
1736-
else
1859+
const auto parentClipBounds = opaqueLayer.getNonObscuredBoundsFor (*this, g.getClipBounds() + getPosition(), ObscuredByKind::children);
1860+
1861+
if (! parentClipBounds.isEmpty())
17371862
{
17381863
Graphics::ScopedSaveState ss (g);
17391864

1740-
if (! (detail::ComponentHelpers::clipObscuredRegions (*this, g, clipBounds, {}) && g.isClipEmpty()))
1741-
paint (g);
1865+
if (! isPaintingUnclipped())
1866+
g.reduceClipRegion (parentClipBounds - getPosition());
1867+
1868+
paint (g);
17421869
}
17431870

1744-
for (int i = 0; i < childComponentList.size(); ++i)
1871+
for (auto* child : getChildren())
17451872
{
1746-
auto& child = *childComponentList.getUnchecked (i);
1873+
if (! detail::ComponentHelpers::isVisible (*child))
1874+
continue;
17471875

1748-
if (child.isVisible())
1876+
if (child->isTransformed())
17491877
{
1750-
if (child.affineTransform != nullptr)
1751-
{
1752-
Graphics::ScopedSaveState ss (g);
1878+
Graphics::ScopedSaveState ss (g);
17531879

1754-
g.addTransform (*child.affineTransform);
1880+
if (auto& transform = child->affineTransform)
1881+
g.addTransform (*transform);
17551882

1756-
if ((child.flags.dontClipGraphicsFlag && ! g.isClipEmpty()) || g.reduceClipRegion (child.getBounds()))
1757-
child.paintWithinParentContext (g);
1758-
}
1759-
else if (clipBounds.intersects (child.getBounds()))
1760-
{
1761-
Graphics::ScopedSaveState ss (g);
1883+
child->paintWithinParentContext (g, opaqueLayer);
1884+
}
1885+
else
1886+
{
1887+
opaqueLayer.popComponent (*child);
17621888

1763-
if (child.flags.dontClipGraphicsFlag)
1764-
{
1765-
child.paintWithinParentContext (g);
1766-
}
1767-
else if (g.reduceClipRegion (child.getBounds()))
1768-
{
1769-
bool nothingClipped = true;
1889+
const auto clipBounds = opaqueLayer.getNonObscuredBoundsFor (*child,
1890+
g.getClipBounds(),
1891+
ObscuredByKind::siblings);
17701892

1771-
for (int j = i + 1; j < childComponentList.size(); ++j)
1772-
{
1773-
auto& sibling = *childComponentList.getUnchecked (j);
1893+
if (clipBounds.isEmpty())
1894+
continue;
17741895

1775-
if (sibling.flags.opaqueFlag && sibling.isVisible() && sibling.affineTransform == nullptr)
1776-
{
1777-
nothingClipped = false;
1778-
g.excludeClipRegion (sibling.getBounds());
1779-
}
1780-
}
1896+
Graphics::ScopedSaveState ss (g);
17811897

1782-
if (nothingClipped || ! g.isClipEmpty())
1783-
child.paintWithinParentContext (g);
1784-
}
1785-
}
1898+
if (! child->isPaintingUnclipped())
1899+
g.reduceClipRegion (clipBounds);
1900+
1901+
child->paintWithinParentContext (g, opaqueLayer);
17861902
}
17871903
}
17881904

17891905
Graphics::ScopedSaveState ss (g);
1906+
1907+
if (! isPaintingUnclipped())
1908+
g.reduceClipRegion (getLocalBounds());
1909+
17901910
paintOverChildren (g);
17911911
}
17921912

17931913
void Component::paintEntireComponent (Graphics& g, bool ignoreAlphaLevel)
1914+
{
1915+
OpaqueLayer opaqueLayer { *this };
1916+
paintEntireComponent (g, ignoreAlphaLevel, opaqueLayer);
1917+
}
1918+
1919+
void Component::paintEntireComponent (Graphics& g, bool ignoreAlphaLevel, OpaqueLayer& opaqueLayer)
17941920
{
17951921
// If sizing a top-level-window and the OS paint message is delivered synchronously
17961922
// before resized() is called, then we'll invoke the callback here, to make sure
@@ -1806,20 +1932,26 @@ void Component::paintEntireComponent (Graphics& g, bool ignoreAlphaLevel)
18061932

18071933
if (effectState != nullptr)
18081934
{
1809-
effectState->paint (g, *this, ignoreAlphaLevel);
1935+
effectState->paint (g, *this, ignoreAlphaLevel, opaqueLayer);
18101936
}
18111937
else if (componentTransparency > 0 && ! ignoreAlphaLevel)
18121938
{
18131939
if (componentTransparency < 255)
18141940
{
1941+
OpaqueLayer transparentOpaqueLayer { *this };
18151942
g.beginTransparencyLayer (getAlpha());
1816-
paintComponentAndChildren (g);
1943+
paintComponentAndChildren (g, transparentOpaqueLayer);
18171944
g.endTransparencyLayer();
18181945
}
18191946
}
1947+
else if (isTransformed())
1948+
{
1949+
OpaqueLayer transformedOpaqueLayer { *this };
1950+
paintComponentAndChildren (g, transformedOpaqueLayer);
1951+
}
18201952
else
18211953
{
1822-
paintComponentAndChildren (g);
1954+
paintComponentAndChildren (g, opaqueLayer);
18231955
}
18241956

18251957
#if JUCE_DEBUG

modules/juce_gui_basics/components/juce_Component.h

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,7 +1194,7 @@ class JUCE_API Component : public MouseListener
11941194
11951195
If you enable this mode, you'll need to make sure your paint method doesn't call anything like
11961196
Graphics::fillAll(), and doesn't draw beyond the component's bounds, because that'll produce
1197-
artifacts. This option will have no effect on components that contain any child components.
1197+
artifacts.
11981198
*/
11991199
void setPaintingIsUnclipped (bool shouldPaintWithoutClipping) noexcept;
12001200

@@ -1276,14 +1276,19 @@ class JUCE_API Component : public MouseListener
12761276
/** Indicates whether any parts of the component might be transparent.
12771277
12781278
Components that always paint all of their contents with solid colour and
1279-
thus completely cover any components behind them should use this method
1279+
thus completely cover any components behind them, can use this method to
12801280
to tell the repaint system that they are opaque.
12811281
12821282
This information is used to optimise drawing, because it means that
1283-
objects underneath opaque windows don't need to be painted.
1283+
objects underneath opaque components or windows don't need to be painted
1284+
or can have their clip bounds reduced to a smaller size.
12841285
1285-
By default, components are considered transparent, unless this is used to
1286-
make it otherwise.
1286+
Note however that there is a cost for every other component to check if
1287+
it is being obscured by opaque components. This cost should be carefully
1288+
weighed up against the benefits before deciding to enable this.
1289+
1290+
By default, components are considered transparent, unless this is used
1291+
to make it otherwise.
12871292
12881293
@see isOpaque
12891294
*/
@@ -2712,6 +2717,8 @@ class JUCE_API Component : public MouseListener
27122717
uint8 componentTransparency = 0;
27132718

27142719
//==============================================================================
2720+
class OpaqueLayer;
2721+
27152722
static void internalMouseEnter (SafePointer<Component>, MouseInputSource, Point<float>, Time);
27162723
static void internalMouseExit (SafePointer<Component>, MouseInputSource, Point<float>, Time);
27172724
static void internalMouseDown (SafePointer<Component>, MouseInputSource, const detail::PointerState&, Time);
@@ -2733,8 +2740,9 @@ class JUCE_API Component : public MouseListener
27332740
void internalRepaintUnchecked (Rectangle<int>, bool);
27342741
Component* removeChildComponent (int index, bool sendParentEvents, bool sendChildEvents);
27352742
void reorderChildInternal (int sourceIndex, int destIndex);
2736-
void paintComponentAndChildren (Graphics&);
2737-
void paintWithinParentContext (Graphics&);
2743+
void paintEntireComponent (Graphics&, bool, OpaqueLayer&);
2744+
void paintComponentAndChildren (Graphics&, OpaqueLayer&);
2745+
void paintWithinParentContext (Graphics&, OpaqueLayer&);
27382746
void sendMovedResizedMessages (bool wasMoved, bool wasResized);
27392747
void sendMovedResizedMessagesIfPending();
27402748
void repaintParent();

0 commit comments

Comments
 (0)