// 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.OpenGL; using osu.Framework.Graphics.OpenGL.Buffers; using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Primitives; using osu.Framework.Statistics; using osuTK; using osuTK.Graphics; namespace osu.Framework.Graphics { public class BufferedDrawNode : TexturedShaderDrawNode { protected new IBufferedDrawable Source => (IBufferedDrawable)base.Source; /// /// The child which is used to populate the s with. /// protected DrawNode Child { get; private set; } /// /// Data shared amongst all s, providing storage for s. /// protected readonly BufferedDrawNodeSharedData SharedData; /// /// Contains the colour and blending information of this . /// protected new DrawColourInfo DrawColourInfo { get; private set; } protected RectangleF DrawRectangle { get; private set; } private Color4 backgroundColour; private RectangleF screenSpaceDrawRectangle; private Vector2 frameBufferScale; private Vector2 frameBufferSize; public BufferedDrawNode(IBufferedDrawable source, DrawNode child, BufferedDrawNodeSharedData sharedData) : base(source) { Child = child; SharedData = sharedData; } public override void ApplyState() { base.ApplyState(); backgroundColour = Source.BackgroundColour; screenSpaceDrawRectangle = Source.ScreenSpaceDrawQuad.AABBFloat; DrawColourInfo = Source.FrameBufferDrawColour ?? new DrawColourInfo(Color4.White, base.DrawColourInfo.Blending); frameBufferScale = Source.FrameBufferScale; frameBufferSize = new Vector2(MathF.Ceiling(screenSpaceDrawRectangle.Width * frameBufferScale.X), MathF.Ceiling(screenSpaceDrawRectangle.Height * frameBufferScale.Y)); DrawRectangle = SharedData.PixelSnapping ? new RectangleF(screenSpaceDrawRectangle.X, screenSpaceDrawRectangle.Y, frameBufferSize.X, frameBufferSize.Y) : screenSpaceDrawRectangle; Child.ApplyState(); } /// /// Whether this should be redrawn. /// protected bool RequiresRedraw => GetDrawVersion() > SharedData.DrawVersion; /// /// Retrieves the version of the state of this . /// The will only re-render if this version is greater than that of the rendered s. /// /// /// By default, the is re-rendered with every invalidation. /// /// A version representing this 's state. protected virtual long GetDrawVersion() => InvalidationID; public sealed override void Draw(Action vertexAction) { if (RequiresRedraw) { FrameStatistics.Increment(StatisticsCounterType.FBORedraw); SharedData.ResetCurrentEffectBuffer(); using (establishFrameBufferViewport()) { // Fill the frame buffer with drawn children using (BindFrameBuffer(SharedData.MainBuffer)) { // We need to draw children as if they were zero-based to the top-left of the texture. // We can do this by adding a translation component to our (orthogonal) projection matrix. GLWrapper.PushOrtho(screenSpaceDrawRectangle); GLWrapper.Clear(new ClearInfo(backgroundColour)); Child.Draw(vertexAction); GLWrapper.PopOrtho(); } PopulateContents(); } SharedData.DrawVersion = GetDrawVersion(); } Shader.Bind(); base.Draw(vertexAction); DrawContents(); Shader.Unbind(); } /// /// Populates the contents of the effect buffers of . /// This is invoked after has been rendered to the main buffer. /// protected virtual void PopulateContents() { } /// /// Draws the applicable effect buffers of to the back buffer. /// protected virtual void DrawContents() { DrawFrameBuffer(SharedData.MainBuffer, DrawRectangle, DrawColourInfo.Colour); } /// /// Binds and initialises a if required. /// /// The to bind. /// A token that must be disposed upon finishing use of . protected IDisposable BindFrameBuffer(FrameBuffer frameBuffer) { // This setter will also take care of allocating a texture of appropriate size within the frame buffer. frameBuffer.Size = frameBufferSize; frameBuffer.Bind(); return new ValueInvokeOnDisposal(frameBuffer, b => b.Unbind()); } private IDisposable establishFrameBufferViewport() { // 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); GLWrapper.PushMaskingInfo(new MaskingInfo { ScreenSpaceAABB = screenSpaceMaskingRect, MaskingRect = screenSpaceDrawRectangle, ToMaskingSpace = Matrix3.Identity, BlendRange = 1, AlphaExponent = 1, }, true); // Match viewport to FrameBuffer such that we don't draw unnecessary pixels. GLWrapper.PushViewport(new RectangleI(0, 0, (int)frameBufferSize.X, (int)frameBufferSize.Y)); GLWrapper.PushScissor(new RectangleI(0, 0, (int)frameBufferSize.X, (int)frameBufferSize.Y)); GLWrapper.PushScissorOffset(screenSpaceMaskingRect.Location); return new ValueInvokeOnDisposal(this, d => d.returnViewport()); } private void returnViewport() { GLWrapper.PopScissorOffset(); GLWrapper.PopViewport(); GLWrapper.PopScissor(); GLWrapper.PopMaskingInfo(); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); Child?.Dispose(); Child = null; } } }