Dashboard/Dashboard.Drawing.OpenGL/Executors/BaseCommandExecutor.cs

333 lines
11 KiB
C#

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<CommandInfo> _commands = new MappableBumpAllocator<CommandInfo>();
private readonly DrawCallRecorder _calls = new DrawCallRecorder();
public bool IsInitialized { get; private set; }
public IEnumerable<string> 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<PointCommandArgs>();
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<LineCommandArgs>();
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<RectCommandArgs>();
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<GradientUniformBuffer>();
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<GradientUniformBuffer>();
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)!;
}
}
}