Begin implementing the GL engine.

This commit is contained in:
H. Utku Maden 2025-01-14 20:34:47 +03:00
parent bb5b87417f
commit b841dbe6c2
18 changed files with 933 additions and 13 deletions

@ -30,7 +30,7 @@ namespace Dashboard
/// <summary>
/// Represents a linear gradient.
/// </summary>
public struct Gradient : ICollection<GradientStop>, ICloneable
public struct Gradient : ICollection<GradientStop>, ICloneable, IEquatable<Gradient>
{
private readonly List<GradientStop> _stops = new List<GradientStop>();
@ -199,5 +199,29 @@ namespace Dashboard
code.Add(item.GetHashCode());
return code.ToHashCode();
}
public bool Equals(Gradient other)
{
return
Type == other.Type &&
C0 == other.C0 &&
C1 == other.C1 &&
_stops.Equals(other._stops);
}
public override bool Equals(object? obj)
{
return obj is Gradient other && Equals(other);
}
public static bool operator ==(Gradient left, Gradient right)
{
return left.Equals(right);
}
public static bool operator !=(Gradient left, Gradient right)
{
return !left.Equals(right);
}
}
}

@ -0,0 +1,77 @@
using System.Collections.Concurrent;
using OpenTK.Graphics.OpenGL;
namespace Dashboard.Drawing.OpenGL
{
public class ContextCollector : IDisposable
{
private readonly ConcurrentQueue<GLObject> _disposedObjects = new ConcurrentQueue<GLObject>();
public void Dispose()
{
while (_disposedObjects.TryDequeue(out GLObject obj))
{
obj.Dispose();
}
}
void DeleteObject(ObjectIdentifier identifier, int handle) => _disposedObjects.Enqueue(new GLObject(identifier, handle));
public void DeleteTexture(int texture) => DeleteObject(ObjectIdentifier.Texture, texture);
public void DeleteBufffer(int buffer) => DeleteObject(ObjectIdentifier.Buffer, buffer);
public void DeleteFramebuffer(int framebuffer) => DeleteObject(ObjectIdentifier.Framebuffer, framebuffer);
public void DeleteRenderBuffer(int renderbuffer) => DeleteObject(ObjectIdentifier.Renderbuffer, renderbuffer);
public void DeleteSampler(int sampler) => DeleteObject(ObjectIdentifier.Sampler, sampler);
public void DeleteShader(int shader) => DeleteObject(ObjectIdentifier.Shader, shader);
public void DeleteProgram(int program) => DeleteObject(ObjectIdentifier.Program, program);
public void DeleteVertexArray(int vertexArray) => DeleteObject(ObjectIdentifier.VertexArray, vertexArray);
public void DeleteQuery(int query) => DeleteObject(ObjectIdentifier.Query, query);
public void DeleteProgramPipeline(int programPipeline) => DeleteObject(ObjectIdentifier.ProgramPipeline, programPipeline);
public void DeleteTransformFeedback(int transformFeedback) => DeleteObject(ObjectIdentifier.TransformFeedback, transformFeedback);
private readonly record struct GLObject(ObjectIdentifier Type, int Handle)
{
public void Dispose()
{
switch (Type)
{
case ObjectIdentifier.Texture:
GL.DeleteTexture(Handle);
break;
case ObjectIdentifier.Buffer:
GL.DeleteBuffer(Handle);
break;
case ObjectIdentifier.Framebuffer:
GL.DeleteFramebuffer(Handle);
break;
case ObjectIdentifier.Renderbuffer:
GL.DeleteRenderbuffer(Handle);
break;
case ObjectIdentifier.Sampler:
GL.DeleteSampler(Handle);
break;
case ObjectIdentifier.Shader:
GL.DeleteShader(Handle);
break;
case ObjectIdentifier.VertexArray:
GL.DeleteVertexArray(Handle);
break;
case ObjectIdentifier.Program:
GL.DeleteProgram(Handle);
break;
case ObjectIdentifier.Query:
GL.DeleteQuery(Handle);
break;
case ObjectIdentifier.ProgramPipeline:
GL.DeleteProgramPipeline(Handle);
break;
case ObjectIdentifier.TransformFeedback:
GL.DeleteTransformFeedback(Handle);
break;
}
}
}
public static readonly ContextCollector Global = new ContextCollector();
}
}

@ -0,0 +1,134 @@
using System.Drawing;
using OpenTK.Graphics.OpenGL;
namespace Dashboard.Drawing.OpenGL
{
public class ContextExecutor : IDisposable
{
public GLEngine Engine { get; }
public IGLContext Context { get; }
public ContextResourcePool ResourcePool { get; }
protected bool IsDisposed { get; private set; } = false;
public bool IsInitialized { get; private set; } = false;
private int _program = 0;
public ContextExecutor(GLEngine engine, IGLContext context)
{
Engine = engine;
Context = context;
ResourcePool = Engine.ResourcePoolManager.Get(context);
ResourcePool.IncrementReference();
}
~ContextExecutor()
{
DisposeInvoker(false);
}
public void Initialize()
{
if (IsInitialized)
return;
IsInitialized = true;
LoadShaders();
}
private void LoadShaders()
{
using Stream vsource = FetchEmbeddedResource("Dashboard.Drawing.OpenGL.default.vert");
using Stream fsource = FetchEmbeddedResource("Dashboard.Drawing.OpenGL.default.frag");
int vs = CompileShader(ShaderType.VertexShader, vsource);
int fs = CompileShader(ShaderType.FragmentShader, fsource);
_program = LinkProgram(vs, fs);
GL.DeleteShader(vs);
GL.DeleteShader(fs);
}
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 EndFrame()
{
}
private void DisposeInvoker(bool disposing)
{
if (!IsDisposed)
return;
IsDisposed = true;
if (disposing)
GC.SuppressFinalize(this);
Dispose(disposing);
}
protected virtual void Dispose(bool disposing)
{
ContextCollector.Global.DeleteProgram(_program);
}
public void Dispose() => DisposeInvoker(true);
private static int CompileShader(ShaderType type, string source)
{
int shader = GL.CreateShader(type);
GL.ShaderSource(shader, source);
GL.CompileShader(shader);
int compileStatus = 0;
GL.GetShaderi(shader, ShaderParameterName.CompileStatus, out compileStatus);
if (compileStatus == 0)
{
GL.GetShaderInfoLog(shader, out string log);
GL.DeleteShader(shader);
throw new Exception($"{type} Shader compilation failed: " + log);
}
return shader;
}
private static int CompileShader(ShaderType type, Stream stream)
{
using StreamReader reader = new StreamReader(stream, leaveOpen: true);
return CompileShader(type, reader.ReadToEnd());
}
private static int LinkProgram(int s1, int s2)
{
int program = GL.CreateProgram();
GL.AttachShader(program, s1);
GL.AttachShader(program, s2);
GL.LinkProgram(program);
int linkStatus = 0;
GL.GetProgrami(program, ProgramProperty.LinkStatus, out linkStatus);
if (linkStatus == 0)
{
GL.GetProgramInfoLog(program, out string log);
GL.DeleteProgram(program);
throw new Exception("Shader program linking failed: " + log);
}
return program;
}
private static Stream FetchEmbeddedResource(string name)
{
return typeof(ContextExecutor).Assembly.GetManifestResourceStream(name)!;
}
}
}

@ -0,0 +1,81 @@
namespace Dashboard.Drawing.OpenGL
{
public class ContextResourcePoolManager
{
private readonly Dictionary<IGLContext, ContextResourcePool> _unique = new Dictionary<IGLContext, ContextResourcePool>();
private readonly Dictionary<int, ContextResourcePool> _shared = new Dictionary<int, ContextResourcePool>();
public ContextResourcePool Get(IGLContext context)
{
if (context.ContextGroup == -1)
{
if (!_unique.TryGetValue(context, out ContextResourcePool? pool))
{
pool = new ContextResourcePool(this, context);
}
return pool;
}
else
{
if (!_shared.TryGetValue(context.ContextGroup, out ContextResourcePool? pool))
{
pool = new ContextResourcePool(this, context.ContextGroup);
}
return pool;
}
}
internal void Disposed(ContextResourcePool pool)
{
// TODO:
}
}
public class ContextResourcePool : IGLDisposable, IArc
{
private int _references = 0;
public ContextResourcePoolManager Manager { get; }
public IGLContext? Context { get; private set; } = null;
public int ContextGroup { get; private set; } = -1;
public int References => _references;
internal ContextResourcePool(ContextResourcePoolManager manager, int contextGroup)
{
Manager = manager;
ContextGroup = contextGroup;
}
internal ContextResourcePool(ContextResourcePoolManager manager, IGLContext context)
{
Manager = manager;
Context = context;
}
~ContextResourcePool()
{
Dispose(true, false);
}
public void Dispose() => Dispose(true, false);
public void Dispose(bool safeExit) => Dispose(safeExit, true);
private void Dispose(bool safeExit, bool disposing)
{
Manager.Disposed(this);
}
public void IncrementReference()
{
Interlocked.Increment(ref _references);
}
public bool DecrementReference()
{
return Interlocked.Decrement(ref _references) == 0;
}
}
}

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTK.Graphics" Version="5.0.0-pre.13" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Dashboard.Drawing\Dashboard.Drawing.csproj" />
</ItemGroup>
<ItemGroup>
<None Remove="default.frag" />
<EmbeddedResource Include="default.frag" />
<None Remove="default.vert" />
<EmbeddedResource Include="default.vert" />
</ItemGroup>
</Project>

@ -0,0 +1,36 @@
using OpenTK;
using OpenTK.Graphics;
namespace Dashboard.Drawing.OpenGL
{
public class GLEngine
{
private readonly Dictionary<IGLContext, ContextExecutor> _executors = new Dictionary<IGLContext, ContextExecutor>();
public bool IsInitialized { get; private set; } = false;
public ContextResourcePoolManager ResourcePoolManager { get; private set; } = new ContextResourcePoolManager();
public void Initialize(IBindingsContext? bindingsContext = null)
{
if (IsInitialized)
return;
IsInitialized = true;
if (bindingsContext != null)
GLLoader.LoadBindings(bindingsContext);
}
public ContextExecutor GetExecutor(IGLContext glContext)
{
if (!_executors.TryGetValue(glContext, out ContextExecutor? executor))
{
executor = new ContextExecutor(this, glContext);
executor.Initialize();
_executors.Add(glContext, executor);
}
return executor;
}
}
}

@ -0,0 +1,68 @@
using System.Runtime.InteropServices;
using OpenTK.Mathematics;
namespace Dashboard.Drawing.OpenGL
{
public class GradientUniformBuffer
{
private int _top = 0;
private readonly MappableBuffer<GradientUniformStruct> _buffer = new MappableBuffer<GradientUniformStruct>();
private readonly Dictionary<Gradient, Entry> _entries = new Dictionary<Gradient, Entry>();
public void Initialize()
{
_buffer.Initialize();
}
public Entry InternGradient(Gradient gradient)
{
if (_entries.TryGetValue(gradient, out Entry entry))
return entry;
int count = gradient.Count;
int offset = _top;
_top += count;
_buffer.EnsureCapacity(_top);
_buffer.Map();
Span<GradientUniformStruct> span = _buffer.AsSpan()[offset.._top];
for (int i = 0; i < count; i++)
{
GradientStop stop = gradient[i];
span[i] = new GradientUniformStruct()
{
Position = stop.Position,
Color = new Vector4(
stop.Color.R / 255f,
stop.Color.G / 255f,
stop.Color.B / 255f,
stop.Color.A / 255f),
};
}
entry = new Entry(offset, count);
_entries.Add(gradient, entry);
return entry;
}
public void Clear()
{
_entries.Clear();
_top = 0;
}
public record struct Entry(int Offset, int Count);
}
[StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(float))]
public struct GradientUniformStruct
{
[FieldOffset(0 * sizeof(float))]
public float Position;
[FieldOffset(4 * sizeof(float))]
public Vector4 Color;
}
}

@ -0,0 +1,24 @@
namespace Dashboard.Drawing.OpenGL
{
/// <summary>
/// Atomic reference counter.
/// </summary>
public interface IArc : IDisposable
{
/// <summary>
/// The number of references to this.
/// </summary>
int References { get; }
/// <summary>
/// Increment the number of references.
/// </summary>
void IncrementReference();
/// <summary>
/// Decrement the number of references.
/// </summary>
/// <returns>True if this was the last reference.</returns>
bool DecrementReference();
}
}

@ -0,0 +1,26 @@
using System.Drawing;
namespace Dashboard.Drawing.OpenGL
{
/// <summary>
/// Interface for GL context operations
/// </summary>
public interface IGLContext
{
/// <summary>
/// The associated group for context sharing.
/// </summary>
/// <remarks>-1 assigns no group.</remarks>
public int ContextGroup { get; }
/// <summary>
/// The size of the framebuffer in pixels.
/// </summary>
public Size FramebufferSize { get; }
/// <summary>
/// Called when the context is disposed.
/// </summary>
event Action Disposed;
}
}

@ -0,0 +1,16 @@
namespace Dashboard.Drawing.OpenGL
{
/// <summary>
/// Interface much like <see cref="IDisposable"/> except GL resources are dropped.
/// </summary>
/// <remarks>
/// The main reason this interface exists is that you need a way to differentiate
/// when a context is deleted, versus when the context is to remain present.
/// </remarks>
public interface IGLDisposable : IDisposable
{
/// <inheritdoc cref="IDisposable.Dispose"/>
/// <param name="safeExit">Set to true to spend the time clearing out GL objects.</param>
void Dispose(bool safeExit);
}
}

@ -0,0 +1,118 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using OpenTK.Graphics.OpenGL;
namespace Dashboard.Drawing.OpenGL
{
public class MappableBuffer<T> : IDisposable
{
public int Handle { get; private set; } = 0;
public int Capacity { get; set; } = BASE_CAPACITY;
public IntPtr Pointer { get; private set; } = IntPtr.Zero;
private bool _isDisposed = false;
private const int BASE_CAPACITY = 4 << 10; // 4 KiB
private const int MAX_INCREMENT = 4 << 20; // 4 MiB
~MappableBuffer()
{
Dispose(false);
}
public void Initialize()
{
Handle = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
GL.BufferData(BufferTarget.ArrayBuffer, Capacity, IntPtr.Zero, BufferUsage.DynamicDraw);
}
public void EnsureCapacity(int count)
{
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count));
if (Capacity >= count)
return;
AssertInitialized();
Unmap();
int sz = Unsafe.SizeOf<T>();
int oldsize = Capacity * sz;
int request = count * sz;
int newsize;
if (request > MAX_INCREMENT)
{
newsize = ((request + MAX_INCREMENT - 1) / MAX_INCREMENT) * MAX_INCREMENT;
}
else
{
newsize = checked((int)BitOperations.RoundUpToPowerOf2((ulong)request));
}
int dest = GL.GenBuffer();
GL.BindBuffer(BufferTarget.CopyWriteBuffer, dest);
GL.BindBuffer(BufferTarget.CopyReadBuffer, Handle);
GL.BufferData(BufferTarget.CopyWriteBuffer, newsize, IntPtr.Zero, BufferUsage.DynamicDraw);
GL.CopyBufferSubData(CopyBufferSubDataTarget.CopyReadBuffer, CopyBufferSubDataTarget.CopyWriteBuffer, 0, 0, oldsize);
GL.DeleteBuffer(Handle);
Handle = dest;
Capacity = newsize;
}
public unsafe void Map()
{
if (Pointer != IntPtr.Zero)
return;
AssertInitialized();
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
Pointer = (IntPtr)GL.MapBuffer(BufferTarget.ArrayBuffer, BufferAccess.ReadWrite);
}
public void Unmap()
{
if (Pointer == IntPtr.Zero)
return;
AssertInitialized();
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
GL.UnmapBuffer(BufferTarget.ArrayBuffer);
}
public unsafe Span<T> AsSpan()
{
if (Pointer == IntPtr.Zero)
throw new InvalidOperationException("The buffer is not currently mapped.");
AssertInitialized();
return new Span<T>(Pointer.ToPointer(), Capacity / Unsafe.SizeOf<T>());
}
private void AssertInitialized()
{
if (Handle == 0)
throw new InvalidOperationException("The buffer is not initialized.");
}
private void Dispose(bool disposing)
{
if (_isDisposed)
return;
_isDisposed = true;
if (disposing)
GC.SuppressFinalize(this);
ContextCollector.Global.DeleteBufffer(Handle);
}
public void Dispose() => Dispose(true);
}
}

@ -0,0 +1,51 @@
using OpenTK.Mathematics;
namespace Dashboard.Drawing.OpenGL
{
/// <summary>
/// The current stack of transformations.
/// </summary>
public class TransformStack
{
private Matrix4 _top = Matrix4.Identity;
private readonly Stack<Matrix4> _stack = new Stack<Matrix4>();
/// <summary>
/// The top-most transform matrix.
/// </summary>
public ref readonly Matrix4 Top => ref _top;
/// <summary>
/// The number of matrices in the stack.
/// </summary>
public int Count => _stack.Count;
/// <summary>
/// Push a transform.
/// </summary>
/// <param name="transform">The transform to push.</param>
public void Push(in Matrix4 transform)
{
_stack.Push(_top);
_top = transform * _top;
}
/// <summary>
/// Pop a transform.
/// </summary>
public void Pop()
{
if (!_stack.TryPop(out _top))
_top = Matrix4.Identity;
}
/// <summary>
/// Clear the stack of transformations.
/// </summary>
public void Clear()
{
_stack.Clear();
_top = Matrix4.Identity;
}
}
}

@ -0,0 +1,97 @@
#version 140
#define DB_GRADIENT_MAX 16
#define DB_COMMAND_MAX 64
#define CMD_POINT 1
#define CMD_LINE 2
#define CMD_RECT 3
#define CMD_TEXT 4
#define STRIKE_INSET -1
#define STRIKE_CENTER 0
#define STRIKE_OUTSET 1
in vec3 v_v3Position;
in vec2 v_v2TexCoords;
in int v_iCmdIndex;
out vec4 f_Color;
uniform sampler2D txForeground;
uniform sampler2D txBackground;
uniform sampler2DArray txCharacters;
struct Gradient_t {
float fPosition;
float pad0;
float pad1;
float pad2;
vec4 v4Color;
};
uniform GradientBlock
{
Gradient_t vstGradientStops[DB_GRADIENT_MAX];
};
vec4 getGradientColor(float position, int index, int count)
{
position = clamp(position, 0, 1);
int i0 = 0;
float p0 = vstGradientStops[index + i0].fPosition;
int i1 = count - 1;
float p1 = vstGradientStops[index + i1].fPosition;
for (int i = 0; i < count; i++)
{
float px = vstGradientStops[index + i].fPosition;
if (px > p0 && px <= position)
{
p0 = px;
i0 = i;
}
if (px < p1 && px >= position)
{
p1 = px;
i1 = i;
}
}
vec4 c0 = vstGradientStops[index + i0].v4Color;
vec4 c1 = vstGradientStops[index + i1].v4Color;
float l = p1 - p0;
float w = (l > 0) ? (position - p0) / (p1 - p0) : 0;
return mix(c0, c1, w);
}
struct CommandCommon {
int iCommand;
int iStrikeKind;
float fBorderRadius;
int _padding;
int iFgGradientIndex;
int iFgGradientCount;
int iBgGradientIndex;
int iBgGradientCount;
vec4 v4FillColor;
vec4 v4BorderColor;
};
uniform CommandBlock
{
CommandCommon vstCommands[DB_COMMAND_MAX];
};
void main(void)
{
}

@ -0,0 +1,24 @@
#version 140
in vec3 a_v3Position;
in vec2 a_v2TexCoords;
in vec3 a_v3CharCoords;
in int a_iCmdIndex;
out vec3 v_v3Position;
out vec2 v_v2TexCoords;
out vec3 v_v3CharCoords;
flat out int v_iCmdIndex;
uniform mat4 m4Transforms;
void main(void)
{
vec4 position = vec4(a_v3Position, 1) * m4Transforms;
gl_Position = position;
v_v3Position = position.xyz/position.w;
v_v2TexCoords = a_v2TexCoords;
v_v3CharCoords = a_v3CharCoords;
v_iCmdIndex = a_iCmdIndex;
}

@ -49,7 +49,7 @@ namespace Dashboard.Drawing
_resources.Clear();
_commands.Clear();
_extensions.Clear();
_commandStream.Capacity = 0;
_commandStream.SetLength(0);
}
public int RequireExtension(IDrawExtension extension)

@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard.TestApplication",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Common", "Dashboard.Common\Dashboard.Common.csproj", "{C77CDD2B-2482-45F9-B330-47A52F5F13C0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Drawing.OpenGL", "Dashboard.Drawing.OpenGL\Dashboard.Drawing.OpenGL.csproj", "{454198BA-CB95-41C5-A934-B1C8FDA35A6B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -39,6 +41,10 @@ Global
{C77CDD2B-2482-45F9-B330-47A52F5F13C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C77CDD2B-2482-45F9-B330-47A52F5F13C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C77CDD2B-2482-45F9-B330-47A52F5F13C0}.Release|Any CPU.Build.0 = Release|Any CPU
{454198BA-CB95-41C5-A934-B1C8FDA35A6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{454198BA-CB95-41C5-A934-B1C8FDA35A6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{454198BA-CB95-41C5-A934-B1C8FDA35A6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{454198BA-CB95-41C5-A934-B1C8FDA35A6B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

@ -8,7 +8,12 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Dashboard.Drawing.OpenGL\Dashboard.Drawing.OpenGL.csproj" />
<ProjectReference Include="..\..\Dashboard.Drawing\Dashboard.Drawing.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="OpenTK" Version="5.0.0-pre.13" />
</ItemGroup>
</Project>

@ -1,23 +1,131 @@
using Dashboard.Drawing;
using System.Diagnostics;
using System.Drawing;
using System.Numerics;
using Dashboard.Drawing.OpenGL;
using OpenTK.Graphics;
using OpenTK.Platform;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using TK = OpenTK.Platform.Toolkit;
using Vector3 = System.Numerics.Vector3;
TK.Init(new ToolkitOptions()
{
ApplicationName = "Dashboard Test Application",
Windows = new ToolkitOptions.WindowsOptions()
{
EnableVisualStyles = true,
IsDPIAware = true,
}
});
WindowHandle wnd = TK.Window.Create(new OpenGLGraphicsApiHints()
{
Version = new Version(3, 2),
ForwardCompatibleFlag = true,
DebugFlag = true,
Profile = OpenGLProfile.Core,
sRGBFramebuffer = true,
SwapMethod = ContextSwapMethod.Undefined,
RedColorBits = 8,
GreenColorBits = 8,
BlueColorBits = 8,
AlphaColorBits = 8,
SupportTransparentFramebufferX11 = true,
});
TK.Window.SetTitle(wnd, "Dashboard Test Application");
TK.Window.SetMinClientSize(wnd, 300, 200);
TK.Window.SetClientSize(wnd, new Vector2i(320, 240));
TK.Window.SetBorderStyle(wnd, WindowBorderStyle.ResizableBorder);
OpenGLContextHandle context = TK.OpenGL.CreateFromWindow(wnd);
TK.OpenGL.SetCurrentContext(context);
TK.OpenGL.SetSwapInterval(1);
GLLoader.LoadBindings(new Pal2BindingsContext(TK.OpenGL, context));
DrawQueue queue = new DrawQueue();
SolidBrush fg = new SolidBrush(Color.Black);
SolidBrush bg = new SolidBrush(Color.White);
bool shouldExit = false;
GLEngine engine = new GLEngine();
engine.Initialize();
GlContext dbGlContext = new GlContext(wnd, context);
ContextExecutor executor = engine.GetExecutor(dbGlContext);
EventQueue.EventRaised += (handle, type, eventArgs) =>
{
if (handle != wnd)
return;
switch (type)
{
case PlatformEventType.Close:
shouldExit = true;
break;
}
};
TK.Window.SetMode(wnd, WindowMode.Normal);
while (!shouldExit)
{
TK.Window.ProcessEvents(true);
TK.Window.GetFramebufferSize(wnd, out Vector2i framebufferSize);
queue.Clear();
queue.Point(Vector3.Zero, 2f, fg);
queue.Line(Vector3.Zero, Vector3.One * 2, 2f, bg);
queue.Rect(Vector3.UnitX, 2 * Vector3.UnitX + Vector3.UnitY, fg, bg, 2f);
foreach (ICommandFrame frame in queue)
{
Console.WriteLine("{0}", frame.Command.Name);
GL.Viewport(0, 0, framebufferSize.X, framebufferSize.Y);
GL.ClearColor(0.3f, 0.3f, 0.3f, 1.0f);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
if (frame.HasParameters)
Console.WriteLine("Param: {0}", frame.GetParameter());
executor.Draw(queue);
executor.EndFrame();
TK.OpenGL.SwapBuffers(context);
}
Debugger.Break();
class GlContext : IGLContext
{
public WindowHandle Window { get; }
public OpenGLContextHandle Context { get; }
public int ContextGroup { get; } = -1;
public Size FramebufferSize
{
get
{
TK.Window.GetFramebufferSize(Window, out Vector2i framebufferSize);
return new Size(framebufferSize.X, framebufferSize.Y);
}
}
public event Action? Disposed;
public GlContext(WindowHandle window, OpenGLContextHandle context)
{
Window = window;
Context = context;
OpenGLContextHandle? shared = TK.OpenGL.GetSharedContext(context);
if (shared != null)
{
ContextGroup = _contexts.IndexOf(shared);
}
else
{
_contexts.Add(context);
}
}
private static readonly List<OpenGLContextHandle> _contexts = new List<OpenGLContextHandle>();
}