A game framework written with osu! in mind.
at master 189 lines 7.7 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System; 5using osu.Framework.Allocation; 6using osu.Framework.Graphics.OpenGL; 7using osu.Framework.Graphics.OpenGL.Buffers; 8using osu.Framework.Graphics.OpenGL.Vertices; 9using osu.Framework.Graphics.Primitives; 10using osu.Framework.Statistics; 11using osuTK; 12using osuTK.Graphics; 13 14namespace osu.Framework.Graphics 15{ 16 public class BufferedDrawNode : TexturedShaderDrawNode 17 { 18 protected new IBufferedDrawable Source => (IBufferedDrawable)base.Source; 19 20 /// <summary> 21 /// The child <see cref="DrawNode"/> which is used to populate the <see cref="FrameBuffer"/>s with. 22 /// </summary> 23 protected DrawNode Child { get; private set; } 24 25 /// <summary> 26 /// Data shared amongst all <see cref="BufferedDrawNode"/>s, providing storage for <see cref="FrameBuffer"/>s. 27 /// </summary> 28 protected readonly BufferedDrawNodeSharedData SharedData; 29 30 /// <summary> 31 /// Contains the colour and blending information of this <see cref="DrawNode"/>. 32 /// </summary> 33 protected new DrawColourInfo DrawColourInfo { get; private set; } 34 35 protected RectangleF DrawRectangle { get; private set; } 36 37 private Color4 backgroundColour; 38 private RectangleF screenSpaceDrawRectangle; 39 private Vector2 frameBufferScale; 40 private Vector2 frameBufferSize; 41 42 public BufferedDrawNode(IBufferedDrawable source, DrawNode child, BufferedDrawNodeSharedData sharedData) 43 : base(source) 44 { 45 Child = child; 46 SharedData = sharedData; 47 } 48 49 public override void ApplyState() 50 { 51 base.ApplyState(); 52 53 backgroundColour = Source.BackgroundColour; 54 screenSpaceDrawRectangle = Source.ScreenSpaceDrawQuad.AABBFloat; 55 DrawColourInfo = Source.FrameBufferDrawColour ?? new DrawColourInfo(Color4.White, base.DrawColourInfo.Blending); 56 frameBufferScale = Source.FrameBufferScale; 57 58 frameBufferSize = new Vector2(MathF.Ceiling(screenSpaceDrawRectangle.Width * frameBufferScale.X), MathF.Ceiling(screenSpaceDrawRectangle.Height * frameBufferScale.Y)); 59 DrawRectangle = SharedData.PixelSnapping 60 ? new RectangleF(screenSpaceDrawRectangle.X, screenSpaceDrawRectangle.Y, frameBufferSize.X, frameBufferSize.Y) 61 : screenSpaceDrawRectangle; 62 63 Child.ApplyState(); 64 } 65 66 /// <summary> 67 /// Whether this <see cref="BufferedDrawNode"/> should be redrawn. 68 /// </summary> 69 protected bool RequiresRedraw => GetDrawVersion() > SharedData.DrawVersion; 70 71 /// <summary> 72 /// Retrieves the version of the state of this <see cref="DrawNode"/>. 73 /// The <see cref="BufferedDrawNode"/> will only re-render if this version is greater than that of the rendered <see cref="FrameBuffer"/>s. 74 /// </summary> 75 /// <remarks> 76 /// By default, the <see cref="BufferedDrawNode"/> is re-rendered with every <see cref="DrawNode"/> invalidation. 77 /// </remarks> 78 /// <returns>A version representing this <see cref="DrawNode"/>'s state.</returns> 79 protected virtual long GetDrawVersion() => InvalidationID; 80 81 public sealed override void Draw(Action<TexturedVertex2D> vertexAction) 82 { 83 if (RequiresRedraw) 84 { 85 FrameStatistics.Increment(StatisticsCounterType.FBORedraw); 86 87 SharedData.ResetCurrentEffectBuffer(); 88 89 using (establishFrameBufferViewport()) 90 { 91 // Fill the frame buffer with drawn children 92 using (BindFrameBuffer(SharedData.MainBuffer)) 93 { 94 // We need to draw children as if they were zero-based to the top-left of the texture. 95 // We can do this by adding a translation component to our (orthogonal) projection matrix. 96 GLWrapper.PushOrtho(screenSpaceDrawRectangle); 97 GLWrapper.Clear(new ClearInfo(backgroundColour)); 98 99 Child.Draw(vertexAction); 100 101 GLWrapper.PopOrtho(); 102 } 103 104 PopulateContents(); 105 } 106 107 SharedData.DrawVersion = GetDrawVersion(); 108 } 109 110 Shader.Bind(); 111 112 base.Draw(vertexAction); 113 DrawContents(); 114 115 Shader.Unbind(); 116 } 117 118 /// <summary> 119 /// Populates the contents of the effect buffers of <see cref="SharedData"/>. 120 /// This is invoked after <see cref="Child"/> has been rendered to the main buffer. 121 /// </summary> 122 protected virtual void PopulateContents() 123 { 124 } 125 126 /// <summary> 127 /// Draws the applicable effect buffers of <see cref="SharedData"/> to the back buffer. 128 /// </summary> 129 protected virtual void DrawContents() 130 { 131 DrawFrameBuffer(SharedData.MainBuffer, DrawRectangle, DrawColourInfo.Colour); 132 } 133 134 /// <summary> 135 /// Binds and initialises a <see cref="FrameBuffer"/> if required. 136 /// </summary> 137 /// <param name="frameBuffer">The <see cref="FrameBuffer"/> to bind.</param> 138 /// <returns>A token that must be disposed upon finishing use of <paramref name="frameBuffer"/>.</returns> 139 protected IDisposable BindFrameBuffer(FrameBuffer frameBuffer) 140 { 141 // This setter will also take care of allocating a texture of appropriate size within the frame buffer. 142 frameBuffer.Size = frameBufferSize; 143 144 frameBuffer.Bind(); 145 146 return new ValueInvokeOnDisposal<FrameBuffer>(frameBuffer, b => b.Unbind()); 147 } 148 149 private IDisposable establishFrameBufferViewport() 150 { 151 // Disable masking for generating the frame buffer since masking will be re-applied 152 // when actually drawing later on anyways. This allows more information to be captured 153 // in the frame buffer and helps with cached buffers being re-used. 154 RectangleI screenSpaceMaskingRect = new RectangleI((int)Math.Floor(screenSpaceDrawRectangle.X), (int)Math.Floor(screenSpaceDrawRectangle.Y), (int)frameBufferSize.X + 1, (int)frameBufferSize.Y + 1); 155 156 GLWrapper.PushMaskingInfo(new MaskingInfo 157 { 158 ScreenSpaceAABB = screenSpaceMaskingRect, 159 MaskingRect = screenSpaceDrawRectangle, 160 ToMaskingSpace = Matrix3.Identity, 161 BlendRange = 1, 162 AlphaExponent = 1, 163 }, true); 164 165 // Match viewport to FrameBuffer such that we don't draw unnecessary pixels. 166 GLWrapper.PushViewport(new RectangleI(0, 0, (int)frameBufferSize.X, (int)frameBufferSize.Y)); 167 GLWrapper.PushScissor(new RectangleI(0, 0, (int)frameBufferSize.X, (int)frameBufferSize.Y)); 168 GLWrapper.PushScissorOffset(screenSpaceMaskingRect.Location); 169 170 return new ValueInvokeOnDisposal<BufferedDrawNode>(this, d => d.returnViewport()); 171 } 172 173 private void returnViewport() 174 { 175 GLWrapper.PopScissorOffset(); 176 GLWrapper.PopViewport(); 177 GLWrapper.PopScissor(); 178 GLWrapper.PopMaskingInfo(); 179 } 180 181 protected override void Dispose(bool isDisposing) 182 { 183 base.Dispose(isDisposing); 184 185 Child?.Dispose(); 186 Child = null; 187 } 188 } 189}