using System.Drawing; using Dashboard.Pal; using OpenTK.Graphics.OpenGL; using OGL = OpenTK.Graphics.OpenGL; namespace Dashboard.Drawing.OpenGL.Pal { public class GLTextureExtension : ITextureExtension, IContextExtensionBase { 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.Context => Context; private List _textures = new List(); 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; 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; } 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.Byte, IntPtr.Zero); break; case TextureType.Texture2D: GL.TexImage2D(Target, 0, (InternalFormat)glFormat, width, height, 0, (OGL.PixelFormat)glFormat, PixelType.Byte, 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.Byte, IntPtr.Zero); break; } } } public void Read(Span buffer, int level = 0, int align = 0) where T : unmanaged { throw new NotImplementedException(); } public unsafe void Write(PixelFormat format, ReadOnlySpan buffer, int level = 0, int align = 4) where T : unmanaged { if (!Context.IsRenderThread) { throw new NotImplementedException(); } 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.Byte, 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); } public void Dispose() { throw new NotImplementedException(); } 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() }; } } }