diff --git a/osu.Framework.Tests/Layout/TestSceneContainerLayout.cs b/osu.Framework.Tests/Layout/TestSceneContainerLayout.cs
index 754f84eb5e..81cd0577d0 100644
--- a/osu.Framework.Tests/Layout/TestSceneContainerLayout.cs
+++ b/osu.Framework.Tests/Layout/TestSceneContainerLayout.cs
@@ -7,6 +7,7 @@
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Layout;
using osu.Framework.Testing;
@@ -498,6 +499,42 @@ public void TestNonAutoSizingParentDoesNotInvalidateSizeDependenciesFromChild()
AddAssert("still valid", () => isValid);
}
+ ///
+ /// Tests that changing Masking property will invalidate child masking bounds.
+ ///
+ [Test]
+ public void TestChildMaskingInvalidationOnMaskingChange()
+ {
+ Container parent = null;
+ Container child = null;
+ RectangleF childMaskingBounds = new RectangleF();
+ RectangleF actualChildMaskingBounds = new RectangleF();
+
+ AddStep("createTest", () =>
+ {
+ Child = parent = new Container
+ {
+ Size = new Vector2(100),
+ Child = child = new Container
+ {
+ RelativeSizeAxes = Axes.Both
+ }
+ };
+ });
+
+ AddAssert("Parent's masking is off", () => parent.Masking == false);
+ AddStep("Save masking bounds", () =>
+ {
+ childMaskingBounds = parent.ChildMaskingBounds;
+ actualChildMaskingBounds = child.ChildMaskingBounds;
+ });
+ AddAssert("Parent and child have the same masking bounds", () => childMaskingBounds == actualChildMaskingBounds);
+ AddStep("Enable parent masking", () => parent.Masking = true);
+ AddAssert("Parent's ChildMaskingBounds has changed", () => childMaskingBounds != parent.ChildMaskingBounds);
+ AddAssert("Child's masking bounds has changed", () => actualChildMaskingBounds != child.ChildMaskingBounds);
+ AddAssert("Parent and child have the same masking bounds", () => parent.ChildMaskingBounds == child.ChildMaskingBounds);
+ }
+
private partial class TestBox1 : Box
{
public override bool RemoveWhenNotAlive => false;
diff --git a/osu.Framework/Graphics/Containers/BufferedContainer.cs b/osu.Framework/Graphics/Containers/BufferedContainer.cs
index 5a83d0b87a..aa9aa210a5 100644
--- a/osu.Framework/Graphics/Containers/BufferedContainer.cs
+++ b/osu.Framework/Graphics/Containers/BufferedContainer.cs
@@ -271,16 +271,16 @@ private void load(ShaderManager shaders)
protected override DrawNode CreateDrawNode() => new BufferedContainerDrawNode(this, sharedData);
- public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds)
+ public override bool UpdateSubTreeMasking()
{
- bool result = base.UpdateSubTreeMasking(source, maskingBounds);
+ bool result = base.UpdateSubTreeMasking();
childrenUpdateVersion = updateVersion;
return result;
}
- protected override RectangleF ComputeChildMaskingBounds(RectangleF maskingBounds) => ScreenSpaceDrawQuad.AABBFloat; // Make sure children never get masked away
+ protected override RectangleF ComputeChildMaskingBounds() => ScreenSpaceDrawQuad.AABBFloat; // Make sure children never get masked away
private Vector2 lastScreenSpaceSize;
diff --git a/osu.Framework/Graphics/Containers/CompositeDrawable.cs b/osu.Framework/Graphics/Containers/CompositeDrawable.cs
index 2c775e1d0f..b113bfb35e 100644
--- a/osu.Framework/Graphics/Containers/CompositeDrawable.cs
+++ b/osu.Framework/Graphics/Containers/CompositeDrawable.cs
@@ -58,6 +58,7 @@ protected CompositeDrawable()
childrenSizeDependencies.Validate();
AddLayout(childrenSizeDependencies);
+ AddLayout(childMaskingBoundsBacking);
}
///
@@ -958,12 +959,10 @@ private void updateChild(Drawable c)
/// Updates all masking calculations for this and its .
/// This occurs post- to ensure that all updates have taken place.
///
- /// The parent that triggered this update on this .
- /// The that defines the masking bounds.
/// Whether masking calculations have taken place.
- public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds)
+ public override bool UpdateSubTreeMasking()
{
- if (!base.UpdateSubTreeMasking(source, maskingBounds))
+ if (!base.UpdateSubTreeMasking())
return false;
if (IsMaskedAway)
@@ -974,10 +973,8 @@ public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBou
if (RequiresChildrenUpdate)
{
- var childMaskingBounds = ComputeChildMaskingBounds(maskingBounds);
-
for (int i = 0; i < aliveInternalChildren.Count; i++)
- aliveInternalChildren[i].UpdateSubTreeMasking(this, childMaskingBounds);
+ aliveInternalChildren[i].UpdateSubTreeMasking();
}
return true;
@@ -996,9 +993,15 @@ protected override bool ComputeIsMaskedAway(RectangleF maskingBounds)
///
/// Computes the to be used as the masking bounds for all .
///
- /// The that defines the masking bounds for this .
/// The to be used as the masking bounds for .
- protected virtual RectangleF ComputeChildMaskingBounds(RectangleF maskingBounds) => Masking ? RectangleF.Intersect(maskingBounds, ScreenSpaceDrawQuad.AABBFloat) : maskingBounds;
+ protected virtual RectangleF ComputeChildMaskingBounds() => Masking ? RectangleF.Intersect(ComputeMaskingBounds(), ScreenSpaceDrawQuad.AABBFloat) : ComputeMaskingBounds();
+
+ private readonly LayoutValue childMaskingBoundsBacking = new LayoutValue(Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence);
+
+ ///
+ /// The to be used as the masking bounds for all .
+ ///
+ public RectangleF ChildMaskingBounds => childMaskingBoundsBacking.IsValid ? childMaskingBoundsBacking : childMaskingBoundsBacking.Value = ComputeChildMaskingBounds();
///
/// Invoked after and state checks have taken place,
@@ -1478,7 +1481,10 @@ protected set
return;
masking = value;
- Invalidate(Invalidation.DrawNode);
+ // DrawInfo invalidation will propagate masking bounds changes in the sub-tree.
+ // While this can invalidate other layouts, there are rarely any use cases of enabling/disabling masking "on the fly"
+ // so this won't hurt performance under normal circumstances.
+ Invalidate(Invalidation.DrawNode | Invalidation.DrawInfo);
}
}
diff --git a/osu.Framework/Graphics/Containers/DelayedLoadWrapper.cs b/osu.Framework/Graphics/Containers/DelayedLoadWrapper.cs
index db123a852d..852da366a2 100644
--- a/osu.Framework/Graphics/Containers/DelayedLoadWrapper.cs
+++ b/osu.Framework/Graphics/Containers/DelayedLoadWrapper.cs
@@ -7,7 +7,6 @@
using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Extensions.PolygonExtensions;
-using osu.Framework.Graphics.Primitives;
using osu.Framework.Layout;
using osu.Framework.Threading;
@@ -211,9 +210,9 @@ protected override bool OnInvalidate(Invalidation invalidation, InvalidationSour
return result;
}
- public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds)
+ public override bool UpdateSubTreeMasking()
{
- bool result = base.UpdateSubTreeMasking(source, maskingBounds);
+ bool result = base.UpdateSubTreeMasking();
// We can accurately compute intersections - the scheduled reset is no longer required.
isIntersectingResetDelegate?.Cancel();
@@ -230,7 +229,7 @@ public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBou
// The first condition is an intersection against the hierarchy, including any parents that may be masking this wrapper.
// It is the same calculation as Drawable.IsMaskedAway, however IsMaskedAway is optimised out for some CompositeDrawables (which this wrapper is).
// The second condition is an exact intersection against the optimising container, which further optimises rotated AABBs where the wrapper content is not visible.
- IsIntersecting = maskingBounds.IntersectsWith(ScreenSpaceDrawQuad.AABBFloat)
+ IsIntersecting = ComputeMaskingBounds().IntersectsWith(ScreenSpaceDrawQuad.AABBFloat)
&& OptimisingContainer?.ScreenSpaceDrawQuad.Intersects(ScreenSpaceDrawQuad) != false;
isIntersectingCache.Validate();
diff --git a/osu.Framework/Graphics/Drawable.cs b/osu.Framework/Graphics/Drawable.cs
index 93d701a4eb..eeecf7f27c 100644
--- a/osu.Framework/Graphics/Drawable.cs
+++ b/osu.Framework/Graphics/Drawable.cs
@@ -495,22 +495,34 @@ public virtual bool UpdateSubTree()
return true;
}
+ ///
+ /// Computes the masking bounds of this .
+ ///
+ /// The that defines the masking bounds.
+ public virtual RectangleF ComputeMaskingBounds()
+ {
+ if (HasProxy)
+ return proxy.ComputeMaskingBounds();
+
+ if (parent == null)
+ return ScreenSpaceDrawQuad.AABBFloat;
+
+ return parent.ChildMaskingBounds;
+ }
+
private RectangleF? lastMaskingBounds;
///
/// Updates all masking calculations for this .
/// This occurs post- to ensure that all updates have taken place.
///
- /// The parent that triggered this update on this .
- /// The that defines the masking bounds.
/// Whether masking calculations have taken place.
- public virtual bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds)
+ public virtual bool UpdateSubTreeMasking()
{
if (!IsPresent)
return false;
- if (HasProxy && source != proxy)
- return false;
+ var maskingBounds = ComputeMaskingBounds();
if (!maskingBacking.IsValid || lastMaskingBounds != maskingBounds)
{
diff --git a/osu.Framework/Graphics/Drawable_ProxyDrawable.cs b/osu.Framework/Graphics/Drawable_ProxyDrawable.cs
index 10daa3ebc9..bb8f76aada 100644
--- a/osu.Framework/Graphics/Drawable_ProxyDrawable.cs
+++ b/osu.Framework/Graphics/Drawable_ProxyDrawable.cs
@@ -3,7 +3,6 @@
#nullable disable
-using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Rendering;
namespace osu.Framework.Graphics
@@ -60,13 +59,7 @@ internal override DrawNode GenerateDrawNodeSubtree(ulong frame, int treeIndex, b
// We do not want to receive updates. That is the business of the original drawable.
public override bool IsPresent => false;
- public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds)
- {
- if (Original.IsDisposed)
- return false;
-
- return Original.UpdateSubTreeMasking(this, maskingBounds);
- }
+ public override bool UpdateSubTreeMasking() => true;
private class ProxyDrawNode : DrawNode
{
diff --git a/osu.Framework/Platform/GameHost.cs b/osu.Framework/Platform/GameHost.cs
index badd1f40ce..fd87b73076 100644
--- a/osu.Framework/Platform/GameHost.cs
+++ b/osu.Framework/Platform/GameHost.cs
@@ -477,7 +477,7 @@ protected virtual void UpdateFrame()
TypePerformanceMonitor.NewFrame();
Root.UpdateSubTree();
- Root.UpdateSubTreeMasking(Root, Root.ScreenSpaceDrawQuad.AABBFloat);
+ Root.UpdateSubTreeMasking();
using (var buffer = DrawRoots.GetForWrite())
buffer.Object = Root.GenerateDrawNodeSubtree(frameCount, buffer.Index, false);