From 8362456148faa1239f4d49bfc1567af3f9c1d3c8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 18 Jul 2025 12:25:54 +0300 Subject: [PATCH 1/6] Add antialiasing to triangles in MarkerVisualisation --- .../Timelines/Summary/Parts/MarkerPart.cs | 110 +++++++++++++++--- 1 file changed, 97 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index 21b3b38388..358e642d9a 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -5,9 +5,14 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Rendering; +using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Threading; +using osu.Game.Graphics.Backgrounds; using osu.Game.Overlays; using osuTK; @@ -78,21 +83,9 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.Y; AutoSizeAxes = Axes.X; + Masking = true; InternalChildren = new Drawable[] { - new Triangle - { - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, - Scale = new Vector2(1, -1), - Size = new Vector2(10, 5), - }, - new Triangle - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Size = new Vector2(10, 5), - }, new Box { Anchor = Anchor.Centre, @@ -100,12 +93,103 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts RelativeSizeAxes = Axes.Y, Width = 1.4f, EdgeSmoothness = new Vector2(1, 0) + }, + new VerticalTriangles + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Width = 10 } }; } [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) => Colour = colours.Highlight1; + + private partial class VerticalTriangles : Sprite + { + [BackgroundDependencyLoader] + private void load(ShaderManager shaders, IRenderer renderer) + { + TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "TriangleBorder"); + Texture = renderer.WhitePixel; + } + + protected override DrawNode CreateDrawNode() => new VerticalTrianglesDrawNode(this); + + private class VerticalTrianglesDrawNode : SpriteDrawNode + { + private const float aa = 1.5f; // across how many pixels antialiasing is being applied + + public VerticalTrianglesDrawNode(VerticalTriangles source) + : base(source) + { + } + + private float texelSize; + private float triangleScreenSpaceHeight; + + public override void ApplyState() + { + base.ApplyState(); + + triangleScreenSpaceHeight = ScreenSpaceDrawQuad.Width * 0.5f; + texelSize = aa / Math.Max(ScreenSpaceDrawQuad.Width, 1); + } + + protected override void Blit(IRenderer renderer) + { + if (triangleScreenSpaceHeight == 0) + return; + + // TriangleBorder shader makes a smooth triangle for all its sides, which we want to avoid at the top and bottom. + // To do that we are expanding triangles outside the drawable by the aa value and applying masking at the top level. + Quad topTriangle = new Quad + ( + ScreenSpaceDrawQuad.TopLeft + new Vector2(-aa, triangleScreenSpaceHeight), + ScreenSpaceDrawQuad.TopRight + new Vector2(aa, triangleScreenSpaceHeight), + ScreenSpaceDrawQuad.TopLeft - new Vector2(aa), + ScreenSpaceDrawQuad.TopRight + new Vector2(aa, -aa) + ); + + Quad bottomTriangle = new Quad + ( + ScreenSpaceDrawQuad.BottomLeft - new Vector2(aa, triangleScreenSpaceHeight), + ScreenSpaceDrawQuad.BottomRight - new Vector2(-aa, triangleScreenSpaceHeight), + ScreenSpaceDrawQuad.BottomLeft + new Vector2(-aa, aa), + ScreenSpaceDrawQuad.BottomRight + new Vector2(aa) + ); + + renderer.DrawQuad(Texture, topTriangle, DrawColourInfo.Colour); + renderer.DrawQuad(Texture, bottomTriangle, DrawColourInfo.Colour); + } + + private IUniformBuffer? borderDataBuffer; + + protected override void BindUniformResources(IShader shader, IRenderer renderer) + { + base.BindUniformResources(shader, renderer); + + borderDataBuffer ??= renderer.CreateUniformBuffer(); + borderDataBuffer.Data = borderDataBuffer.Data with + { + Thickness = 1f, + TexelSize = texelSize + }; + + shader.BindUniformBlock("m_BorderData", borderDataBuffer); + } + + protected override bool CanDrawOpaqueInterior => false; + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + borderDataBuffer?.Dispose(); + } + } + } } } } From a7da7554bc38103dd7c4a586692688253dd8baa4 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 18 Jul 2025 12:43:58 +0300 Subject: [PATCH 2/6] Add xmldoc explaining the purpose of VerticalTrianglesDrawNode --- .../Edit/Components/Timelines/Summary/Parts/MarkerPart.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index 358e642d9a..14d5393780 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -118,6 +118,13 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts protected override DrawNode CreateDrawNode() => new VerticalTrianglesDrawNode(this); + /// + /// Triangles drawn at the top and bottom of . + /// + /// + /// Since framework-side triangles don't support antialiasing we are using custom implementation involving shaders to avoid mismatch + /// in antialiasing between top and bottom triangles when drawable moves across the screen. + /// private class VerticalTrianglesDrawNode : SpriteDrawNode { private const float aa = 1.5f; // across how many pixels antialiasing is being applied From c8eae6fd866e1506bcd16b754a1ae022de1857a6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 18 Jul 2025 12:46:21 +0300 Subject: [PATCH 3/6] Move xmldoc to correct place --- .../Timelines/Summary/Parts/MarkerPart.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index 14d5393780..057a6b63ab 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -107,6 +107,13 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) => Colour = colours.Highlight1; + /// + /// Triangles drawn at the top and bottom of . + /// + /// + /// Since framework-side triangles don't support antialiasing we are using custom implementation involving shaders to avoid mismatch + /// in antialiasing between top and bottom triangles when drawable moves across the screen. + /// private partial class VerticalTriangles : Sprite { [BackgroundDependencyLoader] @@ -118,13 +125,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts protected override DrawNode CreateDrawNode() => new VerticalTrianglesDrawNode(this); - /// - /// Triangles drawn at the top and bottom of . - /// - /// - /// Since framework-side triangles don't support antialiasing we are using custom implementation involving shaders to avoid mismatch - /// in antialiasing between top and bottom triangles when drawable moves across the screen. - /// private class VerticalTrianglesDrawNode : SpriteDrawNode { private const float aa = 1.5f; // across how many pixels antialiasing is being applied From 5cb51e5e215c874c6ece0b6ac47a8e9188d2a956 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 18 Jul 2025 13:19:41 +0300 Subject: [PATCH 4/6] Combine CentreMarker with MarkerVisualisation --- .../Timelines/Summary/Parts/MarkerPart.cs | 143 +--------------- .../Components/Timeline/CentreMarker.cs | 160 ++++++++++++++---- .../Compose/Components/Timeline/Timeline.cs | 9 +- 3 files changed, 148 insertions(+), 164 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index 057a6b63ab..afe14de3ea 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -4,16 +4,9 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Rendering; -using osu.Framework.Graphics.Shaders; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Threading; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Overlays; +using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts @@ -31,7 +24,14 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts [BackgroundDependencyLoader] private void load() { - Add(marker = new MarkerVisualisation()); + Add(marker = new CentreMarker + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + Width = 10, + TriangleHeightRatio = 0.5f + }); } protected override bool OnDragStart(DragStartEvent e) => true; @@ -73,130 +73,5 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { // block base call so we don't clear our marker (can be reused on beatmap change). } - - private partial class MarkerVisualisation : CompositeDrawable - { - public MarkerVisualisation() - { - Anchor = Anchor.CentreLeft; - Origin = Anchor.Centre; - RelativePositionAxes = Axes.X; - RelativeSizeAxes = Axes.Y; - AutoSizeAxes = Axes.X; - Masking = true; - InternalChildren = new Drawable[] - { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Width = 1.4f, - EdgeSmoothness = new Vector2(1, 0) - }, - new VerticalTriangles - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Width = 10 - } - }; - } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colours) => Colour = colours.Highlight1; - - /// - /// Triangles drawn at the top and bottom of . - /// - /// - /// Since framework-side triangles don't support antialiasing we are using custom implementation involving shaders to avoid mismatch - /// in antialiasing between top and bottom triangles when drawable moves across the screen. - /// - private partial class VerticalTriangles : Sprite - { - [BackgroundDependencyLoader] - private void load(ShaderManager shaders, IRenderer renderer) - { - TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "TriangleBorder"); - Texture = renderer.WhitePixel; - } - - protected override DrawNode CreateDrawNode() => new VerticalTrianglesDrawNode(this); - - private class VerticalTrianglesDrawNode : SpriteDrawNode - { - private const float aa = 1.5f; // across how many pixels antialiasing is being applied - - public VerticalTrianglesDrawNode(VerticalTriangles source) - : base(source) - { - } - - private float texelSize; - private float triangleScreenSpaceHeight; - - public override void ApplyState() - { - base.ApplyState(); - - triangleScreenSpaceHeight = ScreenSpaceDrawQuad.Width * 0.5f; - texelSize = aa / Math.Max(ScreenSpaceDrawQuad.Width, 1); - } - - protected override void Blit(IRenderer renderer) - { - if (triangleScreenSpaceHeight == 0) - return; - - // TriangleBorder shader makes a smooth triangle for all its sides, which we want to avoid at the top and bottom. - // To do that we are expanding triangles outside the drawable by the aa value and applying masking at the top level. - Quad topTriangle = new Quad - ( - ScreenSpaceDrawQuad.TopLeft + new Vector2(-aa, triangleScreenSpaceHeight), - ScreenSpaceDrawQuad.TopRight + new Vector2(aa, triangleScreenSpaceHeight), - ScreenSpaceDrawQuad.TopLeft - new Vector2(aa), - ScreenSpaceDrawQuad.TopRight + new Vector2(aa, -aa) - ); - - Quad bottomTriangle = new Quad - ( - ScreenSpaceDrawQuad.BottomLeft - new Vector2(aa, triangleScreenSpaceHeight), - ScreenSpaceDrawQuad.BottomRight - new Vector2(-aa, triangleScreenSpaceHeight), - ScreenSpaceDrawQuad.BottomLeft + new Vector2(-aa, aa), - ScreenSpaceDrawQuad.BottomRight + new Vector2(aa) - ); - - renderer.DrawQuad(Texture, topTriangle, DrawColourInfo.Colour); - renderer.DrawQuad(Texture, bottomTriangle, DrawColourInfo.Colour); - } - - private IUniformBuffer? borderDataBuffer; - - protected override void BindUniformResources(IShader shader, IRenderer renderer) - { - base.BindUniformResources(shader, renderer); - - borderDataBuffer ??= renderer.CreateUniformBuffer(); - borderDataBuffer.Data = borderDataBuffer.Data with - { - Thickness = 1f, - TexelSize = texelSize - }; - - shader.BindUniformBlock("m_BorderData", borderDataBuffer); - } - - protected override bool CanDrawOpaqueInterior => false; - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - borderDataBuffer?.Dispose(); - } - } - } - } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs index c63dfdfb55..439d8abc7d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs @@ -1,10 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Rendering; +using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Backgrounds; using osu.Game.Overlays; using osuTK; @@ -12,47 +18,143 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public partial class CentreMarker : CompositeDrawable { - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colours) + public float TriangleHeightRatio { - const float triangle_width = 8; - const float bar_width = 2f; + get => triangles.TriangleHeightRatio; + set => triangles.TriangleHeightRatio = value; + } + private readonly VerticalTriangles triangles; + + public CentreMarker() + { RelativeSizeAxes = Axes.Y; - - Anchor = Anchor.TopCentre; - Origin = Anchor.TopCentre; - - Size = new Vector2(triangle_width, 1); - + Masking = true; InternalChildren = new Drawable[] { - new Circle + new Box { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, - Width = bar_width, - Colour = colours.Colour2, + Width = 1.4f, + EdgeSmoothness = new Vector2(1, 0) }, - new Triangle + triangles = new VerticalTriangles { - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, - Size = new Vector2(triangle_width, triangle_width * 0.8f), - Scale = new Vector2(1, -1), - EdgeSmoothness = new Vector2(1, 0), - Colour = colours.Colour2, - }, - new Triangle - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Size = new Vector2(triangle_width, triangle_width * 0.8f), - Scale = new Vector2(1, 1), - Colour = colours.Colour2, - }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both + } }; } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colours) => Colour = colours.Highlight1; + + /// + /// Triangles drawn at the top and bottom of . + /// + /// + /// Since framework-side triangles don't support antialiasing we are using custom implementation involving shaders to avoid mismatch + /// in antialiasing between top and bottom triangles when drawable moves across the screen. + /// + private partial class VerticalTriangles : Sprite + { + private float triangleHeightRatio = 1f; + + public float TriangleHeightRatio + { + get => triangleHeightRatio; + set + { + triangleHeightRatio = value; + Invalidate(Invalidation.DrawNode); + } + } + + [BackgroundDependencyLoader] + private void load(ShaderManager shaders, IRenderer renderer) + { + TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "TriangleBorder"); + Texture = renderer.WhitePixel; + } + + protected override DrawNode CreateDrawNode() => new VerticalTrianglesDrawNode(this); + + private class VerticalTrianglesDrawNode : SpriteDrawNode + { + private const float aa = 1.5f; // across how many pixels antialiasing is being applied + + public new VerticalTriangles Source => (VerticalTriangles)base.Source; + + public VerticalTrianglesDrawNode(VerticalTriangles source) + : base(source) + { + } + + private float texelSize; + private float triangleScreenSpaceHeight; + + public override void ApplyState() + { + base.ApplyState(); + + triangleScreenSpaceHeight = ScreenSpaceDrawQuad.Width * Source.TriangleHeightRatio; + texelSize = aa / Math.Max(ScreenSpaceDrawQuad.Width, 1); + } + + protected override void Blit(IRenderer renderer) + { + if (triangleScreenSpaceHeight == 0) + return; + + // TriangleBorder shader makes a smooth triangle for all its sides, which we want to avoid at the top and bottom. + // To do that we are expanding triangles outside the drawable by the aa value and applying masking at the top level. + Quad topTriangle = new Quad + ( + ScreenSpaceDrawQuad.TopLeft + new Vector2(-aa, triangleScreenSpaceHeight), + ScreenSpaceDrawQuad.TopRight + new Vector2(aa, triangleScreenSpaceHeight), + ScreenSpaceDrawQuad.TopLeft - new Vector2(aa), + ScreenSpaceDrawQuad.TopRight + new Vector2(aa, -aa) + ); + + Quad bottomTriangle = new Quad + ( + ScreenSpaceDrawQuad.BottomLeft - new Vector2(aa, triangleScreenSpaceHeight), + ScreenSpaceDrawQuad.BottomRight - new Vector2(-aa, triangleScreenSpaceHeight), + ScreenSpaceDrawQuad.BottomLeft + new Vector2(-aa, aa), + ScreenSpaceDrawQuad.BottomRight + new Vector2(aa) + ); + + renderer.DrawQuad(Texture, topTriangle, DrawColourInfo.Colour); + renderer.DrawQuad(Texture, bottomTriangle, DrawColourInfo.Colour); + } + + private IUniformBuffer? borderDataBuffer; + + protected override void BindUniformResources(IShader shader, IRenderer renderer) + { + base.BindUniformResources(shader, renderer); + + borderDataBuffer ??= renderer.CreateUniformBuffer(); + borderDataBuffer.Data = borderDataBuffer.Data with + { + Thickness = 1f, + TexelSize = texelSize + }; + + shader.BindUniformBlock("m_BorderData", borderDataBuffer); + } + + protected override bool CanDrawOpaqueInterior => false; + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + borderDataBuffer?.Dispose(); + } + } + } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index cbf49e62e7..cbafea7600 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -107,7 +107,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline CentreMarker centreMarker; // We don't want the centre marker to scroll - AddInternal(centreMarker = new CentreMarker()); + AddInternal(centreMarker = new CentreMarker + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Width = 8, + TriangleHeightRatio = 0.8f, + Colour = colourProvider.Colour2 + }); AddRange(new Drawable[] { From 0f2a07844747a8bf6eff94f23ffe76fdbc2eb8ca Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 18 Jul 2025 15:52:51 +0300 Subject: [PATCH 5/6] Use boxes with inflation instead of a shader --- .../Components/Timeline/CentreMarker.cs | 64 ++++++------------- 1 file changed, 18 insertions(+), 46 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs index 439d8abc7d..ec6c742b6b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs @@ -1,16 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Rendering; -using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Backgrounds; using osu.Game.Overlays; using osuTK; @@ -44,7 +41,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { Anchor = Anchor.Centre, Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + EdgeSmoothness = Vector2.One } }; } @@ -56,8 +54,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// Triangles drawn at the top and bottom of . /// /// - /// Since framework-side triangles don't support antialiasing we are using custom implementation involving shaders to avoid mismatch - /// in antialiasing between top and bottom triangles when drawable moves across the screen. + /// Since framework-side triangles don't support antialiasing we are using custom implementation involving rotated smoothened boxes to avoid + /// mismatch in antialiasing between top and bottom triangles when drawable moves across the screen. + /// To "trim" boxes we must enable masking at the top level. /// private partial class VerticalTriangles : Sprite { @@ -74,9 +73,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } [BackgroundDependencyLoader] - private void load(ShaderManager shaders, IRenderer renderer) + private void load(IRenderer renderer) { - TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "TriangleBorder"); Texture = renderer.WhitePixel; } @@ -84,8 +82,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private class VerticalTrianglesDrawNode : SpriteDrawNode { - private const float aa = 1.5f; // across how many pixels antialiasing is being applied - public new VerticalTriangles Source => (VerticalTriangles)base.Source; public VerticalTrianglesDrawNode(VerticalTriangles source) @@ -93,15 +89,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { } - private float texelSize; private float triangleScreenSpaceHeight; + private Vector2 inflation; public override void ApplyState() { base.ApplyState(); triangleScreenSpaceHeight = ScreenSpaceDrawQuad.Width * Source.TriangleHeightRatio; - texelSize = aa / Math.Max(ScreenSpaceDrawQuad.Width, 1); + inflation = new Vector2(InflationAmount.X / DrawRectangle.Width, InflationAmount.Y / (DrawRectangle.Width * Source.TriangleHeightRatio)); } protected override void Blit(IRenderer renderer) @@ -109,51 +105,27 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (triangleScreenSpaceHeight == 0) return; - // TriangleBorder shader makes a smooth triangle for all its sides, which we want to avoid at the top and bottom. - // To do that we are expanding triangles outside the drawable by the aa value and applying masking at the top level. Quad topTriangle = new Quad ( - ScreenSpaceDrawQuad.TopLeft + new Vector2(-aa, triangleScreenSpaceHeight), - ScreenSpaceDrawQuad.TopRight + new Vector2(aa, triangleScreenSpaceHeight), - ScreenSpaceDrawQuad.TopLeft - new Vector2(aa), - ScreenSpaceDrawQuad.TopRight + new Vector2(aa, -aa) + ScreenSpaceDrawQuad.TopLeft, + ScreenSpaceDrawQuad.TopLeft + new Vector2(ScreenSpaceDrawQuad.Width * 0.5f, -triangleScreenSpaceHeight), + ScreenSpaceDrawQuad.TopLeft + new Vector2(ScreenSpaceDrawQuad.Width * 0.5f, triangleScreenSpaceHeight), + ScreenSpaceDrawQuad.TopRight ); Quad bottomTriangle = new Quad ( - ScreenSpaceDrawQuad.BottomLeft - new Vector2(aa, triangleScreenSpaceHeight), - ScreenSpaceDrawQuad.BottomRight - new Vector2(-aa, triangleScreenSpaceHeight), - ScreenSpaceDrawQuad.BottomLeft + new Vector2(-aa, aa), - ScreenSpaceDrawQuad.BottomRight + new Vector2(aa) + ScreenSpaceDrawQuad.BottomLeft, + ScreenSpaceDrawQuad.BottomLeft + new Vector2(ScreenSpaceDrawQuad.Width * 0.5f, -triangleScreenSpaceHeight), + ScreenSpaceDrawQuad.BottomLeft + new Vector2(ScreenSpaceDrawQuad.Width * 0.5f, triangleScreenSpaceHeight), + ScreenSpaceDrawQuad.BottomRight ); - renderer.DrawQuad(Texture, topTriangle, DrawColourInfo.Colour); - renderer.DrawQuad(Texture, bottomTriangle, DrawColourInfo.Colour); - } - - private IUniformBuffer? borderDataBuffer; - - protected override void BindUniformResources(IShader shader, IRenderer renderer) - { - base.BindUniformResources(shader, renderer); - - borderDataBuffer ??= renderer.CreateUniformBuffer(); - borderDataBuffer.Data = borderDataBuffer.Data with - { - Thickness = 1f, - TexelSize = texelSize - }; - - shader.BindUniformBlock("m_BorderData", borderDataBuffer); + renderer.DrawQuad(Texture, topTriangle, DrawColourInfo.Colour, inflationPercentage: inflation); + renderer.DrawQuad(Texture, bottomTriangle, DrawColourInfo.Colour, inflationPercentage: inflation); } protected override bool CanDrawOpaqueInterior => false; - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - borderDataBuffer?.Dispose(); - } } } } From 04cd91bd36a185e7ea3f800257cdcb2210ec8122 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 18 Jul 2025 15:55:33 +0300 Subject: [PATCH 6/6] Fix potential div-by-zero --- .../Edit/Compose/Components/Timeline/CentreMarker.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs index ec6c742b6b..145049e1dd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs @@ -90,21 +90,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } private float triangleScreenSpaceHeight; - private Vector2 inflation; public override void ApplyState() { base.ApplyState(); triangleScreenSpaceHeight = ScreenSpaceDrawQuad.Width * Source.TriangleHeightRatio; - inflation = new Vector2(InflationAmount.X / DrawRectangle.Width, InflationAmount.Y / (DrawRectangle.Width * Source.TriangleHeightRatio)); } protected override void Blit(IRenderer renderer) { - if (triangleScreenSpaceHeight == 0) + if (triangleScreenSpaceHeight == 0 || DrawRectangle.Width == 0 || DrawRectangle.Height == 0) return; + Vector2 inflation = new Vector2(InflationAmount.X / DrawRectangle.Width, InflationAmount.Y / (DrawRectangle.Width * Source.TriangleHeightRatio)); + Quad topTriangle = new Quad ( ScreenSpaceDrawQuad.TopLeft,