Compare commits
15 Commits
1067d3e188
...
95cc1648b2
Author | SHA1 | Date | |
---|---|---|---|
95cc1648b2 | |||
5014d218e2 | |||
8ce1329dfc | |||
cde0fe2901 | |||
1a52f17990 | |||
b841dbe6c2 | |||
bb5b87417f | |||
165801800e | |||
cf7cf9a77a | |||
3dff7438b3 | |||
6ad11812e2 | |||
f8dea00021 | |||
7485ec5eaa | |||
6acaf0a7d5 | |||
b5fda1ce3e |
15
Dashboard.Common/Anchor.cs
Normal file
15
Dashboard.Common/Anchor.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace Dashboard
|
||||
{
|
||||
[Flags]
|
||||
public enum Anchor
|
||||
{
|
||||
Auto = 0,
|
||||
Right = (1 << 0),
|
||||
Left = (1 << 1),
|
||||
HCenter = Left | Right,
|
||||
Top = (1 << 2),
|
||||
Bottom = (1 << 3),
|
||||
VCenter = Top | Bottom,
|
||||
Middle = HCenter | VCenter,
|
||||
}
|
||||
}
|
65
Dashboard.Common/Box2d.cs
Normal file
65
Dashboard.Common/Box2d.cs
Normal file
@ -0,0 +1,65 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Dashboard
|
||||
{
|
||||
public readonly record struct Box2d(Vector2 Min, Vector2 Max)
|
||||
{
|
||||
public float Left => Min.X;
|
||||
public float Right => Max.X;
|
||||
public float Top => Min.Y;
|
||||
public float Bottom => Max.Y;
|
||||
|
||||
public Vector2 Size => Max - Min;
|
||||
public Vector2 Center => (Min + Max) * 0.5f;
|
||||
|
||||
public Box2d(RectangleF rectangle)
|
||||
: this(new Vector2(rectangle.Left, rectangle.Top), new Vector2(rectangle.Right, rectangle.Bottom))
|
||||
{
|
||||
}
|
||||
|
||||
public Box2d(float x0, float y0, float x1, float y1)
|
||||
: this(new Vector2(x0, y0), new Vector2(x1, y1))
|
||||
{
|
||||
}
|
||||
|
||||
public static Box2d FromPositionAndSize(Vector2 position, Vector2 size, Origin anchor = Origin.Center)
|
||||
{
|
||||
Vector2 half = size * 0.5f;
|
||||
switch (anchor)
|
||||
{
|
||||
case Origin.Center:
|
||||
return new Box2d(position - half, position + half);
|
||||
case Origin.TopLeft:
|
||||
return new Box2d(position, position + size);
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public static Box2d Union(Box2d left, Box2d right)
|
||||
{
|
||||
Vector2 min = Vector2.Min(left.Min, right.Min);
|
||||
Vector2 max = Vector2.Max(left.Max, right.Max);
|
||||
return new Box2d(min, max);
|
||||
}
|
||||
|
||||
public static Box2d Intersect(Box2d left, Box2d right)
|
||||
{
|
||||
Vector2 min = Vector2.Max(left.Min, right.Min);
|
||||
Vector2 max = Vector2.Min(left.Max, right.Max);
|
||||
return new Box2d(min, max);
|
||||
}
|
||||
|
||||
public static explicit operator RectangleF(Box2d box2d)
|
||||
{
|
||||
return new RectangleF((PointF)box2d.Center, (SizeF)box2d.Size);
|
||||
}
|
||||
|
||||
public static explicit operator Box2d(RectangleF rectangle)
|
||||
{
|
||||
return new Box2d(rectangle);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Dashboard
|
||||
{
|
||||
@ -12,7 +13,7 @@ namespace Dashboard
|
||||
public float Near => Max.Z;
|
||||
|
||||
public Vector3 Size => Max - Min;
|
||||
public Vector3 Center => Min + Size / 2f;
|
||||
public Vector3 Center => Min + Size * 0.5f;
|
||||
|
||||
public static Box3d Union(Box3d left, Box3d right)
|
||||
{
|
||||
@ -21,11 +22,25 @@ namespace Dashboard
|
||||
return new Box3d(min, max);
|
||||
}
|
||||
|
||||
public static Box3d Union(Box3d box, Box2d bounds, float depth)
|
||||
{
|
||||
Vector3 min = Vector3.Min(box.Min, new Vector3(bounds.Left, bounds.Top, depth));
|
||||
Vector3 max = Vector3.Max(box.Max, new Vector3(bounds.Right, bounds.Bottom, depth));
|
||||
return new Box3d(min, max);
|
||||
}
|
||||
|
||||
public static Box3d Intersect(Box3d left, Box3d right)
|
||||
{
|
||||
Vector3 min = Vector3.Max(left.Min, right.Min);
|
||||
Vector3 max = Vector3.Min(left.Max, right.Max);
|
||||
return new Box3d(min, max);
|
||||
}
|
||||
|
||||
public static Box3d Intersect(Box3d box, Box2d bounds, float depth)
|
||||
{
|
||||
Vector3 min = Vector3.Max(box.Min, new Vector3(bounds.Min, depth));
|
||||
Vector3 max = Vector3.Min(box.Max, new Vector3(bounds.Max, depth));
|
||||
return new Box3d(min, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Dashboard</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
40
Dashboard.Common/FontProperties.cs
Normal file
40
Dashboard.Common/FontProperties.cs
Normal file
@ -0,0 +1,40 @@
|
||||
namespace Dashboard
|
||||
{
|
||||
public enum FontWeight
|
||||
{
|
||||
_100 = 100,
|
||||
_200 = 200,
|
||||
_300 = 300,
|
||||
_400 = 400,
|
||||
_500 = 500,
|
||||
_600 = 600,
|
||||
_800 = 800,
|
||||
_900 = 900,
|
||||
|
||||
Thin = _100,
|
||||
Normal = _400,
|
||||
Bold = _600,
|
||||
Heavy = _900,
|
||||
}
|
||||
|
||||
public enum FontSlant
|
||||
{
|
||||
Normal,
|
||||
Italic,
|
||||
Oblique,
|
||||
}
|
||||
|
||||
public enum FontStretch
|
||||
{
|
||||
UltraCondensed = 500,
|
||||
ExtraCondensed = 625,
|
||||
Condensed = 750,
|
||||
SemiCondensed = 875,
|
||||
Normal = 1000,
|
||||
SemiExpanded = 1125,
|
||||
Expanded = 1250,
|
||||
ExtraExpanded = 1500,
|
||||
UltraExpanded = 2000,
|
||||
}
|
||||
|
||||
}
|
227
Dashboard.Common/Gradient.cs
Normal file
227
Dashboard.Common/Gradient.cs
Normal file
@ -0,0 +1,227 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Dashboard
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration of the kinds of gradients available.
|
||||
/// </summary>
|
||||
public enum GradientType
|
||||
{
|
||||
/// <summary>
|
||||
/// A gradient which transitions over a set axis.
|
||||
/// </summary>
|
||||
Axial,
|
||||
/// <summary>
|
||||
/// A gradient which transitions along elliptical curves.
|
||||
/// </summary>
|
||||
Radial,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A single gradient stop.
|
||||
/// </summary>
|
||||
/// <param name="Position">The position of the gradient stop. Must be [0,1].</param>
|
||||
/// <param name="Color">The color value for the stop.</param>
|
||||
public record struct GradientStop(float Position, Color Color);
|
||||
|
||||
/// <summary>
|
||||
/// Represents a linear gradient.
|
||||
/// </summary>
|
||||
public struct Gradient : ICollection<GradientStop>, ICloneable, IEquatable<Gradient>
|
||||
{
|
||||
private readonly List<GradientStop> _stops = new List<GradientStop>();
|
||||
|
||||
/// <summary>
|
||||
/// Gradient type.
|
||||
/// </summary>
|
||||
public GradientType Type { get; set; } = GradientType.Axial;
|
||||
|
||||
/// <summary>
|
||||
/// First gradient control point.
|
||||
/// </summary>
|
||||
public Vector2 C0 { get; set; } = Vector2.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Second gradient control point.
|
||||
/// </summary>
|
||||
public Vector2 C1 { get; set; } = Vector2.One;
|
||||
|
||||
/// <summary>
|
||||
/// Number of stops in a gradient.
|
||||
/// </summary>
|
||||
public int Count => _stops.Count;
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
/// <summary>
|
||||
/// Get a gradient control point.
|
||||
/// </summary>
|
||||
/// <param name="index">The index to get the control point for.</param>
|
||||
public GradientStop this[int index]
|
||||
{
|
||||
get => _stops[index];
|
||||
set
|
||||
{
|
||||
RemoveAt(index);
|
||||
Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
public Gradient()
|
||||
{
|
||||
}
|
||||
|
||||
public Gradient(Color a, Color b)
|
||||
{
|
||||
Add(new GradientStop(0, a));
|
||||
Add(new GradientStop(1, b));
|
||||
}
|
||||
|
||||
public Gradient(IEnumerable<GradientStop> stops)
|
||||
{
|
||||
_stops.AddRange(stops);
|
||||
|
||||
if (_stops.Any(x => x.Position < 0 || x.Position > 1))
|
||||
throw new Exception("Gradient stop positions must be in the range [0, 1].");
|
||||
|
||||
_stops.Sort((a, b) => a.Position.CompareTo(b.Position));
|
||||
}
|
||||
|
||||
public Color GetColor(float position)
|
||||
{
|
||||
if (Count == 0)
|
||||
return Color.Black;
|
||||
else if (Count == 1)
|
||||
return _stops[0].Color;
|
||||
|
||||
int pivot = _stops.FindIndex(x => x.Position < position);
|
||||
|
||||
GradientStop left, right;
|
||||
if (pivot == -1)
|
||||
{
|
||||
left = right = _stops[^1];
|
||||
}
|
||||
else if (pivot == 0)
|
||||
{
|
||||
left = right = _stops[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
left = _stops[pivot-1];
|
||||
right = _stops[pivot];
|
||||
}
|
||||
|
||||
float weight = (position - left.Position) / (right.Position - left.Position);
|
||||
|
||||
Vector4 lcolor = new Vector4(left.Color.R, left.Color.G, left.Color.B, left.Color.A) * (1-weight);
|
||||
Vector4 rcolor = new Vector4(right.Color.R, right.Color.G, right.Color.B, right.Color.A) * weight;
|
||||
Vector4 color = lcolor + rcolor;
|
||||
|
||||
return Color.FromArgb((byte)color.W, (byte)color.X, (byte)color.Y, (byte)color.Z);
|
||||
}
|
||||
|
||||
public Gradient Clone()
|
||||
{
|
||||
Gradient gradient = new Gradient()
|
||||
{
|
||||
Type = Type,
|
||||
C0 = C0,
|
||||
C1 = C1,
|
||||
};
|
||||
|
||||
foreach (GradientStop stop in _stops)
|
||||
{
|
||||
gradient.Add(stop);
|
||||
}
|
||||
|
||||
return gradient;
|
||||
}
|
||||
|
||||
object ICloneable.Clone()
|
||||
{
|
||||
return Clone();
|
||||
}
|
||||
|
||||
public IEnumerator<GradientStop> GetEnumerator()
|
||||
{
|
||||
return _stops.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable)_stops).GetEnumerator();
|
||||
}
|
||||
|
||||
public void Add(GradientStop item)
|
||||
{
|
||||
if (item.Position < 0 || item.Position > 1)
|
||||
throw new Exception("Gradient stop positions must be in the range [0, 1].");
|
||||
|
||||
int index = _stops.FindIndex(x => x.Position > item.Position);
|
||||
if (index == -1)
|
||||
index = _stops.Count;
|
||||
|
||||
_stops.Insert(index, item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_stops.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(GradientStop item)
|
||||
{
|
||||
return _stops.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(GradientStop[] array, int arrayIndex)
|
||||
{
|
||||
_stops.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public bool Remove(GradientStop item)
|
||||
{
|
||||
return _stops.Remove(item);
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
_stops.RemoveAt(index);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
HashCode code = new HashCode();
|
||||
code.Add(Count);
|
||||
foreach (GradientStop item in this)
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Dashboard.Drawing
|
||||
namespace Dashboard
|
||||
{
|
||||
public class HashList<T> : IReadOnlyList<T>
|
||||
where T : notnull
|
||||
@ -35,4 +35,4 @@ namespace Dashboard.Drawing
|
||||
public IEnumerator<T> GetEnumerator() => _list.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
196
Dashboard.Common/ImageProperties.cs
Normal file
196
Dashboard.Common/ImageProperties.cs
Normal file
@ -0,0 +1,196 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Dashboard
|
||||
{
|
||||
/// <summary>
|
||||
/// Pixel format for images.
|
||||
/// </summary>
|
||||
public enum PixelFormat
|
||||
{
|
||||
R8I,
|
||||
Rg8I,
|
||||
Rgb8I,
|
||||
Rgba8I,
|
||||
R16F,
|
||||
Rg816F,
|
||||
Rgb16F,
|
||||
Rgba16F,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Color channels for images.
|
||||
/// </summary>
|
||||
public enum ColorChannel
|
||||
{
|
||||
/// <summary>
|
||||
/// The zero channel. Used for swizzle masks.
|
||||
/// </summary>
|
||||
Zero = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The one channel. Used for swizzle masks.
|
||||
/// </summary>
|
||||
One = 1,
|
||||
|
||||
/// <summary>
|
||||
/// An invalid swizzle mask.
|
||||
/// </summary>
|
||||
Unknown = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The red channel.
|
||||
/// </summary>
|
||||
Red = 4,
|
||||
|
||||
/// <summary>
|
||||
/// The green channel.
|
||||
/// </summary>
|
||||
Green = 5,
|
||||
|
||||
/// <summary>
|
||||
/// The blue channel.
|
||||
/// </summary>
|
||||
Blue = 6,
|
||||
|
||||
/// <summary>
|
||||
/// The alpha channel.
|
||||
/// </summary>
|
||||
Alpha = 7,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a image swizzle mask.
|
||||
/// </summary>
|
||||
public struct ColorSwizzle : IEquatable<ColorSwizzle>
|
||||
{
|
||||
public short Mask;
|
||||
|
||||
private const int MASK = 7;
|
||||
private const int RBIT = 0;
|
||||
private const int GBIT = 3;
|
||||
private const int BBIT = 6;
|
||||
private const int ABIT = 9;
|
||||
|
||||
/// <summary>
|
||||
/// Swizzle the red channel.
|
||||
/// </summary>
|
||||
public ColorChannel R
|
||||
{
|
||||
get => (ColorChannel)((Mask >> RBIT) & MASK);
|
||||
set => Mask = (short)(((int)value << RBIT) | (Mask & ~(MASK << RBIT)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Swizzle the green channel.
|
||||
/// </summary>
|
||||
public ColorChannel G
|
||||
{
|
||||
get => (ColorChannel)((Mask >> GBIT) & MASK);
|
||||
set => Mask = (short)(((int)value << GBIT) | (Mask & ~(MASK << GBIT)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Swizzle the blue channel.
|
||||
/// </summary>
|
||||
public ColorChannel B
|
||||
{
|
||||
get => (ColorChannel)((Mask >> BBIT) & MASK);
|
||||
set => Mask = (short)(((int)value << BBIT) | (Mask & ~(MASK << BBIT)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Swizzle the alpha channel.
|
||||
/// </summary>
|
||||
public ColorChannel A
|
||||
{
|
||||
get => (ColorChannel)((Mask >> ABIT) & MASK);
|
||||
set => Mask = (short)(((int)value << ABIT) | (Mask & ~(MASK << ABIT)));
|
||||
}
|
||||
|
||||
public ColorSwizzle(short mask)
|
||||
{
|
||||
Mask = mask;
|
||||
}
|
||||
|
||||
public ColorSwizzle(ColorChannel r, ColorChannel g, ColorChannel b, ColorChannel a)
|
||||
{
|
||||
Mask = (short)(((int)r << RBIT) | ((int)g << GBIT) | ((int)b << BBIT) | ((int)a << ABIT));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{GetChannelChar(R)}{GetChannelChar(G)}{GetChannelChar(B)}{GetChannelChar(A)}";
|
||||
|
||||
char GetChannelChar(ColorChannel channel) => channel switch
|
||||
{
|
||||
ColorChannel.Zero => '0',
|
||||
ColorChannel.Red => 'R',
|
||||
ColorChannel.Green => 'G',
|
||||
ColorChannel.Blue => 'B',
|
||||
ColorChannel.Alpha => 'A',
|
||||
ColorChannel.One => '1',
|
||||
_ => '?',
|
||||
};
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Mask.GetHashCode();
|
||||
}
|
||||
|
||||
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||
{
|
||||
return obj is ColorSwizzle other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(ColorSwizzle other)
|
||||
{
|
||||
return Mask == other.Mask;
|
||||
}
|
||||
|
||||
public static readonly ColorSwizzle Default = Parse("RGBA");
|
||||
public static readonly ColorSwizzle White = Parse("1111");
|
||||
public static readonly ColorSwizzle Black = Parse("0001");
|
||||
public static readonly ColorSwizzle Transparent = Parse("0000");
|
||||
public static readonly ColorSwizzle RedToGrayscale = Parse("RRR1");
|
||||
public static readonly ColorSwizzle RedToWhiteAlpha = Parse("111A");
|
||||
|
||||
public static bool TryParse(ReadOnlySpan<char> str, out ColorSwizzle value)
|
||||
{
|
||||
if (str.Length < 4)
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
ColorChannel r = GetChannelFromChar(str[0]);
|
||||
ColorChannel g = GetChannelFromChar(str[1]);
|
||||
ColorChannel b = GetChannelFromChar(str[2]);
|
||||
ColorChannel a = GetChannelFromChar(str[3]);
|
||||
|
||||
value = new ColorSwizzle(r, g, b, a);
|
||||
return true;
|
||||
|
||||
ColorChannel GetChannelFromChar(char chr) => chr switch
|
||||
{
|
||||
'0' => ColorChannel.Zero,
|
||||
'R' => ColorChannel.Red,
|
||||
'G' => ColorChannel.Green,
|
||||
'B' => ColorChannel.Blue,
|
||||
'A' => ColorChannel.Alpha,
|
||||
'1' => ColorChannel.One,
|
||||
_ => ColorChannel.Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
public static ColorSwizzle Parse(ReadOnlySpan<char> str) =>
|
||||
TryParse(str, out ColorSwizzle value) ? value : throw new FormatException(nameof(str));
|
||||
|
||||
public static bool operator ==(ColorSwizzle left, ColorSwizzle right) =>
|
||||
left.Mask == right.Mask;
|
||||
|
||||
public static bool operator !=(ColorSwizzle left, ColorSwizzle right) =>
|
||||
left.Mask != right.Mask;
|
||||
}
|
||||
}
|
23
Dashboard.Common/LineProperties.cs
Normal file
23
Dashboard.Common/LineProperties.cs
Normal file
@ -0,0 +1,23 @@
|
||||
namespace Dashboard
|
||||
{
|
||||
public enum BorderKind
|
||||
{
|
||||
Inset = -1,
|
||||
Center = 0,
|
||||
Outset = 1,
|
||||
}
|
||||
|
||||
public enum CapType
|
||||
{
|
||||
None,
|
||||
Circular,
|
||||
Rectangular,
|
||||
}
|
||||
|
||||
public enum CuspType
|
||||
{
|
||||
None,
|
||||
Circular,
|
||||
Rectangular,
|
||||
}
|
||||
}
|
17
Dashboard.Common/Origin.cs
Normal file
17
Dashboard.Common/Origin.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace Dashboard
|
||||
{
|
||||
public enum Origin
|
||||
{
|
||||
Center = 0,
|
||||
|
||||
Left = (1 << 0),
|
||||
Top = (1 << 1),
|
||||
Right = (1 << 2),
|
||||
Bottom = (1 << 3),
|
||||
|
||||
TopLeft = Top | Left,
|
||||
BottomLeft = Bottom | Left,
|
||||
BottomRight = Bottom | Right,
|
||||
TopRight = Top | Right,
|
||||
}
|
||||
}
|
48
Dashboard.Drawing.OpenGL/CommandInfo.cs
Normal file
48
Dashboard.Drawing.OpenGL/CommandInfo.cs
Normal file
@ -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,
|
||||
|
||||
/// <summary>
|
||||
/// Make sure your custom commands have values higher than this if you plan on using the default command
|
||||
/// buffer.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
77
Dashboard.Drawing.OpenGL/ContextCollector.cs
Normal file
77
Dashboard.Drawing.OpenGL/ContextCollector.cs
Normal file
@ -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();
|
||||
}
|
||||
}
|
178
Dashboard.Drawing.OpenGL/ContextExecutor.cs
Normal file
178
Dashboard.Drawing.OpenGL/ContextExecutor.cs
Normal file
@ -0,0 +1,178 @@
|
||||
using System.Drawing;
|
||||
using Dashboard.Drawing.OpenGL.Executors;
|
||||
|
||||
namespace Dashboard.Drawing.OpenGL
|
||||
{
|
||||
public interface ICommandExecutor
|
||||
{
|
||||
IEnumerable<string> 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 readonly List<ICommandExecutor> _executorsList = new List<ICommandExecutor>();
|
||||
|
||||
private readonly Dictionary<string, ICommandExecutor> _executorsMap = new Dictionary<string, ICommandExecutor>();
|
||||
|
||||
public ContextExecutor(GLEngine engine, IGLContext context)
|
||||
{
|
||||
Engine = engine;
|
||||
Context = context;
|
||||
|
||||
ResourcePool = Engine.ResourcePoolManager.Get(context);
|
||||
ResourcePool.IncrementReference();
|
||||
|
||||
AddExecutor(new BaseCommandExecutor());
|
||||
}
|
||||
|
||||
~ContextExecutor()
|
||||
{
|
||||
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()
|
||||
{
|
||||
if (IsInitialized)
|
||||
return;
|
||||
IsInitialized = true;
|
||||
|
||||
foreach (ICommandExecutor executor in _executorsList)
|
||||
{
|
||||
if (executor is IInitializer initializer)
|
||||
initializer.Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void BeginFrame()
|
||||
{
|
||||
foreach (ICommandExecutor executor in _executorsList)
|
||||
executor.BeginFrame();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
IsDisposed = true;
|
||||
|
||||
if (disposing)
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
Dispose(safeExit, disposing);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool safeExit, bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() => DisposeInvoker(true, true);
|
||||
|
||||
public void Dispose(bool safeExit) => DisposeInvoker(safeExit, true);
|
||||
}
|
||||
}
|
131
Dashboard.Drawing.OpenGL/ContextResourcePool.cs
Normal file
131
Dashboard.Drawing.OpenGL/ContextResourcePool.cs
Normal file
@ -0,0 +1,131 @@
|
||||
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);
|
||||
_unique.Add(context, pool);
|
||||
}
|
||||
|
||||
return pool;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_shared.TryGetValue(context.ContextGroup, out ContextResourcePool? pool))
|
||||
{
|
||||
pool = new ContextResourcePool(this, context.ContextGroup);
|
||||
_shared.Add(context.ContextGroup, pool);
|
||||
}
|
||||
|
||||
return pool;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Disposed(ContextResourcePool pool)
|
||||
{
|
||||
// TODO:
|
||||
}
|
||||
}
|
||||
|
||||
public class ContextResourcePool : IGLDisposable, IArc
|
||||
{
|
||||
private int _references = 0;
|
||||
private bool _isDisposed = false;
|
||||
private readonly Dictionary<int, IResourceManager> _managers = new Dictionary<int, IResourceManager>();
|
||||
|
||||
public ContextResourcePoolManager Manager { get; }
|
||||
public IGLContext? Context { get; private set; } = null;
|
||||
public int ContextGroup { get; private set; } = -1;
|
||||
public int References => _references;
|
||||
public ContextCollector Collector { get; } = new ContextCollector();
|
||||
|
||||
internal ContextResourcePool(ContextResourcePoolManager manager, int contextGroup)
|
||||
{
|
||||
Manager = manager;
|
||||
ContextGroup = contextGroup;
|
||||
}
|
||||
|
||||
internal ContextResourcePool(ContextResourcePoolManager manager, IGLContext context)
|
||||
{
|
||||
Manager = manager;
|
||||
Context = context;
|
||||
}
|
||||
|
||||
public T GetResourceManager<T>(bool init = true) where T : IResourceManager, new()
|
||||
{
|
||||
int index = ManagerAtom<T>.Atom;
|
||||
|
||||
if (!_managers.TryGetValue(index, out IResourceManager? resourceClass))
|
||||
{
|
||||
_managers[index] = resourceClass = new T();
|
||||
}
|
||||
|
||||
if (init && resourceClass is IInitializer initializer)
|
||||
{
|
||||
initializer.Initialize();
|
||||
}
|
||||
|
||||
return (T)resourceClass;
|
||||
}
|
||||
|
||||
~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)
|
||||
{
|
||||
if (_isDisposed)
|
||||
return;
|
||||
_isDisposed = true;
|
||||
|
||||
Manager.Disposed(this);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
foreach ((int _, IResourceManager manager) in _managers)
|
||||
{
|
||||
if (manager is IGLDisposable glDisposable)
|
||||
glDisposable.Dispose(safeExit);
|
||||
else if (manager is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void IncrementReference()
|
||||
{
|
||||
Interlocked.Increment(ref _references);
|
||||
}
|
||||
|
||||
public bool DecrementReference()
|
||||
{
|
||||
return Interlocked.Decrement(ref _references) == 0;
|
||||
}
|
||||
|
||||
|
||||
private class ManagerAtom
|
||||
{
|
||||
private static int _counter = -1;
|
||||
|
||||
protected static int Acquire() => Interlocked.Increment(ref _counter);
|
||||
}
|
||||
private class ManagerAtom<T> : ManagerAtom where T : IResourceManager
|
||||
{
|
||||
public static int Atom { get; } = Acquire();
|
||||
}
|
||||
}
|
||||
}
|
23
Dashboard.Drawing.OpenGL/Dashboard.Drawing.OpenGL.csproj
Normal file
23
Dashboard.Drawing.OpenGL/Dashboard.Drawing.OpenGL.csproj
Normal file
@ -0,0 +1,23 @@
|
||||
<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>
|
||||
<EmbeddedResource Include="Executors\simple.frag" />
|
||||
<EmbeddedResource Include="Executors\simple.vert" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
227
Dashboard.Drawing.OpenGL/DrawCallRecorder.cs
Normal file
227
Dashboard.Drawing.OpenGL/DrawCallRecorder.cs
Normal file
@ -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<DrawVertex> _vertices = new List<DrawVertex>();
|
||||
private readonly List<DrawCall> _calls = new List<DrawCall>();
|
||||
|
||||
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<DrawVertex> vertices = CollectionsMarshal.AsSpan(_vertices);
|
||||
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Count * Unsafe.SizeOf<DrawVertex>(), 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;
|
||||
}
|
||||
}
|
||||
}
|
332
Dashboard.Drawing.OpenGL/Executors/BaseCommandExecutor.cs
Normal file
332
Dashboard.Drawing.OpenGL/Executors/BaseCommandExecutor.cs
Normal file
@ -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<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)!;
|
||||
}
|
||||
}
|
||||
}
|
224
Dashboard.Drawing.OpenGL/Executors/simple.frag
Normal file
224
Dashboard.Drawing.OpenGL/Executors/simple.frag
Normal file
@ -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;
|
||||
}
|
||||
}
|
21
Dashboard.Drawing.OpenGL/Executors/simple.vert
Normal file
21
Dashboard.Drawing.OpenGL/Executors/simple.vert
Normal file
@ -0,0 +1,21 @@
|
||||
#version 140
|
||||
|
||||
in vec3 a_v3Position;
|
||||
in vec2 a_v2TexCoords;
|
||||
in int a_iCmdIndex;
|
||||
|
||||
out vec3 v_v3Position;
|
||||
out vec2 v_v2TexCoords;
|
||||
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_iCmdIndex = a_iCmdIndex;
|
||||
}
|
36
Dashboard.Drawing.OpenGL/GLEngine.cs
Normal file
36
Dashboard.Drawing.OpenGL/GLEngine.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
89
Dashboard.Drawing.OpenGL/GradientUniformBuffer.cs
Normal file
89
Dashboard.Drawing.OpenGL/GradientUniformBuffer.cs
Normal file
@ -0,0 +1,89 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace Dashboard.Drawing.OpenGL
|
||||
{
|
||||
public class GradientUniformBuffer : IInitializer, IGLDisposable, IResourceManager
|
||||
{
|
||||
private bool _isDisposed;
|
||||
private int _top = 0;
|
||||
private readonly MappableBuffer<GradientUniformStruct> _buffer = new MappableBuffer<GradientUniformStruct>();
|
||||
private readonly Dictionary<Gradient, Entry> _entries = new Dictionary<Gradient, Entry>();
|
||||
|
||||
public bool IsInitialized { get; private set; } = false;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
if (IsInitialized)
|
||||
return;
|
||||
|
||||
IsInitialized = true;
|
||||
|
||||
_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);
|
||||
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
public void Dispose(bool safeExit)
|
||||
{
|
||||
if (_isDisposed)
|
||||
return;
|
||||
_isDisposed = true;
|
||||
|
||||
_buffer.Dispose(safeExit);
|
||||
}
|
||||
|
||||
string IResourceManager.Name { get; } = nameof(GradientUniformBuffer);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(float))]
|
||||
public struct GradientUniformStruct
|
||||
{
|
||||
[FieldOffset(0 * sizeof(float))]
|
||||
public float Position;
|
||||
|
||||
[FieldOffset(4 * sizeof(float))]
|
||||
public Vector4 Color;
|
||||
}
|
||||
}
|
24
Dashboard.Drawing.OpenGL/IArc.cs
Normal file
24
Dashboard.Drawing.OpenGL/IArc.cs
Normal file
@ -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();
|
||||
}
|
||||
}
|
26
Dashboard.Drawing.OpenGL/IGLContext.cs
Normal file
26
Dashboard.Drawing.OpenGL/IGLContext.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
16
Dashboard.Drawing.OpenGL/IGLDisposable.cs
Normal file
16
Dashboard.Drawing.OpenGL/IGLDisposable.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
9
Dashboard.Drawing.OpenGL/IInitializer.cs
Normal file
9
Dashboard.Drawing.OpenGL/IInitializer.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Dashboard.Drawing.OpenGL
|
||||
{
|
||||
public interface IInitializer
|
||||
{
|
||||
bool IsInitialized { get; }
|
||||
|
||||
void Initialize();
|
||||
}
|
||||
}
|
7
Dashboard.Drawing.OpenGL/IResourceManager.cs
Normal file
7
Dashboard.Drawing.OpenGL/IResourceManager.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Dashboard.Drawing.OpenGL
|
||||
{
|
||||
public interface IResourceManager
|
||||
{
|
||||
public string Name { get; }
|
||||
}
|
||||
}
|
167
Dashboard.Drawing.OpenGL/MappableBuffer.cs
Normal file
167
Dashboard.Drawing.OpenGL/MappableBuffer.cs
Normal file
@ -0,0 +1,167 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
|
||||
namespace Dashboard.Drawing.OpenGL
|
||||
{
|
||||
public class MappableBuffer<T> : IInitializer, IGLDisposable where T : struct
|
||||
{
|
||||
public int Handle { get; private set; } = 0;
|
||||
public int Capacity { get; set; } = BASE_CAPACITY;
|
||||
public IntPtr Pointer { get; private set; } = IntPtr.Zero;
|
||||
|
||||
public bool IsInitialized => Handle != 0;
|
||||
|
||||
private bool _isDisposed = false;
|
||||
private const int BASE_CAPACITY = 4 << 10; // 4 KiB
|
||||
private const int MAX_INCREMENT = 4 << 20; // 4 MiB
|
||||
|
||||
~MappableBuffer()
|
||||
{
|
||||
Dispose(true, false);
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
if (IsInitialized)
|
||||
return;
|
||||
|
||||
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;
|
||||
|
||||
SetSize(count, false);
|
||||
}
|
||||
|
||||
public void SetSize(int count, bool clear = false)
|
||||
{
|
||||
AssertInitialized();
|
||||
Unmap();
|
||||
|
||||
int sz = Unsafe.SizeOf<T>();
|
||||
int oldsize = Capacity * sz;
|
||||
int request = count * sz;
|
||||
int newsize;
|
||||
|
||||
if (request < BASE_CAPACITY)
|
||||
request = BASE_CAPACITY;
|
||||
|
||||
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();
|
||||
|
||||
if (clear)
|
||||
{
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, dest);
|
||||
GL.BufferData(BufferTarget.ArrayBuffer, newsize, IntPtr.Zero, BufferUsage.DynamicDraw);
|
||||
}
|
||||
else
|
||||
{
|
||||
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, Math.Min(newsize, oldsize));
|
||||
}
|
||||
|
||||
GL.DeleteBuffer(Handle);
|
||||
Handle = dest;
|
||||
Capacity = newsize / Unsafe.SizeOf<T>();
|
||||
}
|
||||
|
||||
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);
|
||||
Pointer = IntPtr.Zero;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private void AssertInitialized()
|
||||
{
|
||||
if (Handle == 0)
|
||||
throw new InvalidOperationException("The buffer is not initialized.");
|
||||
}
|
||||
|
||||
private void Dispose(bool safeExit, bool disposing)
|
||||
{
|
||||
if (_isDisposed)
|
||||
return;
|
||||
_isDisposed = true;
|
||||
|
||||
if (disposing)
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
if (safeExit)
|
||||
ContextCollector.Global.DeleteBufffer(Handle);
|
||||
}
|
||||
|
||||
public void Dispose() => Dispose(true, true);
|
||||
public void Dispose(bool safeExit) => Dispose(safeExit, true);
|
||||
}
|
||||
|
||||
public class MappableBumpAllocator<T> : MappableBuffer<T> where T : struct
|
||||
{
|
||||
private int _top = 0;
|
||||
private int _previousTop = 0;
|
||||
|
||||
public ref T Take(out int index)
|
||||
{
|
||||
index = _top;
|
||||
EnsureCapacity(++_top);
|
||||
Map();
|
||||
|
||||
return ref AsSpan()[index];
|
||||
}
|
||||
|
||||
public ref T Take() => ref Take(out _);
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
SetSize(0, true);
|
||||
_previousTop = _top;
|
||||
_top = 0;
|
||||
}
|
||||
}
|
||||
}
|
60
Dashboard.Drawing.OpenGL/ShaderUtil.cs
Normal file
60
Dashboard.Drawing.OpenGL/ShaderUtil.cs
Normal file
@ -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<string>? 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;
|
||||
}
|
||||
}
|
||||
}
|
51
Dashboard.Drawing.OpenGL/TransformStack.cs
Normal file
51
Dashboard.Drawing.OpenGL/TransformStack.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -14,15 +14,10 @@ namespace Dashboard.Drawing
|
||||
{
|
||||
}
|
||||
|
||||
public readonly struct SolidBrush : IBrush
|
||||
public readonly struct SolidBrush(Color color) : IBrush
|
||||
{
|
||||
public IDrawExtension Kind { get; } = SolidBrushExtension.Instance;
|
||||
public Color Color { get; }
|
||||
|
||||
public SolidBrush(Color color)
|
||||
{
|
||||
Color = color;
|
||||
}
|
||||
public Color Color { get; } = color;
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
@ -30,10 +25,28 @@ namespace Dashboard.Drawing
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct GradientBrush(Gradient gradient) : IBrush
|
||||
{
|
||||
public IDrawExtension Kind { get; } = GradientBrushExtension.Instance;
|
||||
public Gradient Gradient { get; } = gradient;
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Kind, Gradient);
|
||||
}
|
||||
}
|
||||
|
||||
public class SolidBrushExtension : DrawExtension
|
||||
{
|
||||
private SolidBrushExtension() : base("DB_Brush_solid", new[] { BrushExtension.Instance }) { }
|
||||
|
||||
public static readonly SolidBrushExtension Instance = new SolidBrushExtension();
|
||||
}
|
||||
|
||||
public class GradientBrushExtension : DrawExtension
|
||||
{
|
||||
private GradientBrushExtension() : base("DB_Brush_gradient", new[] { BrushExtension.Instance }) { }
|
||||
|
||||
public static readonly GradientBrushExtension Instance = new GradientBrushExtension();
|
||||
}
|
||||
}
|
@ -23,9 +23,9 @@ namespace Dashboard.Drawing
|
||||
{
|
||||
AddCommand(DrawPoint = new DrawCommand<PointCommandArgs>("Point", this, PointCommandArgs.CommandSize));
|
||||
AddCommand(DrawLine = new DrawCommand<LineCommandArgs>("Line", this, LineCommandArgs.CommandSize));
|
||||
AddCommand(DrawRectF = new RectCommand(RectCommand.Mode.Fill));
|
||||
AddCommand(DrawRectS = new RectCommand(RectCommand.Mode.Strike));
|
||||
AddCommand(DrawRectFS = new RectCommand(RectCommand.Mode.FillStrike));
|
||||
AddCommand(DrawRectF = new RectCommand(this, RectCommand.Mode.Fill));
|
||||
AddCommand(DrawRectS = new RectCommand(this, RectCommand.Mode.Strike));
|
||||
AddCommand(DrawRectFS = new RectCommand(this, RectCommand.Mode.FillStrike));
|
||||
}
|
||||
|
||||
public static readonly DbBaseCommands Instance = new DbBaseCommands();
|
||||
@ -33,13 +33,15 @@ namespace Dashboard.Drawing
|
||||
|
||||
public struct PointCommandArgs : IParameterSerializer<PointCommandArgs>
|
||||
{
|
||||
public Vector3 Position { get; private set; }
|
||||
public Vector2 Position { get; private set; }
|
||||
public float Depth { get; private set; }
|
||||
public float Size { get; private set; }
|
||||
public IBrush? Brush { get; private set; }
|
||||
|
||||
public PointCommandArgs(Vector3 position, float size, IBrush brush)
|
||||
public PointCommandArgs(Vector2 position, float depth, float size, IBrush brush)
|
||||
{
|
||||
Position = position;
|
||||
Depth = depth;
|
||||
Brush = brush;
|
||||
Size = size;
|
||||
}
|
||||
@ -51,7 +53,7 @@ namespace Dashboard.Drawing
|
||||
|
||||
Span<Value> value = stackalloc Value[]
|
||||
{
|
||||
new Value(Position, Size, queue.RequireResource(Brush!))
|
||||
new Value(Position, Depth, Size, queue.RequireResource(Brush!))
|
||||
};
|
||||
|
||||
MemoryMarshal.AsBytes(value).CopyTo(bytes);
|
||||
@ -67,28 +69,31 @@ namespace Dashboard.Drawing
|
||||
Value value = MemoryMarshal.AsRef<Value>(bytes);
|
||||
|
||||
Position = value.Position;
|
||||
Depth = value.Depth;
|
||||
Size = value.Size;
|
||||
Brush = (IBrush)queue.Resources[value.BrushIndex];
|
||||
}
|
||||
|
||||
private record struct Value(Vector3 Position, float Size, int BrushIndex);
|
||||
private record struct Value(Vector2 Position, float Depth, float Size, int BrushIndex);
|
||||
|
||||
public static readonly int CommandSize = Unsafe.SizeOf<Value>();
|
||||
}
|
||||
|
||||
public struct LineCommandArgs : IParameterSerializer<LineCommandArgs>
|
||||
{
|
||||
public Vector3 Start { get; private set; }
|
||||
public Vector3 End { get; private set; }
|
||||
public Vector2 Start { get; private set; }
|
||||
public Vector2 End { get; private set; }
|
||||
public float Depth { get; private set; }
|
||||
public float Size { get; private set; }
|
||||
public IBrush? Brush { get; private set; }
|
||||
|
||||
public LineCommandArgs(Vector3 start, Vector3 end, float size, IBrush brush)
|
||||
public LineCommandArgs(Vector2 start, Vector2 end, float depth, float size, IBrush brush)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
Brush = brush;
|
||||
Depth = depth;
|
||||
Size = size;
|
||||
Brush = brush;
|
||||
}
|
||||
|
||||
public int Serialize(DrawQueue queue, Span<byte> bytes)
|
||||
@ -98,7 +103,7 @@ namespace Dashboard.Drawing
|
||||
|
||||
Span<Value> value = stackalloc Value[]
|
||||
{
|
||||
new Value(Start, End, Size, queue.RequireResource(Brush!))
|
||||
new Value(Start, End, Depth, Size, queue.RequireResource(Brush!))
|
||||
};
|
||||
|
||||
MemoryMarshal.AsBytes(value).CopyTo(bytes);
|
||||
@ -114,21 +119,17 @@ namespace Dashboard.Drawing
|
||||
|
||||
Start = value.Start;
|
||||
End = value.End;
|
||||
Depth = value.Depth;
|
||||
Size = value.Size;
|
||||
Brush = (IBrush)queue.Resources[value.BrushIndex];
|
||||
}
|
||||
|
||||
private record struct Value(Vector3 Start, Vector3 End, float Size, int BrushIndex);
|
||||
private record struct Value(Vector2 Start, Vector2 End, float Depth, float Size, int BrushIndex);
|
||||
|
||||
public static readonly int CommandSize = Unsafe.SizeOf<Value>();
|
||||
}
|
||||
|
||||
public enum StrikeKind
|
||||
{
|
||||
Inset = -1,
|
||||
Center = 0,
|
||||
Outset = 1,
|
||||
}
|
||||
|
||||
|
||||
public class RectCommand : IDrawCommand<RectCommandArgs>
|
||||
{
|
||||
@ -137,9 +138,9 @@ namespace Dashboard.Drawing
|
||||
public IDrawExtension Extension { get; }
|
||||
public int Length { get; }
|
||||
|
||||
public RectCommand(Mode mode)
|
||||
public RectCommand(IDrawExtension extension, Mode mode)
|
||||
{
|
||||
Extension = DbBaseCommands.Instance;
|
||||
Extension = extension;
|
||||
_mode = mode;
|
||||
|
||||
switch (mode)
|
||||
@ -175,16 +176,16 @@ namespace Dashboard.Drawing
|
||||
{
|
||||
case Mode.Fill:
|
||||
ref readonly RectF f = ref MemoryMarshal.AsRef<RectF>(param);
|
||||
args = new RectCommandArgs(f.Start, f.End, (IBrush)queue.Resources[f.FillBrushIndex]);
|
||||
args = new RectCommandArgs(f.Start, f.End, f.Depth, (IBrush)queue.Resources[f.FillBrushIndex]);
|
||||
break;
|
||||
case Mode.Strike:
|
||||
ref readonly RectS s = ref MemoryMarshal.AsRef<RectS>(param);
|
||||
args = new RectCommandArgs(s.Start, s.End, (IBrush)queue.Resources[s.StrikeBrushIndex], s.StrikeSize, s.StrikeKind);
|
||||
args = new RectCommandArgs(s.Start, s.End, s.Depth, (IBrush)queue.Resources[s.StrikeBrushIndex], s.StrikeSize, s.BorderKind);
|
||||
break;
|
||||
default:
|
||||
ref readonly RectFS fs = ref MemoryMarshal.AsRef<RectFS>(param);
|
||||
args = new RectCommandArgs(fs.Start, fs.End, (IBrush)queue.Resources[fs.FillBrushIndex],
|
||||
(IBrush)queue.Resources[fs.StrikeBrushIndex], fs.StrikeSize, fs.StrikeKind);
|
||||
args = new RectCommandArgs(fs.Start, fs.End, fs.Depth, (IBrush)queue.Resources[fs.FillBrushIndex],
|
||||
(IBrush)queue.Resources[fs.StrikeBrushIndex], fs.StrikeSize, fs.BorderKind);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -207,24 +208,27 @@ namespace Dashboard.Drawing
|
||||
ref RectF f = ref MemoryMarshal.AsRef<RectF>(param);
|
||||
f.Start = obj.Start;
|
||||
f.End = obj.End;
|
||||
f.Depth = obj.Depth;
|
||||
f.FillBrushIndex = queue.RequireResource(obj.FillBrush!);
|
||||
break;
|
||||
case Mode.Strike:
|
||||
ref RectS s = ref MemoryMarshal.AsRef<RectS>(param);
|
||||
s.Start = obj.Start;
|
||||
s.End = obj.End;
|
||||
s.Depth = obj.Depth;
|
||||
s.StrikeBrushIndex = queue.RequireResource(obj.StrikeBrush!);
|
||||
s.StrikeSize = obj.StrikeSize;
|
||||
s.StrikeKind = obj.StrikeKind;
|
||||
s.BorderKind = obj.BorderKind;
|
||||
break;
|
||||
default:
|
||||
ref RectFS fs = ref MemoryMarshal.AsRef<RectFS>(param);
|
||||
fs.Start = obj.Start;
|
||||
fs.End = obj.End;
|
||||
fs.Depth = obj.Depth;
|
||||
fs.FillBrushIndex = queue.RequireResource(obj.FillBrush!);
|
||||
fs.StrikeBrushIndex = queue.RequireResource(obj.StrikeBrush!);
|
||||
fs.StrikeSize = obj.StrikeSize;
|
||||
fs.StrikeKind = obj.StrikeKind;
|
||||
fs.BorderKind = obj.BorderKind;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -239,46 +243,50 @@ namespace Dashboard.Drawing
|
||||
FillStrike = Fill | Strike,
|
||||
}
|
||||
|
||||
private record struct RectF(Vector3 Start, Vector3 End, int FillBrushIndex);
|
||||
private record struct RectS(Vector3 Start, Vector3 End, int StrikeBrushIndex, float StrikeSize, StrikeKind StrikeKind);
|
||||
private record struct RectFS(Vector3 Start, Vector3 End, int FillBrushIndex, int StrikeBrushIndex, float StrikeSize, StrikeKind StrikeKind);
|
||||
private record struct RectF(Vector2 Start, Vector2 End, float Depth, int FillBrushIndex);
|
||||
private record struct RectS(Vector2 Start, Vector2 End, float Depth, int StrikeBrushIndex, float StrikeSize, BorderKind BorderKind);
|
||||
private record struct RectFS(Vector2 Start, Vector2 End, float Depth, int FillBrushIndex, int StrikeBrushIndex, float StrikeSize, BorderKind BorderKind);
|
||||
}
|
||||
|
||||
public struct RectCommandArgs
|
||||
{
|
||||
public Vector3 Start { get; private set; }
|
||||
public Vector3 End { get; private set; }
|
||||
public StrikeKind StrikeKind { get; private set; } = StrikeKind.Center;
|
||||
public Vector2 Start { get; private set; }
|
||||
public Vector2 End { get; private set; }
|
||||
public float Depth { get; private set; }
|
||||
public float StrikeSize { get; private set; } = 0f;
|
||||
public BorderKind BorderKind { get; private set; } = BorderKind.Center;
|
||||
public IBrush? FillBrush { get; private set; } = null;
|
||||
public IBrush? StrikeBrush { get; private set; } = null;
|
||||
public bool IsStruck => StrikeSize != 0;
|
||||
|
||||
public RectCommandArgs(Vector3 start, Vector3 end, IBrush fillBrush)
|
||||
public RectCommandArgs(Vector2 start, Vector2 end, float depth, IBrush fillBrush)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
Depth = depth;
|
||||
FillBrush = fillBrush;
|
||||
}
|
||||
|
||||
public RectCommandArgs(Vector3 start, Vector3 end, IBrush strikeBrush, float strikeSize, StrikeKind strikeKind)
|
||||
public RectCommandArgs(Vector2 start, Vector2 end, float depth, IBrush strikeBrush, float strikeSize, BorderKind borderKind)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
Depth = depth;
|
||||
StrikeBrush = strikeBrush;
|
||||
StrikeSize = strikeSize;
|
||||
StrikeKind = strikeKind;
|
||||
BorderKind = borderKind;
|
||||
}
|
||||
|
||||
public RectCommandArgs(Vector3 start, Vector3 end, IBrush fillBrush, IBrush strikeBrush, float strikeSize,
|
||||
StrikeKind strikeKind)
|
||||
public RectCommandArgs(Vector2 start, Vector2 end, float depth, IBrush fillBrush, IBrush strikeBrush, float strikeSize,
|
||||
BorderKind borderKind)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
Depth = depth;
|
||||
FillBrush = fillBrush;
|
||||
StrikeBrush = strikeBrush;
|
||||
StrikeSize = strikeSize;
|
||||
StrikeKind = strikeKind;
|
||||
BorderKind = borderKind;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dashboard.Drawing
|
||||
{
|
||||
@ -62,55 +64,76 @@ namespace Dashboard.Drawing
|
||||
return queue.GetController(extension);
|
||||
}
|
||||
|
||||
public static void Point(this DrawQueue queue, Vector3 position, float size, IBrush brush)
|
||||
public static void Point(this DrawQueue queue, Vector2 position, float depth, float size, IBrush brush)
|
||||
{
|
||||
Vector3 radius = new Vector3(size / 2f);
|
||||
Box3d bounds = new Box3d(position - radius, position + radius);
|
||||
Vector2 radius = new Vector2(0.5f * size);
|
||||
Box2d bounds = new Box2d(position - radius, position + radius);
|
||||
|
||||
IDrawController controller = queue.GetController(DbBaseCommands.Instance);
|
||||
controller.EnsureBounds(bounds);
|
||||
controller.Write(DbBaseCommands.Instance.DrawPoint, new PointCommandArgs(position, size, brush));
|
||||
controller.EnsureBounds(bounds, depth);
|
||||
controller.Write(DbBaseCommands.Instance.DrawPoint, new PointCommandArgs(position, depth, size, brush));
|
||||
}
|
||||
|
||||
public static void Line(this DrawQueue queue, Vector3 start, Vector3 end, float size, IBrush brush)
|
||||
public static void Line(this DrawQueue queue, Vector2 start, Vector2 end, float depth, float size, IBrush brush)
|
||||
{
|
||||
Vector3 radius = new Vector3(size / 2f);
|
||||
Vector3 min = Vector3.Min(start, end) - radius;
|
||||
Vector3 max = Vector3.Max(start, end) + radius;
|
||||
Box3d bounds = new Box3d(min, max);
|
||||
Vector2 radius = new Vector2(size / 2f);
|
||||
Vector2 min = Vector2.Min(start, end) - radius;
|
||||
Vector2 max = Vector2.Max(start, end) + radius;
|
||||
Box2d bounds = new Box2d(min, max);
|
||||
|
||||
IDrawController controller = queue.GetController(DbBaseCommands.Instance);
|
||||
controller.EnsureBounds(bounds);
|
||||
controller.Write(DbBaseCommands.Instance.DrawLine, new LineCommandArgs(start, end, size, brush));
|
||||
controller.EnsureBounds(bounds, depth);
|
||||
controller.Write(DbBaseCommands.Instance.DrawLine, new LineCommandArgs(start, end, depth, size, brush));
|
||||
}
|
||||
|
||||
public static void Rect(this DrawQueue queue, Vector3 start, Vector3 end, IBrush fillBrush)
|
||||
public static void Rect(this DrawQueue queue, Vector2 start, Vector2 end, float depth, IBrush fillBrush)
|
||||
{
|
||||
IDrawController controller = queue.GetController(DbBaseCommands.Instance);
|
||||
Vector3 min = Vector3.Min(start, end);
|
||||
Vector3 max = Vector3.Max(start, end);
|
||||
controller.EnsureBounds(new Box3d(min, max));
|
||||
controller.Write(DbBaseCommands.Instance.DrawRectF, new RectCommandArgs(start, end, fillBrush));
|
||||
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));
|
||||
}
|
||||
|
||||
public static void Rect(this DrawQueue queue, Vector3 start, Vector3 end, IBrush strikeBrush, float strikeSize,
|
||||
StrikeKind kind = StrikeKind.Center)
|
||||
public static void Rect(this DrawQueue queue, Vector2 start, Vector2 end, float depth, IBrush strikeBrush, float strikeSize,
|
||||
BorderKind kind = BorderKind.Center)
|
||||
{
|
||||
IDrawController controller = queue.GetController(DbBaseCommands.Instance);
|
||||
Vector3 min = Vector3.Min(start, end);
|
||||
Vector3 max = Vector3.Max(start, end);
|
||||
controller.EnsureBounds(new Box3d(min, max));
|
||||
controller.Write(DbBaseCommands.Instance.DrawRectF, new RectCommandArgs(start, end, strikeBrush, strikeSize, kind));
|
||||
Vector2 min = Vector2.Min(start, end);
|
||||
Vector2 max = Vector2.Max(start, end);
|
||||
controller.EnsureBounds(new Box2d(min, max), depth);
|
||||
controller.Write(DbBaseCommands.Instance.DrawRectS, new RectCommandArgs(start, end, depth, strikeBrush, strikeSize, kind));
|
||||
}
|
||||
|
||||
public static void Rect(this DrawQueue queue, Vector3 start, Vector3 end, IBrush fillBrush, IBrush strikeBrush,
|
||||
float strikeSize, StrikeKind kind = StrikeKind.Center)
|
||||
public static void Rect(this DrawQueue queue, Vector2 start, Vector2 end, float depth, IBrush fillBrush, IBrush strikeBrush,
|
||||
float strikeSize, BorderKind kind = BorderKind.Center)
|
||||
{
|
||||
IDrawController controller = queue.GetController(DbBaseCommands.Instance);
|
||||
Vector3 min = Vector3.Min(start, end);
|
||||
Vector3 max = Vector3.Max(start, end);
|
||||
controller.EnsureBounds(new Box3d(min, max));
|
||||
controller.Write(DbBaseCommands.Instance.DrawRectF, new RectCommandArgs(start, end, fillBrush, strikeBrush, strikeSize, kind));
|
||||
Vector2 min = Vector2.Min(start, end);
|
||||
Vector2 max = Vector2.Max(start, end);
|
||||
controller.EnsureBounds(new Box2d(min, max), depth);
|
||||
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,
|
||||
Anchor anchor = Anchor.Left)
|
||||
{
|
||||
IDrawController controller = queue.GetController(DbBaseCommands.Instance);
|
||||
controller.EnsureBounds(new Box2d(position.X, position.Y, position.X, position.Y), position.Z);
|
||||
controller.Write(TextExtension.Instance.TextCommand, new TextCommandArgs(font, brush, anchor, position, text));
|
||||
}
|
||||
|
||||
public static void Text(this DrawQueue queue, Vector3 position, IBrush textBrush, IBrush borderBrush,
|
||||
float borderRadius, string text, IFont font, Anchor anchor = Anchor.Left, BorderKind borderKind = BorderKind.Outset)
|
||||
{
|
||||
IDrawController controller = queue.GetController(DbBaseCommands.Instance);
|
||||
controller.EnsureBounds(new Box2d(position.X, position.Y, position.X, position.Y), position.Z);
|
||||
controller.Write(TextExtension.Instance.TextCommand, new TextCommandArgs(font, textBrush, anchor, position, text)
|
||||
{
|
||||
BorderBrush = borderBrush,
|
||||
BorderRadius = borderRadius,
|
||||
BorderKind = borderKind,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ namespace Dashboard.Drawing
|
||||
_resources.Clear();
|
||||
_commands.Clear();
|
||||
_extensions.Clear();
|
||||
_commandStream.Capacity = 0;
|
||||
_commandStream.SetLength(0);
|
||||
}
|
||||
|
||||
public int RequireExtension(IDrawExtension extension)
|
||||
@ -178,9 +178,9 @@ namespace Dashboard.Drawing
|
||||
|
||||
private class DrawController(DrawQueue Queue) : IDrawController
|
||||
{
|
||||
public void EnsureBounds(Box3d bounds)
|
||||
public void EnsureBounds(Box2d bounds, float depth)
|
||||
{
|
||||
Queue.Bounds = Box3d.Union(Queue.Bounds, bounds);
|
||||
Queue.Bounds = Box3d.Union(Queue.Bounds, bounds, depth);
|
||||
}
|
||||
|
||||
public void Write(IDrawCommand command)
|
||||
@ -239,11 +239,12 @@ namespace Dashboard.Drawing
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_index == -1)
|
||||
_index = 0;
|
||||
|
||||
if (_index >= _length)
|
||||
return false;
|
||||
|
||||
if (_index == -1)
|
||||
_index = 0;
|
||||
|
||||
_index += FromVlq(_stream[_index .. (_index + 5)], out int command);
|
||||
_current = _queue.Command[command];
|
||||
@ -336,7 +337,7 @@ namespace Dashboard.Drawing
|
||||
/// Ensures that the canvas is at least a certain size.
|
||||
/// </summary>
|
||||
/// <param name="bounds">The bounding box.</param>
|
||||
void EnsureBounds(Box3d bounds);
|
||||
void EnsureBounds(Box2d bounds, float depth);
|
||||
|
||||
/// <summary>
|
||||
/// Write into the command stream.
|
||||
|
22
Dashboard.Drawing/Font.cs
Normal file
22
Dashboard.Drawing/Font.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace Dashboard.Drawing
|
||||
{
|
||||
public class FontExtension : DrawExtension
|
||||
{
|
||||
private FontExtension() : base("DB_Font", Enumerable.Empty<DrawExtension>())
|
||||
{
|
||||
}
|
||||
|
||||
public static readonly IDrawExtension Instance = new FontExtension();
|
||||
}
|
||||
|
||||
public interface IFont : IDrawResource
|
||||
{
|
||||
public string Family { get; }
|
||||
public float Size { get; }
|
||||
public FontWeight Weight { get; }
|
||||
public FontSlant Slant { get; }
|
||||
public FontStretch Stretch { get; }
|
||||
}
|
||||
}
|
134
Dashboard.Drawing/TextExtension.cs
Normal file
134
Dashboard.Drawing/TextExtension.cs
Normal file
@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dashboard.Drawing
|
||||
{
|
||||
public class TextExtension : DrawExtension
|
||||
{
|
||||
public TextCommand TextCommand { get; } = new TextCommand();
|
||||
|
||||
private TextExtension() : base("DB_Text", new [] { FontExtension.Instance, BrushExtension.Instance })
|
||||
{
|
||||
}
|
||||
|
||||
public static readonly TextExtension Instance = new TextExtension();
|
||||
}
|
||||
|
||||
public class TextCommand : IDrawCommand<TextCommandArgs>
|
||||
{
|
||||
public string Name { get; } = "Text";
|
||||
public IDrawExtension Extension { get; } = TextExtension.Instance;
|
||||
public int Length { get; } = -1;
|
||||
|
||||
public int WriteParams(DrawQueue queue, TextCommandArgs obj, Span<byte> param)
|
||||
{
|
||||
int size = Unsafe.SizeOf<Header>() + obj.Text.Length + sizeof(char);
|
||||
|
||||
if (param.Length < size)
|
||||
return size;
|
||||
|
||||
ref Header header = ref MemoryMarshal.Cast<byte, Header>(param[0..Unsafe.SizeOf<Header>()])[0];
|
||||
Span<char> text = MemoryMarshal.Cast<byte, char>(param[Unsafe.SizeOf<Header>()..]);
|
||||
|
||||
header = new Header()
|
||||
{
|
||||
Font = queue.RequireResource(obj.Font),
|
||||
TextBrush = queue.RequireResource(obj.TextBrush),
|
||||
BorderBrush = (obj.BorderBrush != null) ? queue.RequireResource(obj.BorderBrush) : -1,
|
||||
BorderRadius = (obj.BorderBrush != null) ? obj.BorderRadius : 0f,
|
||||
Anchor = obj.Anchor,
|
||||
Position = obj.Position,
|
||||
BorderKind = obj.BorderKind,
|
||||
};
|
||||
obj.Text.CopyTo(text);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
public TextCommandArgs GetParams(DrawQueue queue, ReadOnlySpan<byte> param)
|
||||
{
|
||||
Header header = MemoryMarshal.Cast<byte, Header>(param[0..Unsafe.SizeOf<Header>()])[0];
|
||||
ReadOnlySpan<char> text = MemoryMarshal.Cast<byte, char>(param[Unsafe.SizeOf<Header>()..]);
|
||||
|
||||
if (header.BorderBrush == -1 || header.BorderRadius == 0)
|
||||
{
|
||||
return new TextCommandArgs(
|
||||
(IFont)queue.Resources[header.Font],
|
||||
(IBrush)queue.Resources[header.TextBrush],
|
||||
header.Anchor,
|
||||
header.Position,
|
||||
text.ToString())
|
||||
{
|
||||
BorderBrush = (IBrush)queue.Resources[header.BorderBrush],
|
||||
BorderRadius = header.BorderRadius,
|
||||
BorderKind = header.BorderKind,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return new TextCommandArgs(
|
||||
(IFont)queue.Resources[header.Font],
|
||||
(IBrush)queue.Resources[header.TextBrush],
|
||||
header.Anchor,
|
||||
header.Position,
|
||||
text.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
int IDrawCommand.WriteParams(DrawQueue queue, object? obj, Span<byte> param)
|
||||
{
|
||||
return WriteParams(queue, (TextCommandArgs)obj!, param);
|
||||
}
|
||||
|
||||
object? IDrawCommand.GetParams(DrawQueue queue, ReadOnlySpan<byte> param)
|
||||
{
|
||||
return GetParams(queue, param);
|
||||
}
|
||||
|
||||
private struct Header
|
||||
{
|
||||
private int _flags;
|
||||
public int Font;
|
||||
public int TextBrush;
|
||||
public int BorderBrush;
|
||||
public Vector3 Position;
|
||||
public float BorderRadius;
|
||||
|
||||
public Anchor Anchor
|
||||
{
|
||||
get => (Anchor)(_flags & 0xF);
|
||||
set => _flags = (_flags & ~0xF) | (int)value;
|
||||
}
|
||||
|
||||
public BorderKind BorderKind
|
||||
{
|
||||
get => (_flags & INSET) switch
|
||||
{
|
||||
OUTSET => BorderKind.Outset,
|
||||
INSET => BorderKind.Inset,
|
||||
_ => BorderKind.Center,
|
||||
};
|
||||
set => _flags = value switch
|
||||
{
|
||||
BorderKind.Outset => (_flags & ~INSET) | OUTSET,
|
||||
BorderKind.Inset => (_flags & ~INSET) | INSET,
|
||||
_ => (_flags & ~INSET) | CENTER,
|
||||
};
|
||||
}
|
||||
|
||||
private const int INSET = 0x30;
|
||||
private const int CENTER = 0x00;
|
||||
private const int OUTSET = 0x10;
|
||||
}
|
||||
}
|
||||
|
||||
public record struct TextCommandArgs(IFont Font, IBrush TextBrush, Anchor Anchor, Vector3 Position, string Text)
|
||||
{
|
||||
public IBrush? BorderBrush { get; init; } = null;
|
||||
public float BorderRadius { get; init; } = 0;
|
||||
|
||||
public BorderKind BorderKind { get; init; } = BorderKind.Center;
|
||||
}
|
||||
}
|
@ -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,160 @@
|
||||
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 sys = System.Numerics;
|
||||
using otk = OpenTK.Mathematics;
|
||||
|
||||
TK.Init(new ToolkitOptions()
|
||||
{
|
||||
ApplicationName = "Paper Punch Out!",
|
||||
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,
|
||||
Multisamples = 0,
|
||||
|
||||
SupportTransparentFramebufferX11 = true,
|
||||
});
|
||||
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);
|
||||
|
||||
TK.OpenGL.SetCurrentContext(context);
|
||||
TK.OpenGL.SetSwapInterval(1);
|
||||
|
||||
GLLoader.LoadBindings(new Pal2BindingsContext(TK.OpenGL, context));
|
||||
|
||||
DrawQueue queue = new DrawQueue();
|
||||
SolidBrush fg = new SolidBrush(Color.FromArgb(0, 0, 0, 0));
|
||||
SolidBrush bg = new SolidBrush(Color.Black);
|
||||
bool shouldExit = false;
|
||||
|
||||
SolidBrush fg = new SolidBrush(Color.Black);
|
||||
SolidBrush bg = new SolidBrush(Color.White);
|
||||
GLEngine engine = new GLEngine();
|
||||
engine.Initialize();
|
||||
|
||||
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);
|
||||
GlContext dbGlContext = new GlContext(wnd, context);
|
||||
ContextExecutor executor = engine.GetExecutor(dbGlContext);
|
||||
|
||||
foreach (ICommandFrame frame in queue)
|
||||
Vector2 mousePos = Vector2.Zero;
|
||||
Random r = new Random();
|
||||
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;
|
||||
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<Vector3> points = new List<Vector3>();
|
||||
|
||||
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.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.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();
|
||||
|
||||
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>();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user