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 System.Runtime.InteropServices;
7using osu.Framework.Extensions.ImageExtensions;
8using osu.Framework.Graphics.OpenGL;
9using osu.Framework.Graphics.OpenGL.Buffers;
10using osu.Framework.Graphics.Primitives;
11using osu.Framework.Logging;
12using osuTK.Graphics.ES30;
13using SixLabors.ImageSharp;
14using SixLabors.ImageSharp.PixelFormats;
15using StbiSharp;
16
17namespace osu.Framework.Graphics.Textures
18{
19 /// <summary>
20 /// Low level class for queueing texture uploads to the GPU.
21 /// Should be manually disposed if not queued for upload via <see cref="Texture.SetData"/>.
22 /// </summary>
23 public class TextureUpload : ITextureUpload
24 {
25 /// <summary>
26 /// The target mipmap level to upload into.
27 /// </summary>
28 public int Level { get; set; }
29
30 /// <summary>
31 /// The texture format for this upload.
32 /// </summary>
33 public PixelFormat Format => PixelFormat.Rgba;
34
35 /// <summary>
36 /// The target bounds for this upload. If not specified, will assume to be (0, 0, width, height).
37 /// </summary>
38 public RectangleI Bounds { get; set; }
39
40 public ReadOnlySpan<Rgba32> Data => pixelMemory.Span;
41
42 public int Width => image?.Width ?? 0;
43
44 public int Height => image?.Height ?? 0;
45
46 /// <summary>
47 /// The backing texture. A handle is kept to avoid early GC.
48 /// </summary>
49 private readonly Image<Rgba32> image;
50
51 private ReadOnlyPixelMemory<Rgba32> pixelMemory;
52
53 /// <summary>
54 /// Create an upload from a <see cref="TextureUpload"/>. This is the preferred method.
55 /// </summary>
56 /// <param name="image">The texture to upload.</param>
57 public TextureUpload(Image<Rgba32> image)
58 {
59 this.image = image;
60
61 if (image.Width > GLWrapper.MaxTextureSize || image.Height > GLWrapper.MaxTextureSize)
62 throw new TextureTooLargeForGLException();
63
64 pixelMemory = image.CreateReadOnlyPixelMemory();
65 }
66
67 /// <summary>
68 /// Create an upload from an arbitrary image stream.
69 /// Note that this bypasses per-platform image loading optimisations.
70 /// Use <see cref="TextureLoaderStore"/> as provided from GameHost where possible.
71 /// </summary>
72 /// <param name="stream">The image content.</param>
73 public TextureUpload(Stream stream)
74 : this(LoadFromStream<Rgba32>(stream))
75 {
76 }
77
78 private static bool stbiNotFound;
79
80 internal static Image<TPixel> LoadFromStream<TPixel>(Stream stream) where TPixel : unmanaged, IPixel<TPixel>
81 {
82 if (stbiNotFound)
83 return Image.Load<TPixel>(stream);
84
85 long initialPos = stream.Position;
86
87 try
88 {
89 using (var m = new MemoryStream())
90 {
91 stream.CopyTo(m);
92 using (var stbiImage = Stbi.LoadFromMemory(m, 4))
93 return Image.LoadPixelData(MemoryMarshal.Cast<byte, TPixel>(stbiImage.Data), stbiImage.Width, stbiImage.Height);
94 }
95 }
96 catch (Exception e)
97 {
98 if (e is DllNotFoundException)
99 stbiNotFound = true;
100
101 Logger.Log($"Texture could not be loaded via STB; falling back to ImageSharp: {e.Message}");
102 stream.Position = initialPos;
103 return Image.Load<TPixel>(stream);
104 }
105 }
106
107 /// <summary>
108 /// Create an empty upload. Used by <see cref="FrameBuffer"/> for initialisation.
109 /// </summary>
110 internal TextureUpload()
111 {
112 }
113
114 #region IDisposable Support
115
116 private bool disposed;
117
118 public void Dispose()
119 {
120 Dispose(true);
121 GC.SuppressFinalize(this);
122 }
123
124 protected virtual void Dispose(bool isDisposing)
125 {
126 if (disposed)
127 return;
128
129 image?.Dispose();
130 pixelMemory.Dispose();
131
132 disposed = true;
133 }
134
135 #endregion
136 }
137}