// 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.IO; using System.Runtime.InteropServices; using osu.Framework.Extensions.ImageExtensions; using osu.Framework.Graphics.OpenGL; using osu.Framework.Graphics.OpenGL.Buffers; using osu.Framework.Graphics.Primitives; using osu.Framework.Logging; using osuTK.Graphics.ES30; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using StbiSharp; namespace osu.Framework.Graphics.Textures { /// /// Low level class for queueing texture uploads to the GPU. /// Should be manually disposed if not queued for upload via . /// public class TextureUpload : ITextureUpload { /// /// The target mipmap level to upload into. /// public int Level { get; set; } /// /// The texture format for this upload. /// public PixelFormat Format => PixelFormat.Rgba; /// /// The target bounds for this upload. If not specified, will assume to be (0, 0, width, height). /// public RectangleI Bounds { get; set; } public ReadOnlySpan Data => pixelMemory.Span; public int Width => image?.Width ?? 0; public int Height => image?.Height ?? 0; /// /// The backing texture. A handle is kept to avoid early GC. /// private readonly Image image; private ReadOnlyPixelMemory pixelMemory; /// /// Create an upload from a . This is the preferred method. /// /// The texture to upload. public TextureUpload(Image image) { this.image = image; if (image.Width > GLWrapper.MaxTextureSize || image.Height > GLWrapper.MaxTextureSize) throw new TextureTooLargeForGLException(); pixelMemory = image.CreateReadOnlyPixelMemory(); } /// /// Create an upload from an arbitrary image stream. /// Note that this bypasses per-platform image loading optimisations. /// Use as provided from GameHost where possible. /// /// The image content. public TextureUpload(Stream stream) : this(LoadFromStream(stream)) { } private static bool stbiNotFound; internal static Image LoadFromStream(Stream stream) where TPixel : unmanaged, IPixel { if (stbiNotFound) return Image.Load(stream); long initialPos = stream.Position; try { using (var m = new MemoryStream()) { stream.CopyTo(m); using (var stbiImage = Stbi.LoadFromMemory(m, 4)) return Image.LoadPixelData(MemoryMarshal.Cast(stbiImage.Data), stbiImage.Width, stbiImage.Height); } } catch (Exception e) { if (e is DllNotFoundException) stbiNotFound = true; Logger.Log($"Texture could not be loaded via STB; falling back to ImageSharp: {e.Message}"); stream.Position = initialPos; return Image.Load(stream); } } /// /// Create an empty upload. Used by for initialisation. /// internal TextureUpload() { } #region IDisposable Support private bool disposed; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool isDisposing) { if (disposed) return; image?.Dispose(); pixelMemory.Dispose(); disposed = true; } #endregion } }