A game framework written with osu! in mind.
at master 304 lines 18 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 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}