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 System.Runtime.CompilerServices;
6using System.Threading;
7using osu.Framework.Graphics.Batches;
8using osu.Framework.Graphics.Colour;
9using osu.Framework.Graphics.OpenGL;
10using osu.Framework.Graphics.OpenGL.Buffers;
11using osu.Framework.Graphics.OpenGL.Textures;
12using osu.Framework.Graphics.OpenGL.Vertices;
13using osu.Framework.Graphics.Primitives;
14using osu.Framework.Graphics.Textures;
15using osu.Framework.Utils;
16using osuTK;
17
18namespace osu.Framework.Graphics
19{
20 /// <summary>
21 /// Contains all the information required to draw a single <see cref="Drawable"/>.
22 /// A hierarchy of <see cref="DrawNode"/>s is passed to the draw thread for rendering every frame.
23 /// </summary>
24 public class DrawNode : IDisposable
25 {
26 /// <summary>
27 /// Contains the linear transformation of this <see cref="DrawNode"/>.
28 /// </summary>
29 protected DrawInfo DrawInfo { get; private set; }
30
31 /// <summary>
32 /// Contains the colour and blending information of this <see cref="DrawNode"/>.
33 /// </summary>
34 protected DrawColourInfo DrawColourInfo { get; private set; }
35
36 /// <summary>
37 /// Identifies the state of this draw node with an invalidation state of its corresponding
38 /// <see cref="Drawable"/>. An update is required when the invalidation state of this draw node disagrees
39 /// with the invalidation state of its <see cref="Drawable"/>.
40 /// </summary>
41 protected internal long InvalidationID { get; private set; }
42
43 /// <summary>
44 /// The <see cref="Drawable"/> which this <see cref="DrawNode"/> draws.
45 /// </summary>
46 protected IDrawable Source { get; private set; }
47
48 private long referenceCount;
49
50 /// <summary>
51 /// The depth at which drawing should take place.
52 /// This is written to from the front-to-back pass and used in both passes.
53 /// </summary>
54 private float drawDepth;
55
56 /// <summary>
57 /// Creates a new <see cref="DrawNode"/>.
58 /// </summary>
59 /// <param name="source">The <see cref="Drawable"/> to draw with this <see cref="DrawNode"/>.</param>
60 public DrawNode(IDrawable source)
61 {
62 Source = source;
63
64 Reference();
65 }
66
67 /// <summary>
68 /// Applies the state of <see cref="Source"/> to this <see cref="DrawNode"/> for use in rendering.
69 /// The applied state must remain immutable.
70 /// </summary>
71 public virtual void ApplyState()
72 {
73 DrawInfo = Source.DrawInfo;
74 DrawColourInfo = Source.DrawColourInfo;
75 InvalidationID = Source.InvalidationID;
76 }
77
78 /// <summary>
79 /// Draws this <see cref="DrawNode"/> to the screen.
80 /// </summary>
81 /// <remarks>
82 /// Subclasses must invoke <code>base.Draw()</code> prior to drawing vertices.
83 /// </remarks>
84 /// <param name="vertexAction">The action to be performed on each vertex of the draw node in order to draw it if required. This is primarily used by textured sprites.</param>
85 public virtual void Draw(Action<TexturedVertex2D> vertexAction)
86 {
87 GLWrapper.SetBlend(DrawColourInfo.Blending);
88
89 // This is the back-to-front (BTF) pass. The back-buffer depth test function used is GL_LESS.
90 // The depth test will fail for samples that overlap the opaque interior of this <see cref="DrawNode"/> and any <see cref="DrawNode"/>s above this one.
91 GLWrapper.SetDrawDepth(drawDepth);
92 }
93
94 /// <summary>
95 /// Draws the opaque interior of this <see cref="DrawNode"/> and all <see cref="DrawNode"/>s further down the scene graph, invoking <see cref="DrawOpaqueInterior"/> if <see cref="CanDrawOpaqueInterior"/>
96 /// indicates that an opaque interior can be drawn for each relevant <see cref="DrawNode"/>.
97 /// </summary>
98 /// <remarks>
99 /// This is the front-to-back pass. The back-buffer depth test function used is GL_LESS.<br />
100 /// During this pass, the opaque interior is drawn BELOW ourselves. For this to occur, <see cref="drawDepth"/> is temporarily incremented and then decremented after drawing is complete.
101 /// Other <see cref="DrawNode"/>s behind ourselves receive the incremented depth value before doing the same themselves, allowing early-z to take place during this pass.
102 /// </remarks>
103 /// <param name="depthValue">The previous depth value.</param>
104 /// <param name="vertexAction">The action to be performed on each vertex of the draw node in order to draw it if required. This is primarily used by textured sprites.</param>
105 internal virtual void DrawOpaqueInteriorSubTree(DepthValue depthValue, Action<TexturedVertex2D> vertexAction)
106 {
107 if (!depthValue.CanIncrement || !CanDrawOpaqueInterior)
108 {
109 // The back-to-front pass requires the depth value.
110 drawDepth = depthValue;
111 return;
112 }
113
114 // For an incoming depth value D, the opaque interior is drawn at depth D+e and the content is drawn at depth D.
115 // As such, when the GL_LESS test function is applied, the content will always pass the depth test for the same DrawNode (D < D+e).
116
117 // Increment the depth.
118 float previousDepthValue = depthValue;
119 drawDepth = depthValue.Increment();
120
121 DrawOpaqueInterior(vertexAction);
122
123 // Decrement the depth.
124 drawDepth = previousDepthValue;
125 }
126
127 /// <summary>
128 /// Draws the opaque interior of this <see cref="DrawNode"/> to the screen.
129 /// The opaque interior must be a fully-opaque, non-blended area of this <see cref="DrawNode"/>, clipped to the current masking area via <code>DrawClipped()</code>.
130 /// See <see cref="Sprites.SpriteDrawNode"/> for an example implementation.
131 /// </summary>
132 /// <remarks>
133 /// Subclasses must invoke <code>base.DrawOpaqueInterior()</code> prior to drawing vertices.
134 /// </remarks>
135 /// <param name="vertexAction">The action to be performed on each vertex of the draw node in order to draw it if required. This is primarily used by textured sprites.</param>
136 protected virtual void DrawOpaqueInterior(Action<TexturedVertex2D> vertexAction)
137 {
138 GLWrapper.SetDrawDepth(drawDepth);
139 }
140
141 /// <summary>
142 /// Whether this <see cref="DrawNode"/> can draw a opaque interior. <see cref="DrawOpaqueInterior"/> will only be invoked if this value is <code>true</code>.
143 /// Should not return <code>true</code> if <see cref="DrawOpaqueInterior"/> will result in a no-op.
144 /// </summary>
145 protected internal virtual bool CanDrawOpaqueInterior => false;
146
147 /// <summary>
148 /// Draws a triangle to the screen.
149 /// </summary>
150 /// <param name="texture">The texture to fill the triangle with.</param>
151 /// <param name="vertexTriangle">The triangle to draw.</param>
152 /// <param name="textureRect">The texture rectangle.</param>
153 /// <param name="drawColour">The vertex colour.</param>
154 /// <param name="vertexAction">An action that adds vertices to a <see cref="VertexBatch{T}"/>.</param>
155 /// <param name="inflationPercentage">The percentage amount that <paramref name="textureRect"/> should be inflated.</param>
156 /// <param name="textureCoords">The texture coordinates of the triangle's vertices (translated from the corresponding quad's rectangle).</param>
157 [MethodImpl(MethodImplOptions.AggressiveInlining)]
158 protected void DrawTriangle(Texture texture, Triangle vertexTriangle, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null,
159 Vector2? inflationPercentage = null, RectangleF? textureCoords = null)
160 => texture.DrawTriangle(vertexTriangle, drawColour, textureRect, vertexAction, inflationPercentage, textureCoords);
161
162 /// <summary>
163 /// Draws a triangle to the screen.
164 /// </summary>
165 /// <param name="texture">The texture to fill the triangle with.</param>
166 /// <param name="vertexTriangle">The triangle to draw.</param>
167 /// <param name="drawColour">The vertex colour.</param>
168 /// <param name="textureRect">The texture rectangle.</param>
169 /// <param name="vertexAction">An action that adds vertices to a <see cref="VertexBatch{T}"/>.</param>
170 /// <param name="inflationPercentage">The percentage amount that <paramref name="textureRect"/> should be inflated.</param>
171 /// <param name="textureCoords">The texture coordinates of the triangle's vertices (translated from the corresponding quad's rectangle).</param>
172 [MethodImpl(MethodImplOptions.AggressiveInlining)]
173 protected void DrawTriangle(TextureGL texture, Triangle vertexTriangle, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null,
174 Vector2? inflationPercentage = null, RectangleF? textureCoords = null)
175 => texture.DrawTriangle(vertexTriangle, drawColour, textureRect, vertexAction, inflationPercentage, textureCoords);
176
177 /// <summary>
178 /// Draws a quad to the screen.
179 /// </summary>
180 /// <param name="texture">The texture to fill the triangle with.</param>
181 /// <param name="vertexQuad">The quad to draw.</param>
182 /// <param name="textureRect">The texture rectangle.</param>
183 /// <param name="drawColour">The vertex colour.</param>
184 /// <param name="vertexAction">An action that adds vertices to a <see cref="VertexBatch{T}"/>.</param>
185 /// <param name="inflationPercentage">The percentage amount that <paramref name="textureRect"/> should be inflated.</param>
186 /// <param name="blendRangeOverride">The range over which the edges of the <paramref name="textureRect"/> should be blended.</param>
187 /// <param name="textureCoords">The texture coordinates of the quad's vertices.</param>
188 [MethodImpl(MethodImplOptions.AggressiveInlining)]
189 protected void DrawQuad(Texture texture, Quad vertexQuad, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null,
190 Vector2? inflationPercentage = null, Vector2? blendRangeOverride = null, RectangleF? textureCoords = null)
191 => texture.DrawQuad(vertexQuad, drawColour, textureRect, vertexAction, inflationPercentage: inflationPercentage, blendRangeOverride: blendRangeOverride, textureCoords: textureCoords);
192
193 /// <summary>
194 /// Draws a quad to the screen.
195 /// </summary>
196 /// <param name="texture">The texture to fill the triangle with.</param>
197 /// <param name="vertexQuad">The quad to draw.</param>
198 /// <param name="drawColour">The vertex colour.</param>
199 /// <param name="textureRect">The texture rectangle.</param>
200 /// <param name="vertexAction">An action that adds vertices to a <see cref="VertexBatch{T}"/>.</param>
201 /// <param name="inflationPercentage">The percentage amount that <paramref name="textureRect"/> should be inflated.</param>
202 /// <param name="blendRangeOverride">The range over which the edges of the <paramref name="textureRect"/> should be blended.</param>
203 /// <param name="textureCoords">The texture coordinates of the quad's vertices.</param>
204 [MethodImpl(MethodImplOptions.AggressiveInlining)]
205 protected void DrawQuad(TextureGL texture, Quad vertexQuad, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null,
206 Vector2? inflationPercentage = null, Vector2? blendRangeOverride = null, RectangleF? textureCoords = null)
207 => texture.DrawQuad(vertexQuad, drawColour, textureRect, vertexAction, inflationPercentage: inflationPercentage, blendRangeOverride: blendRangeOverride, textureCoords: textureCoords);
208
209 /// <summary>
210 /// Clips a <see cref="IConvexPolygon"/> to the current masking area and draws the resulting triangles to the screen using the specified texture.
211 /// </summary>
212 /// <param name="polygon">The polygon to draw.</param>
213 /// <param name="texture">The texture to fill the triangle with.</param>
214 /// <param name="textureRect">The texture rectangle.</param>
215 /// <param name="drawColour">The vertex colour.</param>
216 /// <param name="vertexAction">An action that adds vertices to a <see cref="VertexBatch{T}"/>.</param>
217 /// <param name="inflationPercentage">The percentage amount that <paramref name="textureRect"/> should be inflated.</param>
218 /// <param name="textureCoords">The texture coordinates of the polygon's vertices (translated from the corresponding quad's rectangle).</param>
219 [MethodImpl(MethodImplOptions.AggressiveInlining)]
220 protected void DrawClipped<T>(ref T polygon, Texture texture, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null,
221 Vector2? inflationPercentage = null, RectangleF? textureCoords = null)
222 where T : IConvexPolygon
223 {
224 var maskingQuad = GLWrapper.CurrentMaskingInfo.ConservativeScreenSpaceQuad;
225
226 var clipper = new ConvexPolygonClipper<Quad, T>(ref maskingQuad, ref polygon);
227 Span<Vector2> buffer = stackalloc Vector2[clipper.GetClipBufferSize()];
228 Span<Vector2> clippedRegion = clipper.Clip(buffer);
229
230 for (int i = 2; i < clippedRegion.Length; i++)
231 DrawTriangle(texture, new Triangle(clippedRegion[0], clippedRegion[i - 1], clippedRegion[i]), drawColour, textureRect, vertexAction, inflationPercentage, textureCoords);
232 }
233
234 /// <summary>
235 /// Clips a <see cref="IConvexPolygon"/> to the current masking area and draws the resulting triangles to the screen using the specified texture.
236 /// </summary>
237 /// <param name="polygon">The polygon to draw.</param>
238 /// <param name="texture">The texture to fill the triangle with.</param>
239 /// <param name="textureRect">The texture rectangle.</param>
240 /// <param name="drawColour">The vertex colour.</param>
241 /// <param name="vertexAction">An action that adds vertices to a <see cref="VertexBatch{T}"/>.</param>
242 /// <param name="inflationPercentage">The percentage amount that <paramref name="textureRect"/> should be inflated.</param>
243 /// <param name="textureCoords">The texture coordinates of the polygon's vertices (translated from the corresponding quad's rectangle).</param>
244 [MethodImpl(MethodImplOptions.AggressiveInlining)]
245 protected void DrawClipped<T>(ref T polygon, TextureGL texture, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null,
246 Vector2? inflationPercentage = null, RectangleF? textureCoords = null)
247 where T : IConvexPolygon
248 {
249 var maskingQuad = GLWrapper.CurrentMaskingInfo.ConservativeScreenSpaceQuad;
250
251 var clipper = new ConvexPolygonClipper<Quad, T>(ref maskingQuad, ref polygon);
252 Span<Vector2> buffer = stackalloc Vector2[clipper.GetClipBufferSize()];
253 Span<Vector2> clippedRegion = clipper.Clip(buffer);
254
255 for (int i = 2; i < clippedRegion.Length; i++)
256 DrawTriangle(texture, new Triangle(clippedRegion[0], clippedRegion[i - 1], clippedRegion[i]), drawColour, textureRect, vertexAction, inflationPercentage, textureCoords);
257 }
258
259 /// <summary>
260 /// Draws a <see cref="FrameBuffer"/> to the screen.
261 /// </summary>
262 /// <param name="frameBuffer">The <see cref="FrameBuffer"/> to draw.</param>
263 /// <param name="vertexQuad">The destination vertices.</param>
264 /// <param name="drawColour">The colour to draw the <paramref name="frameBuffer"/> with.</param>
265 /// <param name="vertexAction">An action that adds vertices to a <see cref="VertexBatch{T}"/>.</param>
266 /// <param name="inflationPercentage">The percentage amount that the frame buffer area should be inflated.</param>
267 /// <param name="blendRangeOverride">The range over which the edges of the frame buffer should be blended.</param>
268 protected void DrawFrameBuffer(FrameBuffer frameBuffer, Quad vertexQuad, ColourInfo drawColour, Action<TexturedVertex2D> vertexAction = null,
269 Vector2? inflationPercentage = null, Vector2? blendRangeOverride = null)
270 {
271 // The strange Y coordinate and Height are a result of OpenGL coordinate systems having Y grow upwards and not downwards.
272 RectangleF textureRect = new RectangleF(0, frameBuffer.Texture.Height, frameBuffer.Texture.Width, -frameBuffer.Texture.Height);
273
274 if (frameBuffer.Texture.Bind())
275 DrawQuad(frameBuffer.Texture, vertexQuad, drawColour, textureRect, vertexAction, inflationPercentage, blendRangeOverride);
276 }
277
278 /// <summary>
279 /// Increments the reference count of this <see cref="DrawNode"/>, blocking <see cref="Dispose()"/> until the count reaches 0.
280 /// Invoke <see cref="Dispose()"/> to remove the reference.
281 /// </summary>
282 /// <remarks>
283 /// All <see cref="DrawNode"/>s start with a reference count of 1.
284 /// </remarks>
285 internal void Reference() => Interlocked.Increment(ref referenceCount);
286
287 protected internal bool IsDisposed { get; private set; }
288
289 public void Dispose()
290 {
291 if (Interlocked.Decrement(ref referenceCount) != 0)
292 return;
293
294 GLWrapper.ScheduleDisposal(() => Dispose(true));
295 GC.SuppressFinalize(this);
296 }
297
298 protected virtual void Dispose(bool isDisposing)
299 {
300 Source = null;
301 IsDisposed = true;
302 }
303 }
304}