Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d1e25e7
Implement Path BBH
EVAST9919 Jul 5, 2025
a80cf02
Implement native start/end progress
EVAST9919 Jul 9, 2025
fe29508
Don't copy segments on range change
EVAST9919 Jul 10, 2025
d179b23
Merge branch 'master' into path-bbh
EVAST9919 Jul 10, 2025
a569eaa
Expose PathBBH.CurvePositionAt
EVAST9919 Jul 11, 2025
2ad3811
Trim array allocated by the tree
EVAST9919 Jul 12, 2025
ec87d99
Avoid array copy if new segment count is smaller
EVAST9919 Jul 12, 2025
eaaebee
Remove not needed checks
EVAST9919 Jul 12, 2025
39865b4
Make node bounds non-nullable
EVAST9919 Jul 13, 2025
64a40b4
Cleanup pass
EVAST9919 Jul 13, 2025
739adaf
Use ArrayPool for tree array
EVAST9919 Jul 13, 2025
e23c7a1
Add benchmarks for tree creation and progress update
EVAST9919 Jul 14, 2025
3fc26e4
Add xmldoc
EVAST9919 Jul 14, 2025
61b60fe
Minor cleanup pass
EVAST9919 Jul 15, 2025
17ca24f
Use BitOperations.RoundUpToPowerOf2 instead of custom implementation
EVAST9919 Jul 15, 2025
aadfe8a
Cleanup bbh benchmarks
EVAST9919 Jul 18, 2025
18200b3
Add BenchmarkPathSegmentCreation
EVAST9919 Jul 18, 2025
c88baef
Make PathBBH IDisposable
EVAST9919 Jul 18, 2025
1683f52
Add BenchmarkPathContains
EVAST9919 Jul 20, 2025
2ee5bdf
Remove duplicate benchmark
EVAST9919 Jul 20, 2025
e952218
Rework bounding box collection
EVAST9919 Jul 20, 2025
06d895d
Implement FastMin and FastMax in hot paths
EVAST9919 Aug 2, 2025
f18fa94
Merge branch 'master' into path-bbh
EVAST9919 Aug 30, 2025
dbba05e
Remove start/end progress logic
EVAST9919 Aug 30, 2025
5893541
Add more xmldoc
EVAST9919 Sep 1, 2025
f60f06f
Merge branch 'master' into path-bbh-no-progress
EVAST9919 Sep 13, 2025
5bfff1f
Fix incorrect segment point positions
EVAST9919 Sep 13, 2025
d744496
Adjust naming
EVAST9919 Sep 14, 2025
8955393
Improve nodes array renting
EVAST9919 Sep 16, 2025
34bfd5d
Merge branch 'master' into path-bbh-no-progress
EVAST9919 Oct 11, 2025
973d464
Avoid re-adding same segments in the draw node
EVAST9919 Oct 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions osu-framework.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AABB/@EntryIndexedValue">AABB</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=API/@EntryIndexedValue">API</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ARGB/@EntryIndexedValue">ARGB</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BBH/@EntryIndexedValue">BBH</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BPM/@EntryIndexedValue">BPM</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CG/@EntryIndexedValue">CG</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FBO/@EntryIndexedValue">FBO</s:String>
Expand Down Expand Up @@ -991,6 +992,7 @@ private void load()
<s:Boolean x:Key="/Default/UserDictionary/Words/=beatmaps/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=beatmap_0027s/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=bindable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=branchless/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Catmull/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Drawables/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=gameplay/@EntryIndexedValue">True</s:Boolean>
Expand Down
85 changes: 85 additions & 0 deletions osu.Framework.Benchmarks/BenchmarkPathReceivePositionalInputAt.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using osu.Framework.Graphics.Lines;
using osuTK;

namespace osu.Framework.Benchmarks
{
public class BenchmarkPathReceivePositionalInputAt : BenchmarkTest
{
// We deliberately set path radius to 0 to introduce worst-case scenario in which with any position given we won't land on a path.
private readonly Path path100 = new Path { PathRadius = 0f };
private readonly Path path1K = new Path { PathRadius = 0f };
private readonly Path path10K = new Path { PathRadius = 0f };
private readonly Path path100K = new Path { PathRadius = 0f };
private readonly Path path1M = new Path { PathRadius = 0f };

private readonly Random random = new Random(1);

public override void SetUp()
{
base.SetUp();

List<Vector2> vertices100 = new List<Vector2>(100);
List<Vector2> vertices1K = new List<Vector2>(1_000);
List<Vector2> vertices10K = new List<Vector2>(10_000);
List<Vector2> vertices100K = new List<Vector2>(100_000);
List<Vector2> vertices1M = new List<Vector2>(1_000_000);

for (int i = 0; i < vertices100.Capacity; i++)
vertices100.Add(new Vector2((float)i / vertices100.Capacity * 100, random.NextSingle() * 100));

for (int i = 0; i < vertices1K.Capacity; i++)
vertices1K.Add(new Vector2((float)i / vertices1K.Capacity * 100, random.NextSingle() * 100));

for (int i = 0; i < vertices10K.Capacity; i++)
vertices10K.Add(new Vector2((float)i / vertices10K.Capacity * 100, random.NextSingle() * 100));

for (int i = 0; i < vertices100K.Capacity; i++)
vertices100K.Add(new Vector2((float)i / vertices100K.Capacity * 100, random.NextSingle() * 100));

for (int i = 0; i < vertices1M.Capacity; i++)
vertices1M.Add(new Vector2((float)i / vertices1M.Capacity * 100, random.NextSingle() * 100));

path100.Vertices = vertices100;
path1K.Vertices = vertices1K;
path10K.Vertices = vertices10K;
path100K.Vertices = vertices100K;
path1M.Vertices = vertices1M;
}

[Benchmark]
public void Contains100()
{
path100.ReceivePositionalInputAt(new Vector2(random.NextSingle() * 100, random.NextSingle() * 100));
}

[Benchmark]
public void Contains1K()
{
path1K.ReceivePositionalInputAt(new Vector2(random.NextSingle() * 100, random.NextSingle() * 100));
}

[Benchmark]
public void Contains10K()
{
path10K.ReceivePositionalInputAt(new Vector2(random.NextSingle() * 100, random.NextSingle() * 100));
}

[Benchmark]
public void Contains100K()
{
path100K.ReceivePositionalInputAt(new Vector2(random.NextSingle() * 100, random.NextSingle() * 100));
}

[Benchmark]
public void Contains1M()
{
path1M.ReceivePositionalInputAt(new Vector2(random.NextSingle() * 100, random.NextSingle() * 100));
}
}
}
87 changes: 87 additions & 0 deletions osu.Framework.Benchmarks/BenchmarkPathSegmentCreation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;
using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Primitives;
using osuTK;

namespace osu.Framework.Benchmarks
{
public partial class BenchmarkPathSegmentCreation : BenchmarkTest
{
private readonly List<Vector2> vertices100 = new List<Vector2>(100);
private readonly List<Vector2> vertices1K = new List<Vector2>(1_000);
private readonly List<Vector2> vertices10K = new List<Vector2>(10_000);
private readonly List<Vector2> vertices100K = new List<Vector2>(100_000);
private readonly List<Vector2> vertices1M = new List<Vector2>(1_000_000);

private readonly BenchPath path = new BenchPath();
private readonly Consumer consumer = new Consumer();

public override void SetUp()
{
base.SetUp();

var rng = new Random(1);

for (int i = 0; i < vertices100.Capacity; i++)
vertices100.Add(new Vector2(rng.NextSingle(), rng.NextSingle()));

for (int i = 0; i < vertices1K.Capacity; i++)
vertices1K.Add(new Vector2(rng.NextSingle(), rng.NextSingle()));

for (int i = 0; i < vertices10K.Capacity; i++)
vertices10K.Add(new Vector2(rng.NextSingle(), rng.NextSingle()));

for (int i = 0; i < vertices100K.Capacity; i++)
vertices100K.Add(new Vector2(rng.NextSingle(), rng.NextSingle()));

for (int i = 0; i < vertices1M.Capacity; i++)
vertices1M.Add(new Vector2(rng.NextSingle(), rng.NextSingle()));
}

[Benchmark]
public void Compute100Segments()
{
path.Vertices = vertices100;
consumer.Consume(path.Segments);
}

[Benchmark]
public void Compute1KSegments()
{
path.Vertices = vertices1K;
consumer.Consume(path.Segments);
}

[Benchmark]
public void Compute10KSegments()
{
path.Vertices = vertices10K;
consumer.Consume(path.Segments);
}

[Benchmark]
public void Compute100KSegments()
{
path.Vertices = vertices100K;
consumer.Consume(path.Segments);
}

[Benchmark]
public void Compute1MSegments()
{
path.Vertices = vertices1M;
consumer.Consume(path.Segments);
}

private partial class BenchPath : Path
{
public IEnumerable<Line> Segments => BBH.Segments;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osuTK.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osuTK.Input;
using osu.Framework.Utils;
Expand All @@ -18,9 +24,10 @@ namespace osu.Framework.Tests.Visual.Drawables
public partial class TestSceneInteractivePathDrawing : FrameworkTestScene
{
private readonly Path rawDrawnPath;
private readonly Path approximatedDrawnPath;
private readonly TestPath approximatedDrawnPath;
private readonly Path controlPointPath;
private readonly Container controlPointViz;
private readonly BoundingBoxVisualizer bbViz;

private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder();

Expand All @@ -36,7 +43,7 @@ public TestSceneInteractivePathDrawing()
Colour = Color4.DeepPink,
PathRadius = 5,
},
approximatedDrawnPath = new Path
approximatedDrawnPath = new TestPath
{
Colour = Color4.Blue,
PathRadius = 3,
Expand All @@ -52,31 +59,42 @@ public TestSceneInteractivePathDrawing()
RelativeSizeAxes = Axes.Both,
Alpha = 0.5f,
},
bbViz = new BoundingBoxVisualizer
{
RelativeSizeAxes = Axes.Both,
}
}
};

updateViz();
OnUpdate += _ => updateViz();

AddStep("Reset path", () =>
{
bSplineBuilder.Clear();
updateViz();
});

AddSliderStep($"{nameof(bSplineBuilder.Degree)}", 1, 4, 3, v =>
{
bSplineBuilder.Degree = v;
updateViz();
});
AddSliderStep($"{nameof(bSplineBuilder.Tolerance)}", 0f, 3f, 2f, v =>
{
bSplineBuilder.Tolerance = v;
updateViz();
});
AddSliderStep($"{nameof(bSplineBuilder.CornerThreshold)}", 0f, 1f, 0.4f, v =>
{
bSplineBuilder.CornerThreshold = v;
updateViz();
});
}

[BackgroundDependencyLoader]
private void load(IRenderer renderer)
{
bbViz.Texture = renderer.WhitePixel;
}

private void updateControlPointsViz()
{
controlPointPath.Vertices = bSplineBuilder.ControlPoints.SelectMany(o => o).ToArray();
Expand Down Expand Up @@ -117,9 +135,18 @@ private void updateViz()
updateControlPointsViz();
}

protected override void Update()
{
base.Update();

approximatedDrawnPath.CollectBoundingBoxes(bbViz.Boxes);
bbViz.Invalidate(Invalidation.DrawNode);
}

protected override void OnDrag(DragEvent e)
{
bSplineBuilder.AddLinearPoint(rawDrawnPath.ToLocalSpace(ToScreenSpace(e.MousePosition)));
updateViz();
}

protected override void OnDragEnd(DragEndEvent e)
Expand All @@ -129,5 +156,89 @@ protected override void OnDragEnd(DragEndEvent e)

base.OnDragEnd(e);
}

private partial class TestPath : Path
{
public void CollectBoundingBoxes(List<RectangleF> list) => BBH.CollectBoundingBoxes(list);
}

private partial class BoundingBoxVisualizer : Sprite
{
public readonly List<RectangleF> Boxes = [];

public BoundingBoxVisualizer()
{
RelativeSizeAxes = Axes.Both;
}

protected override DrawNode CreateDrawNode() => new BoundingBoxDrawNode(this);

private class BoundingBoxDrawNode : SpriteDrawNode
{
public new BoundingBoxVisualizer Source => (BoundingBoxVisualizer)base.Source;

public BoundingBoxDrawNode(BoundingBoxVisualizer source)
: base(source)
{
}

private readonly List<RectangleF> boxes = new List<RectangleF>();

public override void ApplyState()
{
base.ApplyState();

boxes.Clear();
boxes.AddRange(Source.Boxes);
}

protected override void Blit(IRenderer renderer)
{
ColourInfo colourInfo = DrawColourInfo.Colour;
colourInfo.ApplyChild(Color4.Red);

foreach (var box in boxes)
{
var drawQuad = new Quad(
Vector2Extensions.Transform(box.TopLeft, DrawInfo.Matrix),
Vector2Extensions.Transform(box.TopRight, DrawInfo.Matrix),
Vector2Extensions.Transform(box.TopLeft + new Vector2(0, 1), DrawInfo.Matrix),
Vector2Extensions.Transform(box.TopRight + new Vector2(0, 1), DrawInfo.Matrix)
);

renderer.DrawQuad(Texture, drawQuad, colourInfo);

drawQuad = new Quad(
Vector2Extensions.Transform(box.BottomLeft - new Vector2(0, 1), DrawInfo.Matrix),
Vector2Extensions.Transform(box.BottomRight - new Vector2(0, 1), DrawInfo.Matrix),
Vector2Extensions.Transform(box.BottomLeft, DrawInfo.Matrix),
Vector2Extensions.Transform(box.BottomRight, DrawInfo.Matrix)
);

renderer.DrawQuad(Texture, drawQuad, colourInfo);

drawQuad = new Quad(
Vector2Extensions.Transform(box.TopLeft, DrawInfo.Matrix),
Vector2Extensions.Transform(box.TopLeft + new Vector2(1, 0), DrawInfo.Matrix),
Vector2Extensions.Transform(box.BottomLeft, DrawInfo.Matrix),
Vector2Extensions.Transform(box.BottomLeft + new Vector2(1, 0), DrawInfo.Matrix)
);

renderer.DrawQuad(Texture, drawQuad, colourInfo);

drawQuad = new Quad(
Vector2Extensions.Transform(box.TopRight - new Vector2(1, 0), DrawInfo.Matrix),
Vector2Extensions.Transform(box.TopRight, DrawInfo.Matrix),
Vector2Extensions.Transform(box.BottomRight - new Vector2(1, 0), DrawInfo.Matrix),
Vector2Extensions.Transform(box.BottomRight, DrawInfo.Matrix)
);

renderer.DrawQuad(Texture, drawQuad, colourInfo);
}
}

protected internal override bool CanDrawOpaqueInterior => false;
}
}
}
}
Loading
Loading