// 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 System.Runtime.CompilerServices; using System.Threading; using osu.Framework.Graphics.Batches; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.OpenGL; using osu.Framework.Graphics.OpenGL.Buffers; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; using osuTK; namespace osu.Framework.Graphics { /// /// Contains all the information required to draw a single . /// A hierarchy of s is passed to the draw thread for rendering every frame. /// public class DrawNode : IDisposable { /// /// Contains the linear transformation of this . /// protected DrawInfo DrawInfo { get; private set; } /// /// Contains the colour and blending information of this . /// protected DrawColourInfo DrawColourInfo { get; private set; } /// /// Identifies the state of this draw node with an invalidation state of its corresponding /// . An update is required when the invalidation state of this draw node disagrees /// with the invalidation state of its . /// protected internal long InvalidationID { get; private set; } /// /// The which this draws. /// protected IDrawable Source { get; private set; } private long referenceCount; /// /// The depth at which drawing should take place. /// This is written to from the front-to-back pass and used in both passes. /// private float drawDepth; /// /// Creates a new . /// /// The to draw with this . public DrawNode(IDrawable source) { Source = source; Reference(); } /// /// Applies the state of to this for use in rendering. /// The applied state must remain immutable. /// public virtual void ApplyState() { DrawInfo = Source.DrawInfo; DrawColourInfo = Source.DrawColourInfo; InvalidationID = Source.InvalidationID; } /// /// Draws this to the screen. /// /// /// Subclasses must invoke base.Draw() prior to drawing vertices. /// /// 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. public virtual void Draw(Action vertexAction) { GLWrapper.SetBlend(DrawColourInfo.Blending); // This is the back-to-front (BTF) pass. The back-buffer depth test function used is GL_LESS. // The depth test will fail for samples that overlap the opaque interior of this and any s above this one. GLWrapper.SetDrawDepth(drawDepth); } /// /// Draws the opaque interior of this and all s further down the scene graph, invoking if /// indicates that an opaque interior can be drawn for each relevant . /// /// /// This is the front-to-back pass. The back-buffer depth test function used is GL_LESS.
/// During this pass, the opaque interior is drawn BELOW ourselves. For this to occur, is temporarily incremented and then decremented after drawing is complete. /// Other s behind ourselves receive the incremented depth value before doing the same themselves, allowing early-z to take place during this pass. ///
/// The previous depth value. /// 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. internal virtual void DrawOpaqueInteriorSubTree(DepthValue depthValue, Action vertexAction) { if (!depthValue.CanIncrement || !CanDrawOpaqueInterior) { // The back-to-front pass requires the depth value. drawDepth = depthValue; return; } // For an incoming depth value D, the opaque interior is drawn at depth D+e and the content is drawn at depth D. // 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). // Increment the depth. float previousDepthValue = depthValue; drawDepth = depthValue.Increment(); DrawOpaqueInterior(vertexAction); // Decrement the depth. drawDepth = previousDepthValue; } /// /// Draws the opaque interior of this to the screen. /// The opaque interior must be a fully-opaque, non-blended area of this , clipped to the current masking area via DrawClipped(). /// See for an example implementation. /// /// /// Subclasses must invoke base.DrawOpaqueInterior() prior to drawing vertices. /// /// 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. protected virtual void DrawOpaqueInterior(Action vertexAction) { GLWrapper.SetDrawDepth(drawDepth); } /// /// Whether this can draw a opaque interior. will only be invoked if this value is true. /// Should not return true if will result in a no-op. /// protected internal virtual bool CanDrawOpaqueInterior => false; /// /// Draws a triangle to the screen. /// /// The texture to fill the triangle with. /// The triangle to draw. /// The texture rectangle. /// The vertex colour. /// An action that adds vertices to a . /// The percentage amount that should be inflated. /// The texture coordinates of the triangle's vertices (translated from the corresponding quad's rectangle). [MethodImpl(MethodImplOptions.AggressiveInlining)] protected void DrawTriangle(Texture texture, Triangle vertexTriangle, ColourInfo drawColour, RectangleF? textureRect = null, Action vertexAction = null, Vector2? inflationPercentage = null, RectangleF? textureCoords = null) => texture.DrawTriangle(vertexTriangle, drawColour, textureRect, vertexAction, inflationPercentage, textureCoords); /// /// Draws a triangle to the screen. /// /// The texture to fill the triangle with. /// The triangle to draw. /// The vertex colour. /// The texture rectangle. /// An action that adds vertices to a . /// The percentage amount that should be inflated. /// The texture coordinates of the triangle's vertices (translated from the corresponding quad's rectangle). [MethodImpl(MethodImplOptions.AggressiveInlining)] protected void DrawTriangle(TextureGL texture, Triangle vertexTriangle, ColourInfo drawColour, RectangleF? textureRect = null, Action vertexAction = null, Vector2? inflationPercentage = null, RectangleF? textureCoords = null) => texture.DrawTriangle(vertexTriangle, drawColour, textureRect, vertexAction, inflationPercentage, textureCoords); /// /// Draws a quad to the screen. /// /// The texture to fill the triangle with. /// The quad to draw. /// The texture rectangle. /// The vertex colour. /// An action that adds vertices to a . /// The percentage amount that should be inflated. /// The range over which the edges of the should be blended. /// The texture coordinates of the quad's vertices. [MethodImpl(MethodImplOptions.AggressiveInlining)] protected void DrawQuad(Texture texture, Quad vertexQuad, ColourInfo drawColour, RectangleF? textureRect = null, Action vertexAction = null, Vector2? inflationPercentage = null, Vector2? blendRangeOverride = null, RectangleF? textureCoords = null) => texture.DrawQuad(vertexQuad, drawColour, textureRect, vertexAction, inflationPercentage: inflationPercentage, blendRangeOverride: blendRangeOverride, textureCoords: textureCoords); /// /// Draws a quad to the screen. /// /// The texture to fill the triangle with. /// The quad to draw. /// The vertex colour. /// The texture rectangle. /// An action that adds vertices to a . /// The percentage amount that should be inflated. /// The range over which the edges of the should be blended. /// The texture coordinates of the quad's vertices. [MethodImpl(MethodImplOptions.AggressiveInlining)] protected void DrawQuad(TextureGL texture, Quad vertexQuad, ColourInfo drawColour, RectangleF? textureRect = null, Action vertexAction = null, Vector2? inflationPercentage = null, Vector2? blendRangeOverride = null, RectangleF? textureCoords = null) => texture.DrawQuad(vertexQuad, drawColour, textureRect, vertexAction, inflationPercentage: inflationPercentage, blendRangeOverride: blendRangeOverride, textureCoords: textureCoords); /// /// Clips a to the current masking area and draws the resulting triangles to the screen using the specified texture. /// /// The polygon to draw. /// The texture to fill the triangle with. /// The texture rectangle. /// The vertex colour. /// An action that adds vertices to a . /// The percentage amount that should be inflated. /// The texture coordinates of the polygon's vertices (translated from the corresponding quad's rectangle). [MethodImpl(MethodImplOptions.AggressiveInlining)] protected void DrawClipped(ref T polygon, Texture texture, ColourInfo drawColour, RectangleF? textureRect = null, Action vertexAction = null, Vector2? inflationPercentage = null, RectangleF? textureCoords = null) where T : IConvexPolygon { var maskingQuad = GLWrapper.CurrentMaskingInfo.ConservativeScreenSpaceQuad; var clipper = new ConvexPolygonClipper(ref maskingQuad, ref polygon); Span buffer = stackalloc Vector2[clipper.GetClipBufferSize()]; Span clippedRegion = clipper.Clip(buffer); for (int i = 2; i < clippedRegion.Length; i++) DrawTriangle(texture, new Triangle(clippedRegion[0], clippedRegion[i - 1], clippedRegion[i]), drawColour, textureRect, vertexAction, inflationPercentage, textureCoords); } /// /// Clips a to the current masking area and draws the resulting triangles to the screen using the specified texture. /// /// The polygon to draw. /// The texture to fill the triangle with. /// The texture rectangle. /// The vertex colour. /// An action that adds vertices to a . /// The percentage amount that should be inflated. /// The texture coordinates of the polygon's vertices (translated from the corresponding quad's rectangle). [MethodImpl(MethodImplOptions.AggressiveInlining)] protected void DrawClipped(ref T polygon, TextureGL texture, ColourInfo drawColour, RectangleF? textureRect = null, Action vertexAction = null, Vector2? inflationPercentage = null, RectangleF? textureCoords = null) where T : IConvexPolygon { var maskingQuad = GLWrapper.CurrentMaskingInfo.ConservativeScreenSpaceQuad; var clipper = new ConvexPolygonClipper(ref maskingQuad, ref polygon); Span buffer = stackalloc Vector2[clipper.GetClipBufferSize()]; Span clippedRegion = clipper.Clip(buffer); for (int i = 2; i < clippedRegion.Length; i++) DrawTriangle(texture, new Triangle(clippedRegion[0], clippedRegion[i - 1], clippedRegion[i]), drawColour, textureRect, vertexAction, inflationPercentage, textureCoords); } /// /// Draws a to the screen. /// /// The to draw. /// The destination vertices. /// The colour to draw the with. /// An action that adds vertices to a . /// The percentage amount that the frame buffer area should be inflated. /// The range over which the edges of the frame buffer should be blended. protected void DrawFrameBuffer(FrameBuffer frameBuffer, Quad vertexQuad, ColourInfo drawColour, Action vertexAction = null, Vector2? inflationPercentage = null, Vector2? blendRangeOverride = null) { // The strange Y coordinate and Height are a result of OpenGL coordinate systems having Y grow upwards and not downwards. RectangleF textureRect = new RectangleF(0, frameBuffer.Texture.Height, frameBuffer.Texture.Width, -frameBuffer.Texture.Height); if (frameBuffer.Texture.Bind()) DrawQuad(frameBuffer.Texture, vertexQuad, drawColour, textureRect, vertexAction, inflationPercentage, blendRangeOverride); } /// /// Increments the reference count of this , blocking until the count reaches 0. /// Invoke to remove the reference. /// /// /// All s start with a reference count of 1. /// internal void Reference() => Interlocked.Increment(ref referenceCount); protected internal bool IsDisposed { get; private set; } public void Dispose() { if (Interlocked.Decrement(ref referenceCount) != 0) return; GLWrapper.ScheduleDisposal(() => Dispose(true)); GC.SuppressFinalize(this); } protected virtual void Dispose(bool isDisposing) { Source = null; IsDisposed = true; } } }