From b841dbe6c23ffde6960d26a010c49fb2b939f994 Mon Sep 17 00:00:00 2001 From: "H. Utku Maden" Date: Tue, 14 Jan 2025 20:34:47 +0300 Subject: [PATCH] Begin implementing the GL engine. --- Dashboard.Common/Gradient.cs | 26 +++- Dashboard.Drawing.OpenGL/ContextCollector.cs | 77 ++++++++++ Dashboard.Drawing.OpenGL/ContextExecutor.cs | 134 ++++++++++++++++++ .../ContextResourcePool.cs | 81 +++++++++++ .../Dashboard.Drawing.OpenGL.csproj | 25 ++++ Dashboard.Drawing.OpenGL/GLEngine.cs | 36 +++++ .../GradientUniformBuffer.cs | 68 +++++++++ Dashboard.Drawing.OpenGL/IArc.cs | 24 ++++ Dashboard.Drawing.OpenGL/IGLContext.cs | 26 ++++ Dashboard.Drawing.OpenGL/IGLDisposable.cs | 16 +++ Dashboard.Drawing.OpenGL/MappableBuffer.cs | 118 +++++++++++++++ Dashboard.Drawing.OpenGL/TransformStack.cs | 51 +++++++ Dashboard.Drawing.OpenGL/default.frag | 97 +++++++++++++ Dashboard.Drawing.OpenGL/default.vert | 24 ++++ Dashboard.Drawing/DrawQueue.cs | 2 +- Dashboard.sln | 6 + .../Dashboard.TestApplication.csproj | 5 + tests/Dashboard.TestApplication/Program.cs | 130 +++++++++++++++-- 18 files changed, 933 insertions(+), 13 deletions(-) create mode 100644 Dashboard.Drawing.OpenGL/ContextCollector.cs create mode 100644 Dashboard.Drawing.OpenGL/ContextExecutor.cs create mode 100644 Dashboard.Drawing.OpenGL/ContextResourcePool.cs create mode 100644 Dashboard.Drawing.OpenGL/Dashboard.Drawing.OpenGL.csproj create mode 100644 Dashboard.Drawing.OpenGL/GLEngine.cs create mode 100644 Dashboard.Drawing.OpenGL/GradientUniformBuffer.cs create mode 100644 Dashboard.Drawing.OpenGL/IArc.cs create mode 100644 Dashboard.Drawing.OpenGL/IGLContext.cs create mode 100644 Dashboard.Drawing.OpenGL/IGLDisposable.cs create mode 100644 Dashboard.Drawing.OpenGL/MappableBuffer.cs create mode 100644 Dashboard.Drawing.OpenGL/TransformStack.cs create mode 100644 Dashboard.Drawing.OpenGL/default.frag create mode 100644 Dashboard.Drawing.OpenGL/default.vert diff --git a/Dashboard.Common/Gradient.cs b/Dashboard.Common/Gradient.cs index f9f98bc..cf27537 100644 --- a/Dashboard.Common/Gradient.cs +++ b/Dashboard.Common/Gradient.cs @@ -30,7 +30,7 @@ namespace Dashboard /// /// Represents a linear gradient. /// - public struct Gradient : ICollection, ICloneable + public struct Gradient : ICollection, ICloneable, IEquatable { private readonly List _stops = new List(); @@ -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); + } } } diff --git a/Dashboard.Drawing.OpenGL/ContextCollector.cs b/Dashboard.Drawing.OpenGL/ContextCollector.cs new file mode 100644 index 0000000..b5ccde8 --- /dev/null +++ b/Dashboard.Drawing.OpenGL/ContextCollector.cs @@ -0,0 +1,77 @@ +using System.Collections.Concurrent; +using OpenTK.Graphics.OpenGL; + +namespace Dashboard.Drawing.OpenGL +{ + public class ContextCollector : IDisposable + { + private readonly ConcurrentQueue _disposedObjects = new ConcurrentQueue(); + + 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(); + } +} diff --git a/Dashboard.Drawing.OpenGL/ContextExecutor.cs b/Dashboard.Drawing.OpenGL/ContextExecutor.cs new file mode 100644 index 0000000..4d59991 --- /dev/null +++ b/Dashboard.Drawing.OpenGL/ContextExecutor.cs @@ -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)!; + } + } +} diff --git a/Dashboard.Drawing.OpenGL/ContextResourcePool.cs b/Dashboard.Drawing.OpenGL/ContextResourcePool.cs new file mode 100644 index 0000000..ddfc556 --- /dev/null +++ b/Dashboard.Drawing.OpenGL/ContextResourcePool.cs @@ -0,0 +1,81 @@ +namespace Dashboard.Drawing.OpenGL +{ + public class ContextResourcePoolManager + { + private readonly Dictionary _unique = new Dictionary(); + private readonly Dictionary _shared = new Dictionary(); + + 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; + } + } +} diff --git a/Dashboard.Drawing.OpenGL/Dashboard.Drawing.OpenGL.csproj b/Dashboard.Drawing.OpenGL/Dashboard.Drawing.OpenGL.csproj new file mode 100644 index 0000000..a094872 --- /dev/null +++ b/Dashboard.Drawing.OpenGL/Dashboard.Drawing.OpenGL.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + + + + + + + + diff --git a/Dashboard.Drawing.OpenGL/GLEngine.cs b/Dashboard.Drawing.OpenGL/GLEngine.cs new file mode 100644 index 0000000..7f84aff --- /dev/null +++ b/Dashboard.Drawing.OpenGL/GLEngine.cs @@ -0,0 +1,36 @@ +using OpenTK; +using OpenTK.Graphics; + +namespace Dashboard.Drawing.OpenGL +{ + public class GLEngine + { + private readonly Dictionary _executors = new Dictionary(); + + 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; + } + } +} diff --git a/Dashboard.Drawing.OpenGL/GradientUniformBuffer.cs b/Dashboard.Drawing.OpenGL/GradientUniformBuffer.cs new file mode 100644 index 0000000..478616d --- /dev/null +++ b/Dashboard.Drawing.OpenGL/GradientUniformBuffer.cs @@ -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 _buffer = new MappableBuffer(); + private readonly Dictionary _entries = new Dictionary(); + + 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 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; + } +} diff --git a/Dashboard.Drawing.OpenGL/IArc.cs b/Dashboard.Drawing.OpenGL/IArc.cs new file mode 100644 index 0000000..1bfc99c --- /dev/null +++ b/Dashboard.Drawing.OpenGL/IArc.cs @@ -0,0 +1,24 @@ +namespace Dashboard.Drawing.OpenGL +{ + /// + /// Atomic reference counter. + /// + public interface IArc : IDisposable + { + /// + /// The number of references to this. + /// + int References { get; } + + /// + /// Increment the number of references. + /// + void IncrementReference(); + + /// + /// Decrement the number of references. + /// + /// True if this was the last reference. + bool DecrementReference(); + } +} diff --git a/Dashboard.Drawing.OpenGL/IGLContext.cs b/Dashboard.Drawing.OpenGL/IGLContext.cs new file mode 100644 index 0000000..b326622 --- /dev/null +++ b/Dashboard.Drawing.OpenGL/IGLContext.cs @@ -0,0 +1,26 @@ +using System.Drawing; + +namespace Dashboard.Drawing.OpenGL +{ + /// + /// Interface for GL context operations + /// + public interface IGLContext + { + /// + /// The associated group for context sharing. + /// + /// -1 assigns no group. + public int ContextGroup { get; } + + /// + /// The size of the framebuffer in pixels. + /// + public Size FramebufferSize { get; } + + /// + /// Called when the context is disposed. + /// + event Action Disposed; + } +} diff --git a/Dashboard.Drawing.OpenGL/IGLDisposable.cs b/Dashboard.Drawing.OpenGL/IGLDisposable.cs new file mode 100644 index 0000000..c1d9aba --- /dev/null +++ b/Dashboard.Drawing.OpenGL/IGLDisposable.cs @@ -0,0 +1,16 @@ +namespace Dashboard.Drawing.OpenGL +{ + /// + /// Interface much like except GL resources are dropped. + /// + /// + /// 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. + /// + public interface IGLDisposable : IDisposable + { + /// + /// Set to true to spend the time clearing out GL objects. + void Dispose(bool safeExit); + } +} diff --git a/Dashboard.Drawing.OpenGL/MappableBuffer.cs b/Dashboard.Drawing.OpenGL/MappableBuffer.cs new file mode 100644 index 0000000..8081dea --- /dev/null +++ b/Dashboard.Drawing.OpenGL/MappableBuffer.cs @@ -0,0 +1,118 @@ +using System.Numerics; +using System.Runtime.CompilerServices; +using OpenTK.Graphics.OpenGL; + +namespace Dashboard.Drawing.OpenGL +{ + public class MappableBuffer : 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(); + 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 AsSpan() + { + if (Pointer == IntPtr.Zero) + throw new InvalidOperationException("The buffer is not currently mapped."); + + AssertInitialized(); + + return new Span(Pointer.ToPointer(), Capacity / Unsafe.SizeOf()); + } + + 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); + } +} diff --git a/Dashboard.Drawing.OpenGL/TransformStack.cs b/Dashboard.Drawing.OpenGL/TransformStack.cs new file mode 100644 index 0000000..000fe44 --- /dev/null +++ b/Dashboard.Drawing.OpenGL/TransformStack.cs @@ -0,0 +1,51 @@ +using OpenTK.Mathematics; + +namespace Dashboard.Drawing.OpenGL +{ + /// + /// The current stack of transformations. + /// + public class TransformStack + { + private Matrix4 _top = Matrix4.Identity; + private readonly Stack _stack = new Stack(); + + /// + /// The top-most transform matrix. + /// + public ref readonly Matrix4 Top => ref _top; + + /// + /// The number of matrices in the stack. + /// + public int Count => _stack.Count; + + /// + /// Push a transform. + /// + /// The transform to push. + public void Push(in Matrix4 transform) + { + _stack.Push(_top); + _top = transform * _top; + } + + /// + /// Pop a transform. + /// + public void Pop() + { + if (!_stack.TryPop(out _top)) + _top = Matrix4.Identity; + } + + /// + /// Clear the stack of transformations. + /// + public void Clear() + { + _stack.Clear(); + _top = Matrix4.Identity; + } + } +} diff --git a/Dashboard.Drawing.OpenGL/default.frag b/Dashboard.Drawing.OpenGL/default.frag new file mode 100644 index 0000000..2e3d45a --- /dev/null +++ b/Dashboard.Drawing.OpenGL/default.frag @@ -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) +{ + +} diff --git a/Dashboard.Drawing.OpenGL/default.vert b/Dashboard.Drawing.OpenGL/default.vert new file mode 100644 index 0000000..019c07d --- /dev/null +++ b/Dashboard.Drawing.OpenGL/default.vert @@ -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; +} diff --git a/Dashboard.Drawing/DrawQueue.cs b/Dashboard.Drawing/DrawQueue.cs index 9f04a20..6df93e2 100644 --- a/Dashboard.Drawing/DrawQueue.cs +++ b/Dashboard.Drawing/DrawQueue.cs @@ -49,7 +49,7 @@ namespace Dashboard.Drawing _resources.Clear(); _commands.Clear(); _extensions.Clear(); - _commandStream.Capacity = 0; + _commandStream.SetLength(0); } public int RequireExtension(IDrawExtension extension) diff --git a/Dashboard.sln b/Dashboard.sln index cbcbcb5..aeb4144 100644 --- a/Dashboard.sln +++ b/Dashboard.sln @@ -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 diff --git a/tests/Dashboard.TestApplication/Dashboard.TestApplication.csproj b/tests/Dashboard.TestApplication/Dashboard.TestApplication.csproj index 80bbb87..e7a339c 100644 --- a/tests/Dashboard.TestApplication/Dashboard.TestApplication.csproj +++ b/tests/Dashboard.TestApplication/Dashboard.TestApplication.csproj @@ -8,7 +8,12 @@ + + + + + diff --git a/tests/Dashboard.TestApplication/Program.cs b/tests/Dashboard.TestApplication/Program.cs index 1295240..ab954ee 100644 --- a/tests/Dashboard.TestApplication/Program.cs +++ b/tests/Dashboard.TestApplication/Program.cs @@ -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; -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); +GLEngine engine = new GLEngine(); +engine.Initialize(); -foreach (ICommandFrame frame in queue) +GlContext dbGlContext = new GlContext(wnd, context); +ContextExecutor executor = engine.GetExecutor(dbGlContext); + +EventQueue.EventRaised += (handle, type, eventArgs) => { - Console.WriteLine("{0}", frame.Command.Name); + if (handle != wnd) + return; - if (frame.HasParameters) - Console.WriteLine("Param: {0}", frame.GetParameter()); + 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); + + GL.Viewport(0, 0, framebufferSize.X, framebufferSize.Y); + GL.ClearColor(0.3f, 0.3f, 0.3f, 1.0f); + GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); + + 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 _contexts = new List(); +}