Skip to content

Commit

Permalink
Merge pull request #5883 from peppy/sprite-text-draw-node-perf
Browse files Browse the repository at this point in the history
Reduce complexity overhead (and avoid one copy) when obtaining character SSDQs in `SpriteTextDrawNode`
  • Loading branch information
bdach authored Jul 4, 2023
2 parents 13644b5 + 8eb1b55 commit 050857d
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 57 deletions.
96 changes: 96 additions & 0 deletions osu.Framework.Benchmarks/BenchmarkSpriteText.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Linq;
using BenchmarkDotNet.Attributes;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Utils;
using osuTK;

namespace osu.Framework.Benchmarks
{
public partial class BenchmarkSpriteText : GameBenchmark
{
private TestGame game = null!;

[Test]
[Benchmark]
public void TestStaticText()
{
game.Mode = 0;
RunSingleFrame();
}

[Test]
[Benchmark]
public void TestMovingText()
{
game.Mode = 1;
RunSingleFrame();
}

[Test]
[Benchmark]
public void TestChangingText()
{
game.Mode = 2;
RunSingleFrame();
}

protected override Game CreateGame() => game = new TestGame();

private partial class TestGame : Game
{
public int Mode;

private readonly string text1 = Guid.NewGuid().ToString();
private readonly string text2 = Guid.NewGuid().ToString();

private long frame;

[BackgroundDependencyLoader]
private void load()
{
for (int i = 0; i < 1000; i++)
{
Add(new SpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "i am some relatively long text",
});
}
}

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

switch (Mode)
{
case 0:
break;

case 1:
var pos = new Vector2(RNG.NextSingle(100));

foreach (var text in Children.OfType<SpriteText>())
text.Position = pos;

break;

case 2:
foreach (var text in Children.OfType<SpriteText>())
text.Text = frame % 2 == 0 ? text1 : text2;
break;
}

frame++;
}
}
}
}
54 changes: 0 additions & 54 deletions osu.Framework/Graphics/Sprites/SpriteText.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ namespace osu.Framework.Graphics.Sprites
/// </summary>
public partial class SpriteText : Drawable, IHasLineBaseHeight, ITexturedShaderDrawable, IHasText, IHasFilterTerms, IFillFlowContainer, IHasCurrentValue<string>
{
private const float default_text_size = 20;

/// <remarks>
/// <c>U+00A0</c> is the Unicode NON-BREAKING SPACE character (distinct from the standard ASCII space).
/// <c>U+202F</c> is the Unicode NARROW NO-BREAK SPACE character.
Expand Down Expand Up @@ -56,8 +54,6 @@ public SpriteText()
});

AddLayout(charactersCache);
AddLayout(parentScreenSpaceCache);
AddLayout(localScreenSpaceCache);
AddLayout(shadowOffsetCache);
AddLayout(textBuilderCache);
}
Expand Down Expand Up @@ -501,53 +497,6 @@ private void computeCharacters()
}
}

private readonly LayoutValue parentScreenSpaceCache = new LayoutValue(Invalidation.DrawSize | Invalidation.Presence | Invalidation.DrawInfo, InvalidationSource.Parent);
private readonly LayoutValue localScreenSpaceCache = new LayoutValue(Invalidation.MiscGeometry, InvalidationSource.Self);

private readonly List<ScreenSpaceCharacterPart> screenSpaceCharactersBacking = new List<ScreenSpaceCharacterPart>();

/// <summary>
/// The characters in screen space. These are ready to be drawn.
/// </summary>
private List<ScreenSpaceCharacterPart> screenSpaceCharacters
{
get
{
computeScreenSpaceCharacters();
return screenSpaceCharactersBacking;
}
}

private void computeScreenSpaceCharacters()
{
if (!parentScreenSpaceCache.IsValid)
{
localScreenSpaceCache.Invalidate();
parentScreenSpaceCache.Validate();
}

if (localScreenSpaceCache.IsValid)
return;

screenSpaceCharactersBacking.Clear();

Vector2 inflationAmount = DrawInfo.MatrixInverse.ExtractScale().Xy;

foreach (var character in characters)
{
screenSpaceCharactersBacking.Add(new ScreenSpaceCharacterPart
{
DrawQuad = ToScreenSpace(character.DrawRectangle.Inflate(inflationAmount)),
InflationPercentage = new Vector2(
character.DrawRectangle.Size.X == 0 ? 0 : inflationAmount.X / character.DrawRectangle.Size.X,
character.DrawRectangle.Size.Y == 0 ? 0 : inflationAmount.Y / character.DrawRectangle.Size.Y),
Texture = character.Texture
});
}

localScreenSpaceCache.Validate();
}

private readonly LayoutValue<Vector2> shadowOffsetCache = new LayoutValue<Vector2>(Invalidation.DrawInfo, InvalidationSource.Parent);

private Vector2 premultipliedShadowOffset =>
Expand All @@ -565,9 +514,6 @@ private void invalidate(bool characters = false, bool textBuilder = false)
if (textBuilder)
InvalidateTextBuilder();

parentScreenSpaceCache.Invalidate();
localScreenSpaceCache.Invalidate();

Invalidate(Invalidation.RequiredParentSizeToFit);
}

Expand Down
38 changes: 35 additions & 3 deletions osu.Framework/Graphics/Sprites/SpriteText_DrawNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Rendering;
Expand All @@ -22,7 +23,7 @@ internal class SpriteTextDrawNode : TexturedShaderDrawNode
private ColourInfo shadowColour;
private Vector2 shadowOffset;

private readonly List<ScreenSpaceCharacterPart> parts = new List<ScreenSpaceCharacterPart>();
private List<ScreenSpaceCharacterPart>? parts;

public SpriteTextDrawNode(SpriteText source)
: base(source)
Expand All @@ -33,8 +34,7 @@ public override void ApplyState()
{
base.ApplyState();

parts.Clear();
parts.AddRange(Source.screenSpaceCharacters);
updateScreenSpaceCharacters();
shadow = Source.Shadow;

if (shadow)
Expand All @@ -46,6 +46,8 @@ public override void ApplyState()

public override void Draw(IRenderer renderer)
{
Debug.Assert(parts != null);

base.Draw(renderer);

BindTextureShader(renderer);
Expand Down Expand Up @@ -78,6 +80,36 @@ public override void Draw(IRenderer renderer)

UnbindTextureShader(renderer);
}

/// <summary>
/// The characters in screen space. These are ready to be drawn.
/// </summary>
private void updateScreenSpaceCharacters()
{
int partCount = Source.characters.Count;

if (parts == null)
parts = new List<ScreenSpaceCharacterPart>(partCount);
else
{
parts.Clear();
parts.EnsureCapacity(partCount);
}

Vector2 inflationAmount = DrawInfo.MatrixInverse.ExtractScale().Xy;

foreach (var character in Source.characters)
{
parts.Add(new ScreenSpaceCharacterPart
{
DrawQuad = Source.ToScreenSpace(character.DrawRectangle.Inflate(inflationAmount)),
InflationPercentage = new Vector2(
character.DrawRectangle.Size.X == 0 ? 0 : inflationAmount.X / character.DrawRectangle.Size.X,
character.DrawRectangle.Size.Y == 0 ? 0 : inflationAmount.Y / character.DrawRectangle.Size.Y),
Texture = character.Texture
});
}
}
}

/// <summary>
Expand Down

0 comments on commit 050857d

Please sign in to comment.