From 7ecf81d3a0c4e5a9b8ae791823b4ca4720fa19f3 Mon Sep 17 00:00:00 2001 From: marvin Date: Wed, 4 Jun 2025 19:58:04 +0200 Subject: [PATCH] Add ghost drawable --- .../UserInterface/TestSceneGhostIcon.cs | 22 +++ osu.Game/Graphics/GhostIcon.cs | 130 ++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneGhostIcon.cs create mode 100644 osu.Game/Graphics/GhostIcon.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneGhostIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneGhostIcon.cs new file mode 100644 index 0000000000..5ae46d0224 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneGhostIcon.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public partial class TestSceneGhostIcon : OsuTestScene + { + public TestSceneGhostIcon() + { + Add(new GhostIcon + { + Size = new Vector2(64), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } + } +} diff --git a/osu.Game/Graphics/GhostIcon.cs b/osu.Game/Graphics/GhostIcon.cs new file mode 100644 index 0000000000..e72359219c --- /dev/null +++ b/osu.Game/Graphics/GhostIcon.cs @@ -0,0 +1,130 @@ +// 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 System.Runtime.InteropServices; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Rendering; +using osu.Framework.Graphics.Rendering.Vertices; +using osu.Framework.Graphics.Shaders; +using osu.Framework.Graphics.Shaders.Types; +using osuTK; + +namespace osu.Game.Graphics +{ + public partial class GhostIcon : Drawable + { + private IShader ghostShader = null!; + + [BackgroundDependencyLoader] + private void load(ShaderManager shaders) + { + ghostShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "Ghost"); + } + + protected override void Update() + { + base.Update(); + + Invalidate(Invalidation.DrawNode); + } + + protected override DrawNode CreateDrawNode() => new GhostIconDrawNode(this); + + private class GhostIconDrawNode : DrawNode + { + protected new GhostIcon Source => (GhostIcon)base.Source; + + public GhostIconDrawNode(IDrawable source) + : base(source) + { + } + + private Quad screenSpaceDrawQuad; + private Vector4 drawRectangle; + private Vector2 blend; + private IShader shader = null!; + private float time; + + public override void ApplyState() + { + base.ApplyState(); + + screenSpaceDrawQuad = Source.ScreenSpaceDrawQuad; + drawRectangle = new Vector4(0, 0, Source.DrawWidth, Source.DrawHeight); + shader = Source.ghostShader; + blend = new Vector2(Math.Min(Source.DrawWidth, Source.DrawHeight) / Math.Min(screenSpaceDrawQuad.Width, screenSpaceDrawQuad.Height)); + time = (float)(Source.Time.Current % 1000f) * 0.0005f; + } + + private IUniformBuffer? ghostParametersBuffer; + + private IVertexBatch? quadBatch; + + protected override void Draw(IRenderer renderer) + { + base.Draw(renderer); + + if (!renderer.BindTexture(renderer.WhitePixel)) + return; + + quadBatch ??= renderer.CreateQuadBatch(1, 2); + ghostParametersBuffer ??= renderer.CreateUniformBuffer(); + + ghostParametersBuffer.Data = new GhostParameters + { + Time = time + }; + + shader.Bind(); + shader.BindUniformBlock("m_GhostParameters", ghostParametersBuffer); + + var vertexAction = quadBatch.AddAction; + + vertexAction(new TexturedVertex2D(renderer) + { + Position = screenSpaceDrawQuad.BottomLeft, + TexturePosition = new Vector2(0, 1), + TextureRect = drawRectangle, + BlendRange = blend, + Colour = DrawColourInfo.Colour.BottomLeft.SRGB, + }); + vertexAction(new TexturedVertex2D(renderer) + { + Position = screenSpaceDrawQuad.BottomRight, + TexturePosition = new Vector2(1, 1), + TextureRect = drawRectangle, + BlendRange = blend, + Colour = DrawColourInfo.Colour.BottomRight.SRGB, + }); + vertexAction(new TexturedVertex2D(renderer) + { + Position = screenSpaceDrawQuad.TopRight, + TexturePosition = new Vector2(1, 0), + TextureRect = drawRectangle, + BlendRange = blend, + Colour = DrawColourInfo.Colour.TopRight.SRGB, + }); + vertexAction(new TexturedVertex2D(renderer) + { + Position = screenSpaceDrawQuad.TopLeft, + TexturePosition = Vector2.Zero, + TextureRect = drawRectangle, + BlendRange = blend, + Colour = DrawColourInfo.Colour.TopLeft.SRGB, + }); + + shader.Unbind(); + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private record struct GhostParameters + { + public UniformFloat Time; + private UniformPadding12 pad; + } + } + } +}