A game framework written with osu! in mind.
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}