diff --git a/osu.Framework.Tests/Visual/Containers/TestSceneBufferedContainerPixelSnapping.cs b/osu.Framework.Tests/Visual/Containers/TestSceneBufferedContainerPixelSnapping.cs new file mode 100644 index 0000000000..5398a90231 --- /dev/null +++ b/osu.Framework.Tests/Visual/Containers/TestSceneBufferedContainerPixelSnapping.cs @@ -0,0 +1,119 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Rendering; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osuTK; +using osuTK.Graphics; + +namespace osu.Framework.Tests.Visual.Containers +{ + public partial class TestSceneBufferedContainerPixelSnapping : TestScene + { + private Texture texture = null!; + private Container content = null!; + private BufferedContainer container = null!; + private bool pixelSnapping = true; + private bool useBufferContainer = true; + private Vector2 frameBufferScale = new Vector2(1); + + [BackgroundDependencyLoader] + private void load(IRenderer renderer, GameHost host, Game game) + { + var textures = new TextureStore(renderer, host.CreateTextureLoaderStore(new NamespacedResourceStore(game.Resources, "Textures")), filteringMode: TextureFilteringMode.Nearest); + + texture = textures.Get("sample-texture"); + + Child = content = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(100), + }; + + content.Loop(t => t.ResizeTo(new Vector2(300), 1000, Easing.InOutSine) + .Then() + .ResizeTo(new Vector2(100), 1000, Easing.InOutSine)); + + updateContent(); + } + + private void updateContent() + { + var drawable = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + new Sprite + { + Texture = texture, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(1.5f) + }, + new Box + { + Size = new Vector2(80), + Colour = Color4.White, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Text", + Colour = Color4.Black, + Font = new FontUsage(size: 35), + } + } + }; + + container = new BufferedContainer(pixelSnapping: pixelSnapping) + { + RelativeSizeAxes = Axes.Both, + FrameBufferScale = frameBufferScale, + }; + + if (useBufferContainer) + { + container.Child = drawable; + content.Child = container; + } + else + { + content.Child = drawable; + } + } + + [SetUpSteps] + public void Setup() + { + AddToggleStep("buffered", value => + { + useBufferContainer = value; + updateContent(); + }); + AddToggleStep("pixel snapping", value => + { + pixelSnapping = value; + updateContent(); + }); + AddSliderStep("framebuffer scale", 0.25f, 2f, 1f, value => + { + frameBufferScale = new Vector2(value); + updateContent(); + }); + } + } +} diff --git a/osu.Framework/Graphics/BufferedDrawNode.cs b/osu.Framework/Graphics/BufferedDrawNode.cs index f250b96411..8069094770 100644 --- a/osu.Framework/Graphics/BufferedDrawNode.cs +++ b/osu.Framework/Graphics/BufferedDrawNode.cs @@ -37,7 +37,7 @@ public class BufferedDrawNode : TexturedShaderDrawNode private Color4 backgroundColour; private RectangleF screenSpaceDrawRectangle; private Vector2 frameBufferScale; - private Vector2 frameBufferSize; + private Vector2I frameBufferSize; private IDrawable rootNodeCached; public BufferedDrawNode(IBufferedDrawable source, DrawNode child, BufferedDrawNodeSharedData sharedData) @@ -52,13 +52,16 @@ public override void ApplyState() base.ApplyState(); backgroundColour = Source.BackgroundColour; - screenSpaceDrawRectangle = Source.ScreenSpaceDrawQuad.AABBFloat; DrawColourInfo = Source.FrameBufferDrawColour ?? new DrawColourInfo(Color4.White, base.DrawColourInfo.Blending); + + var drawRect = Source.ScreenSpaceDrawQuad.AABBFloat; + frameBufferScale = Source.FrameBufferScale; + frameBufferSize = new Vector2I((int)MathF.Ceiling(drawRect.Width * frameBufferScale.X), (int)MathF.Ceiling(drawRect.Height * frameBufferScale.Y)); - clipDrawRectangle(); + screenSpaceDrawRectangle = new RectangleF((int)drawRect.X, (int)drawRect.Y, frameBufferSize.X / frameBufferScale.X, frameBufferSize.Y / frameBufferScale.Y); - frameBufferSize = new Vector2(MathF.Ceiling(screenSpaceDrawRectangle.Width * frameBufferScale.X), MathF.Ceiling(screenSpaceDrawRectangle.Height * frameBufferScale.Y)); + clipDrawRectangle(); DrawRectangle = screenSpaceDrawRectangle; @@ -158,8 +161,7 @@ protected ValueInvokeOnDisposal BindFrameBuffer(IFrameBuffer frame // Disable masking for generating the frame buffer since masking will be re-applied // when actually drawing later on anyways. This allows more information to be captured // in the frame buffer and helps with cached buffers being re-used. - RectangleI screenSpaceMaskingRect = new RectangleI((int)Math.Floor(screenSpaceDrawRectangle.X), (int)Math.Floor(screenSpaceDrawRectangle.Y), (int)frameBufferSize.X + 1, - (int)frameBufferSize.Y + 1); + RectangleI screenSpaceMaskingRect = new RectangleI((int)Math.Floor(screenSpaceDrawRectangle.X), (int)Math.Floor(screenSpaceDrawRectangle.Y), frameBufferSize.X + 1, frameBufferSize.Y + 1); renderer.PushMaskingInfo(new MaskingInfo { @@ -171,8 +173,8 @@ protected ValueInvokeOnDisposal BindFrameBuffer(IFrameBuffer frame }, true); // Match viewport to FrameBuffer such that we don't draw unnecessary pixels. - renderer.PushViewport(new RectangleI(0, 0, (int)frameBufferSize.X, (int)frameBufferSize.Y)); - renderer.PushScissor(new RectangleI(0, 0, (int)frameBufferSize.X, (int)frameBufferSize.Y)); + renderer.PushViewport(new RectangleI(0, 0, frameBufferSize.X, frameBufferSize.Y)); + renderer.PushScissor(new RectangleI(0, 0, frameBufferSize.X, frameBufferSize.Y)); renderer.PushScissorOffset(screenSpaceMaskingRect.Location); return new ValueInvokeOnDisposal<(BufferedDrawNode node, IRenderer renderer)>((this, renderer), static tup => tup.node.returnViewport(tup.renderer));