209 lines
6.5 KiB
C#
209 lines
6.5 KiB
C#
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;
|
|
}
|
|
}
|