Dashboard/Dashboard.OpenGL/GLTextureExtension.cs

308 lines
11 KiB
C#

using System.Drawing;
using Dashboard.Drawing;
using Dashboard.Pal;
using OpenTK.Graphics.OpenGL;
using OGL = OpenTK.Graphics.OpenGL;
namespace Dashboard.OpenGL
{
public class GLTextureExtension : ITextureExtension, IContextExtensionBase<GLDeviceContext>
{
public string DriverName => "Dashboard OpenGL Texture Extension";
public string DriverVendor => "Dashboard";
public Version DriverVersion => new Version(0, 1, 0);
public GLDeviceContext Context { get; private set; } = null!;
public bool SupportsArbTextureStorage { get; private set; }
public bool SupportsAnisotropy { get; private set; }
IContextBase IContextExtensionBase.Context => Context;
DeviceContext IContextExtensionBase<DeviceContext>.Context => Context;
private List<GLTexture> _textures = new List<GLTexture>();
public void Dispose()
{
throw new NotImplementedException();
}
public void Require(GLDeviceContext context)
{
Context = context;
SupportsArbTextureStorage = Context.DriverVersion >= new Version(4, 2) ||
Context.IsGLExtensionAvailable("GL_ARB_texture_storage");
SupportsAnisotropy = Context.DriverVersion >= new Version() ||
Context.IsGLExtensionAvailable("GL_EXT_texture_filter_anisotropic") ||
Context.IsGLExtensionAvailable("GL_ARB_texture_filter_anisotropic");
}
public void Require(DeviceContext context) => Require((GLDeviceContext)context);
public void Require(IContextBase context) => Require((GLDeviceContext)context);
public GLTexture CreateTexture(TextureType type)
{
GLTexture texture = new GLTexture(this, type);
lock (_textures) _textures.Add(texture);
return texture;
}
internal void TextureDisposed(GLTexture texture)
{
lock (_textures) _textures.Remove(texture);
}
ITexture ITextureExtension.CreateTexture(TextureType type) => CreateTexture(type);
}
public class GLTexture(GLTextureExtension extension, TextureType type) : ITexture
{
public int Handle { get; private set; } = 0;
public bool IsValid => Handle != 0;
public TextureType Type { get; } = type;
public PixelFormat Format { get; private set; } = PixelFormat.Rgba8I;
public ColorSwizzle Swizzle { get; set; } = ColorSwizzle.Default;
public TextureFilter MinifyFilter { get; set; } = TextureFilter.Linear;
public TextureFilter MagnifyFilter { get; set; } = TextureFilter.Linear;
public Color BorderColor { get; set; } = Color.White;
public TextureRepeat RepeatS { get; set; } = TextureRepeat.Repeat;
public TextureRepeat RepeatT { get; set; } = TextureRepeat.Repeat;
public TextureRepeat RepeatR { get; set; } = TextureRepeat.Repeat;
public int Anisotropy { get; set; } = 0;
public int Width { get; private set; } = 0;
public int Height { get; private set; } = 0;
public int Depth { get; private set; } = 0;
public int Levels { get; private set; } = 0;
public bool Premultiplied { get; set; } = false;
private TextureTarget Target { get; } = type switch
{
TextureType.Texture1D => TextureTarget.Texture1d,
TextureType.Texture2D => TextureTarget.Texture2d,
TextureType.Texture3D => TextureTarget.Texture3d,
TextureType.Texture2DArray => TextureTarget.Texture2dArray,
TextureType.Texture2DCube => TextureTarget.TextureCubeMap,
_ => throw new NotSupportedException()
};
private GLTextureExtension Extension { get; } = extension;
private GLDeviceContext Context => Extension.Context;
~GLTexture()
{
Dispose(false);
}
public void SetStorage(PixelFormat format, int width, int height, int depth, int levels)
{
if (!Context.IsRenderThread)
{
Context.InvokeBeforeDraw(() => SetStorage(format, width, height, depth, levels)).Wait();
return;
}
if (levels == 0)
{
levels = Math.Max(Math.ILogB(width), Math.ILogB(height));
}
Bind();
SizedInternalFormat glFormat = GetFormat(format);
if (Extension.SupportsArbTextureStorage)
{
switch (Type)
{
case TextureType.Texture1D:
GL.TexStorage1D(Target, levels, glFormat, width);
break;
case TextureType.Texture2D:
GL.TexStorage2D(Target, levels, glFormat, width, height);
break;
case TextureType.Texture3D:
case TextureType.Texture2DArray:
case TextureType.Texture2DCube:
GL.TexStorage3D(Target, levels, glFormat, width, height, depth);
break;
}
}
else
{
switch (Type)
{
case TextureType.Texture1D:
GL.TexImage1D(Target, 0, (InternalFormat)glFormat, width, 0, (OGL.PixelFormat)glFormat, PixelType.UnsignedByte, IntPtr.Zero);
break;
case TextureType.Texture2D:
GL.TexImage2D(Target, 0, (InternalFormat)glFormat, width, height, 0, (OGL.PixelFormat)glFormat, PixelType.UnsignedByte, IntPtr.Zero);
break;
case TextureType.Texture3D:
case TextureType.Texture2DArray:
case TextureType.Texture2DCube:
GL.TexImage3D(Target, 0, (InternalFormat)glFormat, width, height, depth, 0, (OGL.PixelFormat)glFormat, PixelType.UnsignedByte, IntPtr.Zero);
break;
}
}
Width = width;
Height = height;
Depth = depth;
Levels = levels;
}
public void Read<T>(Span<T> buffer, int level = 0, int align = 0) where T : unmanaged
{
throw new NotImplementedException();
}
void ITexture.Write<T>(PixelFormat format, ReadOnlySpan<T> buffer, int level, int align) => Write(format, buffer, level, align, null);
public unsafe void Write<T>(PixelFormat format, ReadOnlySpan<T> buffer, int level = 0, int align = 4, TimeSpan? timeout = null) where T : unmanaged
{
if (!Context.IsRenderThread)
{
T[] bufferArray = buffer.ToArray();
Task task = Context.InvokeBeforeDraw(() => Write<T>(format, bufferArray, level, align));
if (timeout.HasValue)
task.Wait(timeout.Value);
else
task.Wait();
}
Bind();
OGL::PixelFormat glFormat = format switch
{
PixelFormat.R8I or PixelFormat.R16F => OGL.PixelFormat.Red,
PixelFormat.Rg8I or PixelFormat.Rg16F => OGL.PixelFormat.Rg,
PixelFormat.Rgb8I or PixelFormat.Rgb16F => OGL.PixelFormat.Rgb,
PixelFormat.Rgba8I or PixelFormat.Rgba16F => OGL.PixelFormat.Rgba,
_ => throw new NotSupportedException()
};
PixelType glType = format switch
{
PixelFormat.R8I or PixelFormat.Rg8I or PixelFormat.Rgb8I or PixelFormat.Rgba8I => PixelType.UnsignedByte,
PixelFormat.R16F or PixelFormat.Rg16F or PixelFormat.Rgb16F or PixelFormat.Rgba16F => PixelType.HalfFloat,
_ => throw new NotSupportedException()
};
GL.PixelStorei(PixelStoreParameter.UnpackAlignment, align);
fixed (T* ptr = buffer)
{
switch (Type)
{
case TextureType.Texture1D:
GL.TexSubImage1D(Target, level, 0, Width, glFormat, glType, ptr);
break;
case TextureType.Texture2D:
GL.TexSubImage2D(Target, level, 0, 0, Width, Height, glFormat, glType, ptr);
break;
case TextureType.Texture2DCube:
case TextureType.Texture3D:
case TextureType.Texture2DArray:
GL.TexSubImage3D(Target, level, 0, 0, 0, Width, Height, Depth, glFormat, glType, ptr);
break;
}
}
}
public void Premultiply()
{
throw new NotImplementedException();
}
public void Unmultiply()
{
throw new NotImplementedException();
}
public void GenerateMipmaps()
{
if (!Context.IsRenderThread)
{
Context.InvokeBeforeDraw(GenerateMipmaps).Wait();
return;
}
Bind();
GL.GenerateMipmap(Target);
}
private void Dispose(bool disposing)
{
if (IsDisposed)
return;
IsDisposed = true;
if (disposing)
{
if (Thread.CurrentThread != Context.RendererThread)
{
Context.Collector.DeleteTexture(Handle);
}
else
{
GL.DeleteTexture(Handle);
}
Handle = 0;
GC.SuppressFinalize(this);
}
else
{
Context.Collector.DeleteTexture(Handle);
}
}
public bool IsDisposed { get; private set; }
public void Dispose() => Dispose(false);
private void Bind()
{
if (Handle == 0)
{
Handle = GL.GenTexture();
}
GL.BindTexture(Target, Handle);
}
private static SizedInternalFormat GetFormat(PixelFormat format)
{
return format switch
{
PixelFormat.R8I => SizedInternalFormat.R8,
PixelFormat.R16F => SizedInternalFormat.R16f,
PixelFormat.Rg8I => SizedInternalFormat.Rg8,
PixelFormat.Rg16F => SizedInternalFormat.Rg16f,
PixelFormat.Rgb8I => SizedInternalFormat.Rgb8,
PixelFormat.Rgb16F => SizedInternalFormat.Rgb16f,
PixelFormat.Rgba8I => SizedInternalFormat.Rgba8,
PixelFormat.Rgba16F => SizedInternalFormat.Rgba16f,
_ => throw new ArgumentOutOfRangeException()
};
}
private static PixelFormat GetFormat(SizedInternalFormat format)
{
return format switch
{
SizedInternalFormat.R8 => PixelFormat.R8I,
SizedInternalFormat.R16f => PixelFormat.R16F,
SizedInternalFormat.Rg8 => PixelFormat.Rg8I,
SizedInternalFormat.Rg16f => PixelFormat.Rg16F,
SizedInternalFormat.Rgb8 => PixelFormat.Rgb8I,
SizedInternalFormat.Rgb16f => PixelFormat.Rgb16F,
SizedInternalFormat.Rgba8 => PixelFormat.Rgba8I,
SizedInternalFormat.Rgba16f => PixelFormat.Rgba16F,
_ => throw new ArgumentOutOfRangeException()
};
}
}
}