Skip to content

Commit

Permalink
Merge pull request #6266 from EVAST9919/masking-bounds
Browse files Browse the repository at this point in the history
Refactor child masking bounds handling
  • Loading branch information
smoogipoo authored May 9, 2024
2 parents 004fae4 + 44a7633 commit bf44358
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 31 deletions.
37 changes: 37 additions & 0 deletions osu.Framework.Tests/Layout/TestSceneContainerLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -498,6 +499,42 @@ public void TestNonAutoSizingParentDoesNotInvalidateSizeDependenciesFromChild()
AddAssert("still valid", () => isValid);
}

/// <summary>
/// Tests that changing Masking property will invalidate child masking bounds.
/// </summary>
[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;
Expand Down
6 changes: 3 additions & 3 deletions osu.Framework/Graphics/Containers/BufferedContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
26 changes: 16 additions & 10 deletions osu.Framework/Graphics/Containers/CompositeDrawable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ protected CompositeDrawable()
childrenSizeDependencies.Validate();

AddLayout(childrenSizeDependencies);
AddLayout(childMaskingBoundsBacking);
}

/// <summary>
Expand Down Expand Up @@ -958,12 +959,10 @@ private void updateChild(Drawable c)
/// Updates all masking calculations for this <see cref="CompositeDrawable"/> and its <see cref="AliveInternalChildren"/>.
/// This occurs post-<see cref="UpdateSubTree"/> to ensure that all <see cref="Drawable"/> updates have taken place.
/// </summary>
/// <param name="source">The parent that triggered this update on this <see cref="Drawable"/>.</param>
/// <param name="maskingBounds">The <see cref="RectangleF"/> that defines the masking bounds.</param>
/// <returns>Whether masking calculations have taken place.</returns>
public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds)
public override bool UpdateSubTreeMasking()
{
if (!base.UpdateSubTreeMasking(source, maskingBounds))
if (!base.UpdateSubTreeMasking())
return false;

if (IsMaskedAway)
Expand All @@ -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;
Expand All @@ -996,9 +993,15 @@ protected override bool ComputeIsMaskedAway(RectangleF maskingBounds)
/// <summary>
/// Computes the <see cref="RectangleF"/> to be used as the masking bounds for all <see cref="AliveInternalChildren"/>.
/// </summary>
/// <param name="maskingBounds">The <see cref="RectangleF"/> that defines the masking bounds for this <see cref="CompositeDrawable"/>.</param>
/// <returns>The <see cref="RectangleF"/> to be used as the masking bounds for <see cref="AliveInternalChildren"/>.</returns>
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<RectangleF> childMaskingBoundsBacking = new LayoutValue<RectangleF>(Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence);

/// <summary>
/// The <see cref="RectangleF"/> to be used as the masking bounds for all <see cref="AliveInternalChildren"/>.
/// </summary>
public RectangleF ChildMaskingBounds => childMaskingBoundsBacking.IsValid ? childMaskingBoundsBacking : childMaskingBoundsBacking.Value = ComputeChildMaskingBounds();

/// <summary>
/// Invoked after <see cref="UpdateChildrenLife"/> and <see cref="Drawable.IsPresent"/> state checks have taken place,
Expand Down Expand Up @@ -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);
}
}

Expand Down
7 changes: 3 additions & 4 deletions osu.Framework/Graphics/Containers/DelayedLoadWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand Down
22 changes: 17 additions & 5 deletions osu.Framework/Graphics/Drawable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -495,22 +495,34 @@ public virtual bool UpdateSubTree()
return true;
}

/// <summary>
/// Computes the masking bounds of this <see cref="Drawable"/>.
/// </summary>
/// <returns>The <see cref="RectangleF"/> that defines the masking bounds.</returns>
public virtual RectangleF ComputeMaskingBounds()
{
if (HasProxy)
return proxy.ComputeMaskingBounds();

if (parent == null)
return ScreenSpaceDrawQuad.AABBFloat;

return parent.ChildMaskingBounds;
}

private RectangleF? lastMaskingBounds;

/// <summary>
/// Updates all masking calculations for this <see cref="Drawable"/>.
/// This occurs post-<see cref="UpdateSubTree"/> to ensure that all <see cref="Drawable"/> updates have taken place.
/// </summary>
/// <param name="source">The parent that triggered this update on this <see cref="Drawable"/>.</param>
/// <param name="maskingBounds">The <see cref="RectangleF"/> that defines the masking bounds.</param>
/// <returns>Whether masking calculations have taken place.</returns>
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)
{
Expand Down
9 changes: 1 addition & 8 deletions osu.Framework/Graphics/Drawable_ProxyDrawable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

#nullable disable

using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Rendering;

namespace osu.Framework.Graphics
Expand Down Expand Up @@ -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
{
Expand Down
2 changes: 1 addition & 1 deletion osu.Framework/Platform/GameHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit bf44358

Please sign in to comment.