Files
SDL3-TestingSuite/SDL3/OpenGLTexture.cs
T
2026-07-26 00:44:17 -05:00

236 lines
7.4 KiB
C#

using System.Numerics;
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 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);
}
}
public static OpenGLTexture FromBgra32(GL gl, nint pixels, int width, int height, int stride)
{
if (pixels == nint.Zero)
{
throw new ArgumentException("Pixels cannot be null.", nameof(pixels));
}
uint texture = CreateTexture(gl, pixels, width, height, stride, GLPixelFormat.Bgra);
return new OpenGLTexture(gl, texture, width, height);
}
public static OpenGLTexture FromHandle(GL gl, uint handle, int width, int height)
{
if (handle == 0)
{
throw new ArgumentException("Texture handle cannot be zero.", nameof(handle));
}
return new OpenGLTexture(gl, handle, width, height);
}
public void UpdateBgra32(nint pixels, int stride)
{
if (pixels == nint.Zero)
{
throw new ArgumentException("Pixels cannot be null.", nameof(pixels));
}
_gl.GetIntegerv(GLGetPName.TextureBinding2D, out int previousTexture);
_gl.BindTexture(GLTextureTarget.Texture2D, Handle);
_gl.PixelStorei(GLPixelStoreParameter.UnpackAlignment, 1);
_gl.PixelStorei(GLPixelStoreParameter.UnpackRowLength, stride / 4);
_gl.TexSubImage2D(
GLTextureTarget.Texture2D,
0,
0,
0,
Width,
Height,
GLPixelFormat.Bgra,
GLPixelType.UnsignedByte,
pixels);
_gl.PixelStorei(GLPixelStoreParameter.UnpackRowLength, 0);
_gl.BindTexture(GLTextureTarget.Texture2D, (uint)previousTexture);
}
private static uint UploadRgba32(GL gl, SDL.Surface* surface)
{
return CreateTexture(gl, surface->Pixels, surface->Width, surface->Height, surface->Pitch, GLPixelFormat.Rgba);
}
private static uint CreateTexture(GL gl, nint pixels, int width, int height, int stride, GLPixelFormat pixelFormat)
{
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, stride / 4);
gl.TexImage2D(
GLTextureTarget.Texture2D,
0,
GLInternalFormat.Rgba,
width,
height,
0,
pixelFormat,
GLPixelType.UnsignedByte,
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;
}
}