using System.Numerics; using Elements.Assets; using Hexa.NET.ImGui; using Hexa.NET.OpenGL; using SDL3; namespace SDL3_TestingSuite.SDL3; public sealed unsafe class OpenGLTexture : IDisposable { private readonly GL _gl; public uint Handle { get; private set; } public int Width { get; } public int Height { get; } public ImTextureRef ImGuiTexture => new ImTextureRef(null, (nint)Handle); public Vector2 Size => new Vector2(Width, Height); public string? FileName; public string? FilePath; public string? Extension; private OpenGLTexture(GL gl, uint handle, int width, int height, string? path = null) { _gl = gl; Handle = handle; Width = width; Height = height; if (path == null) return; FileName = Path.GetFileName(path); FilePath = path; Extension = Path.GetExtension(path); } public static OpenGLTexture FromFile(GL gl, string path) { nint surface = string.Equals(Path.GetExtension(path), ".bmp", StringComparison.OrdinalIgnoreCase) ? SDL.LoadBMP(path) : Image.Load(path); if (surface == nint.Zero) { throw new InvalidOperationException($"Failed to load image '{path}': {SDL.GetError()}"); } try { return FromSurface(gl, surface, path); } finally { SDL.DestroySurface(surface); } } public static OpenGLTexture FromBitmap2D(GL gl, Bitmap2D bmp) { if (bmp == null) throw new ArgumentNullException(nameof(bmp)); int width = bmp.Size.x; int height = bmp.Size.y; // Ensure we upload RGBA8 as unsigned bytes byte[] pixels = new byte[width * height * 4]; for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { var p = bmp.GetPixel32(x, y); int idx = (y * width + x) * 4; pixels[idx + 0] = p.r; pixels[idx + 1] = p.g; pixels[idx + 2] = p.b; pixels[idx + 3] = p.a; } } uint tex = UploadRgba32FromPixels(gl, pixels, width, height); return new OpenGLTexture(gl, tex, width, height); } public static OpenGLTexture FromSurface(GL gl, nint surface, string? path = null) { if (surface == nint.Zero) { throw new ArgumentException("Surface cannot be null.", nameof(surface)); } SDL.PixelFormat uploadFormat = BitConverter.IsLittleEndian ? SDL.PixelFormat.ABGR8888 : SDL.PixelFormat.RGBA8888; nint converted = SDL.ConvertSurface(surface, uploadFormat); if (converted == nint.Zero) { throw new InvalidOperationException($"Failed to convert image surface to RGBA32: {SDL.GetError()}"); } try { SDL.Surface* convertedSurface = (SDL.Surface*)converted; bool locked = false; if (SDL.MustLock(*convertedSurface)) { if (!SDL.LockSurface(converted)) { throw new InvalidOperationException($"Failed to lock image surface: {SDL.GetError()}"); } locked = true; } try { uint texture = UploadRgba32(gl, convertedSurface); return new OpenGLTexture(gl, texture, convertedSurface->Width, convertedSurface->Height, path); } finally { if (locked) { SDL.UnlockSurface(converted); } } } finally { SDL.DestroySurface(converted); } } private static uint UploadRgba32(GL gl, SDL.Surface* surface) { uint texture = 0; gl.GenTextures(1, ref texture); gl.GetIntegerv(GLGetPName.TextureBinding2D, out int previousTexture); gl.BindTexture(GLTextureTarget.Texture2D, texture); gl.TexParameteri(GLTextureTarget.Texture2D, GLTextureParameterName.MinFilter, (int)GLTextureMinFilter.Linear); gl.TexParameteri(GLTextureTarget.Texture2D, GLTextureParameterName.MagFilter, (int)GLTextureMagFilter.Linear); gl.TexParameteri(GLTextureTarget.Texture2D, GLTextureParameterName.WrapS, (int)GLEnum.ClampToEdge); gl.TexParameteri(GLTextureTarget.Texture2D, GLTextureParameterName.WrapT, (int)GLEnum.ClampToEdge); gl.PixelStorei(GLPixelStoreParameter.UnpackAlignment, 1); gl.PixelStorei(GLPixelStoreParameter.UnpackRowLength, surface->Pitch / 4); gl.TexImage2D( GLTextureTarget.Texture2D, 0, GLInternalFormat.Rgba, surface->Width, surface->Height, 0, GLPixelFormat.Rgba, GLPixelType.UnsignedByte, surface->Pixels); gl.PixelStorei(GLPixelStoreParameter.UnpackRowLength, 0); gl.BindTexture(GLTextureTarget.Texture2D, (uint)previousTexture); return texture; } private static uint UploadRgba32FromPixels(GL gl, byte[] pixels, int width, int height) { uint texture = 0; gl.GenTextures(1, ref texture); gl.GetIntegerv(GLGetPName.TextureBinding2D, out int previousTexture); gl.BindTexture(GLTextureTarget.Texture2D, texture); gl.TexParameteri(GLTextureTarget.Texture2D, GLTextureParameterName.MinFilter, (int)GLTextureMinFilter.Linear); gl.TexParameteri(GLTextureTarget.Texture2D, GLTextureParameterName.MagFilter, (int)GLTextureMagFilter.Linear); gl.TexParameteri(GLTextureTarget.Texture2D, GLTextureParameterName.WrapS, (int)GLEnum.ClampToEdge); gl.TexParameteri(GLTextureTarget.Texture2D, GLTextureParameterName.WrapT, (int)GLEnum.ClampToEdge); gl.PixelStorei(GLPixelStoreParameter.UnpackAlignment, 1); fixed (byte* ptr = &pixels[0]) { gl.TexImage2D( GLTextureTarget.Texture2D, 0, GLInternalFormat.Rgba, width, height, 0, GLPixelFormat.Rgba, GLPixelType.UnsignedByte, (nint)ptr); } gl.BindTexture(GLTextureTarget.Texture2D, (uint)previousTexture); return texture; } public void Dispose() { if (Handle == 0) { return; } _gl.DeleteTexture(Handle); Handle = 0; } }