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.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}