diff --git a/Dashboard.Drawing.OpenGL/CommandInfo.cs b/Dashboard.Drawing.OpenGL/CommandInfo.cs
new file mode 100644
index 0000000..2e14aa4
--- /dev/null
+++ b/Dashboard.Drawing.OpenGL/CommandInfo.cs
@@ -0,0 +1,48 @@
+using System.Numerics;
+using System.Runtime.InteropServices;
+
+namespace Dashboard.Drawing.OpenGL
+{
+ public enum SimpleDrawCommand : int
+ {
+ Point = 1,
+ Line = 2,
+ Rect = 3,
+
+ ///
+ /// Make sure your custom commands have values higher than this if you plan on using the default command
+ /// buffer.
+ ///
+ CustomCommandStart = 4096
+ }
+
+ [StructLayout(LayoutKind.Explicit, Size = 64)]
+ public struct CommandInfo
+ {
+ [FieldOffset(0)]
+ public SimpleDrawCommand Type;
+
+ [FieldOffset(4)]
+ public int Flags;
+
+ [FieldOffset(8)]
+ public float Arg0;
+ [FieldOffset(12)]
+ public float Arg1;
+
+ [FieldOffset(16)]
+ public int FgGradientIndex;
+ [FieldOffset(20)]
+ public int FgGradientCount;
+ [FieldOffset(24)]
+ public int BgGradientIndex;
+ [FieldOffset(28)]
+ public int BgGradientCount;
+
+ [FieldOffset(32)]
+ public Vector4 FgColor;
+
+ [FieldOffset(48)]
+ public Vector4 BgColor;
+ }
+}
diff --git a/Dashboard.Drawing.OpenGL/ContextExecutor.cs b/Dashboard.Drawing.OpenGL/ContextExecutor.cs
index 4d59991..510a8aa 100644
--- a/Dashboard.Drawing.OpenGL/ContextExecutor.cs
+++ b/Dashboard.Drawing.OpenGL/ContextExecutor.cs
@@ -1,18 +1,47 @@
using System.Drawing;
-using OpenTK.Graphics.OpenGL;
+using Dashboard.Drawing.OpenGL.Executors;
namespace Dashboard.Drawing.OpenGL
{
- public class ContextExecutor : IDisposable
+ public interface ICommandExecutor
+ {
+ IEnumerable Extensions { get; }
+ IContextExecutor Executor { get; }
+
+ void SetContextExecutor(IContextExecutor executor);
+
+ void BeginFrame();
+
+ void BeginDraw();
+
+ void EndDraw();
+
+ void EndFrame();
+
+ void ProcessCommand(ICommandFrame frame);
+ }
+
+ public interface IContextExecutor : IInitializer, IGLDisposable
+ {
+ GLEngine Engine { get; }
+ IGLContext Context { get; }
+ ContextResourcePool ResourcePool { get; }
+ TransformStack TransformStack { get; }
+ }
+
+ public class ContextExecutor : IContextExecutor
{
public GLEngine Engine { get; }
public IGLContext Context { get; }
public ContextResourcePool ResourcePool { get; }
+ public TransformStack TransformStack { get; } = new TransformStack();
protected bool IsDisposed { get; private set; } = false;
-
public bool IsInitialized { get; private set; } = false;
- private int _program = 0;
+ private readonly List _executorsList = new List();
+
+ private readonly Dictionary _executorsMap = new Dictionary();
+
public ContextExecutor(GLEngine engine, IGLContext context)
{
Engine = engine;
@@ -20,11 +49,41 @@ namespace Dashboard.Drawing.OpenGL
ResourcePool = Engine.ResourcePoolManager.Get(context);
ResourcePool.IncrementReference();
+
+ AddExecutor(new BaseCommandExecutor());
}
~ContextExecutor()
{
- DisposeInvoker(false);
+ DisposeInvoker(true, false);
+ }
+
+ public void AddExecutor(ICommandExecutor executor, bool overwrite = false)
+ {
+ if (IsInitialized)
+ throw new Exception("This context executor is already initialized. Cannot add new command executors.");
+
+ IInitializer? initializer = executor as IInitializer;
+
+ if (initializer?.IsInitialized == true)
+ throw new InvalidOperationException("This command executor has already been initialized, cannot add here.");
+
+ if (!overwrite)
+ {
+ foreach (string extension in executor.Extensions)
+ {
+ if (_executorsMap.ContainsKey(extension))
+ throw new InvalidOperationException("An executor already handles this extension.");
+ }
+ }
+
+ foreach (string extension in executor.Extensions)
+ {
+ _executorsMap[extension] = executor;
+ }
+ _executorsList.Add(executor);
+
+ executor.SetContextExecutor(this);
}
public void Initialize()
@@ -33,32 +92,56 @@ namespace Dashboard.Drawing.OpenGL
return;
IsInitialized = true;
- LoadShaders();
+ foreach (ICommandExecutor executor in _executorsList)
+ {
+ if (executor is IInitializer initializer)
+ initializer.Initialize();
+ }
}
- private void LoadShaders()
+ public virtual void BeginFrame()
{
- 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);
+ foreach (ICommandExecutor executor in _executorsList)
+ executor.BeginFrame();
}
- public void Draw(DrawQueue drawqueue) => Draw(drawqueue, new RectangleF(new PointF(0f,0f), Context.FramebufferSize));
- public virtual void Draw(DrawQueue drawQueue, RectangleF bounds)
+ protected virtual void BeginDraw()
{
+ foreach (ICommandExecutor executor in _executorsList)
+ executor.BeginDraw();
+ }
+ protected virtual void EndDraw()
+ {
+ foreach (ICommandExecutor executor in _executorsList)
+ executor.EndDraw();
}
public virtual void EndFrame()
{
+ ResourcePool.Collector.Dispose();
+ TransformStack.Clear();
+ foreach (ICommandExecutor executor in _executorsList)
+ executor.EndFrame();
}
- private void DisposeInvoker(bool disposing)
+ public void Draw(DrawQueue drawqueue) => Draw(drawqueue, new RectangleF(new PointF(0f,0f), Context.FramebufferSize));
+
+ public virtual void Draw(DrawQueue drawQueue, RectangleF bounds)
+ {
+ BeginDraw();
+
+ foreach (ICommandFrame frame in drawQueue)
+ {
+ if (_executorsMap.TryGetValue(frame.Command.Extension.Name, out ICommandExecutor? executor))
+ executor.ProcessCommand(frame);
+ }
+
+ EndDraw();
+ }
+
+ private void DisposeInvoker(bool safeExit, bool disposing)
{
if (!IsDisposed)
return;
@@ -68,67 +151,28 @@ namespace Dashboard.Drawing.OpenGL
if (disposing)
GC.SuppressFinalize(this);
- Dispose(disposing);
+ Dispose(safeExit, disposing);
}
- protected virtual void Dispose(bool disposing)
+ protected virtual void Dispose(bool safeExit, 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)
+ if (disposing)
{
- GL.GetShaderInfoLog(shader, out string log);
- GL.DeleteShader(shader);
- throw new Exception($"{type} Shader compilation failed: " + log);
+ foreach (ICommandExecutor executor in _executorsList)
+ {
+ if (executor is IGLDisposable glDisposable)
+ glDisposable.Dispose(safeExit);
+ else if (executor is IDisposable disposable)
+ disposable.Dispose();
+ }
+
+ if (ResourcePool.DecrementReference())
+ Dispose();
}
-
- return shader;
}
- private static int CompileShader(ShaderType type, Stream stream)
- {
- using StreamReader reader = new StreamReader(stream, leaveOpen: true);
+ public void Dispose() => DisposeInvoker(true, 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)!;
- }
+ public void Dispose(bool safeExit) => DisposeInvoker(safeExit, true);
}
}
diff --git a/Dashboard.Drawing.OpenGL/Dashboard.Drawing.OpenGL.csproj b/Dashboard.Drawing.OpenGL/Dashboard.Drawing.OpenGL.csproj
index a094872..4c9837e 100644
--- a/Dashboard.Drawing.OpenGL/Dashboard.Drawing.OpenGL.csproj
+++ b/Dashboard.Drawing.OpenGL/Dashboard.Drawing.OpenGL.csproj
@@ -16,10 +16,8 @@
-
-
-
-
+
+
diff --git a/Dashboard.Drawing.OpenGL/DrawCallRecorder.cs b/Dashboard.Drawing.OpenGL/DrawCallRecorder.cs
new file mode 100644
index 0000000..5b6a482
--- /dev/null
+++ b/Dashboard.Drawing.OpenGL/DrawCallRecorder.cs
@@ -0,0 +1,227 @@
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Mathematics;
+using Vector2 = System.Numerics.Vector2;
+using Vector3 = System.Numerics.Vector3;
+
+namespace Dashboard.Drawing.OpenGL
+{
+ public class DrawCallRecorder : IGLDisposable, IInitializer
+ {
+ private int _vao = 0;
+ private int _vbo = 0;
+ private readonly List _vertices = new List();
+ private readonly List _calls = new List();
+
+ private int _start = 0;
+ private int _count = 0;
+ private int _primitives = 0;
+ private Vector3 _charCoords;
+ private int _cmdIndex;
+ private int _texture0, _texture1, _texture2, _texture3;
+ private TextureTarget _target0, _target1, _target2, _target3;
+ private Matrix4 _transforms = Matrix4.Identity;
+
+ public int CommandModulus = 64;
+ public int CommandBuffer = 0;
+ public int CommandSize = 64;
+
+ private int CommandByteSize => CommandModulus * CommandSize;
+
+ public int TransformsLocation { get; set; }
+
+ public void Transforms(in Matrix4 transforms)
+ {
+ _transforms = transforms;
+ }
+
+ public void Begin(PrimitiveType type)
+ {
+ if (_primitives != 0)
+ throw new InvalidOperationException("Attempt to begin new draw call before finishing previous one.");
+
+ _primitives = (int)type;
+ _start = _vertices.Count;
+ _count = 0;
+ }
+
+ public void TexCoords2(Vector2 texCoords)
+ {
+ _charCoords = new Vector3(texCoords, 0);
+ }
+
+ public void CharCoords(Vector3 charCoords)
+ {
+ _charCoords = charCoords;
+ }
+
+ public void CommandIndex(int index)
+ {
+ _cmdIndex = index;
+ }
+
+ public void Vertex3(Vector3 vertex)
+ {
+ _vertices.Add(new DrawVertex()
+ {
+ Position = vertex,
+ CharCoords = _charCoords,
+ CmdIndex = _cmdIndex % CommandModulus,
+ });
+ _count++;
+ }
+
+ public void End()
+ {
+ if (_primitives == 0)
+ throw new InvalidOperationException("Attempt to end draw call before starting one.");
+
+ _calls.Add(
+ new DrawCall()
+ {
+ Type = (PrimitiveType)_primitives,
+ Start = _start,
+ Count = _count,
+ CmdIndex = _cmdIndex,
+ Target0 = _target0,
+ Target1 = _target1,
+ Target2 = _target2,
+ Target3 = _target3,
+ Texture0 = _texture0,
+ Texture1 = _texture1,
+ Texture2 = _texture2,
+ Texture3 = _texture3,
+ Transforms = _transforms,
+ });
+
+ _primitives = 0;
+ }
+
+ public void BindTexture(TextureTarget target, int texture) => BindTexture(target, 0, texture);
+
+ public void BindTexture(TextureTarget target, int unit, int texture)
+ {
+ switch (unit)
+ {
+ case 0:
+ _texture0 = 0;
+ _target0 = target;
+ break;
+ case 1:
+ _texture1 = 0;
+ _target1 = target;
+ break;
+ case 2:
+ _texture2 = 0;
+ _target2 = target;
+ break;
+ case 3:
+ _texture3 = 0;
+ _target3 = target;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(unit), "I did not write support for more than 4 textures.");
+ }
+ }
+
+ public void DrawArrays(PrimitiveType type, int first, int count)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Execute()
+ {
+ GL.BindVertexArray(_vao);
+ GL.BindBuffer(BufferTarget.ArrayBuffer, _vbo);
+
+ ReadOnlySpan vertices = CollectionsMarshal.AsSpan(_vertices);
+ GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Count * Unsafe.SizeOf(), vertices, BufferUsage.DynamicDraw);
+
+ foreach (DrawCall call in _calls)
+ {
+ GL.BindBufferRange(BufferTarget.UniformBuffer, 0, CommandBuffer, call.CmdIndex / CommandModulus * CommandByteSize, CommandByteSize);
+ GL.ActiveTexture(TextureUnit.Texture0);
+ GL.BindTexture(call.Target0, call.Texture0);
+ GL.ActiveTexture(TextureUnit.Texture1);
+ GL.BindTexture(call.Target1, call.Texture1);
+ GL.ActiveTexture(TextureUnit.Texture2);
+ GL.BindTexture(call.Target2, call.Texture2);
+ GL.ActiveTexture(TextureUnit.Texture3);
+ GL.BindTexture(call.Target3, call.Texture3);
+
+ Matrix4 transforms = call.Transforms;
+ GL.UniformMatrix4f(TransformsLocation, 1, true, ref transforms);
+ GL.DrawArrays(call.Type, call.Start, call.Count);
+ }
+ }
+
+ public void Clear()
+ {
+ _vertices.Clear();
+ _calls.Clear();
+ }
+
+ public void Dispose()
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Dispose(bool safeExit)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool IsInitialized { get; private set; }
+ public void Initialize()
+ {
+ if (IsInitialized)
+ return;
+ IsInitialized = true;
+
+ _vao = GL.CreateVertexArray();
+ _vbo = GL.CreateBuffer();
+
+ GL.BindVertexArray(_vao);
+ GL.BindBuffer(BufferTarget.ArrayBuffer, _vbo);
+
+ GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 32, 0);
+ GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, 32, 16);
+ GL.VertexAttribIPointer(2, 1, VertexAttribIType.Int, 32, 28);
+ GL.EnableVertexAttribArray(0);
+ GL.EnableVertexAttribArray(1);
+ GL.EnableVertexAttribArray(2);
+ }
+
+ private struct DrawCall
+ {
+ public PrimitiveType Type;
+ public int Start;
+ public int Count;
+ public int CmdIndex;
+
+ public int Texture0;
+ public int Texture1;
+ public int Texture2;
+ public int Texture3;
+
+ public TextureTarget Target0;
+ public TextureTarget Target1;
+ public TextureTarget Target2;
+ public TextureTarget Target3;
+
+ public Matrix4 Transforms;
+ }
+
+ [StructLayout(LayoutKind.Explicit, Size = 32)]
+ private struct DrawVertex
+ {
+ [FieldOffset(0)]
+ public Vector3 Position;
+ [FieldOffset(16)]
+ public Vector3 CharCoords;
+ [FieldOffset(28)]
+ public int CmdIndex;
+ }
+ }
+}
diff --git a/Dashboard.Drawing.OpenGL/Executors/BaseCommandExecutor.cs b/Dashboard.Drawing.OpenGL/Executors/BaseCommandExecutor.cs
new file mode 100644
index 0000000..6ee7c1a
--- /dev/null
+++ b/Dashboard.Drawing.OpenGL/Executors/BaseCommandExecutor.cs
@@ -0,0 +1,332 @@
+using System.Drawing;
+using OpenTK.Graphics.OpenGL;
+using System.Numerics;
+using OTK = OpenTK.Mathematics;
+
+namespace Dashboard.Drawing.OpenGL.Executors
+{
+ public class BaseCommandExecutor : IInitializer, ICommandExecutor
+ {
+ private int _program = 0;
+ private readonly MappableBumpAllocator _commands = new MappableBumpAllocator();
+ private readonly DrawCallRecorder _calls = new DrawCallRecorder();
+
+ public bool IsInitialized { get; private set; }
+ public IEnumerable Extensions { get; } = new[] { "DB_base" };
+ public IContextExecutor Executor { get; private set; } = null!;
+
+ public void Initialize()
+ {
+ if (IsInitialized) return;
+
+ if (Executor == null)
+ throw new Exception("Executor has not been set.");
+
+ IsInitialized = true;
+
+ LoadShaders();
+ }
+
+ public void SetContextExecutor(IContextExecutor executor)
+ {
+ Executor = executor;
+ }
+
+ public void BeginFrame()
+ {
+ }
+
+ public void BeginDraw()
+ {
+ _commands.Initialize();
+ _calls.Initialize();
+
+ Size size = Executor.Context.FramebufferSize;
+
+ Executor.TransformStack.Push(OTK.Matrix4.CreateOrthographicOffCenter(
+ 0,
+ size.Width,
+ size.Height,
+ 0,
+ 1,
+ -1));
+
+ GL.Viewport(0, 0, size.Width, size.Height);
+ }
+
+ public void EndDraw()
+ {
+ _commands.Unmap();
+ GL.UseProgram(_program);
+ _calls.CommandBuffer = _commands.Handle;
+ _calls.Execute();
+ }
+
+ public void EndFrame()
+ {
+ _commands.Clear();
+ _calls.Clear();
+ }
+
+ public void ProcessCommand(ICommandFrame frame)
+ {
+ switch (frame.Command.Name)
+ {
+ case "Point":
+ DrawBasePoint(frame);
+ break;
+ case "Line":
+ DrawBaseLine(frame);
+ break;
+ case "RectF":
+ case "RectS":
+ case "RectFS":
+ DrawRect(frame);
+ break;
+ }
+ }
+
+ private void DrawBasePoint(ICommandFrame frame)
+ {
+ ref CommandInfo info = ref _commands.Take(out int index);
+
+ PointCommandArgs args = frame.GetParameter();
+
+ info = new CommandInfo()
+ {
+ Type = SimpleDrawCommand.Point,
+ Arg0 = args.Size,
+ };
+
+ SetCommandCommonBrush(ref info, args.Brush, args.Brush);
+
+ _calls.Transforms(Executor.TransformStack.Top);
+ _calls.Begin(PrimitiveType.Triangles);
+ _calls.CommandIndex(index);
+ DrawPoint(args.Position, args.Depth, args.Size);
+ _calls.End();
+ }
+
+ private void DrawPoint(Vector2 position, float depth, float diameter)
+ {
+ // Draw a point as a isocles triangle.
+ const float adjust = 1.1f;
+ const float cos30 = 0.8660254038f;
+ Vector2 top = adjust * new Vector2(0, -cos30);
+ Vector2 left = adjust * new Vector2(-cos30, 0.5f);
+ Vector2 right = adjust * new Vector2(cos30, 0.5f);
+
+ _calls.TexCoords2(top);
+ _calls.Vertex3(new Vector3(position + top * diameter, depth));
+ _calls.TexCoords2(left);
+ _calls.Vertex3(new Vector3(position + left * diameter, depth));
+ _calls.TexCoords2(right);
+ _calls.Vertex3(new Vector3(position + right * diameter, depth));
+ }
+
+ private void DrawBaseLine(ICommandFrame frame)
+ {
+ ref CommandInfo info = ref _commands.Take(out int index);
+
+ LineCommandArgs args = frame.GetParameter();
+
+ info = new CommandInfo()
+ {
+ Type = SimpleDrawCommand.Line,
+ Arg0 = 0.5f * args.Size / (args.End - args.Start).Length(),
+ };
+
+ SetCommandCommonBrush(ref info, args.Brush, args.Brush);
+
+ _calls.Transforms(Executor.TransformStack.Top);
+ _calls.Begin(PrimitiveType.Triangles);
+
+ _calls.CommandIndex(index);
+
+ DrawLine(args.Start, args.End, args.Depth, args.Size);
+
+ _calls.End();
+ }
+
+ private void DrawLine(Vector2 start, Vector2 end, float depth, float width)
+ {
+ float radius = 0.5f * width;
+ Vector2 segment = end - start;
+ float length = segment.Length();
+ float ratio = radius / length;
+ Vector2 n = ratio * segment;
+ Vector2 t = new Vector2(-n.Y, n.X);
+
+ Vector2 t00 = new Vector2(-ratio, -ratio);
+ Vector2 t10 = new Vector2(1+ratio, -ratio);
+ Vector2 t01 = new Vector2(-ratio, +ratio);
+ Vector2 t11 = new Vector2(1+ratio, +ratio);
+
+ Vector3 x00 = new Vector3(start - n - t, depth);
+ Vector3 x10 = new Vector3(end + n - t, depth);
+ Vector3 x01 = new Vector3(start - n + t, depth);
+ Vector3 x11 = new Vector3(end + n + t, depth);
+
+ _calls.TexCoords2(t00);
+ _calls.Vertex3(x00);
+ _calls.TexCoords2(t01);
+ _calls.Vertex3(x01);
+ _calls.TexCoords2(t11);
+ _calls.Vertex3(x11);
+
+ _calls.TexCoords2(t00);
+ _calls.Vertex3(x00);
+ _calls.TexCoords2(t11);
+ _calls.Vertex3(x11);
+ _calls.TexCoords2(t10);
+ _calls.Vertex3(x10);
+ }
+
+ private void DrawRect(ICommandFrame frame)
+ {
+ ref CommandInfo info = ref _commands.Take(out int index);
+
+ RectCommandArgs args = frame.GetParameter();
+
+ Vector2 size = Vector2.Abs(args.End - args.Start);
+ float aspect = size.X / size.Y;
+ float border = args.StrikeSize;
+ float normRad = args.StrikeSize / size.Y;
+ float wideRad = aspect * normRad;
+
+ int flags = 0;
+
+ switch (frame.Command.Name)
+ {
+ case "RectF":
+ flags |= 1;
+ break;
+ case "RectS":
+ flags |= 2;
+ break;
+ case "RectFS":
+ flags |= 3;
+ break;
+ }
+
+ switch (args.BorderKind)
+ {
+ case BorderKind.Inset:
+ flags |= 2 << 2;
+ break;
+ case BorderKind.Outset:
+ flags |= 1 << 2;
+ break;
+ }
+
+ info = new CommandInfo()
+ {
+ Type = SimpleDrawCommand.Rect,
+ Flags = flags,
+ Arg0 = aspect,
+ Arg1 = normRad,
+ };
+
+ SetCommandCommonBrush(ref info, args.FillBrush, args.StrikeBrush);
+
+ _calls.Transforms(Executor.TransformStack.Top);
+ _calls.Begin(PrimitiveType.Triangles);
+
+ _calls.CommandIndex(index);
+
+ Vector2 t00 = new Vector2(-wideRad, -normRad);
+ Vector2 t10 = new Vector2(1+wideRad, -normRad);
+ Vector2 t01 = new Vector2(-wideRad, 1+normRad);
+ Vector2 t11 = new Vector2(1+wideRad, 1+normRad);
+
+ Vector3 x00 = new Vector3(args.Start.X - border, args.Start.Y - border, args.Depth);
+ Vector3 x10 = new Vector3(args.End.X + border, args.Start.Y - border, args.Depth);
+ Vector3 x01 = new Vector3(args.Start.X - border, args.End.Y + border, args.Depth);
+ Vector3 x11 = new Vector3(args.End.X + border, args.End.Y + border, args.Depth);
+
+ _calls.TexCoords2(t00);
+ _calls.Vertex3(x00);
+ _calls.TexCoords2(t01);
+ _calls.Vertex3(x01);
+ _calls.TexCoords2(t11);
+ _calls.Vertex3(x11);
+
+ _calls.TexCoords2(t00);
+ _calls.Vertex3(x00);
+ _calls.TexCoords2(t11);
+ _calls.Vertex3(x11);
+ _calls.TexCoords2(t10);
+ _calls.Vertex3(x10);
+
+ _calls.End();
+ }
+
+ protected void SetCommandCommonBrush(ref CommandInfo info, IBrush? fill, IBrush? border)
+ {
+ switch (fill?.Kind.Name)
+ {
+ case "DB_Brush_solid":
+ SolidBrush solid = (SolidBrush)fill;
+ Vector4 color = new Vector4(solid.Color.R/255f, solid.Color.G/255f, solid.Color.B/255f, solid.Color.A/255f);
+ info.FgColor = color;
+ break;
+ case "DB_Brush_gradient":
+ GradientBrush gradient = (GradientBrush)fill;
+ GradientUniformBuffer gradients = Executor.ResourcePool.GetResourceManager();
+ gradients.Initialize();
+ GradientUniformBuffer.Entry entry = gradients.InternGradient(gradient.Gradient);
+ info.FgGradientIndex = entry.Offset;
+ info.FgGradientCount = entry.Count;
+ break;
+ case null:
+ // Craete a magenta brush for this.
+ info.FgColor = new Vector4(1, 0, 1, 1);
+ break;
+ }
+
+ switch (border?.Kind.Name)
+ {
+ case "DB_Brush_solid":
+ SolidBrush solid = (SolidBrush)border;
+ Vector4 color = new Vector4(solid.Color.R/255f, solid.Color.G/255f, solid.Color.B/255f, solid.Color.A/255f);
+ info.BgColor = color;
+ break;
+ case "DB_Brush_gradient":
+ GradientBrush gradient = (GradientBrush)border;
+ GradientUniformBuffer gradients = Executor.ResourcePool.GetResourceManager();
+ gradients.Initialize();
+ GradientUniformBuffer.Entry entry = gradients.InternGradient(gradient.Gradient);
+ info.BgGradientIndex = entry.Offset;
+ info.BgGradientCount = entry.Count;
+ break;
+ case null:
+ // Craete a magenta brush for this.
+ info.BgColor = new Vector4(1, 0, 1, 1);
+ break;
+ }
+ }
+
+ private void LoadShaders()
+ {
+ using Stream vsource = FetchEmbeddedResource("Dashboard.Drawing.OpenGL.Executors.simple.vert");
+ using Stream fsource = FetchEmbeddedResource("Dashboard.Drawing.OpenGL.Executors.simple.frag");
+ int vs = ShaderUtil.CompileShader(ShaderType.VertexShader, vsource);
+ int fs = ShaderUtil.CompileShader(ShaderType.FragmentShader, fsource);
+ _program = ShaderUtil.LinkProgram(vs, fs, new []
+ {
+ "a_v3Position",
+ "a_v2TexCoords",
+ "a_iCmdIndex",
+ });
+ GL.DeleteShader(vs);
+ GL.DeleteShader(fs);
+
+ GL.UniformBlockBinding(_program, GL.GetUniformBlockIndex(_program, "CommandBlock"), 0);
+ }
+
+ private static Stream FetchEmbeddedResource(string name)
+ {
+ return typeof(BaseCommandExecutor).Assembly.GetManifestResourceStream(name)!;
+ }
+ }
+}
diff --git a/Dashboard.Drawing.OpenGL/Executors/simple.frag b/Dashboard.Drawing.OpenGL/Executors/simple.frag
new file mode 100644
index 0000000..9271218
--- /dev/null
+++ b/Dashboard.Drawing.OpenGL/Executors/simple.frag
@@ -0,0 +1,224 @@
+#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 STRIKE_CENTER 0
+#define STRIKE_OUTSET 1
+#define STRIKE_INSET 2
+
+in vec3 v_v3Position;
+in vec2 v_v2TexCoords;
+flat in int v_iCmdIndex;
+
+out vec4 f_Color;
+
+uniform sampler2D txForeground;
+uniform sampler2D txBackground;
+
+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 CommandInfo_t {
+ int iCommand;
+ int iFlags;
+ float fArg0;
+ float fArg1;
+
+ int iFgGradientIndex;
+ int iFgGradientCount;
+ int iBgGradientIndex;
+ int iBgGradientCount;
+
+ vec4 v4FgColor;
+ vec4 v4BgColor;
+};
+
+uniform CommandBlock
+{
+ CommandInfo_t vstCommandInfo[DB_COMMAND_MAX];
+};
+
+CommandInfo_t getCommandInfo()
+{
+ return vstCommandInfo[v_iCmdIndex];
+}
+
+vec4 fgColor()
+{
+ return getCommandInfo().v4FgColor;
+}
+
+vec4 bgColor()
+{
+ return getCommandInfo().v4BgColor;
+}
+
+void Point(void)
+{
+ vec4 fg = fgColor();
+
+ if (dot(v_v2TexCoords, v_v2TexCoords) <= 0.25)
+ f_Color = fg;
+ else
+ discard;
+}
+
+#define LINE_NORMALIZED_RADIUS(cmd) cmd.fArg0
+void Line(void)
+{
+ vec4 fg = fgColor();
+ CommandInfo_t cmd = getCommandInfo();
+
+ float t = clamp(v_v2TexCoords.x, 0, 1);
+ vec2 dv = v_v2TexCoords - vec2(t, 0);
+ float d = dot(dv, dv);
+
+ float lim = LINE_NORMALIZED_RADIUS(cmd);
+ lim *= lim;
+
+ if (d <= lim)
+ f_Color = fg;
+ else
+ discard;
+}
+
+#define RECT_ASPECT_RATIO(cmd) (cmd.fArg0)
+#define RECT_BORDER_WIDTH(cmd) (cmd.fArg1)
+#define RECT_FILL(cmd) ((cmd.iFlags & (1 << 0)) != 0)
+#define RECT_BORDER(cmd) ((cmd.iFlags & (1 << 1)) != 0)
+#define RECT_STRIKE_MASK 3
+#define RECT_STRIKE_SHIFT 2
+#define RECT_STRIKE_KIND(cmd) ((cmd.iFlags & RECT_STRIKE_MASK) >> RECT_STRIKE_SHIFT)
+void Rect(void)
+{
+ vec4 fg = fgColor();
+ vec4 bg = bgColor();
+
+ CommandInfo_t cmd = getCommandInfo();
+ float aspect = RECT_ASPECT_RATIO(cmd);
+ float border = RECT_BORDER_WIDTH(cmd);
+ int strikeKind = RECT_STRIKE_KIND(cmd);
+
+ vec2 p = abs(2*v_v2TexCoords - vec2(1));
+ p.x = p.x/aspect;
+
+ float m0;
+ float m1;
+ if (!RECT_BORDER(cmd))
+ {
+ m0 = 1;
+ m1 = 1;
+ }
+ else if (strikeKind == STRIKE_OUTSET)
+ {
+ m0 = 1;
+ m1 = border;
+ }
+ else if (strikeKind == STRIKE_INSET)
+ {
+ m0 = 1-border;
+ m1 = 1;
+ }
+ else // strikeKind == STRIKE_CENTER
+ {
+ float h = 0.5 * border;
+ m0 = 1-border;
+ m1 = 1+border;
+ }
+
+ if (p.x > m1*aspect || p.y > m1)
+ {
+ discard;
+ }
+
+ if (RECT_FILL(cmd))
+ {
+ if (p.x <= 1 && p.y <= 1)
+ {
+ f_Color = fg;
+ }
+ }
+
+ if (RECT_BORDER(cmd))
+ {
+ float x = clamp(p.x, aspect*m0, aspect*m1);
+ float y = clamp(p.y, m0, m1);
+
+ if (p.x == x || p.y == y)
+ {
+ f_Color = bg;
+ }
+ }
+}
+
+void main(void)
+{
+ switch (getCommandInfo().iCommand)
+ {
+ case CMD_POINT:
+ Point();
+ break;
+ case CMD_LINE:
+ Line();
+ break;
+ case CMD_RECT:
+ Rect();
+ break;
+
+ default:
+ // Unimplemented value.
+ f_Color = vec4(1, 0, 1, 1);
+ break;
+ }
+}
diff --git a/Dashboard.Drawing.OpenGL/default.vert b/Dashboard.Drawing.OpenGL/Executors/simple.vert
similarity index 82%
rename from Dashboard.Drawing.OpenGL/default.vert
rename to Dashboard.Drawing.OpenGL/Executors/simple.vert
index 019c07d..89248f0 100644
--- a/Dashboard.Drawing.OpenGL/default.vert
+++ b/Dashboard.Drawing.OpenGL/Executors/simple.vert
@@ -2,12 +2,10 @@
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;
@@ -19,6 +17,5 @@ void main(void)
v_v3Position = position.xyz/position.w;
v_v2TexCoords = a_v2TexCoords;
- v_v3CharCoords = a_v3CharCoords;
v_iCmdIndex = a_iCmdIndex;
}
diff --git a/Dashboard.Drawing.OpenGL/ShaderUtil.cs b/Dashboard.Drawing.OpenGL/ShaderUtil.cs
new file mode 100644
index 0000000..a835e15
--- /dev/null
+++ b/Dashboard.Drawing.OpenGL/ShaderUtil.cs
@@ -0,0 +1,60 @@
+using OpenTK.Graphics.OpenGL;
+
+namespace Dashboard.Drawing.OpenGL
+{
+ public static class ShaderUtil
+ {
+ public 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;
+ }
+
+ public static int CompileShader(ShaderType type, Stream stream)
+ {
+ using StreamReader reader = new StreamReader(stream, leaveOpen: true);
+
+ return CompileShader(type, reader.ReadToEnd());
+ }
+
+ public static int LinkProgram(int s1, int s2, IReadOnlyList? attribLocations = null)
+ {
+ int program = GL.CreateProgram();
+
+ GL.AttachShader(program, s1);
+ GL.AttachShader(program, s2);
+
+ for (int i = 0; i < attribLocations?.Count; i++)
+ {
+ GL.BindAttribLocation(program, (uint)i, attribLocations[i]);
+ }
+
+ 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;
+ }
+ }
+}
diff --git a/Dashboard.Drawing.OpenGL/default.frag b/Dashboard.Drawing.OpenGL/default.frag
deleted file mode 100644
index 2e3d45a..0000000
--- a/Dashboard.Drawing.OpenGL/default.frag
+++ /dev/null
@@ -1,97 +0,0 @@
-#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/DrawExtension.cs b/Dashboard.Drawing/DrawExtension.cs
index f95fbfd..55a2d3e 100644
--- a/Dashboard.Drawing/DrawExtension.cs
+++ b/Dashboard.Drawing/DrawExtension.cs
@@ -102,7 +102,7 @@ namespace Dashboard.Drawing
Vector2 min = Vector2.Min(start, end);
Vector2 max = Vector2.Max(start, end);
controller.EnsureBounds(new Box2d(min, max), depth);
- controller.Write(DbBaseCommands.Instance.DrawRectF, new RectCommandArgs(start, end, depth, strikeBrush, strikeSize, kind));
+ controller.Write(DbBaseCommands.Instance.DrawRectS, new RectCommandArgs(start, end, depth, strikeBrush, strikeSize, kind));
}
public static void Rect(this DrawQueue queue, Vector2 start, Vector2 end, float depth, IBrush fillBrush, IBrush strikeBrush,
@@ -112,7 +112,7 @@ namespace Dashboard.Drawing
Vector2 min = Vector2.Min(start, end);
Vector2 max = Vector2.Max(start, end);
controller.EnsureBounds(new Box2d(min, max), depth);
- controller.Write(DbBaseCommands.Instance.DrawRectF, new RectCommandArgs(start, end, depth, fillBrush, strikeBrush, strikeSize, kind));
+ controller.Write(DbBaseCommands.Instance.DrawRectFS, new RectCommandArgs(start, end, depth, fillBrush, strikeBrush, strikeSize, kind));
}
public static void Text(this DrawQueue queue, Vector3 position, IBrush brush, string text, IFont font,
diff --git a/tests/Dashboard.TestApplication/Program.cs b/tests/Dashboard.TestApplication/Program.cs
index ab954ee..f8bbcd3 100644
--- a/tests/Dashboard.TestApplication/Program.cs
+++ b/tests/Dashboard.TestApplication/Program.cs
@@ -6,11 +6,12 @@ using OpenTK.Platform;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using TK = OpenTK.Platform.Toolkit;
-using Vector3 = System.Numerics.Vector3;
+using sys = System.Numerics;
+using otk = OpenTK.Mathematics;
TK.Init(new ToolkitOptions()
{
- ApplicationName = "Dashboard Test Application",
+ ApplicationName = "Paper Punch Out!",
Windows = new ToolkitOptions.WindowsOptions()
{
EnableVisualStyles = true,
@@ -32,13 +33,15 @@ WindowHandle wnd = TK.Window.Create(new OpenGLGraphicsApiHints()
GreenColorBits = 8,
BlueColorBits = 8,
AlphaColorBits = 8,
+ Multisamples = 0,
SupportTransparentFramebufferX11 = true,
});
-TK.Window.SetTitle(wnd, "Dashboard Test Application");
+TK.Window.SetTitle(wnd, "Paper Punch Out!");
TK.Window.SetMinClientSize(wnd, 300, 200);
TK.Window.SetClientSize(wnd, new Vector2i(320, 240));
TK.Window.SetBorderStyle(wnd, WindowBorderStyle.ResizableBorder);
+TK.Window.SetTransparencyMode(wnd, WindowTransparencyMode.TransparentFramebuffer, 0.1f);
OpenGLContextHandle context = TK.OpenGL.CreateFromWindow(wnd);
@@ -48,8 +51,8 @@ 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);
+SolidBrush fg = new SolidBrush(Color.FromArgb(0, 0, 0, 0));
+SolidBrush bg = new SolidBrush(Color.Black);
bool shouldExit = false;
GLEngine engine = new GLEngine();
@@ -58,6 +61,8 @@ engine.Initialize();
GlContext dbGlContext = new GlContext(wnd, context);
ContextExecutor executor = engine.GetExecutor(dbGlContext);
+Vector2 mousePos = Vector2.Zero;
+Random r = new Random();
EventQueue.EventRaised += (handle, type, eventArgs) =>
{
if (handle != wnd)
@@ -68,25 +73,49 @@ EventQueue.EventRaised += (handle, type, eventArgs) =>
case PlatformEventType.Close:
shouldExit = true;
break;
+ case PlatformEventType.MouseMove:
+ mousePos = ((MouseMoveEventArgs)eventArgs).ClientPosition;
+ break;
+ case PlatformEventType.MouseUp:
+ MouseButtonUpEventArgs mouseUp = (MouseButtonUpEventArgs)eventArgs;
+ if (mouseUp.Button == MouseButton.Button1)
+ {
+ SolidBrush brush = new SolidBrush(Color.FromArgb(r.Next(256), r.Next(256), r.Next(256), 0));
+ queue.Point(new sys::Vector2(mousePos.X, mousePos.Y), 0f, 24f, brush);
+ }
+ break;
+ case PlatformEventType.KeyDown:
+ KeyDownEventArgs keyDown = (KeyDownEventArgs)eventArgs;
+ if (keyDown.Key == Key.Escape || keyDown.Key == Key.Delete || keyDown.Key == Key.Backspace)
+ queue.Clear();
+ break;
}
};
TK.Window.SetMode(wnd, WindowMode.Normal);
+List points = new List();
+
+queue.Line(new sys.Vector2(64, 256), new sys.Vector2(64+256, 256), 0, 64f, fg);
+queue.Rect(new sys.Vector2(16, 16), new sys.Vector2(96, 96), 0, fg, bg, 8f);
+
while (!shouldExit)
{
TK.Window.ProcessEvents(true);
TK.Window.GetFramebufferSize(wnd, out Vector2i framebufferSize);
+ executor.BeginFrame();
- 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);
+ // queue.Line(Vector3.Zero, new Vector3(System.Numerics.Vector2.One * 256f, 0), 4f, 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.ClearColor(0.9f, 0.9f, 0.7f, 1.0f);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
+ GL.Disable(EnableCap.DepthTest);
+ // GL.Enable(EnableCap.Blend);
+ // GL.BlendFuncSeparate(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha, BlendingFactor.One, BlendingFactor.Zero);
+ GL.ColorMask(true, true, true, true);
executor.Draw(queue);
executor.EndFrame();