Create a new context system for dashboard.

This commit is contained in:
H. Utku Maden 2025-08-05 23:50:29 +03:00
parent 50eda46b13
commit edc85c3f24
19 changed files with 1064 additions and 317 deletions

View File

@ -6,16 +6,31 @@ namespace Dashboard
/// <summary>
/// Pixel format for images.
/// </summary>
[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,
}
/// <summary>

View File

@ -0,0 +1,136 @@
using Dashboard.Collections;
using Dashboard.Windowing;
namespace Dashboard.Pal
{
public abstract class AppContext : IContextBase<AppContext, IAppContextExtension>
{
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<IAppContextExtension> _extensions =
new TypeDictionary<IAppContextExtension>(true);
private readonly TypeDictionary<IAppContextExtension, Func<IAppContextExtension>> _preloadedExtensions =
new TypeDictionary<IAppContextExtension, Func<IAppContextExtension>>(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
/// <summary>
/// Creates a window. It could be a virtual window, or a physical window.
/// </summary>
/// <returns>A window.</returns>
public abstract IWindow CreateWindow();
/// <summary>
/// Always creates a physical window.
/// </summary>
/// <returns>A physical window.</returns>
public abstract IPhysicalWindow CreatePhysicalWindow();
#endregion
public bool IsExtensionAvailable<T>() where T : IAppContextExtension
{
return _extensions.Contains<T>() || _preloadedExtensions.Contains<T>();
}
public bool ExtensionPreload<T>() where T : IAppContextExtension, new()
{
return _preloadedExtensions.Add<T>(() => new T());
}
public T ExtensionRequire<T>() 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<T>(out Func<IAppContextExtension>? 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);
}
}

View File

@ -0,0 +1,98 @@
using Dashboard.Collections;
namespace Dashboard.Pal
{
public abstract class DeviceContext : IContextBase<DeviceContext, IDeviceContextExtension>
{
private readonly TypeDictionary<IDeviceContextExtension> _extensions =
new TypeDictionary<IDeviceContextExtension>(true);
private readonly TypeDictionary<IDeviceContextExtension, Func<IDeviceContextExtension>> _preloadedExtensions =
new TypeDictionary<IDeviceContextExtension, Func<IDeviceContextExtension>>(true);
public abstract string DriverName { get; }
public abstract string DriverVendor { get; }
public abstract Version DriverVersion { get; }
public bool IsDisposed { get; private set; }
/// <summary>
/// Optional debugging object for your pleasure.
/// </summary>
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<T>() where T : IDeviceContextExtension
{
return _extensions.Contains<T>() || _preloadedExtensions.Contains<T>();
}
public bool ExtensionPreload<T>() where T : IDeviceContextExtension, new()
{
return _preloadedExtensions.Add<T>(() => new T());
}
public T ExtensionRequire<T>() 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<T>(out Func<IDeviceContextExtension>? loader))
{
extension = (T)loader!();
}
else
{
extension = new T();
}
_extensions.Add(extension);
extension.Require(this);
}
return extension;
}
/// <summary>
/// Implement your dispose in this function.
/// </summary>
/// <param name="isDisposing">True if disposing, false otherwise.</param>
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);
}
}

View File

@ -0,0 +1,7 @@
namespace Dashboard.Pal
{
public interface IAppContextExtension : IContextExtensionBase<AppContext>
{
}
}

View File

@ -0,0 +1,103 @@
namespace Dashboard.Pal
{
/// <summary>
/// Information about this context interface.
/// </summary>
public interface IContextInterfaceInfo
{
/// <summary>
/// Name of this driver.
/// </summary>
string DriverName { get; }
/// <summary>
/// The vendor for this driver.
/// </summary>
string DriverVendor { get; }
/// <summary>
/// The version of this driver.
/// </summary>
Version DriverVersion { get; }
}
/// <summary>
/// The base context interface.
/// </summary>
public interface IContextBase : IContextInterfaceInfo, IDisposable
{
/// <summary>
/// The debugger for this context.
/// </summary>
IContextDebugger? Debugger { get; set; }
}
/// <summary>
/// The base context interface.
/// </summary>
/// <typeparam name="TContext">The context type.</typeparam>
/// <typeparam name="TExtension">The extension type, if used.</typeparam>
public interface IContextBase<TContext, in TExtension> : IContextBase
where TContext : IContextBase<TContext, TExtension>
where TExtension : IContextExtensionBase<TContext>
{
/// <summary>
/// Is such an extension available?
/// </summary>
/// <typeparam name="T">The extension to check.</typeparam>
/// <returns>True if the extension is available.</returns>
bool IsExtensionAvailable<T>() where T : TExtension;
/// <summary>
/// Preload extensions, to be lazy loaded when required.
/// </summary>
/// <typeparam name="T">The extension to preload.</typeparam>
/// <returns>
/// True if the extension was added to the preload set. Otherwise, already loaded or another extension
/// exists which provides this.
/// </returns>
bool ExtensionPreload<T>() where T : TExtension, new();
/// <summary>
/// Require an extension.
/// </summary>
/// <typeparam name="T">The extension to require.</typeparam>
/// <returns>The extension instance.</returns>
T ExtensionRequire<T>() where T : TExtension, new();
}
/// <summary>
/// Base interface for all context extensions.
/// </summary>
public interface IContextExtensionBase : IContextInterfaceInfo, IDisposable
{
/// <summary>
/// The context that loaded this extension.
/// </summary>
IContextBase Context { get; }
/// <summary>
/// Require this extension.
/// </summary>
/// <param name="context">The context that required this extension.</param>
void Require(IContextBase context);
}
/// <summary>
/// Base interface for all context extensions.
/// </summary>
public interface IContextExtensionBase<TContext> : IContextExtensionBase
where TContext : IContextBase
{
/// <summary>
/// The context that loaded this extension.
/// </summary>
new TContext Context { get; }
/// <summary>
/// Require this extension.
/// </summary>
/// <param name="context">The context that required this extension.</param>
void Require(TContext context);
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,6 @@
namespace Dashboard.Pal
{
public interface IDeviceContextExtension : IContextExtensionBase<DeviceContext>
{
}
}

View File

@ -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<T>(Span<T> buffer, int level = 0, int align = 0) where T : unmanaged;
void Write<T>(PixelFormat format, ReadOnlySpan<T> buffer, int level = 0, int align = 4) where T : unmanaged;
void Premultiply();
void Unmultiply();
void GenerateMipmaps();
}
}

View File

@ -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))

View File

@ -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<string> Extensions { get; }
public Thread RendererThread { get; } = Thread.CurrentThread;
public bool IsRenderThread => RendererThread == Thread.CurrentThread;
private readonly ConcurrentQueue<Task> _beforeDrawActions = new ConcurrentQueue<Task>();
private readonly ConcurrentQueue<Task> _afterDrawActions = new ConcurrentQueue<Task>();
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<string> extensions = new HashSet<string>();
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<GLTextureExtension>();
}
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<T> InvokeBeforeDraw<T>(Func<T> function)
{
Task<T> task = new Task<T>(function);
_beforeDrawActions.Enqueue(task);
return task;
}
public Task<T> InvokeAfterDraw<T>(Func<T> function)
{
Task<T> task = new Task<T>(function);
_afterDrawActions.Enqueue(task);
return task;
}
public Task InvokeOnRenderThread(Action action)
{
if (IsRenderThread)
{
action();
return Task.CompletedTask;
}
return InvokeBeforeDraw(action);
}
public Task<T> InvokeOnRenderThread<T>(Func<T> 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;
}
}
}
}

View File

@ -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<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;
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<T>(Span<T> buffer, int level = 0, int align = 0) where T : unmanaged
{
throw new NotImplementedException();
}
public unsafe void Write<T>(PixelFormat format, ReadOnlySpan<T> 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()
};
}
}
}

View File

@ -28,5 +28,7 @@ namespace Dashboard.OpenGL
/// Activate this OpenGL Context.
/// </summary>
void MakeCurrent();
IntPtr GetProcAddress(string procName);
}
}

View File

@ -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);

View File

@ -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<PhysicalWindow> _windows = new List<PhysicalWindow>();
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();
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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<IPhysicalWindow> _physicalWindows = new List<IPhysicalWindow>();
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();
}
/// <inheritdoc cref="IWindowFactory.CreateWindow"/>
public IWindow CreateWindow()
{
IWindow window = WindowFactory.CreateWindow();
if (window is IPhysicalWindow physical)
_physicalWindows.Add(physical);
return window;
}
/// <inheritdoc cref="IWindowFactory.CreatePhysicalWindow"/>
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);
}
}

View File

@ -1,29 +0,0 @@
using System;
using Dashboard.Windowing;
namespace Dashboard
{
public interface IWindowFactory
{
/// <summary>
/// Creates a window. It could be a virtual window, or a physical window.
/// </summary>
/// <returns>A window.</returns>
IWindow CreateWindow();
/// <summary>
/// Always creates a physical window.
/// </summary>
/// <returns>A physical window.</returns>
IPhysicalWindow CreatePhysicalWindow();
}
public interface IDashboardBackend : IDisposable, IWindowFactory
{
void Initialize();
void RunEvents(bool wait);
void Leave();
}
}

View File

@ -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<Vector3> points = new List<Vector3>();
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);