mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-08 23:24:19 +00:00
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
This commit is contained in:
parent
79dfa1d392
commit
961ff32b9e
3 changed files with 204 additions and 92 deletions
|
|
@ -209,7 +209,7 @@ public:
|
|||
return std::exchange (effect, &i) != &i;
|
||||
}
|
||||
|
||||
void paint (Graphics& g, Component& c, bool ignoreAlphaLevel)
|
||||
void paint (Graphics& g, Component& c, bool ignoreAlphaLevel, OpaqueLayer& opaqueLayer)
|
||||
{
|
||||
auto scale = g.getInternalContext().getPhysicalPixelScaleFactor();
|
||||
auto scaledBounds = c.getLocalBounds() * scale;
|
||||
|
|
@ -238,7 +238,7 @@ public:
|
|||
Graphics g2 (effectImage);
|
||||
g2.addTransform (AffineTransform::scale ((float) scaledBounds.getWidth() / (float) c.getWidth(),
|
||||
(float) scaledBounds.getHeight() / (float) c.getHeight()));
|
||||
c.paintComponentAndChildren (g2);
|
||||
c.paintComponentAndChildren (g2, opaqueLayer);
|
||||
}
|
||||
|
||||
Graphics::ScopedSaveState ss (g);
|
||||
|
|
@ -257,6 +257,133 @@ private:
|
|||
ImageEffectFilter* effect;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class Component::OpaqueLayer
|
||||
{
|
||||
public:
|
||||
explicit OpaqueLayer (const Component&& c) = delete;
|
||||
explicit OpaqueLayer (const Component& c)
|
||||
{
|
||||
appendOpaqueChildren (c);
|
||||
}
|
||||
|
||||
enum class ObscuredByKind
|
||||
{
|
||||
children,
|
||||
siblings
|
||||
};
|
||||
|
||||
void popComponent (Component& c)
|
||||
{
|
||||
// The most likely scenario is that a component isn't opaque
|
||||
if (! c.isOpaque())
|
||||
return;
|
||||
|
||||
// As the component is opaque chances are it's the next item in the list
|
||||
// of opaque components.
|
||||
// If not, it has been skipped, probably because it's covered by another opaque component.
|
||||
// Chances are that the component is only one or two steps away in the list.
|
||||
for (auto i = currentPosition; i < opaqueComponents.size(); ++i)
|
||||
{
|
||||
if (opaqueComponents[i] == &c)
|
||||
{
|
||||
currentPosition = i + 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// This suggests we've encountered an opaque component that should be
|
||||
// in the list but isn't! Please contact the JUCE team if you encounter
|
||||
// this assertion.
|
||||
jassert (c.getAlpha() < 1.0f);
|
||||
}
|
||||
|
||||
Rectangle<int> getNonObscuredBoundsFor (const Component& component,
|
||||
Rectangle<int> clipBounds,
|
||||
ObscuredByKind obscuredBy) const
|
||||
{
|
||||
auto visibleBounds = component.getBounds().getIntersection (clipBounds);
|
||||
|
||||
if (visibleBounds.isEmpty())
|
||||
return {};
|
||||
|
||||
RectangleList visibleRegions { visibleBounds };
|
||||
|
||||
const auto obscureWith = [&] (const Component& opaqueComponent)
|
||||
{
|
||||
const auto offset = positionOffsets[&opaqueComponent] - positionOffsets[&component];
|
||||
const auto opaqueBounds = opaqueComponent.getBounds() + offset;
|
||||
|
||||
if (! opaqueBounds.intersects (visibleBounds))
|
||||
return;
|
||||
|
||||
if (opaqueBounds.contains (visibleBounds))
|
||||
visibleRegions.clear();
|
||||
else
|
||||
visibleRegions.subtract (opaqueBounds);
|
||||
|
||||
visibleBounds = visibleRegions.getBounds();
|
||||
};
|
||||
|
||||
if (obscuredBy == ObscuredByKind::children)
|
||||
{
|
||||
for (auto i = currentPosition; i < opaqueComponents.size(); ++i)
|
||||
{
|
||||
const auto* opaqueComponent = opaqueComponents.getUnchecked (i);
|
||||
|
||||
if (! component.isParentOf (opaqueComponent))
|
||||
break;
|
||||
|
||||
obscureWith (*opaqueComponent);
|
||||
|
||||
if (visibleBounds.isEmpty())
|
||||
return {};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = opaqueComponents.size(); --i >= currentPosition;)
|
||||
{
|
||||
const auto* opaqueComponent = opaqueComponents.getUnchecked (i);
|
||||
|
||||
if (component.isParentOf (opaqueComponent))
|
||||
break;
|
||||
|
||||
obscureWith (*opaqueComponent);
|
||||
|
||||
if (visibleBounds.isEmpty())
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return visibleBounds;
|
||||
}
|
||||
|
||||
private:
|
||||
void appendOpaqueChildren (const Component& parent, Point<int> offset = {})
|
||||
{
|
||||
for (auto* child : parent.getChildren())
|
||||
{
|
||||
positionOffsets.set (child, offset);
|
||||
|
||||
if (! detail::ComponentHelpers::isVisible (*child, false)
|
||||
|| child->isTransformed())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (child->isOpaque())
|
||||
opaqueComponents.add (child);
|
||||
|
||||
appendOpaqueChildren (*child, child->getPosition() + offset);
|
||||
}
|
||||
}
|
||||
|
||||
Array<Component*> opaqueComponents;
|
||||
int currentPosition = 0;
|
||||
HashMap<const Component*, Point<int>> positionOffsets;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
Component::Component() noexcept
|
||||
: componentFlags (0)
|
||||
|
|
@ -1701,17 +1828,17 @@ void Component::paintOverChildren (Graphics&)
|
|||
}
|
||||
|
||||
//==============================================================================
|
||||
void Component::paintWithinParentContext (Graphics& g)
|
||||
void Component::paintWithinParentContext (Graphics& g, OpaqueLayer& opaqueLayer)
|
||||
{
|
||||
g.setOrigin (getPosition());
|
||||
|
||||
if (cachedImage != nullptr)
|
||||
cachedImage->paint (g);
|
||||
else
|
||||
paintEntireComponent (g, false);
|
||||
paintEntireComponent (g, false, opaqueLayer);
|
||||
}
|
||||
|
||||
void Component::paintComponentAndChildren (Graphics& g)
|
||||
void Component::paintComponentAndChildren (Graphics& g, OpaqueLayer& opaqueLayer)
|
||||
{
|
||||
#if JUCE_ETW_TRACELOGGING
|
||||
{
|
||||
|
|
@ -1727,70 +1854,69 @@ void Component::paintComponentAndChildren (Graphics& g)
|
|||
}
|
||||
#endif
|
||||
|
||||
auto clipBounds = g.getClipBounds();
|
||||
using ObscuredByKind = OpaqueLayer::ObscuredByKind;
|
||||
|
||||
if (flags.dontClipGraphicsFlag && getNumChildComponents() == 0)
|
||||
{
|
||||
paint (g);
|
||||
}
|
||||
else
|
||||
const auto parentClipBounds = opaqueLayer.getNonObscuredBoundsFor (*this, g.getClipBounds() + getPosition(), ObscuredByKind::children);
|
||||
|
||||
if (! parentClipBounds.isEmpty())
|
||||
{
|
||||
Graphics::ScopedSaveState ss (g);
|
||||
|
||||
if (! (detail::ComponentHelpers::clipObscuredRegions (*this, g, clipBounds, {}) && g.isClipEmpty()))
|
||||
paint (g);
|
||||
if (! isPaintingUnclipped())
|
||||
g.reduceClipRegion (parentClipBounds - getPosition());
|
||||
|
||||
paint (g);
|
||||
}
|
||||
|
||||
for (int i = 0; i < childComponentList.size(); ++i)
|
||||
for (auto* child : getChildren())
|
||||
{
|
||||
auto& child = *childComponentList.getUnchecked (i);
|
||||
if (! detail::ComponentHelpers::isVisible (*child))
|
||||
continue;
|
||||
|
||||
if (child.isVisible())
|
||||
if (child->isTransformed())
|
||||
{
|
||||
if (child.affineTransform != nullptr)
|
||||
{
|
||||
Graphics::ScopedSaveState ss (g);
|
||||
Graphics::ScopedSaveState ss (g);
|
||||
|
||||
g.addTransform (*child.affineTransform);
|
||||
if (auto& transform = child->affineTransform)
|
||||
g.addTransform (*transform);
|
||||
|
||||
if ((child.flags.dontClipGraphicsFlag && ! g.isClipEmpty()) || g.reduceClipRegion (child.getBounds()))
|
||||
child.paintWithinParentContext (g);
|
||||
}
|
||||
else if (clipBounds.intersects (child.getBounds()))
|
||||
{
|
||||
Graphics::ScopedSaveState ss (g);
|
||||
child->paintWithinParentContext (g, opaqueLayer);
|
||||
}
|
||||
else
|
||||
{
|
||||
opaqueLayer.popComponent (*child);
|
||||
|
||||
if (child.flags.dontClipGraphicsFlag)
|
||||
{
|
||||
child.paintWithinParentContext (g);
|
||||
}
|
||||
else if (g.reduceClipRegion (child.getBounds()))
|
||||
{
|
||||
bool nothingClipped = true;
|
||||
const auto clipBounds = opaqueLayer.getNonObscuredBoundsFor (*child,
|
||||
g.getClipBounds(),
|
||||
ObscuredByKind::siblings);
|
||||
|
||||
for (int j = i + 1; j < childComponentList.size(); ++j)
|
||||
{
|
||||
auto& sibling = *childComponentList.getUnchecked (j);
|
||||
if (clipBounds.isEmpty())
|
||||
continue;
|
||||
|
||||
if (sibling.flags.opaqueFlag && sibling.isVisible() && sibling.affineTransform == nullptr)
|
||||
{
|
||||
nothingClipped = false;
|
||||
g.excludeClipRegion (sibling.getBounds());
|
||||
}
|
||||
}
|
||||
Graphics::ScopedSaveState ss (g);
|
||||
|
||||
if (nothingClipped || ! g.isClipEmpty())
|
||||
child.paintWithinParentContext (g);
|
||||
}
|
||||
}
|
||||
if (! child->isPaintingUnclipped())
|
||||
g.reduceClipRegion (clipBounds);
|
||||
|
||||
child->paintWithinParentContext (g, opaqueLayer);
|
||||
}
|
||||
}
|
||||
|
||||
Graphics::ScopedSaveState ss (g);
|
||||
|
||||
if (! isPaintingUnclipped())
|
||||
g.reduceClipRegion (getLocalBounds());
|
||||
|
||||
paintOverChildren (g);
|
||||
}
|
||||
|
||||
void Component::paintEntireComponent (Graphics& g, bool ignoreAlphaLevel)
|
||||
{
|
||||
OpaqueLayer opaqueLayer { *this };
|
||||
paintEntireComponent (g, ignoreAlphaLevel, opaqueLayer);
|
||||
}
|
||||
|
||||
void Component::paintEntireComponent (Graphics& g, bool ignoreAlphaLevel, OpaqueLayer& opaqueLayer)
|
||||
{
|
||||
// If sizing a top-level-window and the OS paint message is delivered synchronously
|
||||
// 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)
|
|||
|
||||
if (effectState != nullptr)
|
||||
{
|
||||
effectState->paint (g, *this, ignoreAlphaLevel);
|
||||
effectState->paint (g, *this, ignoreAlphaLevel, opaqueLayer);
|
||||
}
|
||||
else if (componentTransparency > 0 && ! ignoreAlphaLevel)
|
||||
{
|
||||
if (componentTransparency < 255)
|
||||
{
|
||||
OpaqueLayer transparentOpaqueLayer { *this };
|
||||
g.beginTransparencyLayer (getAlpha());
|
||||
paintComponentAndChildren (g);
|
||||
paintComponentAndChildren (g, transparentOpaqueLayer);
|
||||
g.endTransparencyLayer();
|
||||
}
|
||||
}
|
||||
else if (isTransformed())
|
||||
{
|
||||
OpaqueLayer transformedOpaqueLayer { *this };
|
||||
paintComponentAndChildren (g, transformedOpaqueLayer);
|
||||
}
|
||||
else
|
||||
{
|
||||
paintComponentAndChildren (g);
|
||||
paintComponentAndChildren (g, opaqueLayer);
|
||||
}
|
||||
|
||||
#if JUCE_DEBUG
|
||||
|
|
|
|||
|
|
@ -1194,7 +1194,7 @@ public:
|
|||
|
||||
If you enable this mode, you'll need to make sure your paint method doesn't call anything like
|
||||
Graphics::fillAll(), and doesn't draw beyond the component's bounds, because that'll produce
|
||||
artifacts. This option will have no effect on components that contain any child components.
|
||||
artifacts.
|
||||
*/
|
||||
void setPaintingIsUnclipped (bool shouldPaintWithoutClipping) noexcept;
|
||||
|
||||
|
|
@ -1276,14 +1276,19 @@ public:
|
|||
/** Indicates whether any parts of the component might be transparent.
|
||||
|
||||
Components that always paint all of their contents with solid colour and
|
||||
thus completely cover any components behind them should use this method
|
||||
thus completely cover any components behind them, can use this method to
|
||||
to tell the repaint system that they are opaque.
|
||||
|
||||
This information is used to optimise drawing, because it means that
|
||||
objects underneath opaque windows don't need to be painted.
|
||||
objects underneath opaque components or windows don't need to be painted
|
||||
or can have their clip bounds reduced to a smaller size.
|
||||
|
||||
By default, components are considered transparent, unless this is used to
|
||||
make it otherwise.
|
||||
Note however that there is a cost for every other component to check if
|
||||
it is being obscured by opaque components. This cost should be carefully
|
||||
weighed up against the benefits before deciding to enable this.
|
||||
|
||||
By default, components are considered transparent, unless this is used
|
||||
to make it otherwise.
|
||||
|
||||
@see isOpaque
|
||||
*/
|
||||
|
|
@ -2712,6 +2717,8 @@ private:
|
|||
uint8 componentTransparency = 0;
|
||||
|
||||
//==============================================================================
|
||||
class OpaqueLayer;
|
||||
|
||||
static void internalMouseEnter (SafePointer<Component>, MouseInputSource, Point<float>, Time);
|
||||
static void internalMouseExit (SafePointer<Component>, MouseInputSource, Point<float>, Time);
|
||||
static void internalMouseDown (SafePointer<Component>, MouseInputSource, const detail::PointerState&, Time);
|
||||
|
|
@ -2733,8 +2740,9 @@ private:
|
|||
void internalRepaintUnchecked (Rectangle<int>, bool);
|
||||
Component* removeChildComponent (int index, bool sendParentEvents, bool sendChildEvents);
|
||||
void reorderChildInternal (int sourceIndex, int destIndex);
|
||||
void paintComponentAndChildren (Graphics&);
|
||||
void paintWithinParentContext (Graphics&);
|
||||
void paintEntireComponent (Graphics&, bool, OpaqueLayer&);
|
||||
void paintComponentAndChildren (Graphics&, OpaqueLayer&);
|
||||
void paintWithinParentContext (Graphics&, OpaqueLayer&);
|
||||
void sendMovedResizedMessages (bool wasMoved, bool wasResized);
|
||||
void sendMovedResizedMessagesIfPending();
|
||||
void repaintParent();
|
||||
|
|
|
|||
|
|
@ -191,42 +191,6 @@ struct ComponentHelpers
|
|||
return convertFromDistantParentSpace (topLevelComp, *target, p);
|
||||
}
|
||||
|
||||
static bool clipChildComponent (const Component& child,
|
||||
Graphics& g,
|
||||
const Rectangle<int> clipRect,
|
||||
Point<int> delta)
|
||||
{
|
||||
if (! child.isVisible() || child.isTransformed())
|
||||
return false;
|
||||
|
||||
const auto newClip = clipRect.getIntersection (child.boundsRelativeToParent);
|
||||
|
||||
if (newClip.isEmpty())
|
||||
return false;
|
||||
|
||||
if (child.isOpaque() && child.componentTransparency == 0)
|
||||
{
|
||||
g.excludeClipRegion (newClip + delta);
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto childPos = child.getPosition();
|
||||
return clipObscuredRegions (child, g, newClip - childPos, childPos + delta);
|
||||
}
|
||||
|
||||
static bool clipObscuredRegions (const Component& comp,
|
||||
Graphics& g,
|
||||
const Rectangle<int> clipRect,
|
||||
Point<int> delta)
|
||||
{
|
||||
auto wasClipped = false;
|
||||
|
||||
for (int i = comp.childComponentList.size(); --i >= 0;)
|
||||
wasClipped |= clipChildComponent (*comp.childComponentList.getUnchecked (i), g, clipRect, delta);
|
||||
|
||||
return wasClipped;
|
||||
}
|
||||
|
||||
static Rectangle<int> getParentOrMainMonitorBounds (const Component& comp)
|
||||
{
|
||||
if (auto* p = comp.getParentComponent())
|
||||
|
|
@ -261,6 +225,14 @@ struct ComponentHelpers
|
|||
function (c, ms, SH::screenPosToLocalPos (*c, ms.getScreenPosition()), Time::getCurrentTime());
|
||||
}
|
||||
|
||||
static bool isVisible (const Component& component, bool allowTransparency = true)
|
||||
{
|
||||
return component.isVisible()
|
||||
&& component.getWidth() > 0
|
||||
&& component.getHeight() > 0
|
||||
&& component.componentTransparency < (allowTransparency ? 255 : 1);
|
||||
}
|
||||
|
||||
class ModalComponentManagerChangeNotifier
|
||||
{
|
||||
public:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue