Skip to content

Commit 384e062

Browse files
authored
Merge pull request #6646 from EVAST9919/progress-triangulation
Implement simple triangulation algorithm for `CircularProgress`
2 parents 249909a + c8bb8d2 commit 384e062

File tree

2 files changed

+99
-1
lines changed

2 files changed

+99
-1
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
2+
// See the LICENCE file in the repository root for full licence text.
3+
4+
using osu.Framework.Graphics.UserInterface;
5+
using osu.Framework.Graphics;
6+
using osuTK;
7+
8+
namespace osu.Framework.Tests.Visual.Performance
9+
{
10+
public partial class TestSceneCircularProgressRingsPerformance : FrameworkTestScene
11+
{
12+
public TestSceneCircularProgressRingsPerformance()
13+
{
14+
for (int i = 0; i < 100; i++)
15+
{
16+
Add(new CircularProgress
17+
{
18+
Anchor = Anchor.Centre,
19+
Origin = Anchor.Centre,
20+
Size = new Vector2(10 * i),
21+
InnerRadius = 0.1f / (i + 1),
22+
Progress = 1
23+
});
24+
}
25+
}
26+
}
27+
}

osu.Framework/Graphics/UserInterface/CircularProgress.cs

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66
using System;
77
using System.Runtime.InteropServices;
88
using osu.Framework.Allocation;
9+
using osu.Framework.Graphics.Primitives;
910
using osu.Framework.Graphics.Rendering;
11+
using osu.Framework.Graphics.Rendering.Vertices;
1012
using osu.Framework.Graphics.Shaders;
1113
using osu.Framework.Graphics.Shaders.Types;
1214
using osu.Framework.Graphics.Sprites;
1315
using osu.Framework.Graphics.Transforms;
16+
using osuTK;
1417

1518
namespace osu.Framework.Graphics.UserInterface
1619
{
@@ -101,26 +104,93 @@ public CircularProgressDrawNode(CircularProgress source)
101104
protected float TexelSize { get; private set; }
102105
protected bool RoundedCaps { get; private set; }
103106

107+
// Even though it's possible to adjust segment count based on thickness and/or progress value
108+
// we are not doing so to avoid creating new vertex batches on said changes.
109+
// Segment count has been chosen to increase fps and decrease gpu usage as much as possible
110+
// by using results from TestSceneCircularProgressRingsPerformance.
111+
private const int segment_count = 20;
112+
private const int vertex_count = (segment_count + 1) * 2;
113+
private static readonly float angle_delta = float.DegreesToRadians(360f / (segment_count * 2));
114+
115+
private Vector2 drawSize;
116+
private RectangleF tRect;
117+
104118
public override void ApplyState()
105119
{
106120
base.ApplyState();
107121

108122
InnerRadius = Source.innerRadius;
109123
Progress = Math.Abs((float)Source.progress);
110124
RoundedCaps = Source.roundedCaps;
125+
drawSize = Source.DrawSize;
111126

112127
// smoothstep looks too sharp with 1px, let's give it a bit more
113128
TexelSize = 1.5f / ScreenSpaceDrawQuad.Size.X;
129+
tRect = Texture.GetTextureRect();
114130
}
115131

116132
private IUniformBuffer<CircularProgressParameters> parametersBuffer;
133+
private IVertexBatch<TexturedVertex2D> vertexBatch;
117134

118135
protected override void Blit(IRenderer renderer)
119136
{
120137
if (InnerRadius == 0 || (!RoundedCaps && Progress == 0))
121138
return;
122139

123-
base.Blit(renderer);
140+
// Don't triangulate in case when circle is almost filled
141+
if (InnerRadius > 0.95f)
142+
{
143+
base.Blit(renderer);
144+
return;
145+
}
146+
147+
drawTriangulatedShape(renderer);
148+
}
149+
150+
private void drawTriangulatedShape(IRenderer renderer)
151+
{
152+
if (!renderer.BindTexture(Texture))
153+
return;
154+
155+
vertexBatch ??= renderer.CreateLinearBatch<TexturedVertex2D>(vertex_count, 1, PrimitiveTopology.TriangleStrip);
156+
157+
Vector2 outer = new Vector2(0.5f, 0.5f - 0.5f / MathF.Cos(angle_delta));
158+
Vector2 inner = new Vector2(0.5f, Math.Min(Math.Max(InnerRadius * 0.5f + TexelSize, TexelSize * 2), 0.5f));
159+
Vector2 origin = new Vector2(0.5f);
160+
161+
float angle = 0;
162+
bool isInnerVertex = true;
163+
164+
renderer.PushLocalMatrix(DrawInfo.Matrix);
165+
166+
for (int i = 0; i < vertex_count; i++)
167+
{
168+
Vector2 relativePos = rotateAround(isInnerVertex ? inner : outer, origin, angle);
169+
170+
vertexBatch?.Add(new TexturedVertex2D(renderer)
171+
{
172+
Position = relativePos * drawSize,
173+
Colour = DrawColourInfo.Colour.Interpolate(relativePos).SRGB,
174+
TextureRect = new Vector4(tRect.Left, tRect.Top, tRect.Right, tRect.Bottom),
175+
TexturePosition = new Vector2(tRect.Left + tRect.Width * relativePos.X, tRect.Top + tRect.Height * relativePos.Y)
176+
});
177+
178+
angle += angle_delta;
179+
isInnerVertex = !isInnerVertex;
180+
}
181+
182+
renderer.PopLocalMatrix();
183+
}
184+
185+
private static Vector2 rotateAround(Vector2 input, Vector2 origin, float angle)
186+
{
187+
float sin = MathF.Sin(angle);
188+
float cos = MathF.Cos(angle);
189+
190+
float xTranslated = input.X - origin.X;
191+
float yTranslated = input.Y - origin.Y;
192+
193+
return new Vector2(xTranslated * cos - yTranslated * sin, xTranslated * sin + yTranslated * cos) + origin;
124194
}
125195

126196
protected override void BindUniformResources(IShader shader, IRenderer renderer)
@@ -145,6 +215,7 @@ protected override void Dispose(bool isDisposing)
145215
{
146216
base.Dispose(isDisposing);
147217
parametersBuffer?.Dispose();
218+
vertexBatch?.Dispose();
148219
}
149220

150221
[StructLayout(LayoutKind.Sequential, Pack = 1)]

0 commit comments

Comments
 (0)