A game framework written with osu! in mind.
at master 209 lines 8.8 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.IO; 6using osu.Framework.Extensions.EnumExtensions; 7using osu.Framework.Graphics.Batches; 8using osu.Framework.Graphics.OpenGL.Textures; 9using osu.Framework.Graphics.Primitives; 10using osuTK; 11using osuTK.Graphics.ES30; 12using osu.Framework.Graphics.Colour; 13using osu.Framework.Graphics.OpenGL.Vertices; 14using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; 15 16namespace osu.Framework.Graphics.Textures 17{ 18 public class Texture : IDisposable 19 { 20 // in case no other textures are used in the project, create a new atlas as a fallback source for the white pixel area (used to draw boxes etc.) 21 private static readonly Lazy<TextureWhitePixel> white_pixel = new Lazy<TextureWhitePixel>(() => 22 new TextureAtlas(TextureAtlas.WHITE_PIXEL_SIZE + TextureAtlas.PADDING, TextureAtlas.WHITE_PIXEL_SIZE + TextureAtlas.PADDING, true).WhitePixel); 23 24 public static Texture WhitePixel => white_pixel.Value; 25 26 public virtual TextureGL TextureGL { get; } 27 28 public string Filename; 29 public string AssetName; 30 31 /// <summary> 32 /// A lookup key used by <see cref="TextureStore"/>s. 33 /// </summary> 34 internal string LookupKey; 35 36 /// <summary> 37 /// At what multiple of our expected resolution is our underlying texture? 38 /// </summary> 39 public float ScaleAdjust = 1; 40 41 public float DisplayWidth => Width / ScaleAdjust; 42 public float DisplayHeight => Height / ScaleAdjust; 43 44 public Opacity Opacity => TextureGL.Opacity; 45 46 public WrapMode WrapModeS => TextureGL.WrapModeS; 47 48 public WrapMode WrapModeT => TextureGL.WrapModeT; 49 50 /// <summary> 51 /// Create a new texture. 52 /// </summary> 53 /// <param name="textureGl">The GL texture.</param> 54 public Texture(TextureGL textureGl) 55 { 56 TextureGL = textureGl ?? throw new ArgumentNullException(nameof(textureGl)); 57 } 58 59 public Texture(int width, int height, bool manualMipmaps = false, All filteringMode = All.Linear) 60 : this(new TextureGLSingle(width, height, manualMipmaps, filteringMode)) 61 { 62 } 63 64 /// <summary> 65 /// Crop the texture. 66 /// </summary> 67 /// <param name="cropRectangle">The rectangle the cropped texture should reference.</param> 68 /// <param name="relativeSizeAxes">Which axes have a relative size in [0,1] in relation to the texture size.</param> 69 /// <param name="wrapModeS">The texture wrap mode in horizontal direction.</param> 70 /// <param name="wrapModeT">The texture wrap mode in vertical direction.</param> 71 /// <returns>The cropped texture.</returns> 72 public Texture Crop(RectangleF cropRectangle, Axes relativeSizeAxes = Axes.None, WrapMode wrapModeS = WrapMode.None, WrapMode wrapModeT = WrapMode.None) 73 { 74 if (relativeSizeAxes != Axes.None) 75 { 76 Vector2 scale = new Vector2( 77 relativeSizeAxes.HasFlagFast(Axes.X) ? Width : 1, 78 relativeSizeAxes.HasFlagFast(Axes.Y) ? Height : 1 79 ); 80 cropRectangle *= scale; 81 } 82 83 return new Texture(new TextureGLSub(cropRectangle, TextureGL, wrapModeS, wrapModeT)); 84 } 85 86 /// <summary> 87 /// Creates a texture from a data stream representing a bitmap. 88 /// </summary> 89 /// <param name="stream">The data stream containing the texture data.</param> 90 /// <param name="atlas">The atlas to add the texture to.</param> 91 /// <returns>The created texture.</returns> 92 public static Texture FromStream(Stream stream, TextureAtlas atlas = null) 93 { 94 if (stream == null || stream.Length == 0) 95 return null; 96 97 try 98 { 99 var data = new TextureUpload(stream); 100 Texture tex = atlas == null ? new Texture(data.Width, data.Height) : new Texture(atlas.Add(data.Width, data.Height)); 101 tex.SetData(data); 102 return tex; 103 } 104 catch (ArgumentException) 105 { 106 return null; 107 } 108 } 109 110 public int Width 111 { 112 get => TextureGL.Width; 113 set => TextureGL.Width = value; 114 } 115 116 public int Height 117 { 118 get => TextureGL.Height; 119 set => TextureGL.Height = value; 120 } 121 122 public Vector2 Size => new Vector2(Width, Height); 123 124 /// <summary> 125 /// Queue a <see cref="TextureUpload"/> to be uploaded on the draw thread. 126 /// The provided upload will be disposed after the upload is completed. 127 /// </summary> 128 /// <param name="upload"></param> 129 public void SetData(ITextureUpload upload) 130 { 131 TextureGL?.SetData(upload); 132 } 133 134 protected virtual RectangleF TextureBounds(RectangleF? textureRect = null) 135 { 136 RectangleF texRect = textureRect ?? new RectangleF(0, 0, DisplayWidth, DisplayHeight); 137 138 if (ScaleAdjust != 1) 139 { 140 texRect.Width *= ScaleAdjust; 141 texRect.Height *= ScaleAdjust; 142 texRect.X *= ScaleAdjust; 143 texRect.Y *= ScaleAdjust; 144 } 145 146 return texRect; 147 } 148 149 public RectangleF GetTextureRect(RectangleF? textureRect = null) => TextureGL.GetTextureRect(TextureBounds(textureRect)); 150 151 /// <summary> 152 /// Draws a triangle to the screen. 153 /// </summary> 154 /// <param name="vertexTriangle">The triangle to draw.</param> 155 /// <param name="drawColour">The vertex colour.</param> 156 /// <param name="textureRect">The texture rectangle in texture space.</param> 157 /// <param name="vertexAction">An action that adds vertices to a <see cref="VertexBatch{T}"/>.</param> 158 /// <param name="inflationPercentage">The percentage amount that <paramref name="textureRect"/> should be inflated.</param> 159 /// <param name="textureCoords">The texture coordinates of the triangle's vertices (translated from the corresponding quad's rectangle).</param> 160 internal void DrawTriangle(Triangle vertexTriangle, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null, 161 Vector2? inflationPercentage = null, RectangleF? textureCoords = null) 162 { 163 if (TextureGL == null || !TextureGL.Bind()) return; 164 165 TextureGL.DrawTriangle(vertexTriangle, drawColour, TextureBounds(textureRect), vertexAction, inflationPercentage, TextureBounds(textureCoords)); 166 } 167 168 /// <summary> 169 /// Draws a quad to the screen. 170 /// </summary> 171 /// <param name="vertexQuad">The quad to draw.</param> 172 /// <param name="drawColour">The vertex colour.</param> 173 /// <param name="textureRect">The texture rectangle in texture space.</param> 174 /// <param name="vertexAction">An action that adds vertices to a <see cref="VertexBatch{T}"/>.</param> 175 /// <param name="inflationPercentage">The percentage amount that <paramref name="textureRect"/> should be inflated.</param> 176 /// <param name="blendRangeOverride">The range over which the edges of the <paramref name="textureRect"/> should be blended.</param> 177 /// <param name="textureCoords">The texture coordinates of the quad's vertices.</param> 178 internal void DrawQuad(Quad vertexQuad, ColourInfo drawColour, RectangleF? textureRect = null, Action<TexturedVertex2D> vertexAction = null, Vector2? inflationPercentage = null, 179 Vector2? blendRangeOverride = null, RectangleF? textureCoords = null) 180 { 181 if (TextureGL == null || !TextureGL.Bind()) return; 182 183 TextureGL.DrawQuad(vertexQuad, drawColour, TextureBounds(textureRect), vertexAction, inflationPercentage, blendRangeOverride, TextureBounds(textureCoords)); 184 } 185 186 public override string ToString() => $@"{AssetName} ({Width}, {Height})"; 187 188 /// <summary> 189 /// Whether <see cref="TextureGL"/> is in a usable state. 190 /// </summary> 191 public virtual bool Available => TextureGL.Available; 192 193 #region Disposal 194 195 // Intentionally no finalizer implementation as our disposal is NOOP. Finalizer is implemented in TextureWithRefCount usage. 196 197 public void Dispose() 198 { 199 Dispose(true); 200 GC.SuppressFinalize(this); 201 } 202 203 protected virtual void Dispose(bool isDisposing) 204 { 205 } 206 207 #endregion 208 } 209}