diff --git a/Dashboard.Common/ImageProperties.cs b/Dashboard.Common/ImageProperties.cs
index a7be347..0317116 100644
--- a/Dashboard.Common/ImageProperties.cs
+++ b/Dashboard.Common/ImageProperties.cs
@@ -6,16 +6,31 @@ namespace Dashboard
///
/// Pixel format for images.
///
+ [Flags]
public enum PixelFormat
{
- R8I,
- Rg8I,
- Rgb8I,
- Rgba8I,
- R16F,
- Rg816F,
- Rgb16F,
- Rgba16F,
+ None = 0,
+
+ R8I = R | I8,
+ Rg8I = Rg | I8,
+ Rgb8I = Rgb | I8,
+ Rgba8I = Rgba | I8,
+ R16F = R | F16,
+ Rg16F = Rg | F16,
+ Rgb16F = Rgb | F16,
+ Rgba16F = Rgba | F16,
+
+ // Channels
+ R = 0x01,
+ Rg = 0x02,
+ Rgb = 0x03,
+ Rgba = 0x04,
+ A = 0x05,
+ ColorMask = 0x0F,
+
+ I8 = 0x10,
+ F16 = 0x20,
+ TypeMask = 0xF0,
}
///
diff --git a/Dashboard.Common/Pal/AppContext.cs b/Dashboard.Common/Pal/AppContext.cs
new file mode 100644
index 0000000..fe4f1f7
--- /dev/null
+++ b/Dashboard.Common/Pal/AppContext.cs
@@ -0,0 +1,136 @@
+using Dashboard.Collections;
+using Dashboard.Windowing;
+
+namespace Dashboard.Pal
+{
+ public abstract class AppContext : IContextBase
+ {
+ public abstract string DriverName { get; }
+ public abstract string DriverVendor { get; }
+ public abstract Version DriverVersion { get; }
+
+ public virtual string AppTitle { get; set; } = "Dashboard Application";
+
+ public bool IsInitialized { get; private set; } = false;
+ public bool IsDisposed { get; private set; } = false;
+ public IContextDebugger? Debugger { get; set; }
+
+ private readonly TypeDictionary _extensions =
+ new TypeDictionary(true);
+ private readonly TypeDictionary> _preloadedExtensions =
+ new TypeDictionary>(true);
+
+ ~AppContext()
+ {
+ InvokeDispose(false);
+ }
+
+ public void Initialize()
+ {
+ if (IsInitialized)
+ return;
+
+ IsInitialized = true;
+
+ InitializeInternal();
+ }
+
+ protected virtual void InitializeInternal()
+ {
+ }
+
+ public virtual void RunEvents(bool wait)
+ {
+ if (!IsInitialized)
+ Initialize();
+ }
+
+ public void Run() => Run(true, CancellationToken.None);
+
+ public void Run(bool wait) => Run(wait, CancellationToken.None);
+
+ public void Run(bool waitForEvents, CancellationToken token)
+ {
+ InitializeInternal();
+
+ while (!token.IsCancellationRequested)
+ {
+ RunEvents(waitForEvents);
+ }
+ }
+
+ #region Window API
+ ///
+ /// Creates a window. It could be a virtual window, or a physical window.
+ ///
+ /// A window.
+ public abstract IWindow CreateWindow();
+
+ ///
+ /// Always creates a physical window.
+ ///
+ /// A physical window.
+ public abstract IPhysicalWindow CreatePhysicalWindow();
+ #endregion
+
+ public bool IsExtensionAvailable() where T : IAppContextExtension
+ {
+ return _extensions.Contains() || _preloadedExtensions.Contains();
+ }
+
+ public bool ExtensionPreload() where T : IAppContextExtension, new()
+ {
+ return _preloadedExtensions.Add(() => new T());
+ }
+
+ public T ExtensionRequire() where T : IAppContextExtension, new()
+ {
+ T? extension = default;
+
+ if (_extensions.TryGet(out extension))
+ return extension;
+
+ lock (_extensions)
+ {
+ if (_extensions.TryGet(out extension))
+ return extension;
+
+ if (_preloadedExtensions.Remove(out Func? loader))
+ {
+ extension = (T)loader!();
+ }
+ else
+ {
+ extension = new T();
+ }
+
+ _extensions.Add(extension);
+ extension.Require(this);
+ }
+
+ return extension;
+ }
+
+ protected virtual void Dispose(bool isDisposing)
+ {
+ if (!isDisposing) return;
+
+ foreach (IAppContextExtension extension in _extensions)
+ {
+ extension.Dispose();
+ }
+
+ GC.SuppressFinalize(this);
+ }
+
+ private void InvokeDispose(bool isDisposing)
+ {
+ if (IsDisposed) return;
+ IsDisposed = true;
+
+ Dispose(isDisposing);
+ }
+
+ public void Dispose() => InvokeDispose(true);
+ }
+}
diff --git a/Dashboard.Common/Pal/DeviceContext.cs b/Dashboard.Common/Pal/DeviceContext.cs
new file mode 100644
index 0000000..a15c87f
--- /dev/null
+++ b/Dashboard.Common/Pal/DeviceContext.cs
@@ -0,0 +1,98 @@
+using Dashboard.Collections;
+
+namespace Dashboard.Pal
+{
+ public abstract class DeviceContext : IContextBase
+ {
+ private readonly TypeDictionary _extensions =
+ new TypeDictionary(true);
+ private readonly TypeDictionary> _preloadedExtensions =
+ new TypeDictionary>(true);
+
+ public abstract string DriverName { get; }
+ public abstract string DriverVendor { get; }
+ public abstract Version DriverVersion { get; }
+
+ public bool IsDisposed { get; private set; }
+
+ ///
+ /// Optional debugging object for your pleasure.
+ ///
+ public IContextDebugger? Debugger { get; set; }
+
+ ~DeviceContext()
+ {
+ Dispose(false);
+ }
+
+ public virtual void Begin() { }
+
+ // public abstract void Paint(object renderbuffer);
+
+ public virtual void End() { }
+
+ public bool IsExtensionAvailable() where T : IDeviceContextExtension
+ {
+ return _extensions.Contains() || _preloadedExtensions.Contains();
+ }
+
+ public bool ExtensionPreload() where T : IDeviceContextExtension, new()
+ {
+ return _preloadedExtensions.Add(() => new T());
+ }
+
+ public T ExtensionRequire() where T : IDeviceContextExtension, new()
+ {
+ T? extension = default;
+
+ if (_extensions.TryGet(out extension))
+ return extension;
+
+ lock (_extensions)
+ {
+ if (_extensions.TryGet(out extension))
+ return extension;
+
+ if (_preloadedExtensions.Remove(out Func? loader))
+ {
+ extension = (T)loader!();
+ }
+ else
+ {
+ extension = new T();
+ }
+
+ _extensions.Add(extension);
+ extension.Require(this);
+ }
+
+ return extension;
+ }
+
+ ///
+ /// Implement your dispose in this function.
+ ///
+ /// True if disposing, false otherwise.
+ protected virtual void Dispose(bool isDisposing)
+ {
+ if (!isDisposing) return;
+
+ foreach (IDeviceContextExtension extension in _extensions)
+ {
+ extension.Dispose();
+ }
+
+ GC.SuppressFinalize(this);
+ }
+
+ private void InvokeDispose(bool isDisposing)
+ {
+ if (IsDisposed) return;
+ IsDisposed = true;
+
+ Dispose(isDisposing);
+ }
+
+ public void Dispose() => InvokeDispose(true);
+ }
+}
diff --git a/Dashboard.Common/Pal/IAppContextExtension.cs b/Dashboard.Common/Pal/IAppContextExtension.cs
new file mode 100644
index 0000000..da27c13
--- /dev/null
+++ b/Dashboard.Common/Pal/IAppContextExtension.cs
@@ -0,0 +1,7 @@
+namespace Dashboard.Pal
+{
+ public interface IAppContextExtension : IContextExtensionBase
+ {
+
+ }
+}
diff --git a/Dashboard.Common/Pal/IContextBase.cs b/Dashboard.Common/Pal/IContextBase.cs
new file mode 100644
index 0000000..f1a8b01
--- /dev/null
+++ b/Dashboard.Common/Pal/IContextBase.cs
@@ -0,0 +1,103 @@
+namespace Dashboard.Pal
+{
+ ///
+ /// Information about this context interface.
+ ///
+ public interface IContextInterfaceInfo
+ {
+ ///
+ /// Name of this driver.
+ ///
+ string DriverName { get; }
+
+ ///
+ /// The vendor for this driver.
+ ///
+ string DriverVendor { get; }
+
+ ///
+ /// The version of this driver.
+ ///
+ Version DriverVersion { get; }
+ }
+
+ ///
+ /// The base context interface.
+ ///
+ public interface IContextBase : IContextInterfaceInfo, IDisposable
+ {
+ ///
+ /// The debugger for this context.
+ ///
+ IContextDebugger? Debugger { get; set; }
+ }
+
+ ///
+ /// The base context interface.
+ ///
+ /// The context type.
+ /// The extension type, if used.
+ public interface IContextBase : IContextBase
+ where TContext : IContextBase
+ where TExtension : IContextExtensionBase
+ {
+ ///
+ /// Is such an extension available?
+ ///
+ /// The extension to check.
+ /// True if the extension is available.
+ bool IsExtensionAvailable() where T : TExtension;
+
+ ///
+ /// Preload extensions, to be lazy loaded when required.
+ ///
+ /// The extension to preload.
+ ///
+ /// True if the extension was added to the preload set. Otherwise, already loaded or another extension
+ /// exists which provides this.
+ ///
+ bool ExtensionPreload() where T : TExtension, new();
+
+ ///
+ /// Require an extension.
+ ///
+ /// The extension to require.
+ /// The extension instance.
+ T ExtensionRequire() where T : TExtension, new();
+ }
+
+ ///
+ /// Base interface for all context extensions.
+ ///
+ public interface IContextExtensionBase : IContextInterfaceInfo, IDisposable
+ {
+ ///
+ /// The context that loaded this extension.
+ ///
+ IContextBase Context { get; }
+
+ ///
+ /// Require this extension.
+ ///
+ /// The context that required this extension.
+ void Require(IContextBase context);
+ }
+
+ ///
+ /// Base interface for all context extensions.
+ ///
+ public interface IContextExtensionBase : IContextExtensionBase
+ where TContext : IContextBase
+ {
+ ///
+ /// The context that loaded this extension.
+ ///
+ new TContext Context { get; }
+
+ ///
+ /// Require this extension.
+ ///
+ /// The context that required this extension.
+ void Require(TContext context);
+ }
+}
diff --git a/Dashboard.Common/Pal/IContextDebugger.cs b/Dashboard.Common/Pal/IContextDebugger.cs
new file mode 100644
index 0000000..6706d57
--- /dev/null
+++ b/Dashboard.Common/Pal/IContextDebugger.cs
@@ -0,0 +1,10 @@
+namespace Dashboard.Pal
+{
+ public interface IContextDebugger : IDisposable
+ {
+ void LogDebug(string message);
+ void LogInfo(string message);
+ void LogWarning(string message);
+ void LogError(string message);
+ }
+}
diff --git a/Dashboard.Common/Pal/IDeviceContextExtension.cs b/Dashboard.Common/Pal/IDeviceContextExtension.cs
new file mode 100644
index 0000000..87f7a8e
--- /dev/null
+++ b/Dashboard.Common/Pal/IDeviceContextExtension.cs
@@ -0,0 +1,6 @@
+namespace Dashboard.Pal
+{
+ public interface IDeviceContextExtension : IContextExtensionBase
+ {
+ }
+}
diff --git a/Dashboard.Common/Pal/ITextureExtension.cs b/Dashboard.Common/Pal/ITextureExtension.cs
new file mode 100644
index 0000000..5432375
--- /dev/null
+++ b/Dashboard.Common/Pal/ITextureExtension.cs
@@ -0,0 +1,83 @@
+using System.Drawing;
+
+namespace Dashboard.Pal
+{
+ public interface ITextureExtension : IDeviceContextExtension
+ {
+ ITexture CreateTexture(TextureType type);
+ }
+
+ public enum TextureType
+ {
+ Texture1D,
+ Texture2D,
+ Texture2DArray,
+ Texture2DCube,
+ Texture3D,
+ }
+
+ public enum TextureFilter
+ {
+ Nearest,
+ Linear,
+ NearestMipmapNearest,
+ LinearMipmapNearest,
+ NearestMipmapLinear,
+ LinearMipmapLinear,
+ Anisotropic,
+ }
+
+ public enum TextureRepeat
+ {
+ Repeat,
+ MirroredRepeat,
+ ClampToEdge,
+ ClampToBorder,
+ MirrorClampToEdge,
+ }
+
+ public enum CubeMapFace
+ {
+ PositiveX,
+ PositiveY,
+ PositiveZ,
+ NegativeX,
+ NegativeY,
+ NegativeZ,
+ }
+
+ public interface ITexture : IDisposable
+ {
+ public TextureType Type { get; }
+ public PixelFormat Format { get; }
+
+ public int Width { get; }
+ public int Height { get; }
+ public int Depth { get; }
+ public int Levels { get; }
+
+ public bool Premultiplied { get; set; }
+
+ public ColorSwizzle Swizzle { get; set; }
+
+ public TextureFilter MinifyFilter { get; set; }
+
+ public TextureFilter MagnifyFilter { get; set; }
+
+ public Color BorderColor { get; set; }
+
+ public TextureRepeat RepeatS { get; set; }
+
+ public TextureRepeat RepeatT { get; set; }
+
+ public TextureRepeat RepeatR { get; set; }
+ public int Anisotropy { get; set; }
+
+ void SetStorage(PixelFormat format, int width, int height, int depth, int levels);
+ void Read(Span buffer, int level = 0, int align = 0) where T : unmanaged;
+ void Write(PixelFormat format, ReadOnlySpan buffer, int level = 0, int align = 4) where T : unmanaged;
+ void Premultiply();
+ void Unmultiply();
+ void GenerateMipmaps();
+ }
+}
diff --git a/Dashboard.Drawing.OpenGL/ContextExecutor.cs b/Dashboard.Drawing.OpenGL/ContextExecutor.cs
index 55513be..5ecb66b 100644
--- a/Dashboard.Drawing.OpenGL/ContextExecutor.cs
+++ b/Dashboard.Drawing.OpenGL/ContextExecutor.cs
@@ -1,6 +1,7 @@
using System.Drawing;
using Dashboard.Drawing.OpenGL.Executors;
using Dashboard.OpenGL;
+using OpenTK.Mathematics;
namespace Dashboard.Drawing.OpenGL
{
@@ -130,10 +131,13 @@ namespace Dashboard.Drawing.OpenGL
public void Draw(DrawQueue drawqueue) => Draw(drawqueue, new RectangleF(new PointF(0f,0f), Context.FramebufferSize));
- public virtual void Draw(DrawQueue drawQueue, RectangleF bounds)
+ public virtual void Draw(DrawQueue drawQueue, RectangleF bounds, float scale = 1.0f)
{
BeginDraw();
+ if (scale != 1.0f)
+ TransformStack.Push(Matrix4.CreateScale(scale, scale, 1));
+
foreach (ICommandFrame frame in drawQueue)
{
if (_executorsMap.TryGetValue(frame.Command.Extension.Name, out ICommandExecutor? executor))
diff --git a/Dashboard.Drawing.OpenGL/Pal/GLDeviceContext.cs b/Dashboard.Drawing.OpenGL/Pal/GLDeviceContext.cs
new file mode 100644
index 0000000..29c489e
--- /dev/null
+++ b/Dashboard.Drawing.OpenGL/Pal/GLDeviceContext.cs
@@ -0,0 +1,160 @@
+using System.Collections.Concurrent;
+using System.Collections.Immutable;
+using Dashboard.OpenGL;
+using Dashboard.Pal;
+using OpenTK;
+using OpenTK.Graphics;
+using OpenTK.Graphics.OpenGL;
+
+namespace Dashboard.Drawing.OpenGL.Pal
+{
+ internal class GLContextBindingsContext(IGLContext context) : IBindingsContext
+ {
+ public IntPtr GetProcAddress(string procName)
+ {
+ return context.GetProcAddress(procName);
+ }
+ }
+
+ public class GLDeviceContext : DeviceContext
+ {
+ public IGLContext GLContext { get; }
+
+ public override string DriverName => "Dashboard OpenGL Device Context";
+ public override string DriverVendor => "Dashboard";
+ public override Version DriverVersion => new Version(0, 1, 0);
+
+ public Version GLVersion { get; }
+ public string GLRenderer { get; }
+ public string GLVendor { get; }
+ public ImmutableHashSet Extensions { get; }
+
+ public Thread RendererThread { get; } = Thread.CurrentThread;
+ public bool IsRenderThread => RendererThread == Thread.CurrentThread;
+
+ private readonly ConcurrentQueue _beforeDrawActions = new ConcurrentQueue();
+ private readonly ConcurrentQueue _afterDrawActions = new ConcurrentQueue();
+
+ public GLDeviceContext(IGLContext context)
+ {
+ GLContext = context;
+ context.MakeCurrent();
+ GLLoader.LoadBindings(new GLContextBindingsContext(context));
+
+ context.Disposed += Dispose;
+
+ GL.GetInteger(GetPName.MajorVersion, out int major);
+ GL.GetInteger(GetPName.MinorVersion, out int minor);
+ GLVersion = new Version(major, minor);
+
+ GLRenderer = GL.GetString(StringName.Renderer) ?? string.Empty;
+ GLVendor = GL.GetString(StringName.Vendor) ?? string.Empty;
+
+ HashSet extensions = new HashSet();
+ GL.GetInteger(GetPName.NumExtensions, out int extensionCount);
+ for (uint i = 0; i < extensionCount; i++)
+ {
+ string? ext = GL.GetStringi(StringName.Extensions, i);
+ if (ext != null)
+ extensions.Add(ext);
+ }
+
+ Extensions = extensions.ToImmutableHashSet();
+
+ ExtensionPreload();
+ }
+
+ public bool IsGLExtensionAvailable(string name)
+ {
+ return Extensions.Contains(name);
+ }
+
+ public void AssertGLExtension(string name)
+ {
+ if (IsGLExtensionAvailable(name))
+ return;
+
+ throw new NotSupportedException($"The OpenGL extension \"{name}\" is not supported by this context.");
+ }
+
+ public void InvokeBeforeDraw(Task task) => _beforeDrawActions.Enqueue(task);
+
+ public void InvokeAfterDraw(Task task) => _afterDrawActions.Enqueue(task);
+
+ public Task InvokeBeforeDraw(Action action)
+ {
+ Task task = new Task(action);
+ _beforeDrawActions.Enqueue(task);
+ return task;
+ }
+
+ public Task InvokeAfterDraw(Action action)
+ {
+ Task task = new Task(action);
+ _afterDrawActions.Enqueue(task);
+ return task;
+ }
+
+ public Task InvokeBeforeDraw(Func function)
+ {
+ Task task = new Task(function);
+ _beforeDrawActions.Enqueue(task);
+ return task;
+ }
+
+ public Task InvokeAfterDraw(Func function)
+ {
+ Task task = new Task(function);
+ _afterDrawActions.Enqueue(task);
+ return task;
+ }
+
+ public Task InvokeOnRenderThread(Action action)
+ {
+ if (IsRenderThread)
+ {
+ action();
+ return Task.CompletedTask;
+ }
+
+ return InvokeBeforeDraw(action);
+ }
+
+ public Task InvokeOnRenderThread(Func function)
+ {
+ return IsRenderThread ? Task.FromResult(function()) : InvokeBeforeDraw(function);
+ }
+
+ public override void Begin()
+ {
+ base.Begin();
+
+ GLContext.MakeCurrent();
+
+ while (_beforeDrawActions.TryDequeue(out Task? action))
+ {
+ action.RunSynchronously();
+ }
+ }
+
+ public override void End()
+ {
+ base.End();
+
+ while (_afterDrawActions.TryDequeue(out Task? action))
+ {
+ action.RunSynchronously();
+ }
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (isDisposing)
+ {
+ GLContext.Disposed -= Dispose;
+ }
+ }
+ }
+}
diff --git a/Dashboard.Drawing.OpenGL/Pal/GLTextureExtension.cs b/Dashboard.Drawing.OpenGL/Pal/GLTextureExtension.cs
new file mode 100644
index 0000000..2050b60
--- /dev/null
+++ b/Dashboard.Drawing.OpenGL/Pal/GLTextureExtension.cs
@@ -0,0 +1,257 @@
+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()
+ };
+ }
+ }
+}
diff --git a/Dashboard.OpenGL/IGLContext.cs b/Dashboard.OpenGL/IGLContext.cs
index 00f4605..9ab3b0a 100644
--- a/Dashboard.OpenGL/IGLContext.cs
+++ b/Dashboard.OpenGL/IGLContext.cs
@@ -28,5 +28,7 @@ namespace Dashboard.OpenGL
/// Activate this OpenGL Context.
///
void MakeCurrent();
+
+ IntPtr GetProcAddress(string procName);
}
}
diff --git a/Dashboard.OpenTK/PAL2/OpenGLDeviceContext.cs b/Dashboard.OpenTK/PAL2/OpenGLDeviceContext.cs
index 6d20797..100314d 100644
--- a/Dashboard.OpenTK/PAL2/OpenGLDeviceContext.cs
+++ b/Dashboard.OpenTK/PAL2/OpenGLDeviceContext.cs
@@ -40,6 +40,11 @@ namespace Dashboard.OpenTK.PAL2
TK.OpenGL.SetCurrentContext(ContextHandle);
}
+ public IntPtr GetProcAddress(string procName)
+ {
+ return TK.OpenGL.GetProcedureAddress(ContextHandle, procName);
+ }
+
private bool _isDisposed = false;
public void Dispose() => Dispose(true);
diff --git a/Dashboard.OpenTK/PAL2/Pal2AppContext.cs b/Dashboard.OpenTK/PAL2/Pal2AppContext.cs
new file mode 100644
index 0000000..c603c9a
--- /dev/null
+++ b/Dashboard.OpenTK/PAL2/Pal2AppContext.cs
@@ -0,0 +1,56 @@
+using Dashboard.Windowing;
+using OpenTK.Graphics;
+using OpenTK.Platform;
+using AppContext = Dashboard.Pal.AppContext;
+using TK = OpenTK.Platform.Toolkit;
+
+namespace Dashboard.OpenTK.PAL2
+{
+ public class Pal2AppContext : AppContext
+ {
+ public override string DriverName => "Dashboard OpenTK PAL2.0 Driver";
+ public override string DriverVendor => "Dashboard";
+ public override Version DriverVersion => new Version(0, 1);
+ public GraphicsApiHints GraphicsApiHints { get; set; } = new OpenGLGraphicsApiHints();
+ public bool OpenGLBindingsInitialized { get; private set; } = false;
+
+ private readonly List _windows = new List();
+
+ public override IPhysicalWindow CreatePhysicalWindow()
+ {
+ PhysicalWindow window = new PhysicalWindow(GraphicsApiHints);
+ _windows.Add(window);
+
+ if (!OpenGLBindingsInitialized)
+ {
+ OpenGLBindingsInitialized = true;
+ GLLoader.LoadBindings(
+ new Pal2BindingsContext(TK.OpenGL,
+ ((OpenGLDeviceContext)window.DeviceContext).ContextHandle));
+ }
+
+ return window;
+ }
+
+ public override IWindow CreateWindow()
+ {
+ return CreatePhysicalWindow();
+ }
+
+ public override void RunEvents(bool wait)
+ {
+ TK.Window.ProcessEvents(wait);
+
+ for (int i = 0; i < _windows.Count; i++)
+ {
+ if (_windows[i].IsDisposed)
+ {
+ _windows.RemoveAt(i);
+ continue;
+ }
+
+ _windows[i].Paint();
+ }
+ }
+ }
+}
diff --git a/Dashboard.OpenTK/PAL2/Pal2DashboardBackend.cs b/Dashboard.OpenTK/PAL2/Pal2DashboardBackend.cs
deleted file mode 100644
index 61b13b4..0000000
--- a/Dashboard.OpenTK/PAL2/Pal2DashboardBackend.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using Dashboard.Windowing;
-using OpenTK.Graphics;
-using OpenTK.Platform;
-using TK = OpenTK.Platform.Toolkit;
-
-namespace Dashboard.OpenTK.PAL2
-{
- public class Pal2DashboardBackend : IDashboardBackend
- {
- public GraphicsApiHints GraphicsApiHints { get; set; } = new OpenGLGraphicsApiHints();
- public bool OpenGLBindingsInitialized { get; set; } = false;
-
- public IPhysicalWindow CreatePhysicalWindow()
- {
- PhysicalWindow window = new PhysicalWindow(GraphicsApiHints);
-
- if (!OpenGLBindingsInitialized)
- {
- OpenGLBindingsInitialized = true;
- GLLoader.LoadBindings(
- new Pal2BindingsContext(TK.OpenGL,
- ((OpenGLDeviceContext)window.DeviceContext).ContextHandle));
- }
-
- return window;
- }
-
- public virtual IWindow CreateWindow()
- {
- return CreatePhysicalWindow();
- }
-
- public void Dispose()
- {
- }
-
- public void Initialize()
- {
- }
-
- public void Leave()
- {
- }
-
- public void RunEvents(bool wait)
- {
- TK.Window.ProcessEvents(wait);
- }
- }
-}
\ No newline at end of file
diff --git a/Dashboard.OpenTK/PAL2/PhysicalWindow.cs b/Dashboard.OpenTK/PAL2/PhysicalWindow.cs
index 569f63f..d467408 100644
--- a/Dashboard.OpenTK/PAL2/PhysicalWindow.cs
+++ b/Dashboard.OpenTK/PAL2/PhysicalWindow.cs
@@ -10,7 +10,7 @@ using TK = OpenTK.Platform.Toolkit;
namespace Dashboard.OpenTK.PAL2
{
- public class PhysicalWindow : IPhysicalWindow, IDrawQueuePaintable, IEventListener
+ public class PhysicalWindow : IPhysicalWindow, IDrawQueuePaintable, IEventListener, IDpiAwareWindow
{
public DrawQueue DrawQueue { get; } = new DrawQueue();
public WindowHandle WindowHandle { get; }
@@ -86,11 +86,11 @@ namespace Dashboard.OpenTK.PAL2
}
}
- private bool _isDisposed = false;
+ public bool IsDisposed { get; private set; } = false;
public void Dispose()
{
- if (_isDisposed) return;
- _isDisposed = true;
+ if (IsDisposed) return;
+ IsDisposed = true;
RemoveWindow(this);
@@ -160,5 +160,16 @@ namespace Dashboard.OpenTK.PAL2
break;
}
}
+
+ public float Dpi => Scale * 96f;
+
+ public float Scale
+ {
+ get
+ {
+ TK.Window.GetScaleFactor(WindowHandle, out float x, out float y);
+ return Math.Max(x, y);
+ }
+ }
}
}
diff --git a/Dashboard/Application.cs b/Dashboard/Application.cs
deleted file mode 100644
index 62db7dc..0000000
--- a/Dashboard/Application.cs
+++ /dev/null
@@ -1,131 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using Dashboard.Windowing;
-
-namespace Dashboard
-{
- public class Application : IDisposable
- {
- public IDashboardBackend Backend { get; }
- public IWindowFactory WindowFactory { get; set; }
-
- public string Name { get; } = "Dashboard Application";
-
- protected bool IsInitialized { get; private set; } = false;
- protected bool IsDisposed { get; private set; } = false;
-
- private readonly List _physicalWindows = new List();
-
- public event EventHandler? PreInitializing;
- public event EventHandler? Initializing;
- public event EventHandler? PostInitializing;
- public event EventHandler? Leaving;
-
- public Application(IDashboardBackend backend)
- {
- Backend = backend;
- WindowFactory = backend;
- }
-
- protected virtual void PreInitialize()
- {
- PreInitializing?.Invoke(this, EventArgs.Empty);
- }
-
- public virtual void Initialize()
- {
- Backend.Initialize();
- Initializing?.Invoke(this, EventArgs.Empty);
- }
-
- protected virtual void PostInitialize()
- {
- PostInitializing?.Invoke(this, EventArgs.Empty);
- }
-
- private void InitializeInternal()
- {
- if (IsInitialized)
- return;
-
- IsInitialized = true;
-
- PreInitialize();
- Initialize();
- PostInitialize();
- }
-
- public virtual void RunEvents(bool wait)
- {
- if (!IsInitialized)
- throw new InvalidOperationException("The application is not initialized. Cannot run events at this time.");
-
- Backend.RunEvents(wait);
- }
-
- public virtual void Leave()
- {
- Backend.Leave();
- Leaving?.Invoke(this, EventArgs.Empty);
- }
-
- public void Run() => Run(true, CancellationToken.None);
- public void Run(bool wait) => Run(wait, CancellationToken.None);
-
- public void Run(bool waitForEvents, CancellationToken token)
- {
- InitializeInternal();
-
- while (!token.IsCancellationRequested)
- {
- RunEvents(waitForEvents);
-
- foreach (IPhysicalWindow window in _physicalWindows)
- {
- window.Paint();
- }
- }
-
- Leave();
- }
-
- ///
- public IWindow CreateWindow()
- {
- IWindow window = WindowFactory.CreateWindow();
-
- if (window is IPhysicalWindow physical)
- _physicalWindows.Add(physical);
-
- return window;
- }
-
- ///
- public IPhysicalWindow CreatePhysicalWindow()
- {
- IPhysicalWindow window = WindowFactory.CreatePhysicalWindow();
- _physicalWindows.Add(window);
- return window;
- }
-
- protected virtual void Dispose(bool disposing)
- {
- }
-
- protected void InvokeDispose(bool disposing)
- {
- if (IsDisposed)
- return;
-
- IsDisposed = true;
-
- Dispose(disposing);
-
- if (disposing)
- GC.SuppressFinalize(this);
- }
-
- public void Dispose() => InvokeDispose(true);
- }
-}
\ No newline at end of file
diff --git a/Dashboard/IDashboardBackend.cs b/Dashboard/IDashboardBackend.cs
deleted file mode 100644
index f56ecb4..0000000
--- a/Dashboard/IDashboardBackend.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using System;
-using Dashboard.Windowing;
-
-namespace Dashboard
-{
- public interface IWindowFactory
- {
- ///
- /// Creates a window. It could be a virtual window, or a physical window.
- ///
- /// A window.
- IWindow CreateWindow();
-
- ///
- /// Always creates a physical window.
- ///
- /// A physical window.
- IPhysicalWindow CreatePhysicalWindow();
- }
-
- public interface IDashboardBackend : IDisposable, IWindowFactory
- {
- void Initialize();
-
- void RunEvents(bool wait);
-
- void Leave();
- }
-}
\ No newline at end of file
diff --git a/tests/Dashboard.TestApplication/Program.cs b/tests/Dashboard.TestApplication/Program.cs
index 1b174b9..4a2786c 100644
--- a/tests/Dashboard.TestApplication/Program.cs
+++ b/tests/Dashboard.TestApplication/Program.cs
@@ -10,6 +10,9 @@ using OpenTK.Mathematics;
using Box2d = Dashboard.Box2d;
using TK = OpenTK.Platform.Toolkit;
using Dashboard;
+using Dashboard.Windowing;
+using OpenTK.Windowing.GraphicsLibraryFramework;
+using AppContext = Dashboard.Pal.AppContext;
TK.Init(new ToolkitOptions()
{
@@ -18,11 +21,12 @@ TK.Init(new ToolkitOptions()
{
EnableVisualStyles = true,
IsDPIAware = true,
- }
+ },
+ FeatureFlags = ToolkitFlags.EnableOpenGL,
});
-Application app = new Application(new Pal2DashboardBackend()
-{
+AppContext app = new Pal2AppContext()
+{
GraphicsApiHints = new OpenGLGraphicsApiHints()
{
Version = new Version(3, 2),
@@ -41,7 +45,7 @@ Application app = new Application(new Pal2DashboardBackend()
SupportTransparentFramebufferX11 = true,
}
-});
+};
PhysicalWindow window;
SolidBrush fg = new SolidBrush(Color.FromArgb(0, 0, 0, 0));
@@ -56,121 +60,121 @@ List points = new List();
IFont font;
StringBuilder builder = new StringBuilder();
-app.PostInitializing += (sender, ea) => {
- window = (PhysicalWindow)app.CreatePhysicalWindow();
+app.Initialize();
- window.Title = "DashTerm";
- TK.Window.SetMinClientSize(window.WindowHandle, 300, 200);
- TK.Window.SetClientSize(window.WindowHandle, new Vector2i(320, 240));
- TK.Window.SetBorderStyle(window.WindowHandle, WindowBorderStyle.ResizableBorder);
- // TK.Window.SetTransparencyMode(wnd, WindowTransparencyMode.TransparentFramebuffer, 0.1f);
+window = (PhysicalWindow)app.CreatePhysicalWindow();
- OpenGLDeviceContext context = (OpenGLDeviceContext)window.DeviceContext;
+window.Title = "DashTerm";
+TK.Window.SetMinClientSize(window.WindowHandle, 300, 200);
+TK.Window.SetClientSize(window.WindowHandle, new Vector2i(320, 240));
+TK.Window.SetBorderStyle(window.WindowHandle, WindowBorderStyle.ResizableBorder);
+// TK.Window.SetTransparencyMode(wnd, WindowTransparencyMode.TransparentFramebuffer, 0.1f);
- context.MakeCurrent();
- context.SwapGroup.SwapInterval = 1;
+OpenGLDeviceContext context = (OpenGLDeviceContext)window.DeviceContext;
- engine = new GLEngine();
- engine.Initialize();
-
- executor = engine.GetExecutor(context);
+context.MakeCurrent();
+context.SwapGroup.SwapInterval = 1;
- dimUI = new DimUI(new DimUIConfig()
+engine = new GLEngine();
+engine.Initialize();
+
+executor = engine.GetExecutor(context);
+
+dimUI = new DimUI(new DimUIConfig()
+{
+ Font = new NamedFont("Noto Sans", 9f),
+});
+
+EventQueue.EventRaised += (handle, type, eventArgs) =>
+{
+ if (handle != window.WindowHandle)
+ return;
+
+ switch (type)
{
- Font = new NamedFont("Noto Sans", 9f),
- });
+ case PlatformEventType.Close:
+ source.Cancel();
+ break;
+ case PlatformEventType.MouseMove:
+ mousePos = ((MouseMoveEventArgs)eventArgs).ClientPosition;
+ break;
+ }
+};
- EventQueue.EventRaised += (handle, type, eventArgs) =>
- {
- if (handle != window.WindowHandle)
- return;
+TK.Window.SetMode(window.WindowHandle, WindowMode.Normal);
+font = Typesetter.LoadFont("Nimbus Mono", 12f);
- switch (type)
+window.Painting += (sender, ea) => {
+ TK.Window.GetSize(context.WindowHandle, out Vector2i size);
+ executor.BeginFrame();
+
+ dimUI.Begin(new Box2d(0, 0, size.X, size.Y), window.DrawQueue);
+ dimUI.Text("Hello World!");
+ dimUI.Button("Cancel"); dimUI.SameLine();
+ dimUI.Button("OK");
+
+ dimUI.Input("type me!", builder);
+
+ dimUI.BeginMenu();
+
+ if (dimUI.MenuItem("File"))
{
- case PlatformEventType.Close:
- source.Cancel();
- break;
- case PlatformEventType.MouseMove:
- mousePos = ((MouseMoveEventArgs)eventArgs).ClientPosition;
- break;
- }
- };
-
- TK.Window.SetMode(window.WindowHandle, WindowMode.Normal);
- font = Typesetter.LoadFont("Nimbus Mono", 12f);
-
- window.Painting += (sender, ea) => {
- TK.Window.GetFramebufferSize(context.WindowHandle, out Vector2i framebufferSize);
- executor.BeginFrame();
-
- dimUI.Begin(new Box2d(0, 0, framebufferSize.X, framebufferSize.Y), window.DrawQueue);
- dimUI.Text("Hello World!");
- dimUI.Button("Cancel"); dimUI.SameLine();
- dimUI.Button("OK");
-
- dimUI.Input("type me!", builder);
-
dimUI.BeginMenu();
+ dimUI.MenuItem("New Window");
+ dimUI.MenuItem("Preferences");
+ dimUI.MenuItem("Exit");
+ dimUI.EndMenu();
+ }
- if (dimUI.MenuItem("File"))
+ if (dimUI.MenuItem("Edit"))
+ {
+ dimUI.BeginMenu();
+ dimUI.MenuItem("Cut");
+ dimUI.MenuItem("Copy");
+ dimUI.MenuItem("Paste");
+
+ if (dimUI.MenuItem("Send Char"))
{
dimUI.BeginMenu();
- dimUI.MenuItem("New Window");
- dimUI.MenuItem("Preferences");
- dimUI.MenuItem("Exit");
dimUI.EndMenu();
}
- if (dimUI.MenuItem("Edit"))
+ dimUI.EndMenu();
+ }
+
+ if (dimUI.MenuItem("View"))
+ {
+ dimUI.BeginMenu();
+ dimUI.MenuItem("Clear");
+
+ if (dimUI.MenuItem("Set Size"))
{
dimUI.BeginMenu();
- dimUI.MenuItem("Cut");
- dimUI.MenuItem("Copy");
- dimUI.MenuItem("Paste");
-
- if (dimUI.MenuItem("Send Char"))
- {
- dimUI.BeginMenu();
- dimUI.EndMenu();
- }
-
+ dimUI.MenuItem("24 x 40");
+ dimUI.MenuItem("25 x 40");
+ dimUI.MenuItem("24 x 80");
+ dimUI.MenuItem("25 x 80");
+ dimUI.MenuItem("25 x 120");
dimUI.EndMenu();
}
- if (dimUI.MenuItem("View"))
- {
- dimUI.BeginMenu();
- dimUI.MenuItem("Clear");
+ dimUI.EndMenu();
+ }
- if (dimUI.MenuItem("Set Size"))
- {
- dimUI.BeginMenu();
- dimUI.MenuItem("24 x 40");
- dimUI.MenuItem("25 x 40");
- dimUI.MenuItem("24 x 80");
- dimUI.MenuItem("25 x 80");
- dimUI.MenuItem("25 x 120");
- dimUI.EndMenu();
- }
+ dimUI.Finish();
- dimUI.EndMenu();
- }
+ GL.Viewport(0, 0, size.X, size.Y);
+ GL.ClearColor(0.3f, 0.3f, 0.3f, 1.0f);
+ GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
+ GL.Disable(EnableCap.DepthTest);
+ GL.Enable(EnableCap.Blend);
+ GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
+ GL.ColorMask(true, true, true, true);
- dimUI.Finish();
+ executor.Draw(window.DrawQueue, new RectangleF(0, 0, size.X, size.Y), 1.5f /*(window as IDpiAwareWindow)?.Scale ?? 1*/);
+ executor.EndFrame();
- GL.Viewport(0, 0, framebufferSize.X, framebufferSize.Y);
- GL.ClearColor(0.3f, 0.3f, 0.3f, 1.0f);
- GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
- GL.Disable(EnableCap.DepthTest);
- GL.Enable(EnableCap.Blend);
- GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
- GL.ColorMask(true, true, true, true);
-
- executor.Draw(window.DrawQueue);
- executor.EndFrame();
-
- context.SwapGroup.Swap();
- };
+ context.SwapGroup.Swap();
};
app.Run(true, source.Token);