236 lines
7.4 KiB
C#
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;
|
|
}
|
|
}
|