Compare commits
No commits in common. "dashboard2" and "master" have entirely different histories.
dashboard2
...
master
@ -2,12 +2,12 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Dashboard.Common\Dashboard.Common.csproj" />
|
||||
<PackageReference Include="BlurgText" Version="0.1.0-nightly-4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -1,15 +0,0 @@
|
||||
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,
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
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,46 +0,0 @@
|
||||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Dashboard
|
||||
{
|
||||
public readonly record struct Box3d(Vector3 Min, Vector3 Max)
|
||||
{
|
||||
public float Left => Min.X;
|
||||
public float Right => Max.X;
|
||||
public float Top => Min.Y;
|
||||
public float Bottom => Max.Y;
|
||||
public float Far => Min.Z;
|
||||
public float Near => Max.Z;
|
||||
|
||||
public Vector3 Size => Max - Min;
|
||||
public Vector3 Center => Min + Size * 0.5f;
|
||||
|
||||
public static Box3d Union(Box3d left, Box3d right)
|
||||
{
|
||||
Vector3 min = Vector3.Min(left.Min, right.Min);
|
||||
Vector3 max = Vector3.Max(left.Max, right.Max);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Dashboard</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
@ -1,40 +0,0 @@
|
||||
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,
|
||||
}
|
||||
|
||||
}
|
@ -1,227 +0,0 @@
|
||||
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,38 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Dashboard
|
||||
{
|
||||
public class HashList<T> : IReadOnlyList<T>
|
||||
where T : notnull
|
||||
{
|
||||
private readonly List<T> _list = new List<T>();
|
||||
private readonly Dictionary<T, int> _map = new Dictionary<T, int>();
|
||||
|
||||
public T this[int index] => _list[index];
|
||||
|
||||
public int Count => _list.Count;
|
||||
|
||||
public int Intern(T value)
|
||||
{
|
||||
if (_map.TryGetValue(value, out int index))
|
||||
return index;
|
||||
|
||||
index = Count;
|
||||
|
||||
_list.Add(value);
|
||||
_map.Add(value, index);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_list.Clear();
|
||||
_map.Clear();
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator() => _list.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator();
|
||||
}
|
||||
}
|
@ -1,196 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
namespace Dashboard
|
||||
{
|
||||
public enum BorderKind
|
||||
{
|
||||
Inset = -1,
|
||||
Center = 0,
|
||||
Outset = 1,
|
||||
}
|
||||
|
||||
public enum CapType
|
||||
{
|
||||
None,
|
||||
Circular,
|
||||
Rectangular,
|
||||
}
|
||||
|
||||
public enum CuspType
|
||||
{
|
||||
None,
|
||||
Circular,
|
||||
Rectangular,
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
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,
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,178 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
<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>
|
@ -1,467 +0,0 @@
|
||||
using System.Diagnostics.Contracts;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A customizable immediate mode draw call queue, for the modern OpenGL user.
|
||||
/// </summary>
|
||||
/// <typeparam name="TCall">The call info type.</typeparam>
|
||||
/// <typeparam name="TVertex">The vertex structure.</typeparam>
|
||||
public abstract class DrawCallRecorder<TCall, TVertex> : IGLDisposable, IInitializer
|
||||
where TVertex : unmanaged
|
||||
{
|
||||
/// <summary>
|
||||
/// The vertex array for this queue.
|
||||
/// </summary>
|
||||
public int Vao { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The vertex buffer for this queue.
|
||||
/// </summary>
|
||||
public int Vbo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of calls recorded in this queue.
|
||||
/// </summary>
|
||||
public int CallCount => Calls.Count;
|
||||
|
||||
/// <summary>
|
||||
/// The number of total vertices recorded.
|
||||
/// </summary>
|
||||
public int TotalVertices => Vertices.Count;
|
||||
|
||||
/// <summary>
|
||||
/// The latest draw call info.
|
||||
/// </summary>
|
||||
public ref TCall CurrentCall => ref _currentCall;
|
||||
|
||||
/// <summary>
|
||||
/// The latest vertex emitted.
|
||||
/// </summary>
|
||||
public ref TVertex CurrentVertex => ref _currentVertex;
|
||||
|
||||
/// <summary>
|
||||
/// True if currently recording a draw call.
|
||||
/// </summary>
|
||||
public bool InCall => _primitiveMode != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Size of one vertex.
|
||||
/// </summary>
|
||||
protected int VertexSize => Unsafe.SizeOf<TVertex>();
|
||||
|
||||
/// <summary>
|
||||
/// The list of draw calls.
|
||||
/// </summary>
|
||||
protected List<DrawCall> Calls { get; } = new List<DrawCall>();
|
||||
|
||||
/// <summary>
|
||||
/// The list of all vertices.
|
||||
/// </summary>
|
||||
protected List<TVertex> Vertices { get; } = new List<TVertex>();
|
||||
|
||||
/// <summary>
|
||||
/// The value to write for the draw call info at the start of a call.
|
||||
/// </summary>
|
||||
[Pure] protected virtual TCall DefaultCall => default(TCall);
|
||||
|
||||
/// <summary>
|
||||
/// The value to write for last vertex at the start of a call.
|
||||
/// </summary>
|
||||
[Pure] protected virtual TVertex DefaultVertex => default;
|
||||
|
||||
private int _start = 0;
|
||||
private int _count = 0;
|
||||
private int _primitiveMode = 0;
|
||||
private TCall _currentCall;
|
||||
private TVertex _currentVertex;
|
||||
|
||||
protected DrawCallRecorder()
|
||||
{
|
||||
_currentCall = DefaultCall;
|
||||
_currentVertex = DefaultVertex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Record a draw call directly.
|
||||
/// </summary>
|
||||
/// <param name="type">The primitive type to use.</param>
|
||||
/// <param name="callInfo">The call info structure to use</param>
|
||||
/// <param name="vertices">The list of vertices to use.</param>
|
||||
/// <exception cref="InvalidOperationException">You attempted to use this function during another draw call.</exception>
|
||||
public void DrawArrays(PrimitiveType type, in TCall callInfo, ReadOnlySpan<TVertex> vertices)
|
||||
{
|
||||
if (InCall)
|
||||
throw new InvalidOperationException("Cannot use draw arrays in the middle of an ongoing immediate-mode call.");
|
||||
|
||||
DrawCall call = new DrawCall()
|
||||
{
|
||||
Type = type,
|
||||
Start = Vertices.Count,
|
||||
Count = vertices.Length,
|
||||
CallInfo = callInfo,
|
||||
};
|
||||
|
||||
Vertices.AddRange(vertices);
|
||||
Calls.Add(call);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start a draw call.
|
||||
/// </summary>
|
||||
/// <param name="type">The primitive type for the call.</param>
|
||||
public void Begin(PrimitiveType type) => Begin(type, DefaultCall);
|
||||
|
||||
/// <summary>
|
||||
/// Start a draw call.
|
||||
/// </summary>
|
||||
/// <param name="type">The primitive type for the call.</param>
|
||||
/// <param name="callInfo">The call info.</param>
|
||||
/// <exception cref="InvalidOperationException">You attempted to create a draw call within a draw call.</exception>
|
||||
public void Begin(PrimitiveType type, TCall callInfo)
|
||||
{
|
||||
if (InCall)
|
||||
throw new InvalidOperationException("Attempt to begin new draw call before finishing previous one.");
|
||||
|
||||
_primitiveMode = (int)type;
|
||||
_start = Vertices.Count;
|
||||
_count = 0;
|
||||
CurrentCall = callInfo;
|
||||
CurrentVertex = DefaultVertex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emit the latest or modified vertex.
|
||||
/// </summary>
|
||||
public void Vertex()
|
||||
{
|
||||
Vertex(CurrentVertex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emit a vertex.
|
||||
/// </summary>
|
||||
/// <param name="vertex">The vertex to emit.</param>
|
||||
public void Vertex(in TVertex vertex)
|
||||
{
|
||||
Vertices.Add(CurrentVertex = vertex);
|
||||
_count++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End the current call.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">You tried to end a call that you didn't begin recording.</exception>
|
||||
public void End()
|
||||
{
|
||||
if (!InCall)
|
||||
throw new InvalidOperationException("Attempt to end draw call before starting one.");
|
||||
|
||||
Calls.Add(new DrawCall()
|
||||
{
|
||||
Start = _start,
|
||||
Count = _count,
|
||||
Type = (PrimitiveType)_primitiveMode,
|
||||
CallInfo = CurrentCall,
|
||||
});
|
||||
|
||||
_primitiveMode = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by the execution engine before a draw call is executed.
|
||||
/// </summary>
|
||||
/// <param name="call">The call to prepare.</param>
|
||||
protected abstract void PrepareCall(in TCall call);
|
||||
|
||||
/// <summary>
|
||||
/// Set the vertex format for the <see cref="Vao"/> and <see cref="Vbo"/> created by the recorder.
|
||||
/// </summary>
|
||||
protected abstract void SetVertexFormat();
|
||||
|
||||
/// <summary>
|
||||
/// Execute all the recorded draw calls.
|
||||
/// </summary>
|
||||
public void Execute()
|
||||
{
|
||||
GL.BindVertexArray(Vao);
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, Vbo);
|
||||
|
||||
ReadOnlySpan<TVertex> vertices = CollectionsMarshal.AsSpan(Vertices);
|
||||
GL.BufferData(BufferTarget.ArrayBuffer, Vertices.Count * VertexSize, vertices, BufferUsage.DynamicDraw);
|
||||
|
||||
foreach (DrawCall call in Calls)
|
||||
{
|
||||
PrepareCall(call.CallInfo);
|
||||
GL.DrawArrays(call.Type, call.Start, call.Count);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the draw call queue.
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
SetVertexFormat();
|
||||
}
|
||||
|
||||
protected struct DrawCall
|
||||
{
|
||||
public PrimitiveType Type;
|
||||
public int Start;
|
||||
public int Count;
|
||||
public TCall CallInfo;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,332 +0,0 @@
|
||||
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)!;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,224 +0,0 @@
|
||||
#version 140
|
||||
|
||||
#define DB_GRADIENT_MAX 16
|
||||
#define DB_COMMAND_MAX 64
|
||||
|
||||
#define CMD_POINT 1
|
||||
#define CMD_LINE 2
|
||||
#define CMD_RECT 3
|
||||
|
||||
#define 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;
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
#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;
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension interface for GL contexts in a DPI-aware environment.
|
||||
/// </summary>
|
||||
public interface IDpiAwareGLContext : IGLContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Dpi for current context.
|
||||
/// </summary>
|
||||
public float Dpi { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Scale for the current context. This will be used to scale drawn geometry.
|
||||
/// </summary>
|
||||
public float Scale { get; }
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
namespace Dashboard.Drawing.OpenGL
|
||||
{
|
||||
public interface IInitializer
|
||||
{
|
||||
bool IsInitialized { get; }
|
||||
|
||||
void Initialize();
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
namespace Dashboard.Drawing.OpenGL
|
||||
{
|
||||
public interface IResourceManager
|
||||
{
|
||||
public string Name { get; }
|
||||
}
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Dashboard.Drawing
|
||||
{
|
||||
public class BrushExtension : DrawExtension
|
||||
{
|
||||
private BrushExtension() : base("DB_Brush") { }
|
||||
|
||||
public static readonly BrushExtension Instance = new BrushExtension();
|
||||
}
|
||||
|
||||
public interface IBrush : IDrawResource
|
||||
{
|
||||
}
|
||||
|
||||
public readonly struct SolidBrush(Color color) : IBrush
|
||||
{
|
||||
public IDrawExtension Kind { get; } = SolidBrushExtension.Instance;
|
||||
public Color Color { get; } = color;
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Kind, Color);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,292 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dashboard.Drawing
|
||||
{
|
||||
public class DbBaseCommands : DrawExtension
|
||||
{
|
||||
public DrawCommand<PointCommandArgs> DrawPoint { get; }
|
||||
public DrawCommand<LineCommandArgs> DrawLine { get; }
|
||||
public RectCommand DrawRectF { get; }
|
||||
public RectCommand DrawRectS { get; }
|
||||
public RectCommand DrawRectFS { get; }
|
||||
|
||||
private DbBaseCommands() : base("DB_base",
|
||||
new[]
|
||||
{
|
||||
BrushExtension.Instance,
|
||||
})
|
||||
{
|
||||
AddCommand(DrawPoint = new DrawCommand<PointCommandArgs>("Point", this, PointCommandArgs.CommandSize));
|
||||
AddCommand(DrawLine = new DrawCommand<LineCommandArgs>("Line", this, LineCommandArgs.CommandSize));
|
||||
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();
|
||||
}
|
||||
|
||||
public struct PointCommandArgs : IParameterSerializer<PointCommandArgs>
|
||||
{
|
||||
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(Vector2 position, float depth, float size, IBrush brush)
|
||||
{
|
||||
Position = position;
|
||||
Depth = depth;
|
||||
Brush = brush;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public int Serialize(DrawQueue queue, Span<byte> bytes)
|
||||
{
|
||||
if (bytes.Length < CommandSize)
|
||||
return CommandSize;
|
||||
|
||||
Span<Value> value = stackalloc Value[]
|
||||
{
|
||||
new Value(Position, Depth, Size, queue.RequireResource(Brush!))
|
||||
};
|
||||
|
||||
MemoryMarshal.AsBytes(value).CopyTo(bytes);
|
||||
return CommandSize;
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(Brush))]
|
||||
public void Deserialize(DrawQueue queue, ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
if (bytes.Length < CommandSize)
|
||||
throw new Exception("Not enough bytes");
|
||||
|
||||
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(Vector2 Position, float Depth, float Size, int BrushIndex);
|
||||
|
||||
public static readonly int CommandSize = Unsafe.SizeOf<Value>();
|
||||
}
|
||||
|
||||
public struct LineCommandArgs : IParameterSerializer<LineCommandArgs>
|
||||
{
|
||||
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(Vector2 start, Vector2 end, float depth, float size, IBrush brush)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
Depth = depth;
|
||||
Size = size;
|
||||
Brush = brush;
|
||||
}
|
||||
|
||||
public int Serialize(DrawQueue queue, Span<byte> bytes)
|
||||
{
|
||||
if (bytes.Length < CommandSize)
|
||||
return CommandSize;
|
||||
|
||||
Span<Value> value = stackalloc Value[]
|
||||
{
|
||||
new Value(Start, End, Depth, Size, queue.RequireResource(Brush!))
|
||||
};
|
||||
|
||||
MemoryMarshal.AsBytes(value).CopyTo(bytes);
|
||||
return CommandSize;
|
||||
}
|
||||
|
||||
public void Deserialize(DrawQueue queue, ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
if (bytes.Length < CommandSize)
|
||||
throw new Exception("Not enough bytes");
|
||||
|
||||
Value value = MemoryMarshal.AsRef<Value>(bytes);
|
||||
|
||||
Start = value.Start;
|
||||
End = value.End;
|
||||
Depth = value.Depth;
|
||||
Size = value.Size;
|
||||
Brush = (IBrush)queue.Resources[value.BrushIndex];
|
||||
}
|
||||
|
||||
private record struct Value(Vector2 Start, Vector2 End, float Depth, float Size, int BrushIndex);
|
||||
|
||||
public static readonly int CommandSize = Unsafe.SizeOf<Value>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class RectCommand : IDrawCommand<RectCommandArgs>
|
||||
{
|
||||
private readonly Mode _mode;
|
||||
public string Name { get; }
|
||||
public IDrawExtension Extension { get; }
|
||||
public int Length { get; }
|
||||
|
||||
public RectCommand(IDrawExtension extension, Mode mode)
|
||||
{
|
||||
Extension = extension;
|
||||
_mode = mode;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case Mode.Fill:
|
||||
Name = "RectF";
|
||||
Length = Unsafe.SizeOf<RectF>();
|
||||
break;
|
||||
case Mode.Strike:
|
||||
Name = "RectS";
|
||||
Length = Unsafe.SizeOf<RectS>();
|
||||
break;
|
||||
default:
|
||||
Name = "RectFS";
|
||||
Length = Unsafe.SizeOf<RectFS>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
object? IDrawCommand.GetParams(DrawQueue queue, ReadOnlySpan<byte> param)
|
||||
{
|
||||
return GetParams(queue, param);
|
||||
}
|
||||
|
||||
public RectCommandArgs GetParams(DrawQueue queue, ReadOnlySpan<byte> param)
|
||||
{
|
||||
if (param.Length < Length)
|
||||
throw new Exception("Not enough bytes");
|
||||
|
||||
RectCommandArgs args;
|
||||
|
||||
switch (_mode)
|
||||
{
|
||||
case Mode.Fill:
|
||||
ref readonly RectF f = ref MemoryMarshal.AsRef<RectF>(param);
|
||||
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, 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, fs.Depth, (IBrush)queue.Resources[fs.FillBrushIndex],
|
||||
(IBrush)queue.Resources[fs.StrikeBrushIndex], fs.StrikeSize, fs.BorderKind);
|
||||
break;
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
public int WriteParams(DrawQueue queue, object? obj, Span<byte> param)
|
||||
{
|
||||
return WriteParams(queue, (RectCommandArgs)obj, param);
|
||||
}
|
||||
|
||||
public int WriteParams(DrawQueue queue, RectCommandArgs obj, Span<byte> param)
|
||||
{
|
||||
if (param.Length < Length)
|
||||
return Length;
|
||||
|
||||
switch (_mode)
|
||||
{
|
||||
case Mode.Fill:
|
||||
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.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.BorderKind = obj.BorderKind;
|
||||
break;
|
||||
}
|
||||
|
||||
return Length;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum Mode
|
||||
{
|
||||
Fill = 1,
|
||||
Strike = 2,
|
||||
FillStrike = Fill | Strike,
|
||||
}
|
||||
|
||||
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 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(Vector2 start, Vector2 end, float depth, IBrush fillBrush)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
Depth = depth;
|
||||
FillBrush = fillBrush;
|
||||
}
|
||||
|
||||
public RectCommandArgs(Vector2 start, Vector2 end, float depth, IBrush strikeBrush, float strikeSize, BorderKind borderKind)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
Depth = depth;
|
||||
StrikeBrush = strikeBrush;
|
||||
StrikeSize = strikeSize;
|
||||
BorderKind = borderKind;
|
||||
}
|
||||
|
||||
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;
|
||||
BorderKind = borderKind;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Dashboard.Drawing
|
||||
{
|
||||
public interface IDrawCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the command.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The draw extension that defines this command.
|
||||
/// </summary>
|
||||
IDrawExtension Extension { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The length of the command data segment, in bytes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Must be 0 for simple commands. For commands that are variadic, the
|
||||
/// value must be less than 0. Any other positive value, otherwise.
|
||||
/// </remarks>
|
||||
int Length { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the parameters object for this command.
|
||||
/// </summary>
|
||||
/// <param name="param">The parameter array.</param>
|
||||
/// <returns>The parameters object.</returns>
|
||||
object? GetParams(DrawQueue queue, ReadOnlySpan<byte> param);
|
||||
|
||||
int WriteParams(DrawQueue queue, object? obj, Span<byte> param);
|
||||
}
|
||||
|
||||
public interface IDrawCommand<T> : IDrawCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the parameters object for this command.
|
||||
/// </summary>
|
||||
/// <param name="param">The parameter array.</param>
|
||||
/// <returns>The parameters object.</returns>
|
||||
new T? GetParams(DrawQueue queue, ReadOnlySpan<byte> param);
|
||||
|
||||
new int WriteParams(DrawQueue queue, T? obj, Span<byte> param);
|
||||
}
|
||||
|
||||
public sealed class DrawCommand : IDrawCommand
|
||||
{
|
||||
public string Name { get; }
|
||||
public IDrawExtension Extension { get; }
|
||||
public int Length { get; } = 0;
|
||||
|
||||
public DrawCommand(string name, IDrawExtension extension)
|
||||
{
|
||||
Name = name;
|
||||
Extension = extension;
|
||||
}
|
||||
|
||||
public object? GetParams(DrawQueue queue, ReadOnlySpan<byte> param)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public int WriteParams(DrawQueue queue, object? obj, Span<byte> param)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DrawCommand<T> : IDrawCommand<T>
|
||||
where T : IParameterSerializer<T>, new()
|
||||
{
|
||||
public string Name { get; }
|
||||
public IDrawExtension Extension { get; }
|
||||
public int Length { get; }
|
||||
|
||||
public DrawCommand(string name, IDrawExtension extension, int length)
|
||||
{
|
||||
Name = name;
|
||||
Extension = extension;
|
||||
Length = length;
|
||||
}
|
||||
|
||||
public T? GetParams(DrawQueue queue, ReadOnlySpan<byte> param)
|
||||
{
|
||||
T t = new T();
|
||||
t.Deserialize(queue, param);
|
||||
return t;
|
||||
}
|
||||
|
||||
public int WriteParams(DrawQueue queue, T? obj, Span<byte> param)
|
||||
{
|
||||
return obj!.Serialize(queue, param);
|
||||
}
|
||||
|
||||
int IDrawCommand.WriteParams(DrawQueue queue, object? obj, Span<byte> param)
|
||||
{
|
||||
return WriteParams(queue, (T?)obj, param);
|
||||
}
|
||||
|
||||
object? IDrawCommand.GetParams(DrawQueue queue, ReadOnlySpan<byte> param)
|
||||
{
|
||||
return GetParams(queue, param);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,139 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dashboard.Drawing
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for all drawing extensions.
|
||||
/// </summary>
|
||||
public interface IDrawExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of this extension.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
public IReadOnlyList<IDrawExtension> Requires { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of commands this extension defines, if any.
|
||||
/// </summary>
|
||||
public IReadOnlyList<IDrawCommand> Commands { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A simple draw extension.
|
||||
/// </summary>
|
||||
public class DrawExtension : IDrawExtension
|
||||
{
|
||||
private readonly List<IDrawCommand> _drawCommands = new List<IDrawCommand>();
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public IReadOnlyList<IDrawCommand> Commands { get; }
|
||||
|
||||
public IReadOnlyList<IDrawExtension> Requires { get; }
|
||||
|
||||
public DrawExtension(string name, IEnumerable<IDrawExtension>? requires = null)
|
||||
{
|
||||
Name = name;
|
||||
Commands = _drawCommands.AsReadOnly();
|
||||
Requires = (requires ?? Enumerable.Empty<IDrawExtension>()).ToImmutableList();
|
||||
}
|
||||
|
||||
protected void AddCommand(IDrawCommand command)
|
||||
{
|
||||
_drawCommands.Add(command);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DrawExtensionClass
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the draw controller for the given queue.
|
||||
/// </summary>
|
||||
/// <param name="extension">The extension instance.</param>
|
||||
/// <param name="queue">The draw queue.</param>
|
||||
/// <returns>The draw controller for this queue.</returns>
|
||||
public static IDrawController GetController(this IDrawExtension extension, DrawQueue queue)
|
||||
{
|
||||
return queue.GetController(extension);
|
||||
}
|
||||
|
||||
public static void Point(this DrawQueue queue, Vector2 position, float depth, float size, IBrush brush)
|
||||
{
|
||||
Vector2 radius = new Vector2(0.5f * size);
|
||||
Box2d bounds = new Box2d(position - radius, position + radius);
|
||||
|
||||
IDrawController controller = queue.GetController(DbBaseCommands.Instance);
|
||||
controller.EnsureBounds(bounds, depth);
|
||||
controller.Write(DbBaseCommands.Instance.DrawPoint, new PointCommandArgs(position, depth, size, brush));
|
||||
}
|
||||
|
||||
public static void Line(this DrawQueue queue, Vector2 start, Vector2 end, float depth, float size, IBrush brush)
|
||||
{
|
||||
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, depth);
|
||||
controller.Write(DbBaseCommands.Instance.DrawLine, new LineCommandArgs(start, end, depth, size, brush));
|
||||
}
|
||||
|
||||
public static void Rect(this DrawQueue queue, Vector2 start, Vector2 end, float depth, IBrush fillBrush)
|
||||
{
|
||||
IDrawController controller = queue.GetController(DbBaseCommands.Instance);
|
||||
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, Vector2 start, Vector2 end, float depth, IBrush strikeBrush, float strikeSize,
|
||||
BorderKind kind = BorderKind.Center)
|
||||
{
|
||||
IDrawController controller = queue.GetController(DbBaseCommands.Instance);
|
||||
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, Vector2 start, Vector2 end, float depth, IBrush fillBrush, IBrush strikeBrush,
|
||||
float strikeSize, BorderKind kind = BorderKind.Center)
|
||||
{
|
||||
IDrawController controller = queue.GetController(DbBaseCommands.Instance);
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,370 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Dashboard.Drawing
|
||||
{
|
||||
public class DrawQueue : IEnumerable<ICommandFrame>, IDisposable
|
||||
{
|
||||
private readonly HashList<IDrawExtension> _extensions = new HashList<IDrawExtension>();
|
||||
private readonly HashList<IDrawCommand> _commands = new HashList<IDrawCommand>();
|
||||
private readonly HashList<IDrawResource> _resources = new HashList<IDrawResource>();
|
||||
private readonly DrawController _controller;
|
||||
private readonly MemoryStream _commandStream = new MemoryStream();
|
||||
|
||||
/// <summary>
|
||||
/// The absolute boundary of all graphics objects.
|
||||
/// </summary>
|
||||
public Box3d Bounds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The extensions required to draw the image.
|
||||
/// </summary>
|
||||
public IReadOnlyList<IDrawExtension> Extensions => _extensions;
|
||||
|
||||
/// <summary>
|
||||
/// The resources used by this draw queue.
|
||||
/// </summary>
|
||||
public IReadOnlyList<IDrawResource> Resources => _resources;
|
||||
|
||||
/// <summary>
|
||||
/// The list of commands used by the extension.
|
||||
/// </summary>
|
||||
public IReadOnlyList<IDrawCommand> Command => _commands;
|
||||
|
||||
public DrawQueue()
|
||||
{
|
||||
_controller = new DrawController(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the queue.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_resources.Clear();
|
||||
_commands.Clear();
|
||||
_extensions.Clear();
|
||||
_commandStream.SetLength(0);
|
||||
}
|
||||
|
||||
public int RequireExtension(IDrawExtension extension)
|
||||
{
|
||||
foreach (IDrawExtension super in extension.Requires)
|
||||
RequireExtension(super);
|
||||
|
||||
return _extensions.Intern(extension);
|
||||
}
|
||||
|
||||
public int RequireResource(IDrawResource resource)
|
||||
{
|
||||
RequireExtension(resource.Kind);
|
||||
return _resources.Intern(resource);
|
||||
}
|
||||
|
||||
internal IDrawController GetController(IDrawExtension extension)
|
||||
{
|
||||
_extensions.Intern(extension);
|
||||
return _controller;
|
||||
}
|
||||
|
||||
private void Write(IDrawCommand command)
|
||||
{
|
||||
if (command.Length > 0)
|
||||
throw new InvalidOperationException("This command has a finite length argument.");
|
||||
|
||||
int cmdIndex = _commands.Intern(command);
|
||||
|
||||
Span<byte> cmd = stackalloc byte[6];
|
||||
int sz;
|
||||
|
||||
if (command.Length == 0)
|
||||
{
|
||||
// Write a fixed command.
|
||||
sz = ToVlq(cmdIndex, cmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Write a variadic with zero length.
|
||||
sz = ToVlq(cmdIndex, cmd);
|
||||
cmd[sz++] = 0;
|
||||
}
|
||||
|
||||
_commandStream.Write(cmd[..sz]);
|
||||
}
|
||||
|
||||
private void Write(IDrawCommand command, ReadOnlySpan<byte> param)
|
||||
{
|
||||
if (command.Length < 0)
|
||||
{
|
||||
Span<byte> cmd = stackalloc byte[10];
|
||||
int cmdIndex = _commands.Intern(command);
|
||||
int sz = ToVlq(cmdIndex, cmd);
|
||||
sz += ToVlq(param.Length, cmd[sz..]);
|
||||
_commandStream.Write(cmd[..sz]);
|
||||
_commandStream.Write(param);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (command.Length != param.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(param.Length), "Length of the parameter does not match the command.");
|
||||
|
||||
Span<byte> cmd = stackalloc byte[5];
|
||||
int cmdIndex = _commands.Intern(command);
|
||||
int sz = ToVlq(cmdIndex, cmd);
|
||||
|
||||
_commandStream.Write(cmd[..sz]);
|
||||
_commandStream.Write(param);
|
||||
}
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator() => new Enumerator(this);
|
||||
IEnumerator<ICommandFrame> IEnumerable<ICommandFrame>.GetEnumerator() => GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
private static int ToVlq(int value, Span<byte> bytes)
|
||||
{
|
||||
if (value < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "Must be a positive integer.");
|
||||
else if (bytes.Length < 5)
|
||||
throw new ArgumentOutOfRangeException(nameof(bytes), "Must at least be five bytes long.");
|
||||
|
||||
if (value == 0)
|
||||
{
|
||||
bytes[0] = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 5 && value != 0; i++, value >>= 7)
|
||||
{
|
||||
if (i > 0)
|
||||
bytes[i - 1] |= 1 << 7;
|
||||
|
||||
bytes[i] = (byte)(value & 0x7F);
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
private static int FromVlq(ReadOnlySpan<byte> bytes, out int value)
|
||||
{
|
||||
value = 0;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
byte b = bytes[i];
|
||||
|
||||
value = (value << 7) | b;
|
||||
|
||||
if ((b & (1 << 7)) == 0)
|
||||
{
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private class DrawController(DrawQueue Queue) : IDrawController
|
||||
{
|
||||
public void EnsureBounds(Box2d bounds, float depth)
|
||||
{
|
||||
Queue.Bounds = Box3d.Union(Queue.Bounds, bounds, depth);
|
||||
}
|
||||
|
||||
public void Write(IDrawCommand command)
|
||||
{
|
||||
Queue.Write(command);
|
||||
}
|
||||
|
||||
public void Write(IDrawCommand command, ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
Queue.Write(command, bytes);
|
||||
}
|
||||
|
||||
public void Write<T>(IDrawCommand command, T param) where T : IParameterSerializer<T>
|
||||
{
|
||||
int length = param.Serialize(Queue, Span<byte>.Empty);
|
||||
Span<byte> bytes = stackalloc byte[length];
|
||||
|
||||
param.Serialize(Queue, bytes);
|
||||
Write(command, bytes);
|
||||
}
|
||||
|
||||
public void Write<T1, T2>(T2 command, T1 param) where T2 : IDrawCommand<T1>
|
||||
{
|
||||
int length = command.WriteParams(Queue, param, Span<byte>.Empty);
|
||||
Span<byte> bytes = stackalloc byte[length];
|
||||
|
||||
command.WriteParams(Queue, param, bytes);
|
||||
Write(command, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
public class Enumerator : ICommandFrame, IEnumerator<ICommandFrame>
|
||||
{
|
||||
private readonly DrawQueue _queue;
|
||||
private readonly byte[] _stream;
|
||||
private int _length;
|
||||
private int _index = -1;
|
||||
private int _paramsIndex = -1;
|
||||
private int _paramLength = 0;
|
||||
private IDrawCommand? _current = null;
|
||||
|
||||
public ICommandFrame Current => this;
|
||||
|
||||
object? IEnumerator.Current => Current;
|
||||
|
||||
public IDrawCommand Command => _current ?? throw new InvalidOperationException();
|
||||
|
||||
public bool HasParameters { get; private set; }
|
||||
|
||||
public Enumerator(DrawQueue queue)
|
||||
{
|
||||
_queue = queue;
|
||||
_stream = queue._commandStream.GetBuffer();
|
||||
_length = (int)queue._commandStream.Length;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_index == -1)
|
||||
_index = 0;
|
||||
|
||||
if (_index >= _length)
|
||||
return false;
|
||||
|
||||
|
||||
_index += FromVlq(_stream[_index .. (_index + 5)], out int command);
|
||||
_current = _queue.Command[command];
|
||||
|
||||
HasParameters = _current.Length != 0;
|
||||
|
||||
if (!HasParameters)
|
||||
{
|
||||
_paramsIndex = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
int length;
|
||||
if (_current.Length < 0)
|
||||
{
|
||||
_index += FromVlq(_stream[_index .. (_index + 5)], out length);
|
||||
}
|
||||
else
|
||||
{
|
||||
length = _current.Length;
|
||||
}
|
||||
|
||||
_paramsIndex = _index;
|
||||
_paramLength = length;
|
||||
_index += length;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_index = -1;
|
||||
_current = null;
|
||||
}
|
||||
|
||||
public object? GetParameter()
|
||||
{
|
||||
return _current?.GetParams(_queue, _stream.AsSpan(_paramsIndex, _paramLength));
|
||||
}
|
||||
|
||||
public T GetParameter<T>()
|
||||
{
|
||||
if (_current is IDrawCommand<T> command)
|
||||
{
|
||||
return command.GetParams(_queue, _stream.AsSpan(_paramsIndex, _paramLength))!;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetParameter<T>([NotNullWhen(true)] out T? parameter)
|
||||
{
|
||||
if (_current is IDrawCommand<T> command)
|
||||
{
|
||||
parameter = command.GetParams(_queue, _stream.AsSpan(_paramsIndex, _paramLength))!;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
parameter = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface ICommandFrame
|
||||
{
|
||||
public IDrawCommand Command { get; }
|
||||
|
||||
public bool HasParameters { get; }
|
||||
|
||||
public object? GetParameter();
|
||||
|
||||
public T GetParameter<T>();
|
||||
|
||||
public bool TryGetParameter<T>([NotNullWhen(true)] out T? parameter);
|
||||
}
|
||||
|
||||
|
||||
public interface IDrawController
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures that the canvas is at least a certain size.
|
||||
/// </summary>
|
||||
/// <param name="bounds">The bounding box.</param>
|
||||
void EnsureBounds(Box2d bounds, float depth);
|
||||
|
||||
/// <summary>
|
||||
/// Write into the command stream.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to write.</param>
|
||||
void Write(IDrawCommand command);
|
||||
|
||||
/// <summary>
|
||||
/// Write into the command stream.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to write.</param>
|
||||
/// <param name="param">Any data associated with the command.</param>
|
||||
void Write(IDrawCommand command, ReadOnlySpan<byte> param);
|
||||
|
||||
/// <summary>
|
||||
/// Write into the command stream.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to write.</param>
|
||||
/// <param name="param">Any data associated with the command.</param>
|
||||
void Write<T>(IDrawCommand command, T param) where T : IParameterSerializer<T>;
|
||||
|
||||
/// <summary>
|
||||
/// Write into the command stream.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to write.</param>
|
||||
/// <param name="param">Any data associated with the command.</param>
|
||||
void Write<T1, T2>(T2 command, T1 param) where T2 : IDrawCommand<T1>;
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
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; }
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
namespace Dashboard.Drawing
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for draw resources.
|
||||
/// </summary>
|
||||
public interface IDrawResource
|
||||
{
|
||||
/// <summary>
|
||||
/// The extension for this kind of resource.
|
||||
/// </summary>
|
||||
IDrawExtension Kind { get; }
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Dashboard.Drawing
|
||||
{
|
||||
public interface IParameterSerializer<T>
|
||||
{
|
||||
int Serialize(DrawQueue queue, Span<byte> bytes);
|
||||
void Deserialize(DrawQueue queue, ReadOnlySpan<byte> bytes);
|
||||
}
|
||||
}
|
@ -1,140 +0,0 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dashboard.Drawing
|
||||
{
|
||||
public class TextExtension : DrawExtension
|
||||
{
|
||||
public TextCommand TextCommand { get; }
|
||||
|
||||
private TextExtension() : base("DB_Text", new [] { FontExtension.Instance, BrushExtension.Instance })
|
||||
{
|
||||
TextCommand = new TextCommand(this);
|
||||
}
|
||||
|
||||
public static readonly TextExtension Instance = new TextExtension();
|
||||
}
|
||||
|
||||
public class TextCommand : IDrawCommand<TextCommandArgs>
|
||||
{
|
||||
public string Name { get; } = "Text";
|
||||
public IDrawExtension Extension { get; }
|
||||
public int Length { get; } = -1;
|
||||
|
||||
public TextCommand(TextExtension ext)
|
||||
{
|
||||
Extension = ext;
|
||||
}
|
||||
|
||||
public int WriteParams(DrawQueue queue, TextCommandArgs obj, Span<byte> param)
|
||||
{
|
||||
int size = Unsafe.SizeOf<Header>() + obj.Text.Length * sizeof(char) + 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;
|
||||
}
|
||||
}
|
19
Dashboard.Media.Defaults/Dashboard.Media.Defaults.csproj
Normal file
19
Dashboard.Media.Defaults/Dashboard.Media.Defaults.csproj
Normal file
@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LanguageVersion>7.3</LanguageVersion>
|
||||
<Nullable>disable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ReFuel.FreeType" Version="0.1.0-rc.5" />
|
||||
<PackageReference Include="ReFuel.StbImage" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Dashboard\Dashboard.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
13
Dashboard.Media.Defaults/EnvironmentVariables.cs
Normal file
13
Dashboard.Media.Defaults/EnvironmentVariables.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace Dashboard.Media.Defaults
|
||||
{
|
||||
internal static class EnvironmentVariables
|
||||
{
|
||||
public const string SerifFont = "QUIK_SERIF_FONT";
|
||||
public const string SansFont = "QUIK_SANS_FONT";
|
||||
public const string MonospaceFont = "QUIK_MONOSPACE_FONT";
|
||||
public const string CursiveFont = "QUIK_CURSIVE_FONT";
|
||||
public const string FantasyFont = "QUIK_FANTASY_FONT";
|
||||
|
||||
public const string FallbackFontDatabase = "QUIK_FALLBACK_FONT_DB";
|
||||
}
|
||||
}
|
16
Dashboard.Media.Defaults/FTProvider.cs
Normal file
16
Dashboard.Media.Defaults/FTProvider.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using ReFuel.FreeType;
|
||||
|
||||
namespace Dashboard.Media.Defaults
|
||||
{
|
||||
public static class FTProvider
|
||||
{
|
||||
private static FTLibrary _ft;
|
||||
public static FTLibrary Ft => _ft;
|
||||
|
||||
static FTProvider()
|
||||
{
|
||||
FT.InitFreeType(out _ft);
|
||||
}
|
||||
}
|
||||
}
|
269
Dashboard.Media.Defaults/Fallback/FallbackFontDatabase.cs
Normal file
269
Dashboard.Media.Defaults/Fallback/FallbackFontDatabase.cs
Normal file
@ -0,0 +1,269 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json;
|
||||
using ReFuel.FreeType;
|
||||
using Dashboard.Media.Font;
|
||||
using Dashboard.PAL;
|
||||
using Dashboard.Media.Defaults.Linux;
|
||||
|
||||
namespace Dashboard.Media.Defaults.Fallback
|
||||
{
|
||||
public class FallbackFontDatabase : IFontDataBase
|
||||
{
|
||||
private readonly string DbPath =
|
||||
Environment.GetEnvironmentVariable(EnvironmentVariables.FallbackFontDatabase) ??
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "QUIK/fontdb.json");
|
||||
|
||||
private Dictionary<FontFace, FileInfo> FilesMap { get; } = new Dictionary<FontFace, FileInfo>();
|
||||
private Dictionary<string, List<FontFace>> ByFamily { get; } = new Dictionary<string, List<FontFace>>();
|
||||
private Dictionary<SystemFontFamily, FontFace> SystemFonts { get; } = new Dictionary<SystemFontFamily, FontFace>();
|
||||
private List<FontFace> All { get; } = new List<FontFace>();
|
||||
|
||||
IEnumerable<FontFace> IFontDataBase.All => this.All;
|
||||
|
||||
public FallbackFontDatabase(bool rebuild = false)
|
||||
{
|
||||
// Load existing database if desired.
|
||||
List<DbEntry> database;
|
||||
|
||||
if(!rebuild)
|
||||
{
|
||||
database = LoadDatabase();
|
||||
}
|
||||
else
|
||||
{
|
||||
database = new List<DbEntry>();
|
||||
}
|
||||
|
||||
VerifyDatabase(database);
|
||||
FlushDatabase(database);
|
||||
|
||||
database.ForEach(x => AddFont(x.Face, new FileInfo(x.FilePath)));
|
||||
|
||||
(FontFace, FileInfo) serif = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.SerifFont, LinuxFonts.DefaultSerifFamilies, this);
|
||||
SystemFonts[SystemFontFamily.Serif] = serif.Item1;
|
||||
(FontFace, FileInfo) sans = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.SansFont, LinuxFonts.DefaultSansFamilies, this);
|
||||
SystemFonts[SystemFontFamily.Sans] = sans.Item1;
|
||||
(FontFace, FileInfo) mono = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.MonospaceFont, LinuxFonts.DefaultMonospaceFamilies, this);
|
||||
SystemFonts[SystemFontFamily.Monospace] = mono.Item1;
|
||||
(FontFace, FileInfo) cursive = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.CursiveFont, LinuxFonts.DefaultCursiveFamilies, this);
|
||||
SystemFonts[SystemFontFamily.Cursive] = cursive.Item1;
|
||||
(FontFace, FileInfo) fantasy = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.FantasyFont, LinuxFonts.DefaultFantasyFamilies, this);
|
||||
SystemFonts[SystemFontFamily.Fantasy] = fantasy.Item1;
|
||||
}
|
||||
|
||||
public FileInfo FontFileInfo(FontFace face)
|
||||
{
|
||||
if (FilesMap.TryGetValue(face, out FileInfo info))
|
||||
return info;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public Stream Open(FontFace face)
|
||||
{
|
||||
return FontFileInfo(face)?.OpenRead() ?? throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
public IEnumerable<FontFace> Search(FontFace prototype, FontMatchCriteria criteria = FontMatchCriteria.All)
|
||||
{
|
||||
// A bit scuffed and LINQ heavy but it should work.
|
||||
IEnumerable<FontFace> candidates;
|
||||
|
||||
if (criteria.HasFlag(FontMatchCriteria.Family))
|
||||
{
|
||||
List<FontFace> siblings;
|
||||
|
||||
if (!ByFamily.TryGetValue(prototype.Family, out siblings))
|
||||
{
|
||||
return Enumerable.Empty<FontFace>();
|
||||
}
|
||||
|
||||
candidates = siblings;
|
||||
}
|
||||
else
|
||||
{
|
||||
candidates = All;
|
||||
}
|
||||
|
||||
return
|
||||
candidates
|
||||
.Where(x =>
|
||||
implies(criteria.HasFlag(FontMatchCriteria.Slant), prototype.Slant == x.Slant) ||
|
||||
implies(criteria.HasFlag(FontMatchCriteria.Weight), prototype.Weight == x.Weight) ||
|
||||
implies(criteria.HasFlag(FontMatchCriteria.Stretch), prototype.Stretch == x.Stretch)
|
||||
)
|
||||
.OrderByDescending(x =>
|
||||
|
||||
(prototype.Slant == x.Slant ? 1 : 0) +
|
||||
(prototype.Weight == x.Weight ? 1 : 0) +
|
||||
(prototype.Stretch == x.Stretch ? 1 : 0) +
|
||||
confidence(prototype.Family, x.Family) * 3
|
||||
);
|
||||
|
||||
// a => b = a'+b
|
||||
static bool implies(bool a, bool b)
|
||||
{
|
||||
return !a || b;
|
||||
}
|
||||
|
||||
static int confidence(string target, string testee)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < target.Length && i < testee.Length && target[i] == testee[i]; i++);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
public FontFace GetSystemFontFace(SystemFontFamily family)
|
||||
{
|
||||
return SystemFonts[family];
|
||||
}
|
||||
|
||||
private void AddFont(FontFace face, FileInfo file)
|
||||
{
|
||||
if (!All.Contains(face))
|
||||
All.Add(face);
|
||||
|
||||
FilesMap.TryAdd(face, file);
|
||||
|
||||
if (!ByFamily.TryGetValue(face.Family, out List<FontFace> siblings))
|
||||
{
|
||||
siblings = new List<FontFace>();
|
||||
ByFamily.Add(face.Family, siblings);
|
||||
}
|
||||
|
||||
if (!siblings.Contains(face))
|
||||
siblings.Add(face);
|
||||
}
|
||||
|
||||
private List<DbEntry> LoadDatabase()
|
||||
{
|
||||
FileInfo info = new FileInfo(DbPath);
|
||||
|
||||
if (!info.Exists)
|
||||
return new List<DbEntry>();
|
||||
|
||||
using Stream str = info.OpenRead();
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<List<DbEntry>>(str);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new List<DbEntry>();
|
||||
}
|
||||
}
|
||||
|
||||
private void VerifyDatabase(List<DbEntry> db)
|
||||
{
|
||||
// Very slow way to do this but how many fonts could a system have on average?
|
||||
Dictionary<string, DbEntry> entires = new Dictionary<string, DbEntry>();
|
||||
|
||||
foreach (DbEntry entry in db)
|
||||
{
|
||||
FileInfo info = new FileInfo(entry.FilePath);
|
||||
|
||||
// Reprocess fonts that appear like this.
|
||||
if (!info.Exists) continue;
|
||||
else if (info.LastWriteTime > entry.AccessTime) continue;
|
||||
}
|
||||
|
||||
string fontpath = null;
|
||||
try
|
||||
{
|
||||
fontpath = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
|
||||
if (string.IsNullOrEmpty(fontpath))
|
||||
throw new Exception();
|
||||
}
|
||||
catch
|
||||
{
|
||||
foreach (string path in FontPaths)
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
fontpath = path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// rip
|
||||
if (string.IsNullOrEmpty(fontpath))
|
||||
return;
|
||||
}
|
||||
|
||||
SearchPathForFonts(entires, fontpath);
|
||||
|
||||
db.Clear();
|
||||
db.AddRange(entires.Values);
|
||||
}
|
||||
|
||||
private static void SearchPathForFonts(Dictionary<string, DbEntry> entries, string path)
|
||||
{
|
||||
DirectoryInfo dir = new DirectoryInfo(path);
|
||||
|
||||
foreach (FileInfo file in dir.EnumerateFiles())
|
||||
{
|
||||
SearchFileForFonts(entries, file);
|
||||
}
|
||||
|
||||
foreach (DirectoryInfo directory in dir.EnumerateDirectories())
|
||||
{
|
||||
SearchPathForFonts(entries, directory.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SearchFileForFonts(Dictionary<string, DbEntry> entries, FileInfo file)
|
||||
{
|
||||
if (entries.ContainsKey(file.FullName))
|
||||
return;
|
||||
|
||||
if (FT.NewFace(FTProvider.Ft, file.FullName, 0, out FTFace face) != FTError.None)
|
||||
return;
|
||||
|
||||
FontFace facename = FontFace.Parse(face.FamilyName, face.StyleName);
|
||||
|
||||
DbEntry entry = new DbEntry(facename, file.FullName);
|
||||
entries.Add(file.FullName, entry);
|
||||
FT.DoneFace(face);
|
||||
}
|
||||
|
||||
private void FlushDatabase(List<DbEntry> db)
|
||||
{
|
||||
FileInfo info = new FileInfo(DbPath);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(DbPath));
|
||||
using Stream str = info.Open(FileMode.Create);
|
||||
JsonSerializer.Serialize(str, db);
|
||||
}
|
||||
|
||||
private static readonly string[] FontPaths = new string[] {
|
||||
"/usr/share/fonts",
|
||||
};
|
||||
|
||||
[JsonSerializable(typeof(DbEntry))]
|
||||
private class DbEntry
|
||||
{
|
||||
[JsonIgnore] public FontFace Face => new FontFace(Family, Slant, Weight, Stretch);
|
||||
public string Family { get; set; }
|
||||
public FontSlant Slant { get; set; }
|
||||
public FontWeight Weight { get; set; }
|
||||
public FontStretch Stretch { get; set; }
|
||||
public string FilePath { get; set; }
|
||||
public DateTime AccessTime { get; set; }
|
||||
|
||||
public DbEntry() {}
|
||||
public DbEntry(FontFace face, string path)
|
||||
{
|
||||
Family = face.Family;
|
||||
Slant = face.Slant;
|
||||
Weight = face.Weight;
|
||||
Stretch = face.Stretch;
|
||||
FilePath = path;
|
||||
AccessTime = DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
93
Dashboard.Media.Defaults/FontDatabaseProvider.cs
Normal file
93
Dashboard.Media.Defaults/FontDatabaseProvider.cs
Normal file
@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using ReFuel.FreeType;
|
||||
using Dashboard.Media.Defaults.Fallback;
|
||||
using Dashboard.Media.Defaults.Linux;
|
||||
using Dashboard.Media.Font;
|
||||
using Dashboard.PAL;
|
||||
|
||||
namespace Dashboard.Media.Defaults
|
||||
{
|
||||
public static class FontDataBaseProvider
|
||||
{
|
||||
public static IFontDataBase Instance { get; }
|
||||
|
||||
static FontDataBaseProvider()
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: add as other operating systems are supported.
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
Instance = new FontConfigFontDatabase();
|
||||
}
|
||||
else
|
||||
{
|
||||
Instance = new FallbackFontDatabase();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new NotSupportedException("Could not load a suitable font database implementation.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static (FontFace, FileInfo) ResolveSystemFont(string envVar, string defaults, IFontDataBase db)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
string user = Environment.GetEnvironmentVariable(envVar);
|
||||
if (user != null)
|
||||
{
|
||||
builder.Append(user);
|
||||
builder.Append(':');
|
||||
}
|
||||
|
||||
builder.Append(defaults);
|
||||
|
||||
string[] list = builder.ToString().Split(':');
|
||||
|
||||
foreach (string item in list)
|
||||
{
|
||||
if (File.Exists(item))
|
||||
{
|
||||
// Process file.
|
||||
if (FT.NewFace(FTProvider.Ft, item, 0, out FTFace ftface) != FTError.None)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FontFace face = FontFace.Parse(ftface.FamilyName, ftface.StyleName);
|
||||
FT.DoneFace(ftface);
|
||||
|
||||
return (face, new FileInfo(item));
|
||||
}
|
||||
else
|
||||
{
|
||||
IEnumerable<FontFace> faces = db.Search(
|
||||
new FontFace(item, FontSlant.Normal, FontWeight.Normal, FontStretch.Normal),
|
||||
FontMatchCriteria.Family);
|
||||
|
||||
if (faces.Any())
|
||||
{
|
||||
FontFace face = faces.First();
|
||||
return (face, db.FontFileInfo(face));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
FontFace face = db.GetSystemFontFace(SystemFontFamily.Sans);
|
||||
return (face, db.FontFileInfo(face));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new NotImplementedException("No fallback font yet.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
24
Dashboard.Media.Defaults/FreeTypeFontFactory.cs
Normal file
24
Dashboard.Media.Defaults/FreeTypeFontFactory.cs
Normal file
@ -0,0 +1,24 @@
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using Dashboard.PAL;
|
||||
|
||||
namespace Dashboard.Media.Defaults
|
||||
{
|
||||
public class FreeTypeFontFactory : IFontFactory
|
||||
{
|
||||
public bool TryOpen(Stream stream, [NotNullWhen(true)] out QFont font)
|
||||
{
|
||||
try
|
||||
{
|
||||
font = new QFontFreeType(stream);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
font = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
230
Dashboard.Media.Defaults/Linux/FontConfig.cs
Normal file
230
Dashboard.Media.Defaults/Linux/FontConfig.cs
Normal file
@ -0,0 +1,230 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using Dashboard;
|
||||
|
||||
namespace Dashboard.Media.Defaults
|
||||
{
|
||||
public static unsafe class FontConfig
|
||||
{
|
||||
private const string fontconfig = "fontconfig";
|
||||
|
||||
public static bool Exists { get; }
|
||||
|
||||
public static IntPtr FAMILY { get; } = Marshal.StringToHGlobalAnsi("family");
|
||||
public static IntPtr STYLE { get; } = Marshal.StringToHGlobalAnsi("style");
|
||||
public static IntPtr FILE { get; } = Marshal.StringToHGlobalAnsi("file");
|
||||
public static IntPtr WEIGHT { get; } = Marshal.StringToHGlobalAnsi("weight");
|
||||
public static IntPtr SLANT { get; } = Marshal.StringToHGlobalAnsi("slant");
|
||||
|
||||
|
||||
static FontConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (FcInitLoadConfigAndFonts() == null)
|
||||
{
|
||||
Exists = false;
|
||||
}
|
||||
Exists = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Exists = false;
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport(fontconfig, EntryPoint = "FcInitLoadConfigAndFonts")]
|
||||
public static extern FcConfig* FcInitLoadConfigAndFonts();
|
||||
|
||||
[DllImport(fontconfig, EntryPoint = "FcConfigGetCurrent")]
|
||||
public static extern FcConfig ConfigGetCurrent();
|
||||
|
||||
[DllImport(fontconfig, EntryPoint = "FcPatternCreate")]
|
||||
public static extern FcPattern PatternCreate();
|
||||
|
||||
[DllImport(fontconfig, EntryPoint = "FcPatternCreate")]
|
||||
public static extern bool FcPatternAdd(FcPattern pattern, IntPtr what, FcValue value, bool append);
|
||||
|
||||
[DllImport(fontconfig, EntryPoint = "FcObjectSetBuild", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern FcObjectSet ObjectSetBuild(IntPtr i1, IntPtr i2, IntPtr i3, IntPtr i4, IntPtr i5, IntPtr i6);
|
||||
|
||||
public static FcObjectSet ObjectSetBuild(IntPtr i1)
|
||||
{
|
||||
return ObjectSetBuild(i1, IntPtr.Zero);
|
||||
}
|
||||
|
||||
public static FcObjectSet ObjectSetBuild(IntPtr i1, IntPtr i2)
|
||||
{
|
||||
return ObjectSetBuild(i1, i2, IntPtr.Zero);
|
||||
}
|
||||
public static FcObjectSet ObjectSetBuild(IntPtr i1, IntPtr i2, IntPtr i3)
|
||||
{
|
||||
return ObjectSetBuild(i1, i2, i3, IntPtr.Zero);
|
||||
}
|
||||
public static FcObjectSet ObjectSetBuild(IntPtr i1, IntPtr i2, IntPtr i3, IntPtr i4)
|
||||
{
|
||||
return ObjectSetBuild(i1, i2, i3, i4, IntPtr.Zero);
|
||||
}
|
||||
public static FcObjectSet ObjectSetBuild(IntPtr i1, IntPtr i2, IntPtr i3, IntPtr i4, IntPtr i5)
|
||||
{
|
||||
return ObjectSetBuild(i1, i2, i3, i4, i5, IntPtr.Zero);
|
||||
}
|
||||
|
||||
[DllImport(fontconfig, EntryPoint = "FcFontList")]
|
||||
public static extern FcFontSet FontList(FcConfig config, FcPattern pattern, FcObjectSet objectSet);
|
||||
|
||||
[DllImport(fontconfig, EntryPoint = "FcNameUnparse")]
|
||||
public static extern IntPtr NameUnparse(FcPattern pat);
|
||||
|
||||
public static string NameUnparseStr(FcPattern pat) => Marshal.PtrToStringAnsi(NameUnparse(pat));
|
||||
|
||||
[DllImport(fontconfig, EntryPoint = "FcPatternGetString")]
|
||||
public static extern FcResult PatternGetString(FcPattern p, IntPtr what, int n, out IntPtr val);
|
||||
|
||||
public static FcResult PatternGetString(FcPattern p, IntPtr what, out string str)
|
||||
{
|
||||
FcResult i = PatternGetString(p, what, 0, out IntPtr ptr);
|
||||
|
||||
if (i == FcResult.Match)
|
||||
{
|
||||
str = Marshal.PtrToStringAnsi(ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
str = null;
|
||||
}
|
||||
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
[DllImport(fontconfig, EntryPoint = "FcPatternGet")]
|
||||
public static extern FcResult PatternGet(FcPattern p, IntPtr what, int id, out FcValue value);
|
||||
|
||||
[DllImport(fontconfig, EntryPoint = "FcFontSetDestroy")]
|
||||
public static extern void FontSetDestroy(FcFontSet fs);
|
||||
|
||||
[DllImport(fontconfig, EntryPoint = "FcObjectSetDestroy")]
|
||||
public static extern void ObjectSetDestroy (FcObjectSet os);
|
||||
|
||||
[DllImport(fontconfig, EntryPoint = "FcConfigDestroy")]
|
||||
public static extern void ConfigDestroy (FcConfig cfg);
|
||||
|
||||
[DllImport(fontconfig, EntryPoint = "FcPatternDestroy")]
|
||||
public static extern void PatternDestroy (FcPattern os);
|
||||
|
||||
#region Range
|
||||
|
||||
[DllImport(fontconfig, EntryPoint = "FcRangeCreateDouble")]
|
||||
public static extern IntPtr RangeCreateDouble(double begin, double end);
|
||||
|
||||
[DllImport(fontconfig, EntryPoint = "FcRangeCreateInteger")]
|
||||
public static extern IntPtr RangeCreateInteger (int begin, int end);
|
||||
|
||||
[DllImport(fontconfig, EntryPoint = "FcRangeDestroy")]
|
||||
public static extern void RangeDestroy(IntPtr range);
|
||||
|
||||
[DllImport(fontconfig, EntryPoint = "FcRangeCopy")]
|
||||
public static extern IntPtr RangeCopy (IntPtr range);
|
||||
|
||||
[DllImport(fontconfig, EntryPoint = "FcRangeGetDouble")]
|
||||
public static extern bool RangeGetDouble(IntPtr range, out double start, out double end);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public enum FcResult
|
||||
{
|
||||
Match,
|
||||
NoMatch,
|
||||
TypeMismatch,
|
||||
NoId,
|
||||
OutOfMemory
|
||||
}
|
||||
|
||||
public struct FcConfig
|
||||
{
|
||||
public readonly IntPtr Handle;
|
||||
}
|
||||
|
||||
public struct FcPattern
|
||||
{
|
||||
public readonly IntPtr Handle;
|
||||
}
|
||||
|
||||
public unsafe struct FcObjectSet
|
||||
{
|
||||
public readonly IntPtr Handle;
|
||||
|
||||
private Accessor* AsPtr => (Accessor*)Handle;
|
||||
|
||||
public int NObject => AsPtr->nobject;
|
||||
|
||||
public int SObject => AsPtr->sobject;
|
||||
|
||||
#pragma warning disable CS0649 // Will always have default value.
|
||||
private struct Accessor
|
||||
{
|
||||
public int nobject;
|
||||
public int sobject;
|
||||
public byte** objects;
|
||||
}
|
||||
#pragma warning restore CS0649
|
||||
}
|
||||
|
||||
public unsafe struct FcFontSet
|
||||
{
|
||||
public readonly IntPtr Handle;
|
||||
private Accessor* AsPtr => (Accessor*)Handle;
|
||||
|
||||
public int NFont => AsPtr->nfont;
|
||||
public int SFont => AsPtr->sfont;
|
||||
|
||||
public FcPattern this[int i]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (i < 0 || i >= NFont)
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
return AsPtr->fonts[i];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable CS0649 // Will always have default value.
|
||||
private struct Accessor
|
||||
{
|
||||
public int nfont;
|
||||
public int sfont;
|
||||
public FcPattern* fonts;
|
||||
}
|
||||
#pragma warning restore CS0649
|
||||
}
|
||||
|
||||
public enum FcType
|
||||
{
|
||||
Unknown = -1,
|
||||
Void,
|
||||
Integer,
|
||||
Double,
|
||||
String,
|
||||
Bool,
|
||||
Matrix,
|
||||
CharSet,
|
||||
FTFace,
|
||||
LangSet,
|
||||
Range
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct FcValue
|
||||
{
|
||||
[FieldOffset(0)] public readonly FcType Type;
|
||||
[FieldOffset(sizeof(FcType))] public readonly IntPtr Pointer;
|
||||
[FieldOffset(sizeof(FcType))] public readonly int Int;
|
||||
[FieldOffset(sizeof(FcType))] public readonly double Double;
|
||||
}
|
||||
}
|
168
Dashboard.Media.Defaults/Linux/FontConfigFontDatabase.cs
Normal file
168
Dashboard.Media.Defaults/Linux/FontConfigFontDatabase.cs
Normal file
@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using ReFuel.FreeType;
|
||||
using Dashboard.Media.Font;
|
||||
using Dashboard.PAL;
|
||||
|
||||
namespace Dashboard.Media.Defaults.Linux
|
||||
{
|
||||
/// <summary>
|
||||
/// Font database for Linux libfontconfig systems.
|
||||
/// </summary>
|
||||
public class FontConfigFontDatabase : IFontDataBase
|
||||
{
|
||||
private Dictionary<FontFace, FileInfo> FilesMap { get; } = new Dictionary<FontFace, FileInfo>();
|
||||
private Dictionary<string, List<FontFace>> ByFamily { get; } = new Dictionary<string, List<FontFace>>();
|
||||
private Dictionary<SystemFontFamily, FontFace> SystemFonts { get; } = new Dictionary<SystemFontFamily, FontFace>();
|
||||
private List<FontFace> All { get; } = new List<FontFace>();
|
||||
|
||||
IEnumerable<FontFace> IFontDataBase.All => this.All;
|
||||
|
||||
public FontConfigFontDatabase()
|
||||
{
|
||||
if (!FontConfig.Exists)
|
||||
{
|
||||
throw new NotSupportedException("This host doesn't have fontconfig installed.");
|
||||
}
|
||||
|
||||
FcConfig config = FontConfig.ConfigGetCurrent();
|
||||
FcPattern pattern = FontConfig.PatternCreate();
|
||||
FcObjectSet os = FontConfig.ObjectSetBuild(FontConfig.FAMILY, FontConfig.STYLE, FontConfig.FILE);
|
||||
FcFontSet fs = FontConfig.FontList(config, pattern, os);
|
||||
|
||||
for (int i = 0; i < fs.NFont; i++)
|
||||
{
|
||||
FcPattern current = fs[i];
|
||||
|
||||
if (
|
||||
FontConfig.PatternGetString(current, FontConfig.FAMILY, 0, out IntPtr pFamily) != FcResult.Match ||
|
||||
FontConfig.PatternGetString(current, FontConfig.STYLE, 0, out IntPtr pStyle) != FcResult.Match)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string family = Marshal.PtrToStringUTF8(pFamily);
|
||||
string style = Marshal.PtrToStringUTF8(pStyle);
|
||||
|
||||
FontFace face = FontFace.Parse(family, style);
|
||||
|
||||
FontConfig.PatternGetString(current, FontConfig.FILE, 0, out IntPtr pFile);
|
||||
string file = Marshal.PtrToStringAnsi(pFile);
|
||||
|
||||
AddFont(face, new FileInfo(file));
|
||||
}
|
||||
|
||||
FontConfig.FontSetDestroy(fs);
|
||||
FontConfig.ObjectSetDestroy(os);
|
||||
FontConfig.PatternDestroy(pattern);
|
||||
|
||||
(FontFace, FileInfo) serif = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.SerifFont, LinuxFonts.DefaultSerifFamilies, this);
|
||||
SystemFonts[SystemFontFamily.Serif] = serif.Item1;
|
||||
(FontFace, FileInfo) sans = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.SansFont, LinuxFonts.DefaultSansFamilies, this);
|
||||
SystemFonts[SystemFontFamily.Sans] = sans.Item1;
|
||||
(FontFace, FileInfo) mono = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.MonospaceFont, LinuxFonts.DefaultMonospaceFamilies, this);
|
||||
SystemFonts[SystemFontFamily.Monospace] = mono.Item1;
|
||||
(FontFace, FileInfo) cursive = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.CursiveFont, LinuxFonts.DefaultCursiveFamilies, this);
|
||||
SystemFonts[SystemFontFamily.Cursive] = cursive.Item1;
|
||||
(FontFace, FileInfo) fantasy = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.FantasyFont, LinuxFonts.DefaultFantasyFamilies, this);
|
||||
SystemFonts[SystemFontFamily.Fantasy] = fantasy.Item1;
|
||||
|
||||
AddFont(serif.Item1, serif.Item2);
|
||||
AddFont(sans.Item1, sans.Item2);
|
||||
AddFont(mono.Item1, mono.Item2);
|
||||
AddFont(cursive.Item1, cursive.Item2);
|
||||
AddFont(fantasy.Item1, fantasy.Item2);
|
||||
|
||||
}
|
||||
|
||||
private void AddFont(FontFace face, FileInfo file)
|
||||
{
|
||||
if (!All.Contains(face))
|
||||
All.Add(face);
|
||||
|
||||
FilesMap.TryAdd(face, file);
|
||||
|
||||
if (!ByFamily.TryGetValue(face.Family, out List<FontFace> siblings))
|
||||
{
|
||||
siblings = new List<FontFace>();
|
||||
ByFamily.Add(face.Family, siblings);
|
||||
}
|
||||
|
||||
if (!siblings.Contains(face))
|
||||
siblings.Add(face);
|
||||
}
|
||||
|
||||
public IEnumerable<FontFace> Search(FontFace prototype, FontMatchCriteria criteria = FontMatchCriteria.All)
|
||||
{
|
||||
// A bit scuffed and LINQ heavy but it should work.
|
||||
IEnumerable<FontFace> candidates;
|
||||
|
||||
if (criteria.HasFlag(FontMatchCriteria.Family))
|
||||
{
|
||||
List<FontFace> siblings;
|
||||
|
||||
if (!ByFamily.TryGetValue(prototype.Family, out siblings))
|
||||
{
|
||||
return Enumerable.Empty<FontFace>();
|
||||
}
|
||||
|
||||
candidates = siblings;
|
||||
}
|
||||
else
|
||||
{
|
||||
candidates = All;
|
||||
}
|
||||
|
||||
return
|
||||
candidates
|
||||
.Where(x =>
|
||||
implies(criteria.HasFlag(FontMatchCriteria.Slant), prototype.Slant == x.Slant) ||
|
||||
implies(criteria.HasFlag(FontMatchCriteria.Weight), prototype.Weight == x.Weight) ||
|
||||
implies(criteria.HasFlag(FontMatchCriteria.Stretch), prototype.Stretch == x.Stretch)
|
||||
)
|
||||
.OrderByDescending(x =>
|
||||
|
||||
(prototype.Slant == x.Slant ? 1 : 0) +
|
||||
(prototype.Weight == x.Weight ? 1 : 0) +
|
||||
(prototype.Stretch == x.Stretch ? 1 : 0) +
|
||||
confidence(prototype.Family, x.Family) * 3
|
||||
);
|
||||
|
||||
// a => b = a'+b
|
||||
static bool implies(bool a, bool b)
|
||||
{
|
||||
return !a || b;
|
||||
}
|
||||
|
||||
static int confidence(string target, string testee)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < target.Length && i < testee.Length && target[i] == testee[i]; i++);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
public FileInfo FontFileInfo(FontFace face)
|
||||
{
|
||||
if (FilesMap.TryGetValue(face, out FileInfo info))
|
||||
return info;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public Stream Open(FontFace face)
|
||||
{
|
||||
return FontFileInfo(face)?.OpenRead() ?? throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
public FontFace GetSystemFontFace(SystemFontFamily family)
|
||||
{
|
||||
return SystemFonts[family];
|
||||
}
|
||||
}
|
||||
}
|
11
Dashboard.Media.Defaults/Linux/LinuxFonts.cs
Normal file
11
Dashboard.Media.Defaults/Linux/LinuxFonts.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Dashboard.Media.Defaults.Linux
|
||||
{
|
||||
internal static class LinuxFonts
|
||||
{
|
||||
public const string DefaultSerifFamilies = "Noto Serif:Nimbus Roman:Liberation Serif:FreeSerif:Times:Times New Roman";
|
||||
public const string DefaultSansFamilies = "Noto Sans:Nimbus Sans:Droid Sans:Liberation Sans:FreeSans:Helvetica Neue:Helvetica:Arial";
|
||||
public const string DefaultMonospaceFamilies = "Noto Mono:Nimbus Mono PS:Liberation Mono:DejaVu Mono:FreeMono:Lucida Console:Consolas:Courier:Courier New";
|
||||
public const string DefaultCursiveFamilies = "";
|
||||
public const string DefaultFantasyFamilies = "";
|
||||
}
|
||||
}
|
83
Dashboard.Media.Defaults/QFontFreeType.cs
Normal file
83
Dashboard.Media.Defaults/QFontFreeType.cs
Normal file
@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using ReFuel.FreeType;
|
||||
using Dashboard.Media.Color;
|
||||
using Dashboard.Media.Font;
|
||||
|
||||
namespace Dashboard.Media.Defaults
|
||||
{
|
||||
public class QFontFreeType : QFont
|
||||
{
|
||||
private MemoryStream ms;
|
||||
private FTFace face;
|
||||
|
||||
public override FontFace Face => throw new NotImplementedException();
|
||||
|
||||
public QFontFreeType(Stream stream)
|
||||
{
|
||||
ms = new MemoryStream();
|
||||
stream.CopyTo(ms);
|
||||
|
||||
FTError e = FT.NewMemoryFace(Ft, ms.GetBuffer(), ms.Length, 0, out face);
|
||||
if (e != FTError.None)
|
||||
{
|
||||
throw new Exception("Could not load font face from stream.");
|
||||
}
|
||||
}
|
||||
|
||||
public override bool HasRune(int rune)
|
||||
{
|
||||
return FT.GetCharIndex(face, (ulong)rune) != 0;
|
||||
}
|
||||
|
||||
protected override QImage Render(out QGlyphMetrics metrics, int codepoint, float size, in FontRasterizerOptions options)
|
||||
{
|
||||
FT.SetCharSize(face, 0, (long)Math.Round(64*size), 0, (uint)Math.Round(options.Resolution));
|
||||
|
||||
uint index = FT.GetCharIndex(face, (ulong)codepoint);
|
||||
FT.LoadGlyph(face, index, FTLoadFlags.Default);
|
||||
|
||||
ref readonly FTGlyphMetrics ftmetrics = ref face.Glyph.Metrics;
|
||||
metrics = new QGlyphMetrics(codepoint,
|
||||
new QVec2(ftmetrics.Width/64f, ftmetrics.Height/64f),
|
||||
new QVec2(ftmetrics.HorizontalBearingX/64f, ftmetrics.HorizontalBearingY/64f),
|
||||
new QVec2(ftmetrics.VerticalBearingX/64f, ftmetrics.VerticalBearingY/64f),
|
||||
new QVec2(ftmetrics.HorizontalAdvance/64f, ftmetrics.VerticalAdvance/64f)
|
||||
);
|
||||
|
||||
FT.RenderGlyph(face.Glyph, options.Sdf ? FTRenderMode.Sdf : FTRenderMode.Normal);
|
||||
ref readonly FTBitmap bitmap = ref face.Glyph.Bitmap;
|
||||
|
||||
if (bitmap.Width == 0 || bitmap.Pitch == 0 || bitmap.Buffer == IntPtr.Zero)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
QImageBuffer image = new QImageBuffer(QImageFormat.AlphaU8, (int)bitmap.Width, (int)bitmap.Rows);
|
||||
image.LockBits2d(out QImageLock lk, QImageLockOptions.Default);
|
||||
|
||||
unsafe
|
||||
{
|
||||
Buffer.MemoryCopy((void*)bitmap.Buffer, (void*)lk.ImagePtr, lk.Width * lk.Height, bitmap.Width * bitmap.Rows);
|
||||
}
|
||||
|
||||
image.UnlockBits();
|
||||
return image;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
ms.Dispose();
|
||||
}
|
||||
|
||||
FT.DoneFace(face);
|
||||
}
|
||||
|
||||
private static FTLibrary Ft => FTProvider.Ft;
|
||||
}
|
||||
}
|
98
Dashboard.Media.Defaults/QImageStbi.cs
Normal file
98
Dashboard.Media.Defaults/QImageStbi.cs
Normal file
@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Dashboard.Media.Color;
|
||||
using ReFuel.Stb;
|
||||
|
||||
namespace Dashboard.Media.Defaults
|
||||
{
|
||||
public unsafe class QImageStbi : QImage
|
||||
{
|
||||
private readonly StbImage image;
|
||||
private QImageBuffer buffer;
|
||||
private bool isSdf = false;
|
||||
|
||||
public override int Width => image.Width;
|
||||
|
||||
public override int Height => image.Height;
|
||||
|
||||
public override int Depth => 1;
|
||||
public override bool IsSdf => isSdf;
|
||||
public override QImageFormat InternalFormat => Stb2QImageFormat(image.Format);
|
||||
|
||||
public QImageStbi(Stream source)
|
||||
{
|
||||
// According to the stbi documentation, only a specific type of PNG
|
||||
// files are premultiplied out of the box (iPhone PNG). Take the
|
||||
// precision loss L and move on.
|
||||
StbImage.FlipVerticallyOnLoad = true;
|
||||
StbImage.UnpremultiplyOnLoad = true;
|
||||
|
||||
image = StbImage.Load(source);
|
||||
}
|
||||
|
||||
public static QImageFormat Stb2QImageFormat(StbiImageFormat src)
|
||||
{
|
||||
switch (src)
|
||||
{
|
||||
case StbiImageFormat.Grey: return QImageFormat.RedU8;
|
||||
case StbiImageFormat.Rgb: return QImageFormat.RgbU8;
|
||||
case StbiImageFormat.Rgba: return QImageFormat.RgbaU8;
|
||||
case StbiImageFormat.GreyAlpha: return QImageFormat.RaU8;
|
||||
default: return QImageFormat.Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public override void LockBits2d(out QImageLock imageLock, QImageLockOptions options)
|
||||
{
|
||||
if (options.MipLevel > 0) throw new Exception("This image has no mip levels.");
|
||||
|
||||
buffer?.Dispose();
|
||||
buffer = new QImageBuffer(options.Format, Width, Height);
|
||||
buffer.LockBits2d(out QImageLock dst, QImageLockOptions.Default);
|
||||
|
||||
byte *srcPtr = (byte*)image.ImagePointer;
|
||||
QImageLock src = new QImageLock(InternalFormat, Width, Height, 1, (IntPtr)srcPtr);
|
||||
FormatConvert.Convert(dst, src);
|
||||
|
||||
if (options.Premultiply)
|
||||
{
|
||||
FormatConvert.Premultiply(dst);
|
||||
}
|
||||
|
||||
imageLock = dst;
|
||||
}
|
||||
|
||||
public override void LockBits3d(out QImageLock imageLock, QImageLockOptions options)
|
||||
{
|
||||
LockBits2d(out imageLock, options);
|
||||
}
|
||||
|
||||
public override void LockBits3d(out QImageLock imageLock, QImageLockOptions options, int depth)
|
||||
{
|
||||
if (depth != 1) throw new ArgumentOutOfRangeException(nameof(depth));
|
||||
|
||||
LockBits2d(out imageLock, options);
|
||||
}
|
||||
|
||||
public override void UnlockBits()
|
||||
{
|
||||
buffer.UnlockBits();
|
||||
}
|
||||
|
||||
public void SdfHint(bool value = true)
|
||||
{
|
||||
isSdf = value;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
buffer?.Dispose();
|
||||
image.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
155
Dashboard.Media.Defaults/StbMediaLoader.cs
Normal file
155
Dashboard.Media.Defaults/StbMediaLoader.cs
Normal file
@ -0,0 +1,155 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Dashboard.Media.Font;
|
||||
|
||||
// WebRequest is obsolete but runs on .NET framework.
|
||||
#pragma warning disable SYSLIB0014
|
||||
|
||||
namespace Dashboard.Media.Defaults
|
||||
{
|
||||
public class StbMediaLoader : MediaLoader<string>, MediaLoader<Uri>, MediaLoader<FileInfo>, MediaLoader<FontFace>
|
||||
{
|
||||
public bool AllowRemoteTransfers { get; set; } = false;
|
||||
private readonly ArrayPool<byte> ByteArrays = ArrayPool<byte>.Create();
|
||||
|
||||
public IDisposable GetMedia(object key, MediaHint hint)
|
||||
{
|
||||
Type t = key.GetType();
|
||||
/**/ if (t == typeof(string))
|
||||
{
|
||||
return GetMedia((string)key, hint);
|
||||
}
|
||||
else if (t == typeof(Uri))
|
||||
{
|
||||
return GetMedia((Uri)key, hint);
|
||||
}
|
||||
else if (t == typeof(FileInfo))
|
||||
{
|
||||
return GetMedia((FileInfo)key, hint);
|
||||
}
|
||||
else if (t == typeof(FontFace))
|
||||
{
|
||||
return GetMedia((FontFace)key, hint);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable GetMedia(Uri uri, MediaHint hint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IDisposable GetMedia(string str, MediaHint hint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IDisposable GetMedia(FileInfo file, MediaHint hint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IDisposable GetMedia(FontFace key, MediaHint hint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Stream OpenResource(FileInfo file)
|
||||
{
|
||||
if (file.Exists)
|
||||
{
|
||||
return file.Open(FileMode.Open);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Stream OpenResource(Uri uri)
|
||||
{
|
||||
switch (uri.Scheme)
|
||||
{
|
||||
case "http":
|
||||
case "https":
|
||||
if (!AllowRemoteTransfers) return null;
|
||||
|
||||
try
|
||||
{
|
||||
WebRequest request = HttpWebRequest.Create(uri);
|
||||
WebResponse response = request.GetResponse();
|
||||
MemoryStream stream = new MemoryStream();
|
||||
|
||||
response.GetResponseStream().CopyTo(stream);
|
||||
response.Close();
|
||||
|
||||
stream.Position = 0;
|
||||
return stream;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
case "file":
|
||||
return OpenResource(new FileInfo(uri.AbsolutePath));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Stream OpenResource(string key)
|
||||
{
|
||||
if (File.Exists(key))
|
||||
{
|
||||
return File.Open(key, FileMode.Open);
|
||||
}
|
||||
else if (Uri.TryCreate(key, UriKind.RelativeOrAbsolute, out Uri uri))
|
||||
{
|
||||
return OpenResource(uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
MediaHint InferMedia(Stream str, MediaHint hint)
|
||||
{
|
||||
if (hint != MediaHint.None)
|
||||
{
|
||||
return hint;
|
||||
}
|
||||
|
||||
byte[] array = ByteArrays.Rent(4);
|
||||
str.Read(array, 0, 4);
|
||||
str.Position = 0;
|
||||
|
||||
foreach (var(type, seq) in MediaTypes)
|
||||
{
|
||||
if (seq.SequenceEqual(array))
|
||||
return hint;
|
||||
}
|
||||
|
||||
return MediaHint.None;
|
||||
}
|
||||
|
||||
private readonly (MediaHint, byte[])[] MediaTypes = new (MediaHint, byte[])[] {
|
||||
(MediaHint.Image, new byte[] { 0x42, 0x4d }), /* .bmp `BM` */
|
||||
(MediaHint.Image, new byte[] { 0x47, 0x49, 0x46, 0x38 }), /* .gif `GIF8` */
|
||||
(MediaHint.Image, new byte[] { 0xff, 0xd8, 0xff, 0xe0 }), /* .jpg (JFIF) */
|
||||
(MediaHint.Image, new byte[] { 0xff, 0xd8, 0xff, 0xe1 }), /* .jpg (EXIF) */
|
||||
(MediaHint.Image, new byte[] { 0x89, 0x50, 0x4e, 0x47 }), /* .png `.PNG `*/
|
||||
(MediaHint.Image, new byte[] { 0x4d, 0x4d, 0x00, 0x2a }), /* .tif (motorola) */
|
||||
(MediaHint.Image, new byte[] { 0x49, 0x49, 0x2a, 0x00 }), /* .tif (intel) */
|
||||
(MediaHint.Font, new byte[] { 0x00, 0x01, 0x00, 0x00 }), /* .ttf */
|
||||
(MediaHint.Font, new byte[] { 0x4F, 0x54, 0x54, 0x4F }), /* .otf */
|
||||
};
|
||||
}
|
||||
}
|
151
Dashboard.Media.Defaults/Win32/EnumerateFonts.cs
Normal file
151
Dashboard.Media.Defaults/Win32/EnumerateFonts.cs
Normal file
@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
#if false
|
||||
namespace Quik.Media.Defaults.Win32
|
||||
{
|
||||
|
||||
public class EnumerateFonts
|
||||
{
|
||||
private const byte DEFAULT_CHARSET = 1;
|
||||
|
||||
public static void Enumerate(FontFace font)
|
||||
{
|
||||
/* It's windows, just borrow the desktop window. */
|
||||
IntPtr hdc = GetDC(GetDesktopWindow());
|
||||
|
||||
List<(LogFontA, TextMetricA)> list = new List<(LogFontA, TextMetricA)>();
|
||||
|
||||
LogFontA font2 = new LogFontA()
|
||||
{
|
||||
//FaceName = font.Family,
|
||||
Weight = ((font.Style & FontStyle.Bold) != 0) ? FontWeight.Bold : FontWeight.Regular,
|
||||
Italic = (font.Style & FontStyle.Italic) != 0,
|
||||
CharSet = DEFAULT_CHARSET
|
||||
};
|
||||
|
||||
Console.WriteLine(font2.FaceName);
|
||||
|
||||
EnumFontFamiliesExProc proc = (in LogFontA font, in TextMetricA metric, int type, IntPtr lparam) =>
|
||||
{
|
||||
list.Add((font, metric));
|
||||
return 0;
|
||||
};
|
||||
|
||||
EnumFontFamiliesExA(hdc, font2, proc, IntPtr.Zero, 0);
|
||||
}
|
||||
|
||||
private const string gdi32 = "Gdi32.dll";
|
||||
private const string user32 = "User32.dll";
|
||||
|
||||
[DllImport(gdi32)]
|
||||
private static extern int EnumFontFamiliesExA(
|
||||
IntPtr hdc,
|
||||
in LogFontA font,
|
||||
[MarshalAs(UnmanagedType.FunctionPtr)] EnumFontFamiliesExProc proc,
|
||||
IntPtr lparam,
|
||||
int flags /* Should be zero. */);
|
||||
|
||||
[DllImport(user32)]
|
||||
private static extern IntPtr /* HWND */ GetDesktopWindow();
|
||||
|
||||
[DllImport(user32)]
|
||||
private static extern IntPtr /* HDC */ GetDC(IntPtr hwnd);
|
||||
|
||||
private delegate int EnumFontFamiliesExProc(in LogFontA font, in TextMetricA metric, int fontType, IntPtr lParam);
|
||||
|
||||
private struct LogFontA
|
||||
{
|
||||
public long Height;
|
||||
public long Width;
|
||||
public long Escapement;
|
||||
public long Orientation;
|
||||
public FontWeight Weight;
|
||||
[MarshalAs(UnmanagedType.U1)]
|
||||
public bool Italic;
|
||||
[MarshalAs(UnmanagedType.U1)]
|
||||
public bool Underline;
|
||||
[MarshalAs(UnmanagedType.U1)]
|
||||
public bool StrikeOut;
|
||||
public byte CharSet;
|
||||
public byte OutPrecision;
|
||||
public byte ClipPrecision;
|
||||
public byte PitchAndFamily;
|
||||
private unsafe fixed byte aFaceName[32];
|
||||
public unsafe string FaceName
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (byte* str = aFaceName)
|
||||
{
|
||||
int len = 0;
|
||||
for (; str[len] != 0 && len < 32; len++) ;
|
||||
return Encoding.UTF8.GetString(str, len);
|
||||
}
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
fixed (byte *str = aFaceName)
|
||||
{
|
||||
Span<byte> span = new Span<byte>(str, 32);
|
||||
Encoding.UTF8.GetBytes(value, span);
|
||||
span[31] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct TextMetricA
|
||||
{
|
||||
public long Height;
|
||||
public long Ascent;
|
||||
public long Descent;
|
||||
public long InternalLeading;
|
||||
public long ExternalLeading;
|
||||
public long AveCharWidth;
|
||||
public long MaxCharWidth;
|
||||
public long Weight;
|
||||
public long Overhang;
|
||||
public long DigitizedAspectX;
|
||||
public long DigitizedAspectY;
|
||||
public byte FirstChar;
|
||||
public byte LastChar;
|
||||
public byte DefaultChar;
|
||||
public byte BreakChar;
|
||||
public byte Italic;
|
||||
public byte Underlined;
|
||||
public byte StruckOut;
|
||||
public byte PitchAndFamily;
|
||||
public byte CharSet;
|
||||
}
|
||||
|
||||
private enum FontWeight : long
|
||||
{
|
||||
DontCare = 0,
|
||||
Thin = 100,
|
||||
ExtraLight = 200,
|
||||
UltraLight = 200,
|
||||
Light = 300,
|
||||
Normal = 400,
|
||||
Regular = 400,
|
||||
Medium = 500,
|
||||
Semibold = 600,
|
||||
Demibold = 600,
|
||||
Bold = 700,
|
||||
Extrabold = 800,
|
||||
Ultrabold = 800,
|
||||
Heavy = 900,
|
||||
Black = 900
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
#endif
|
17
Dashboard.OpenTK/Dashboard.OpenTK.csproj
Normal file
17
Dashboard.OpenTK/Dashboard.OpenTK.csproj
Normal file
@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTK" Version="4.8.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Dashboard\Dashboard.csproj" />
|
||||
<EmbeddedResource Include="glsl\**"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
104
Dashboard.OpenTK/OpenTKPlatform.cs
Normal file
104
Dashboard.OpenTK/OpenTKPlatform.cs
Normal file
@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
using Dashboard.CommandMachine;
|
||||
using Dashboard.Media;
|
||||
using Dashboard.OpenGL;
|
||||
using Dashboard.PAL;
|
||||
|
||||
namespace Dashboard.OpenTK
|
||||
{
|
||||
public class OpenTKPlatform : IDashboardPlatform
|
||||
{
|
||||
private readonly List<OpenTKPort> _ports = new List<OpenTKPort>();
|
||||
|
||||
// These shall remain a sad nop for now.
|
||||
public string? Title { get; set; }
|
||||
public QImage? Icon { get; set; } = null;
|
||||
|
||||
public event EventHandler? EventRaised;
|
||||
|
||||
public NativeWindowSettings DefaultSettings { get; set; } = NativeWindowSettings.Default;
|
||||
|
||||
public IReadOnlyList<OpenTKPort> Ports => _ports;
|
||||
|
||||
private bool IsGLInitialized = false;
|
||||
|
||||
public IDashHandle CreatePort()
|
||||
{
|
||||
NativeWindow window = new NativeWindow(DefaultSettings);
|
||||
OpenTKPort port = new OpenTKPort(window);
|
||||
_ports.Add(port);
|
||||
|
||||
if (!IsGLInitialized)
|
||||
{
|
||||
window.Context.MakeCurrent();
|
||||
GL.LoadBindings(GLFW.GetProcAddress);
|
||||
IsGLInitialized = true;
|
||||
}
|
||||
|
||||
window.Closing += (ea) =>
|
||||
{
|
||||
Environment.Exit(0);
|
||||
};
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// FIXME: dispose pattern here!
|
||||
|
||||
// Copy the array to prevent collection modification exceptions.
|
||||
foreach (OpenTKPort port in _ports.ToArray())
|
||||
{
|
||||
port.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessEvents(bool block)
|
||||
{
|
||||
NativeWindow.ProcessWindowEvents(block);
|
||||
}
|
||||
|
||||
public void DestroyPort(IDashHandle port) => ((OpenTKPort)port).Dispose();
|
||||
|
||||
public string PortGetTitle(IDashHandle port) => ((OpenTKPort)port).Title;
|
||||
|
||||
public void PortSetTitle(IDashHandle port, string title) => ((OpenTKPort)port).Title = title;
|
||||
|
||||
public QVec2 PortGetSize(IDashHandle port) => ((OpenTKPort)port).Size;
|
||||
|
||||
public void PortSetSize(IDashHandle port, QVec2 size) => ((OpenTKPort)port).Size = size;
|
||||
|
||||
public QVec2 PortGetPosition(IDashHandle port) => ((OpenTKPort)port).Position;
|
||||
|
||||
public void PortSetPosition(IDashHandle port, QVec2 position) => ((OpenTKPort)port).Position = position;
|
||||
|
||||
public bool PortIsValid(IDashHandle port) => ((OpenTKPort)port).IsValid;
|
||||
|
||||
public void PortSubscribeEvent(IDashHandle port, EventHandler handler) => ((OpenTKPort)port).EventRaised += handler;
|
||||
|
||||
public void PortUnsubscribeEvent(IDashHandle port, EventHandler handler) => ((OpenTKPort)port).EventRaised -= handler;
|
||||
|
||||
public void PortFocus(IDashHandle port) => ((OpenTKPort)port).Focus();
|
||||
|
||||
public void PortShow(IDashHandle port, bool shown = true) => ((OpenTKPort)port).Show(shown);
|
||||
|
||||
public void PortPaint(IDashHandle port, CommandList commands) => ((OpenTKPort)port).Paint(commands);
|
||||
|
||||
public void GetMaximumImage(out int width, out int height)
|
||||
{
|
||||
GL.Get(GLEnum.GL_MAX_TEXTURE_SIZE, out int value);
|
||||
width = height = value;
|
||||
}
|
||||
|
||||
public void GetMaximumImage(out int width, out int height, out int depth)
|
||||
{
|
||||
GetMaximumImage(out width, out height);
|
||||
GL.Get(GLEnum.GL_MAX_ARRAY_TEXTURE_LAYERS, out int value);
|
||||
depth = value;
|
||||
}
|
||||
}
|
||||
}
|
107
Dashboard.OpenTK/OpenTKPort.cs
Normal file
107
Dashboard.OpenTK/OpenTKPort.cs
Normal file
@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using OpenTK.Mathematics;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
using Dashboard.OpenGL;
|
||||
using Dashboard.CommandMachine;
|
||||
using Dashboard.PAL;
|
||||
using Dashboard.VertexGenerator;
|
||||
|
||||
namespace Dashboard.OpenTK
|
||||
{
|
||||
public class OpenTKPort : IDashHandle
|
||||
{
|
||||
private readonly NativeWindow _window;
|
||||
private readonly GL21Driver _glDriver;
|
||||
private readonly VertexGeneratorEngine _vertexEngine;
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => _window.Title;
|
||||
set => _window.Title = value;
|
||||
}
|
||||
|
||||
public QVec2 Size
|
||||
{
|
||||
get
|
||||
{
|
||||
Vector2i size = _window.ClientSize;
|
||||
return new QVec2(size.X, size.Y);
|
||||
}
|
||||
set
|
||||
{
|
||||
// OpenTK being OpenTK as usual, you can't set the client size.
|
||||
Vector2i extents = _window.Size - _window.ClientSize;
|
||||
Vector2i size = extents + new Vector2i((int)value.X, (int)value.Y);
|
||||
_window.Size = size;
|
||||
}
|
||||
}
|
||||
public QVec2 Position
|
||||
{
|
||||
get
|
||||
{
|
||||
Vector2i location = _window.Location;
|
||||
return new QVec2(location.X, location.Y);
|
||||
}
|
||||
set
|
||||
{
|
||||
Vector2i location = new Vector2i((int)value.X, (int)value.Y);
|
||||
_window.Location = location;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsValid => !isDisposed;
|
||||
|
||||
public event EventHandler? EventRaised;
|
||||
|
||||
public OpenTKPort(NativeWindow window)
|
||||
{
|
||||
_window = window;
|
||||
_glDriver = new GL21Driver();
|
||||
_vertexEngine = new VertexGeneratorEngine();
|
||||
}
|
||||
|
||||
public void Focus()
|
||||
{
|
||||
_window.Focus();
|
||||
}
|
||||
|
||||
public void Paint(CommandList queue)
|
||||
{
|
||||
QRectangle view = new QRectangle(Size, new QVec2(0, 0));
|
||||
|
||||
_vertexEngine.Reset();
|
||||
_vertexEngine.ProcessCommands(view, queue);
|
||||
|
||||
if (!_window.Context.IsCurrent)
|
||||
_window.Context.MakeCurrent();
|
||||
|
||||
if (!_glDriver.IsInit)
|
||||
_glDriver.Init();
|
||||
|
||||
GL.Clear(GLEnum.GL_COLOR_BUFFER_BIT | GLEnum.GL_DEPTH_BUFFER_BIT);
|
||||
_glDriver.Draw(_vertexEngine.DrawQueue, view);
|
||||
|
||||
_window.Context.SwapBuffers();
|
||||
}
|
||||
|
||||
public void Show(bool shown = true)
|
||||
{
|
||||
_window.IsVisible = shown;
|
||||
}
|
||||
|
||||
private bool isDisposed;
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (isDisposed) return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_window?.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
isDisposed = true;
|
||||
}
|
||||
public void Dispose() => Dispose(true);
|
||||
}
|
||||
}
|
@ -3,53 +3,49 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard", "Dashboard\Dashboard.csproj", "{49A62F46-AC1C-4240-8615-020D4FBBF964}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard", "Dashboard\Dashboard.csproj", "{4FE772DD-F424-4EAC-BF88-CB8F751B4926}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard.Drawing", "Dashboard.Drawing\Dashboard.Drawing.csproj", "{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Media.Defaults", "Dashboard.Media.Defaults\Dashboard.Media.Defaults.csproj", "{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9D6CCC74-4DF3-47CB-B9B2-6BB75DF2BC40}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.OpenTK", "Dashboard.OpenTK\Dashboard.OpenTK.csproj", "{2013470A-915C-46F2-BDD3-FCAA39C845EE}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard.TestApplication", "tests\Dashboard.TestApplication\Dashboard.TestApplication.csproj", "{7C90B90B-DF31-439B-9080-CD805383B014}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5} = {1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}
|
||||
{49A62F46-AC1C-4240-8615-020D4FBBF964} = {49A62F46-AC1C-4240-8615-020D4FBBF964}
|
||||
EndProjectSection
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{40F3B724-88A1-4D4F-93AB-FE0DC07A347E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Common", "Dashboard.Common\Dashboard.Common.csproj", "{C77CDD2B-2482-45F9-B330-47A52F5F13C0}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Demo", "tests\Dashboard.Demo\Dashboard.Demo.csproj", "{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Drawing.OpenGL", "Dashboard.Drawing.OpenGL\Dashboard.Drawing.OpenGL.csproj", "{454198BA-CB95-41C5-A934-B1C8FDA35A6B}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.BlurgText", "Dashboard.BlurgText\Dashboard.BlurgText.csproj", "{D05A9DEA-A5D1-43DC-AB41-36B07598B749}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{49A62F46-AC1C-4240-8615-020D4FBBF964}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{49A62F46-AC1C-4240-8615-020D4FBBF964}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{49A62F46-AC1C-4240-8615-020D4FBBF964}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{49A62F46-AC1C-4240-8615-020D4FBBF964}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7C90B90B-DF31-439B-9080-CD805383B014}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7C90B90B-DF31-439B-9080-CD805383B014}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7C90B90B-DF31-439B-9080-CD805383B014}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7C90B90B-DF31-439B-9080-CD805383B014}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C77CDD2B-2482-45F9-B330-47A52F5F13C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{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
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{4FE772DD-F424-4EAC-BF88-CB8F751B4926}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4FE772DD-F424-4EAC-BF88-CB8F751B4926}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4FE772DD-F424-4EAC-BF88-CB8F751B4926}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4FE772DD-F424-4EAC-BF88-CB8F751B4926}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2013470A-915C-46F2-BDD3-FCAA39C845EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2013470A-915C-46F2-BDD3-FCAA39C845EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2013470A-915C-46F2-BDD3-FCAA39C845EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2013470A-915C-46F2-BDD3-FCAA39C845EE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D05A9DEA-A5D1-43DC-AB41-36B07598B749}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D05A9DEA-A5D1-43DC-AB41-36B07598B749}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D05A9DEA-A5D1-43DC-AB41-36B07598B749}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D05A9DEA-A5D1-43DC-AB41-36B07598B749}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{7C90B90B-DF31-439B-9080-CD805383B014} = {9D6CCC74-4DF3-47CB-B9B2-6BB75DF2BC40}
|
||||
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC} = {40F3B724-88A1-4D4F-93AB-FE0DC07A347E}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
62
Dashboard/CommandMachine/Command.cs
Normal file
62
Dashboard/CommandMachine/Command.cs
Normal file
@ -0,0 +1,62 @@
|
||||
namespace Dashboard.CommandMachine
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration of built-in Quik commands.
|
||||
/// </summary>
|
||||
public enum Command
|
||||
{
|
||||
#region Control Commands
|
||||
/// <summary>
|
||||
/// Invoke a function directly.
|
||||
/// </summary>
|
||||
Invoke,
|
||||
|
||||
/// <summary>
|
||||
/// Begin conditional rendering segment.
|
||||
/// </summary>
|
||||
ConditionalBegin,
|
||||
|
||||
/// <summary>
|
||||
/// End conditional rendering segment.
|
||||
/// </summary>
|
||||
ConditionalEnd,
|
||||
|
||||
PushViewport,
|
||||
IntersectViewport,
|
||||
StoreViewport,
|
||||
PopViewport,
|
||||
|
||||
PushZ,
|
||||
IncrementZ,
|
||||
AddZ,
|
||||
StoreZ,
|
||||
DecrementZ,
|
||||
PopZ,
|
||||
|
||||
PushMatrix,
|
||||
StoreIdentityMatrix,
|
||||
StoreMatrix,
|
||||
PopMatrix,
|
||||
|
||||
PushStyle,
|
||||
StoreStyle,
|
||||
PopStyle,
|
||||
#endregion
|
||||
|
||||
#region Draw Commands
|
||||
Line,
|
||||
Bezier,
|
||||
Rectangle,
|
||||
Ellipse,
|
||||
Triangle,
|
||||
Polygon,
|
||||
Image,
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Start index for custom commands.
|
||||
/// </summary>
|
||||
CustomCommandBase = 1024,
|
||||
}
|
||||
}
|
232
Dashboard/CommandMachine/CommandEngine.cs
Normal file
232
Dashboard/CommandMachine/CommandEngine.cs
Normal file
@ -0,0 +1,232 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Dashboard.CommandMachine
|
||||
{
|
||||
public class CommandEngine
|
||||
{
|
||||
private int _zIndex = 0;
|
||||
private readonly Stack<int> _zStack = new Stack<int>();
|
||||
public int ZIndex => _zIndex;
|
||||
|
||||
private QRectangle _viewport;
|
||||
private readonly Stack<QRectangle> _viewportStack = new Stack<QRectangle>();
|
||||
private readonly Stack<QMat4> _matrixStack = new Stack<QMat4>();
|
||||
|
||||
private Command _customCommandBase = Command.CustomCommandBase;
|
||||
private readonly List<QuikCommandHandler> _customCommands = new List<QuikCommandHandler>();
|
||||
|
||||
public QRectangle Viewport => _viewport;
|
||||
|
||||
public QMat4 ActiveTransforms { get; }
|
||||
|
||||
public StyleStack Style { get; } = new StyleStack(new Style());
|
||||
|
||||
protected CommandEngine()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
public Command RegisterCustomCommand(QuikCommandHandler handler)
|
||||
{
|
||||
Command id = _customCommandBase++;
|
||||
_customCommands.Insert(id - Command.CustomCommandBase, handler);
|
||||
return id;
|
||||
}
|
||||
|
||||
public void ProcessCommands(QRectangle bounds, CommandList queue)
|
||||
{
|
||||
CommandQueue iterator = queue.GetEnumerator();
|
||||
|
||||
if (!iterator.Peek().IsCommand)
|
||||
throw new ArgumentException("The first element in the iterator must be a command frame.");
|
||||
|
||||
Reset();
|
||||
|
||||
_viewport = bounds;
|
||||
_viewportStack.Push(_viewport);
|
||||
|
||||
Frame frame;
|
||||
while (iterator.TryDequeue(out frame))
|
||||
{
|
||||
Command cmd = (Command)frame;
|
||||
switch (cmd)
|
||||
{
|
||||
default:
|
||||
if (cmd > Command.CustomCommandBase)
|
||||
{
|
||||
_customCommands[cmd - Command.CustomCommandBase].Invoke(this, iterator);
|
||||
}
|
||||
else
|
||||
{
|
||||
ChildProcessCommand(cmd, iterator);
|
||||
}
|
||||
break;
|
||||
|
||||
case Command.ConditionalBegin: ConditionalHandler(iterator); break;
|
||||
case Command.ConditionalEnd: /* nop */ break;
|
||||
|
||||
case Command.Invoke:
|
||||
iterator.Dequeue().As<QuikCommandHandler>().Invoke(this, iterator);
|
||||
break;
|
||||
case Command.PushViewport:
|
||||
_viewportStack.Push(_viewport);
|
||||
break;
|
||||
case Command.IntersectViewport:
|
||||
_viewport = QRectangle.Intersect((QRectangle)iterator.Dequeue(), _viewport);
|
||||
break;
|
||||
case Command.StoreViewport:
|
||||
_viewport = (QRectangle)iterator.Dequeue();
|
||||
break;
|
||||
case Command.PopViewport:
|
||||
_viewport = _viewportStack.TryPop(out QRectangle viewport) ? viewport : bounds;
|
||||
break;
|
||||
case Command.PushZ:
|
||||
_zStack.Push(_zIndex);
|
||||
break;
|
||||
case Command.IncrementZ:
|
||||
_zIndex++;
|
||||
break;
|
||||
case Command.AddZ:
|
||||
_zIndex += (int)iterator.Dequeue();
|
||||
break;
|
||||
case Command.StoreZ:
|
||||
_zIndex = (int)iterator.Dequeue();
|
||||
break;
|
||||
case Command.DecrementZ:
|
||||
_zIndex--;
|
||||
break;
|
||||
case Command.PopZ:
|
||||
_zIndex = _zStack.TryPop(out int zindex) ? zindex : 0;
|
||||
break;
|
||||
case Command.PushStyle:
|
||||
Style.Push(iterator.Dequeue().As<Style>());
|
||||
break;
|
||||
case Command.StoreStyle:
|
||||
Style.Pop();
|
||||
Style.Push(iterator.Dequeue().As<Style>());
|
||||
break;
|
||||
case Command.PopStyle:
|
||||
Style.Pop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ChildProcessCommand(Command name, CommandQueue queue)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
_zIndex = 0;
|
||||
_zStack.Clear();
|
||||
|
||||
_viewport = new QRectangle(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue);
|
||||
_viewportStack.Clear();
|
||||
|
||||
_matrixStack.Clear();
|
||||
_matrixStack.Push(QMat4.Identity);
|
||||
}
|
||||
|
||||
private void ConditionalHandler(CommandQueue iterator)
|
||||
{
|
||||
Frame frame = iterator.Dequeue();
|
||||
|
||||
if (
|
||||
frame.IsInteger && (int)frame != 0 ||
|
||||
frame.As<Func<bool>>().Invoke())
|
||||
{
|
||||
// Take this branch.
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip this branch.
|
||||
int depth = 1;
|
||||
while (iterator.TryPeek(out frame))
|
||||
{
|
||||
if (!frame.IsCommand)
|
||||
{
|
||||
iterator.Dequeue();
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ((Command)frame)
|
||||
{
|
||||
case Command.ConditionalBegin:
|
||||
// Increment conditional depth.
|
||||
depth++;
|
||||
break;
|
||||
case Command.ConditionalEnd:
|
||||
// Decrement condional depth, exit if zero.
|
||||
if (--depth == 0)
|
||||
{
|
||||
iterator.Dequeue();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
iterator.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Dictionary<Type, ICommandListSerializer> s_serializers = new Dictionary<Type, ICommandListSerializer>();
|
||||
|
||||
/// <summary>
|
||||
/// Add a custom serializer to the command engine.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type object type.</typeparam>
|
||||
/// <param name="serializer">The serializer.</param>
|
||||
/// <param name="overwrite">True to allow overwriting.</param>
|
||||
public static void AddSerializer<T>(ICommandListSerializer serializer, bool overwrite = false)
|
||||
{
|
||||
if (overwrite)
|
||||
{
|
||||
s_serializers[typeof(T)] = serializer;
|
||||
}
|
||||
else
|
||||
{
|
||||
s_serializers.Add(typeof(T), serializer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a custom serializer to the command engine.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type object type.</typeparam>
|
||||
/// <param name="serializer">The serializer.</param>
|
||||
/// <param name="overwrite">True to allow overwriting.</param>
|
||||
public static void AddSerializer<T>(ICommandListSerializer<T> serializer, bool overwrite = false)
|
||||
=> AddSerializer<T>((ICommandListSerializer)serializer, overwrite);
|
||||
|
||||
/// <summary>
|
||||
/// Get a serializer for the given object.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The object type.</typeparam>
|
||||
/// <param name="value">Required parameter for the C# type inference to work.</param>
|
||||
/// <returns>The serializer.</returns>
|
||||
public static ICommandListSerializer<T> GetSerializer<T>(ICommandListSerializable<T>? value)
|
||||
where T : ICommandListSerializable<T>, new()
|
||||
{
|
||||
if (!s_serializers.TryGetValue(typeof(T), out var serializer))
|
||||
{
|
||||
serializer = new CommandListSerializableSerializer<T>();
|
||||
AddSerializer<T>(serializer);
|
||||
}
|
||||
|
||||
return (ICommandListSerializer<T>)serializer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a serializer for the given object.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The object type.</typeparam>
|
||||
/// <param name="value">Required parameter for the C# type inference to work.</param>
|
||||
/// <returns>The serializer.</returns>
|
||||
public static ICommandListSerializer<T> GetSerializer<T>(T? value)
|
||||
{
|
||||
return (ICommandListSerializer<T>)s_serializers[typeof(T)];
|
||||
}
|
||||
}
|
||||
}
|
8
Dashboard/CommandMachine/CommandHandler.cs
Normal file
8
Dashboard/CommandMachine/CommandHandler.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Dashboard.CommandMachine
|
||||
{
|
||||
/// <summary>
|
||||
/// A delegate for a QUIK command.
|
||||
/// </summary>
|
||||
/// <param name="stack">The current stack.</param>
|
||||
public delegate void QuikCommandHandler(CommandEngine state, CommandQueue queue);
|
||||
}
|
433
Dashboard/CommandMachine/CommandList.cs
Normal file
433
Dashboard/CommandMachine/CommandList.cs
Normal file
@ -0,0 +1,433 @@
|
||||
using Dashboard.Media;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Dashboard.CommandMachine
|
||||
{
|
||||
public class CommandList : IEnumerable<Frame>
|
||||
{
|
||||
private readonly List<Frame> _frames = new List<Frame>();
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_frames.Clear();
|
||||
}
|
||||
|
||||
protected void Enqueue(in Frame frame)
|
||||
{
|
||||
_frames.Add(frame);
|
||||
}
|
||||
|
||||
public void Invoke(QuikCommandHandler handler)
|
||||
{
|
||||
Enqueue(Command.Invoke);
|
||||
Enqueue(new Frame(handler));
|
||||
}
|
||||
|
||||
public void ConditionalBegin(bool value)
|
||||
{
|
||||
Enqueue(Command.ConditionalBegin);
|
||||
Enqueue((Frame)(value ? 1 : 0));
|
||||
}
|
||||
|
||||
public void ConditionalBegin(Func<bool> condition)
|
||||
{
|
||||
Enqueue(Command.ConditionalBegin);
|
||||
Enqueue(new Frame(condition));
|
||||
}
|
||||
|
||||
public void ConditionalEnd()
|
||||
{
|
||||
Enqueue(Command.ConditionalEnd);
|
||||
}
|
||||
|
||||
public void PushViewport()
|
||||
{
|
||||
Enqueue(Command.PushViewport);
|
||||
}
|
||||
|
||||
public void IntersectViewport(in QRectangle viewport)
|
||||
{
|
||||
Enqueue(Command.IntersectViewport);
|
||||
Enqueue(viewport);
|
||||
}
|
||||
|
||||
public void StoreViewport(in QRectangle viewport)
|
||||
{
|
||||
Enqueue(Command.StoreViewport);
|
||||
Enqueue(viewport);
|
||||
}
|
||||
|
||||
public void PopViewport()
|
||||
{
|
||||
Enqueue(Command.PopViewport);
|
||||
}
|
||||
|
||||
public void PushZ()
|
||||
{
|
||||
Enqueue(Command.PushZ);
|
||||
}
|
||||
|
||||
public void IncrementZ()
|
||||
{
|
||||
Enqueue(Command.IncrementZ);
|
||||
}
|
||||
|
||||
public void AddZ(int value)
|
||||
{
|
||||
if (value == 1)
|
||||
{
|
||||
IncrementZ();
|
||||
}
|
||||
else if (value == -1)
|
||||
{
|
||||
DecrementZ();
|
||||
}
|
||||
else
|
||||
{
|
||||
Enqueue(Command.AddZ);
|
||||
Enqueue((Frame)value);
|
||||
}
|
||||
}
|
||||
|
||||
public void StoreZ(int value)
|
||||
{
|
||||
Enqueue(Command.StoreZ);
|
||||
Enqueue((Frame)value);
|
||||
}
|
||||
|
||||
public void DecrementZ()
|
||||
{
|
||||
Enqueue(Command.DecrementZ);
|
||||
}
|
||||
|
||||
public void PopZ()
|
||||
{
|
||||
Enqueue(Command.PopZ);
|
||||
}
|
||||
|
||||
public void PushStyle(Style style)
|
||||
{
|
||||
Enqueue(Command.PushStyle);
|
||||
Enqueue(new Frame(style));
|
||||
}
|
||||
|
||||
public void StoreStyle(Style style)
|
||||
{
|
||||
Enqueue(Command.StoreStyle);
|
||||
Enqueue(new Frame(style));
|
||||
}
|
||||
|
||||
public void PopStyle()
|
||||
{
|
||||
Enqueue(Command.PopStyle);
|
||||
}
|
||||
|
||||
public void Line(in QLine line)
|
||||
{
|
||||
Enqueue(Command.Line);
|
||||
Enqueue(line);
|
||||
}
|
||||
|
||||
public void Line(params QLine[] lines)
|
||||
{
|
||||
Enqueue(Command.Line);
|
||||
Enqueue((Frame)lines.Length);
|
||||
foreach (QLine line in lines)
|
||||
Enqueue(line);
|
||||
}
|
||||
|
||||
public void Bezier(in QBezier bezier)
|
||||
{
|
||||
Frame a, b;
|
||||
Frame.Create(bezier, out a, out b);
|
||||
|
||||
Enqueue(Command.Bezier);
|
||||
Enqueue(a);
|
||||
Enqueue(b);
|
||||
}
|
||||
|
||||
public void Bezier(params QBezier[] beziers)
|
||||
{
|
||||
Frame a, b;
|
||||
|
||||
Enqueue(Command.Bezier);
|
||||
Enqueue((Frame)beziers.Length);
|
||||
|
||||
foreach (QBezier bezier in beziers)
|
||||
{
|
||||
Frame.Create(bezier, out a, out b);
|
||||
Enqueue(a);
|
||||
Enqueue(b);
|
||||
}
|
||||
}
|
||||
|
||||
public void Rectangle(in QRectangle rectangle)
|
||||
{
|
||||
Enqueue(Command.Rectangle);
|
||||
Enqueue(rectangle);
|
||||
}
|
||||
|
||||
public void Rectangle(QRectangle[] rectangles)
|
||||
{
|
||||
Enqueue(Command.Rectangle);
|
||||
Enqueue((Frame)rectangles.Length);
|
||||
foreach (QRectangle rectangle in rectangles)
|
||||
Enqueue(rectangle);
|
||||
}
|
||||
|
||||
public void Ellipse(in QEllipse ellipse)
|
||||
{
|
||||
Frame a, b;
|
||||
Frame.Create(ellipse, out a, out b);
|
||||
|
||||
Enqueue(Command.Ellipse);
|
||||
Enqueue(a);
|
||||
Enqueue(b);
|
||||
}
|
||||
|
||||
public void Ellipse(params QEllipse[] ellipses)
|
||||
{
|
||||
Frame a, b;
|
||||
Enqueue(Command.Ellipse);
|
||||
Enqueue((Frame)ellipses.Length);
|
||||
foreach (QEllipse ellipse in ellipses)
|
||||
{
|
||||
Frame.Create(ellipse, out a, out b);
|
||||
Enqueue(a);
|
||||
Enqueue(b);
|
||||
}
|
||||
}
|
||||
|
||||
public void Triangle(in QTriangle triangle)
|
||||
{
|
||||
Enqueue(Command.Triangle);
|
||||
Enqueue(triangle.A);
|
||||
Enqueue(triangle.B);
|
||||
Enqueue(triangle.C);
|
||||
}
|
||||
|
||||
public void Triangle(params QTriangle[] triangles)
|
||||
{
|
||||
Enqueue(Command.Triangle);
|
||||
Enqueue((Frame)triangles.Length);
|
||||
foreach (QTriangle triangle in triangles)
|
||||
{
|
||||
Enqueue(triangle.A);
|
||||
Enqueue(triangle.B);
|
||||
Enqueue(triangle.C);
|
||||
}
|
||||
}
|
||||
|
||||
public void Polygon(params QVec2[] polygon)
|
||||
{
|
||||
Enqueue(Command.Polygon);
|
||||
Enqueue((Frame)polygon.Length);
|
||||
foreach (QVec2 vertex in polygon)
|
||||
{
|
||||
Enqueue(vertex);
|
||||
}
|
||||
}
|
||||
|
||||
public void Image(QImage texture, in QRectangle rectangle)
|
||||
{
|
||||
Enqueue(Command.Image);
|
||||
Enqueue((Frame)(int)ImageCommandFlags.Single);
|
||||
Enqueue(new Frame(texture));
|
||||
Enqueue(rectangle);
|
||||
}
|
||||
|
||||
public void Image(QImage texture, in QRectangle rectangle, in QRectangle uv)
|
||||
{
|
||||
Enqueue(Command.Image);
|
||||
Enqueue((Frame)(int)(ImageCommandFlags.Single | ImageCommandFlags.UVs));
|
||||
Enqueue(new Frame(texture));
|
||||
Enqueue(rectangle);
|
||||
Enqueue(uv);
|
||||
}
|
||||
|
||||
public void Image(QImage texture, ReadOnlySpan<QRectangle> rectangles, bool interleavedUV = false)
|
||||
{
|
||||
int count = rectangles.Length;
|
||||
ImageCommandFlags flags = ImageCommandFlags.None;
|
||||
|
||||
if (interleavedUV)
|
||||
{
|
||||
count /= 2;
|
||||
flags |= ImageCommandFlags.UVs;
|
||||
}
|
||||
|
||||
Enqueue(Command.Image);
|
||||
Enqueue(new Frame((int)flags, count));
|
||||
Enqueue(new Frame(texture));
|
||||
|
||||
foreach (QRectangle rectangle in rectangles)
|
||||
{
|
||||
Enqueue(rectangle);
|
||||
}
|
||||
}
|
||||
|
||||
public void Image(QImage texture, ReadOnlySpan<QRectangle> rectangles, ReadOnlySpan<QRectangle> uvs)
|
||||
{
|
||||
int count = Math.Min(rectangles.Length, uvs.Length);
|
||||
Enqueue(Command.Image);
|
||||
Enqueue(new Frame((int)ImageCommandFlags.UVs, count));
|
||||
Enqueue(new Frame(texture));
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Enqueue(rectangles[i]);
|
||||
Enqueue(uvs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void Image3D(QImage texture, in Image3DCall call)
|
||||
{
|
||||
Enqueue(Command.Image);
|
||||
Enqueue(new Frame(ImageCommandFlags.Image3d | ImageCommandFlags.Single));
|
||||
Enqueue(new Frame(texture));
|
||||
Enqueue(call.Rectangle);
|
||||
Enqueue(call.UVs);
|
||||
Enqueue(new Frame(call.Layer));
|
||||
}
|
||||
|
||||
public void Image3D(QImage texture, ReadOnlySpan<Image3DCall> calls)
|
||||
{
|
||||
Enqueue(Command.Image);
|
||||
Enqueue(new Frame((int)ImageCommandFlags.Image3d, calls.Length));
|
||||
Enqueue(new Frame(texture));
|
||||
|
||||
foreach (Image3DCall call in calls)
|
||||
{
|
||||
Enqueue(call.Rectangle);
|
||||
Enqueue(call.UVs);
|
||||
Enqueue(new Frame(call.Layer));
|
||||
}
|
||||
}
|
||||
|
||||
public void Splice(CommandList list)
|
||||
{
|
||||
foreach (Frame frame in list)
|
||||
{
|
||||
Enqueue(frame);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an object into the command list.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value to serialize.</typeparam>
|
||||
/// <param name="value">What to write into the command list.</param>
|
||||
public void Write<T>(T value)
|
||||
{
|
||||
CommandEngine.GetSerializer(value).Serialize(value, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an object into the command list.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value to serialize.</typeparam>
|
||||
/// <param name="value">What to write into the command list.</param>
|
||||
public void Write<T>(ICommandListSerializable<T> value)
|
||||
where T : ICommandListSerializable<T>, new()
|
||||
{
|
||||
CommandEngine.GetSerializer(value).Serialize((T)value, this);
|
||||
}
|
||||
|
||||
public CommandQueue GetEnumerator() => new CommandQueue(_frames);
|
||||
IEnumerator<Frame> IEnumerable<Frame>.GetEnumerator() => GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
||||
public class CommandQueue : IEnumerator<Frame>
|
||||
{
|
||||
private readonly IReadOnlyList<Frame> _frames;
|
||||
private int _current;
|
||||
|
||||
public Frame Current => _frames[_current];
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
public CommandQueue(IReadOnlyList<Frame> frames)
|
||||
{
|
||||
_current = -1;
|
||||
_frames = frames;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public bool TryDequeue([NotNullWhen(true)] out Frame frame)
|
||||
{
|
||||
if (MoveNext())
|
||||
{
|
||||
frame = Current;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
frame = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Frame Dequeue() => TryDequeue(out Frame frame) ? frame : throw new Exception("No more frames left.");
|
||||
|
||||
public bool TryPeek([NotNullWhen(true)] out Frame frame)
|
||||
{
|
||||
if (_current + 1 < _frames.Count)
|
||||
{
|
||||
frame = _frames[_current + 1];
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
frame = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Frame Peek() => TryPeek(out Frame frame) ? frame : throw new Exception("No more frames left.");
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize an object from the command queue.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the object to deserialize.</typeparam>
|
||||
/// <param name="value">The deserialized value.</param>
|
||||
public void Read<T>([NotNull] out T? value)
|
||||
{
|
||||
value = CommandEngine.GetSerializer(default(T)).Deserialize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize an object from the command queue.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the object to deserialize.</typeparam>
|
||||
/// <param name="value">The deserialized value.</param>
|
||||
public void Read<T>([NotNull] out ICommandListSerializable<T>? value)
|
||||
where T : ICommandListSerializable<T>, new()
|
||||
{
|
||||
value = CommandEngine.GetSerializer(value = null).Deserialize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_current + 1 < _frames.Count)
|
||||
{
|
||||
_current++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Reset()
|
||||
{
|
||||
_current = -1;
|
||||
}
|
||||
}
|
||||
}
|
356
Dashboard/CommandMachine/Frame.cs
Normal file
356
Dashboard/CommandMachine/Frame.cs
Normal file
@ -0,0 +1,356 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dashboard.CommandMachine
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct Frame
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
private FrameType _type;
|
||||
|
||||
[FieldOffset(sizeof(FrameType) + 0 * sizeof(int))]
|
||||
private int _i1;
|
||||
[FieldOffset(sizeof(FrameType) + 1 * sizeof(int))]
|
||||
private int _i2;
|
||||
[FieldOffset(sizeof(FrameType) + 2 * sizeof(int))]
|
||||
private int _i3;
|
||||
[FieldOffset(sizeof(FrameType) + 3 * sizeof(int))]
|
||||
private int _i4;
|
||||
|
||||
[FieldOffset(sizeof(FrameType) + 0 * sizeof(float))]
|
||||
private float _f1;
|
||||
[FieldOffset(sizeof(FrameType) + 1 * sizeof(float))]
|
||||
private float _f2;
|
||||
[FieldOffset(sizeof(FrameType) + 2 * sizeof(float))]
|
||||
private float _f3;
|
||||
[FieldOffset(sizeof(FrameType) + 3 * sizeof(float))]
|
||||
private float _f4;
|
||||
|
||||
[FieldOffset(24)]
|
||||
private object? _object = null;
|
||||
|
||||
public bool IsCommand => _type == FrameType.Command;
|
||||
public bool IsInteger =>
|
||||
_type == FrameType.IVec1 ||
|
||||
_type == FrameType.IVec2 ||
|
||||
_type == FrameType.IVec3 ||
|
||||
_type == FrameType.IVec4;
|
||||
public bool IsFloat =>
|
||||
_type == FrameType.Vec1 ||
|
||||
_type == FrameType.Vec2 ||
|
||||
_type == FrameType.Vec3 ||
|
||||
_type == FrameType.Vec4;
|
||||
|
||||
public int VectorSize
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (_type)
|
||||
{
|
||||
case FrameType.None:
|
||||
return 0;
|
||||
default:
|
||||
return 1;
|
||||
case FrameType.Vec2: case FrameType.IVec2:
|
||||
return 2;
|
||||
case FrameType.Vec3: case FrameType.IVec3:
|
||||
return 3;
|
||||
case FrameType.Vec4: case FrameType.IVec4:
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FrameType Type => _type;
|
||||
|
||||
public int I1 => _i1;
|
||||
public int I2 => _i2;
|
||||
public int I3 => _i3;
|
||||
public int I4 => _i4;
|
||||
|
||||
public float F1 => _f1;
|
||||
public float F2 => _f2;
|
||||
public float F3 => _f3;
|
||||
public float F4 => _f4;
|
||||
|
||||
public static Frame None { get; } = new Frame() {
|
||||
_type = FrameType.None
|
||||
};
|
||||
|
||||
#region Constructors
|
||||
public Frame(Command command) : this()
|
||||
{
|
||||
_type = FrameType.Command;
|
||||
_i1 = (int)command;
|
||||
}
|
||||
|
||||
public Frame(object o)
|
||||
{
|
||||
_type = FrameType.Object;
|
||||
|
||||
_i1 = _i2 = _i3 = _i4 = default;
|
||||
_f1 = _f2 = _f3 = _f4 = default;
|
||||
_object = null;
|
||||
|
||||
_object = o;
|
||||
}
|
||||
|
||||
public Frame(int i1)
|
||||
{
|
||||
_type = FrameType.IVec1;
|
||||
|
||||
_i1 = _i2 = _i3 = _i4 = default;
|
||||
_f1 = _f2 = _f3 = _f4 = default;
|
||||
_object = null;
|
||||
|
||||
_i1 = i1;
|
||||
}
|
||||
|
||||
public Frame(int i1, int i2)
|
||||
{
|
||||
_type = FrameType.IVec2;
|
||||
|
||||
_i1 = _i2 = _i3 = _i4 = default;
|
||||
_f1 = _f2 = _f3 = _f4 = default;
|
||||
_object = null;
|
||||
|
||||
_i1 = i1;
|
||||
_i2 = i2;
|
||||
}
|
||||
|
||||
public Frame(int i1, int i2, int i3)
|
||||
{
|
||||
_type = FrameType.IVec3;
|
||||
|
||||
_i1 = _i2 = _i3 = _i4 = default;
|
||||
_f1 = _f2 = _f3 = _f4 = default;
|
||||
_object = null;
|
||||
|
||||
_i1 = i1;
|
||||
_i2 = i2;
|
||||
_i3 = i3;
|
||||
}
|
||||
|
||||
public Frame(int i1, int i2, int i3, int i4)
|
||||
{
|
||||
_type = FrameType.IVec4;
|
||||
|
||||
_i1 = _i2 = _i3 = _i4 = default;
|
||||
_f1 = _f2 = _f3 = _f4 = default;
|
||||
_object = null;
|
||||
|
||||
_i1 = i1;
|
||||
_i2 = i2;
|
||||
_i3 = i3;
|
||||
_i4 = i4;
|
||||
}
|
||||
|
||||
public Frame(float f1)
|
||||
{
|
||||
_type = FrameType.Vec1;
|
||||
|
||||
_i1 = _i2 = _i3 = _i4 = default;
|
||||
_f1 = _f2 = _f3 = _f4 = default;
|
||||
_object = null;
|
||||
|
||||
_f1 = f1;
|
||||
}
|
||||
|
||||
public Frame(float f1, float f2)
|
||||
{
|
||||
_type = FrameType.Vec2;
|
||||
|
||||
_i1 = _i2 = _i3 = _i4 = default;
|
||||
_f1 = _f2 = _f3 = _f4 = default;
|
||||
_object = null;
|
||||
|
||||
_f1 = f1;
|
||||
_f2 = f2;
|
||||
}
|
||||
|
||||
public Frame(float f1, float f2, float f3)
|
||||
{
|
||||
_type = FrameType.Vec3;
|
||||
|
||||
_i1 = _i2 = _i3 = _i4 = default;
|
||||
_f1 = _f2 = _f3 = _f4 = default;
|
||||
_object = null;
|
||||
|
||||
_f1 = f1;
|
||||
_f2 = f2;
|
||||
_f3 = f3;
|
||||
}
|
||||
|
||||
public Frame(float f1, float f2, float f3, float f4)
|
||||
{
|
||||
_type = FrameType.Vec4;
|
||||
|
||||
_i1 = _i2 = _i3 = _i4 = default;
|
||||
_f1 = _f2 = _f3 = _f4 = default;
|
||||
_object = null;
|
||||
|
||||
_f1 = f1;
|
||||
_f2 = f2;
|
||||
_f3 = f3;
|
||||
_f4 = f4;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public T As<T>()
|
||||
{
|
||||
return (T)_object!;
|
||||
}
|
||||
|
||||
public float GetF(int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return _f1;
|
||||
case 1: return _f2;
|
||||
case 2: return _f3;
|
||||
case 3: return _f4;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
public int GetI(int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0: return _i1;
|
||||
case 1: return _i2;
|
||||
case 2: return _i3;
|
||||
case 3: return _i4;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
#region Frame->T Conversion
|
||||
|
||||
public static explicit operator int(in Frame frame)
|
||||
{
|
||||
switch (frame.Type)
|
||||
{
|
||||
default:
|
||||
throw new InvalidCastException();
|
||||
case FrameType.Command:
|
||||
case FrameType.IVec1:
|
||||
case FrameType.IVec2:
|
||||
case FrameType.IVec3:
|
||||
case FrameType.IVec4:
|
||||
return frame._i1;
|
||||
case FrameType.Vec1:
|
||||
case FrameType.Vec2:
|
||||
case FrameType.Vec3:
|
||||
case FrameType.Vec4:
|
||||
return (int)frame._f1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static explicit operator float(in Frame frame)
|
||||
{
|
||||
switch (frame.Type)
|
||||
{
|
||||
default:
|
||||
throw new InvalidCastException();
|
||||
case FrameType.IVec1:
|
||||
case FrameType.IVec2:
|
||||
case FrameType.IVec3:
|
||||
case FrameType.IVec4:
|
||||
return frame._i1;
|
||||
case FrameType.Vec1:
|
||||
case FrameType.Vec2:
|
||||
case FrameType.Vec3:
|
||||
case FrameType.Vec4:
|
||||
return frame._f1;
|
||||
}
|
||||
}
|
||||
|
||||
public static explicit operator Command(in Frame frame)
|
||||
{
|
||||
if (frame.Type != FrameType.Command)
|
||||
{
|
||||
throw new InvalidCastException("Not a command frame.");
|
||||
}
|
||||
|
||||
return (Command)frame._i1;
|
||||
}
|
||||
|
||||
public static explicit operator QVec2(in Frame frame)
|
||||
{
|
||||
switch (frame.Type)
|
||||
{
|
||||
default:
|
||||
throw new InvalidCastException();
|
||||
case FrameType.IVec2:
|
||||
case FrameType.IVec3:
|
||||
case FrameType.IVec4:
|
||||
return new QVec2(frame._i1, frame._i2);
|
||||
case FrameType.Vec2:
|
||||
case FrameType.Vec3:
|
||||
case FrameType.Vec4:
|
||||
return new QVec2(frame._f1, frame._f2);
|
||||
}
|
||||
}
|
||||
|
||||
public static explicit operator QColor(in Frame frame)
|
||||
{
|
||||
if (frame.Type != FrameType.IVec4)
|
||||
throw new InvalidCastException();
|
||||
|
||||
return new QColor((byte)frame._i1, (byte)frame._i2, (byte)frame._i3, (byte)frame._i4);
|
||||
}
|
||||
|
||||
public static explicit operator QRectangle(in Frame frame)
|
||||
{
|
||||
switch (frame.Type)
|
||||
{
|
||||
default:
|
||||
throw new InvalidCastException();
|
||||
case FrameType.IVec4:
|
||||
return new QRectangle(frame._i1, frame._i2, frame._i3, frame._i4);
|
||||
case FrameType.Vec4:
|
||||
return new QRectangle(frame._f1, frame._f2, frame._f3, frame._f4);
|
||||
}
|
||||
}
|
||||
|
||||
public static explicit operator QLine(in Frame frame)
|
||||
{
|
||||
switch (frame.Type)
|
||||
{
|
||||
default:
|
||||
throw new InvalidCastException();
|
||||
case FrameType.IVec4:
|
||||
return new QLine(frame._i1, frame._i2, frame._i3, frame._i4);
|
||||
case FrameType.Vec4:
|
||||
return new QLine(frame._f1, frame._f2, frame._f3, frame._f4);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static explicit operator Frame(int i) => new Frame(i);
|
||||
public static explicit operator Frame(float f) => new Frame(f);
|
||||
public static implicit operator Frame(Command cmd) => new Frame(cmd);
|
||||
public static implicit operator Frame(in QVec2 vector) => new Frame(vector.X, vector.Y);
|
||||
public static implicit operator Frame(in QColor color) => new Frame(color.R, color.G, color.B, color.A);
|
||||
public static implicit operator Frame(in QRectangle rect) => new Frame(rect.Max.X, rect.Max.Y, rect.Min.X, rect.Min.Y);
|
||||
public static implicit operator Frame(in QLine line) => new Frame(line.Start.X, line.Start.Y, line.End.X, line.Start.Y);
|
||||
|
||||
public static void Create(in QBezier bezier, out Frame a, out Frame b)
|
||||
{
|
||||
a = new Frame(bezier.Start.X, bezier.Start.Y, bezier.End.X, bezier.End.Y);
|
||||
b = new Frame(bezier.ControlA.X, bezier.ControlA.Y, bezier.ControlB.X, bezier.ControlB.Y);
|
||||
}
|
||||
|
||||
public static void Create(in QEllipse ellipse, out Frame a, out Frame b)
|
||||
{
|
||||
a = new Frame(ellipse.Center.X, ellipse.Center.Y);
|
||||
b = new Frame(ellipse.AxisA.X, ellipse.AxisA.Y, ellipse.AxisB.X, ellipse.AxisB.Y);
|
||||
}
|
||||
}
|
||||
}
|
68
Dashboard/CommandMachine/FrameType.cs
Normal file
68
Dashboard/CommandMachine/FrameType.cs
Normal file
@ -0,0 +1,68 @@
|
||||
namespace Dashboard.CommandMachine
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration of command types in the Dashboard command lists.
|
||||
/// </summary>
|
||||
public enum FrameType
|
||||
{
|
||||
/// <summary>
|
||||
/// A null value.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// A command frame.
|
||||
/// </summary>
|
||||
Command,
|
||||
|
||||
/// <summary>
|
||||
/// An integer frame.
|
||||
/// </summary>
|
||||
IVec1,
|
||||
|
||||
/// <summary>
|
||||
/// A two dimensional integer vector frame.
|
||||
/// </summary>
|
||||
IVec2,
|
||||
|
||||
/// <summary>
|
||||
/// A three dimensional integer vector frame.
|
||||
/// </summary>
|
||||
IVec3,
|
||||
|
||||
/// <summary>
|
||||
/// A four dimensional integer vector frame.
|
||||
/// </summary>
|
||||
IVec4,
|
||||
|
||||
/// <summary>
|
||||
/// A floating point frame.
|
||||
/// </summary>
|
||||
Vec1,
|
||||
|
||||
/// <summary>
|
||||
/// A two dimensional floating point vector frame.
|
||||
/// </summary>
|
||||
Vec2,
|
||||
|
||||
/// <summary>
|
||||
/// A three dimensional floating point vector frame.
|
||||
/// </summary>
|
||||
Vec3,
|
||||
|
||||
/// <summary>
|
||||
/// A four dimensional floating point vector frame.
|
||||
/// </summary>
|
||||
Vec4,
|
||||
|
||||
/// <summary>
|
||||
/// A serialized object frame.
|
||||
/// </summary>
|
||||
Serialized,
|
||||
|
||||
/// <summary>
|
||||
/// A .Net object frame.
|
||||
/// </summary>
|
||||
Object,
|
||||
}
|
||||
}
|
18
Dashboard/CommandMachine/Image.cs
Normal file
18
Dashboard/CommandMachine/Image.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace Dashboard.CommandMachine
|
||||
{
|
||||
public enum ImageCommandFlags
|
||||
{
|
||||
None = 0,
|
||||
|
||||
Single = 1 << 0,
|
||||
UVs = 1 << 1,
|
||||
Image3d = 1 << 2,
|
||||
}
|
||||
|
||||
public struct Image3DCall
|
||||
{
|
||||
public QRectangle Rectangle;
|
||||
public QRectangle UVs;
|
||||
public int Layer;
|
||||
}
|
||||
}
|
69
Dashboard/CommandMachine/Serializers.cs
Normal file
69
Dashboard/CommandMachine/Serializers.cs
Normal file
@ -0,0 +1,69 @@
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
|
||||
namespace Dashboard.CommandMachine
|
||||
{
|
||||
public interface ICommandListSerializable { }
|
||||
|
||||
/// <summary>
|
||||
/// Interface for objects that can be serialized into the Dashboard command stream.
|
||||
/// </summary>
|
||||
public interface ICommandListSerializable<T> : ICommandListSerializable
|
||||
{
|
||||
/// <summary>
|
||||
/// Seralize object.
|
||||
/// </summary>
|
||||
/// <param name="list">The object to serialize into.</param>
|
||||
void Serialize(CommandList list);
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize object.
|
||||
/// </summary>
|
||||
/// <param name="queue">The command queue to deserialize from.</param>
|
||||
void Deserialize(CommandQueue queue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base interface for all Command List serializers.
|
||||
/// </summary>
|
||||
public interface ICommandListSerializer { }
|
||||
|
||||
public interface ICommandListSerializer<T> : ICommandListSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Serialize an object into the command list.
|
||||
/// </summary>
|
||||
/// <param name="value">The object to serialize.</param>
|
||||
/// <param name="list">The command list to serialize into.</param>
|
||||
void Serialize(T value, CommandList list);
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize an object from the command queue.
|
||||
/// </summary>
|
||||
/// <param name="queue">The command queue.</param>
|
||||
/// <returns>The object deserialized from the command queue.</returns>
|
||||
[return: NotNull]
|
||||
T Deserialize(CommandQueue queue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class for automatic serialization of <see cref="ICommandListSerializable"/> objects.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The object type to convert.</typeparam>
|
||||
internal class CommandListSerializableSerializer<T> : ICommandListSerializer<T>
|
||||
where T : ICommandListSerializable<T>, new()
|
||||
{
|
||||
public T Deserialize(CommandQueue queue)
|
||||
{
|
||||
T value = new T();
|
||||
value.Deserialize(queue);
|
||||
return value;
|
||||
}
|
||||
|
||||
public void Serialize(T value, CommandList list)
|
||||
{
|
||||
value.Serialize(list);
|
||||
}
|
||||
}
|
||||
}
|
56
Dashboard/Controls/CommonEnums.cs
Normal file
56
Dashboard/Controls/CommonEnums.cs
Normal file
@ -0,0 +1,56 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace Dashboard.Controls
|
||||
{
|
||||
public enum Dock
|
||||
{
|
||||
None,
|
||||
Top,
|
||||
Left,
|
||||
Bottom,
|
||||
Right,
|
||||
Center
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum Anchor
|
||||
{
|
||||
None = 0,
|
||||
Top = 1 << 0,
|
||||
Left = 1 << 1,
|
||||
Bottom = 1 << 2,
|
||||
Right = 1 << 3,
|
||||
All = Top | Left | Bottom | Right
|
||||
}
|
||||
|
||||
public enum Direction
|
||||
{
|
||||
Vertical,
|
||||
Horizontal
|
||||
}
|
||||
|
||||
public enum TextAlignment
|
||||
{
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
Justify,
|
||||
}
|
||||
|
||||
public enum VerticalAlignment
|
||||
{
|
||||
Top,
|
||||
Center,
|
||||
Bottom,
|
||||
Justify,
|
||||
}
|
||||
|
||||
public enum HorizontalAlignment
|
||||
{
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
Justify
|
||||
}
|
||||
}
|
50
Dashboard/Controls/ContainerControl.cs
Normal file
50
Dashboard/Controls/ContainerControl.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Dashboard.Controls
|
||||
{
|
||||
public abstract class ContainerControl : Control, ICollection<Control>
|
||||
{
|
||||
private readonly List<Control> children = new List<Control>();
|
||||
|
||||
public int Count => children.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public void Add(Control item)
|
||||
{
|
||||
children.Add(item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
children.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(Control item)
|
||||
{
|
||||
return children.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(Control[] array, int arrayIndex)
|
||||
{
|
||||
children.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public IEnumerator<Control> GetEnumerator()
|
||||
{
|
||||
return children.GetEnumerator();
|
||||
}
|
||||
|
||||
public bool Remove(Control item)
|
||||
{
|
||||
return children.Remove(item);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return children.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
125
Dashboard/Controls/Control.cs
Normal file
125
Dashboard/Controls/Control.cs
Normal file
@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dashboard.CommandMachine;
|
||||
|
||||
namespace Dashboard.Controls
|
||||
{
|
||||
public abstract class Control : UIBase
|
||||
{
|
||||
private readonly CommandList drawCommands = new CommandList();
|
||||
|
||||
public Style Style { get; set; } = new Style();
|
||||
public float Padding
|
||||
{
|
||||
get => (float)(Style["padding"] ?? 0.0f);
|
||||
set => Style["padding"] = value;
|
||||
}
|
||||
|
||||
public bool IsVisualsValid { get; private set; } = false;
|
||||
public bool IsLayoutValid { get; private set; } = false;
|
||||
|
||||
protected bool IsLayoutSuspended { get; private set; } = false;
|
||||
|
||||
public void InvalidateVisual()
|
||||
{
|
||||
IsVisualsValid = false;
|
||||
OnVisualsInvalidated(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void InvalidateLayout()
|
||||
{
|
||||
IsLayoutValid = false;
|
||||
OnLayoutInvalidated(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void SuspendLayout()
|
||||
{
|
||||
IsLayoutSuspended = true;
|
||||
}
|
||||
|
||||
public void ResumeLayout()
|
||||
{
|
||||
IsLayoutSuspended = false;
|
||||
InvalidateLayout();
|
||||
}
|
||||
|
||||
protected abstract void ValidateVisual(CommandList cmd);
|
||||
protected abstract void ValidateLayout();
|
||||
|
||||
protected override void PaintBegin(CommandList cmd)
|
||||
{
|
||||
base.PaintBegin(cmd);
|
||||
|
||||
if (!IsLayoutValid && !IsLayoutSuspended)
|
||||
{
|
||||
ValidateLayout();
|
||||
OnLayoutValidated(this, EventArgs.Empty);
|
||||
IsLayoutValid = true;
|
||||
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
if (!IsVisualsValid)
|
||||
{
|
||||
ValidateVisual(drawCommands);
|
||||
OnVisualsValidated(this, EventArgs.Empty);
|
||||
IsVisualsValid = true;
|
||||
}
|
||||
|
||||
cmd.PushStyle(Style);
|
||||
cmd.PushViewport();
|
||||
cmd.StoreViewport(AbsoluteBounds);
|
||||
|
||||
cmd.Splice(drawCommands);
|
||||
|
||||
cmd.PopViewport();
|
||||
cmd.PopStyle();
|
||||
}
|
||||
|
||||
public event EventHandler? StyleChanged;
|
||||
public event EventHandler? VisualsInvalidated;
|
||||
public event EventHandler? VisualsValidated;
|
||||
public event EventHandler? LayoutInvalidated;
|
||||
public event EventHandler? LayoutValidated;
|
||||
|
||||
protected virtual void OnStyleChanged(object sender, EventArgs ea)
|
||||
{
|
||||
StyleChanged?.Invoke(sender, ea);
|
||||
InvalidateLayout();
|
||||
}
|
||||
|
||||
protected virtual void OnVisualsInvalidated(object sender, EventArgs ea)
|
||||
{
|
||||
VisualsInvalidated?.Invoke(sender, ea);
|
||||
}
|
||||
|
||||
protected virtual void OnVisualsValidated(object sender, EventArgs ea)
|
||||
{
|
||||
VisualsValidated?.Invoke(sender, ea);
|
||||
}
|
||||
|
||||
protected virtual void OnLayoutInvalidated(object sender, EventArgs ea)
|
||||
{
|
||||
LayoutInvalidated?.Invoke(sender, ea);
|
||||
}
|
||||
|
||||
protected virtual void OnLayoutValidated(object sender, EventArgs ea)
|
||||
{
|
||||
LayoutValidated?.Invoke(sender, ea);
|
||||
}
|
||||
|
||||
protected void ValidateChildrenLayout()
|
||||
{
|
||||
if (this is IEnumerable<Control> enumerable)
|
||||
{
|
||||
foreach (Control child in enumerable)
|
||||
{
|
||||
if (child.IsLayoutValid)
|
||||
continue;
|
||||
|
||||
child.ValidateLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
100
Dashboard/Controls/FlowBox.cs
Normal file
100
Dashboard/Controls/FlowBox.cs
Normal file
@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dashboard.CommandMachine;
|
||||
|
||||
namespace Dashboard.Controls
|
||||
{
|
||||
public class FlowBox : ContainerControl
|
||||
{
|
||||
public Direction FlowDirection { get; set; }
|
||||
public bool AllowWrap { get; set; }
|
||||
public VerticalAlignment VerticalAlignment { get; set; }
|
||||
public HorizontalAlignment HorizontalAlignment { get; set; }
|
||||
public float ItemPadding { get; set; } = 4f;
|
||||
|
||||
protected override void ValidateLayout()
|
||||
{
|
||||
ValidateChildrenLayout();
|
||||
}
|
||||
|
||||
protected override void ValidateVisual(CommandList cmd)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private void FlowHorizontal()
|
||||
{
|
||||
IEnumerator<Control> controls = this.GetEnumerator();
|
||||
List<Control> row;
|
||||
}
|
||||
|
||||
// Enumerate a row.
|
||||
private bool EnumerateRows(IEnumerator<Control> iterator, List<Control> row)
|
||||
{
|
||||
float width = 0;
|
||||
|
||||
do
|
||||
{
|
||||
if (width + iterator.Current.Size.X < Size.X)
|
||||
{
|
||||
row.Add(iterator.Current);
|
||||
width += iterator.Current.Size.X + ItemPadding;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
} while (iterator.MoveNext());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Flows a row of children.
|
||||
private void FlowRow(List<Control> line, QVec2 offset, QVec2 size, float packedWidth)
|
||||
{
|
||||
QVec2 pointer = offset;
|
||||
|
||||
pointer.X += hstart();
|
||||
|
||||
foreach (Control child in line)
|
||||
{
|
||||
child.Position = pointer;
|
||||
pointer += new QVec2(child.Size.X + hoffset(child), voffset(child));
|
||||
}
|
||||
|
||||
float hstart()
|
||||
{
|
||||
return HorizontalAlignment switch {
|
||||
HorizontalAlignment.Center => (size.Y - packedWidth) / 2,
|
||||
HorizontalAlignment.Right => size.Y - packedWidth,
|
||||
_ => 0f
|
||||
};
|
||||
}
|
||||
|
||||
float hoffset(Control child)
|
||||
{
|
||||
if (line.Count == 1)
|
||||
return 0;
|
||||
else if (HorizontalAlignment == HorizontalAlignment.Justify)
|
||||
{
|
||||
return ItemPadding + ((size.Y - packedWidth) / (line.Count - 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
return ItemPadding;
|
||||
}
|
||||
}
|
||||
|
||||
float voffset(Control child)
|
||||
{
|
||||
return VerticalAlignment switch {
|
||||
VerticalAlignment.Top => 0f,
|
||||
VerticalAlignment.Bottom => size.Y - child.Size.Y,
|
||||
_ => (size.Y - child.Size.Y) / 2,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
31
Dashboard/Controls/Label.cs
Normal file
31
Dashboard/Controls/Label.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using Dashboard.CommandMachine;
|
||||
using Dashboard.Media;
|
||||
using Dashboard.Typography;
|
||||
|
||||
namespace Dashboard.Controls
|
||||
{
|
||||
public class Label : Control
|
||||
{
|
||||
public string Text { get; set; } = string.Empty;
|
||||
public QFont? Font { get; set; }
|
||||
public float TextSize { get; set; }
|
||||
public bool AutoSize { get; set; } = true;
|
||||
|
||||
protected override void ValidateLayout()
|
||||
{
|
||||
if (AutoSize)
|
||||
{
|
||||
QVec2 size = Typesetter.MeasureHorizontal(Text, TextSize, Font!);
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ValidateVisual(CommandList cmd)
|
||||
{
|
||||
float padding = Padding;
|
||||
QVec2 origin = new QVec2(padding, padding);
|
||||
|
||||
cmd.TypesetHorizontalDirect(Text, origin, TextSize, Font!);
|
||||
}
|
||||
}
|
||||
}
|
102
Dashboard/Controls/UIBase.cs
Normal file
102
Dashboard/Controls/UIBase.cs
Normal file
@ -0,0 +1,102 @@
|
||||
|
||||
using System;
|
||||
using Dashboard.CommandMachine;
|
||||
|
||||
namespace Dashboard.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Bases for all UI elements.
|
||||
/// </summary>
|
||||
public abstract class UIBase
|
||||
{
|
||||
private QVec2 size;
|
||||
|
||||
public UIBase? Parent { get; protected set; }
|
||||
public string? Id { get; set; }
|
||||
public QRectangle Bounds
|
||||
{
|
||||
get => new QRectangle(Position + Size, Position);
|
||||
set
|
||||
{
|
||||
Size = value.Size;
|
||||
Position = value.Min;
|
||||
}
|
||||
}
|
||||
|
||||
public QVec2 Position { get; set; }
|
||||
|
||||
public QVec2 Size
|
||||
{
|
||||
get => size;
|
||||
set
|
||||
{
|
||||
QVec2 oldSize = size;
|
||||
size = value;
|
||||
|
||||
OnResized(this, new ResizedEventArgs(size, oldSize));
|
||||
}
|
||||
}
|
||||
|
||||
public QRectangle AbsoluteBounds
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Parent == null)
|
||||
{
|
||||
return Bounds;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new QRectangle(Bounds.Max + Parent.Position, Bounds.Min + Parent.Position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public QVec2 MaximumSize { get; set; } = new QVec2(-1, -1);
|
||||
public QVec2 MinimumSize { get; set; } = new QVec2(-1, -1);
|
||||
|
||||
public bool IsMaximumSizeSet => MaximumSize != new QVec2(-1, -1);
|
||||
public bool IsMinimumSizeSet => MinimumSize != new QVec2(-1, -1);
|
||||
|
||||
public virtual void NotifyEvent(object? sender, EventArgs args)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void PaintBegin(CommandList cmd)
|
||||
{
|
||||
cmd.PushViewport();
|
||||
cmd.StoreViewport(AbsoluteBounds);
|
||||
cmd.PushZ();
|
||||
}
|
||||
|
||||
protected virtual void PaintEnd(CommandList cmd)
|
||||
{
|
||||
cmd.PopViewport();
|
||||
}
|
||||
|
||||
public void Paint(CommandList cmd)
|
||||
{
|
||||
PaintBegin(cmd);
|
||||
PaintEnd(cmd);
|
||||
}
|
||||
|
||||
public event EventHandler<ResizedEventArgs>? Resized;
|
||||
|
||||
public virtual void OnResized(object sender, ResizedEventArgs ea)
|
||||
{
|
||||
Resized?.Invoke(sender, ea);
|
||||
}
|
||||
}
|
||||
|
||||
public class ResizedEventArgs : EventArgs
|
||||
{
|
||||
public QVec2 NewSize { get; }
|
||||
public QVec2 OldSize { get; }
|
||||
|
||||
public ResizedEventArgs(QVec2 newSize, QVec2 oldSize)
|
||||
{
|
||||
NewSize = newSize;
|
||||
OldSize = oldSize;
|
||||
}
|
||||
}
|
||||
}
|
8
Dashboard/Controls/View.cs
Normal file
8
Dashboard/Controls/View.cs
Normal file
@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace Dashboard.Controls
|
||||
{
|
||||
public class View : UIBase
|
||||
{
|
||||
}
|
||||
}
|
@ -1,9 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="res/**" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
109
Dashboard/Media/Color/FormatConvert.cs
Normal file
109
Dashboard/Media/Color/FormatConvert.cs
Normal file
@ -0,0 +1,109 @@
|
||||
using System;
|
||||
|
||||
namespace Dashboard.Media.Color
|
||||
{
|
||||
public static class FormatConvert
|
||||
{
|
||||
public static void Premultiply(QImageLock image)
|
||||
{
|
||||
switch (image.Format)
|
||||
{
|
||||
case QImageFormat.RaF:
|
||||
case QImageFormat.RaU8:
|
||||
case QImageFormat.RgbF:
|
||||
case QImageFormat.RgbU8:
|
||||
case QImageFormat.RgbaF:
|
||||
case QImageFormat.RgbaU8:
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
int count = image.Width * image.Height * image.Depth;
|
||||
|
||||
if (image.Format.IsFloat())
|
||||
{
|
||||
LockIOF io = new LockIOF(image);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
QColorF color = io[i];
|
||||
|
||||
color.R *= color.A;
|
||||
color.G *= color.A;
|
||||
color.G *= color.A;
|
||||
|
||||
io[i] = color;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LockIO io = new LockIO(image);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
QColor color = io[i];
|
||||
float a = color.A/255.0f;
|
||||
|
||||
color.R = (byte)(color.R * a);
|
||||
color.G = (byte)(color.G * a);
|
||||
color.B = (byte)(color.B * a);
|
||||
|
||||
io[i] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Convert(QImageLock dst, QImageLock src)
|
||||
{
|
||||
if (dst.Format.IsU8() && src.Format.IsU8())
|
||||
{
|
||||
LockIO dstIO = new LockIO(dst);
|
||||
LockIO srcIO = new LockIO(src);
|
||||
|
||||
int count = dst.Width * dst.Height * dst.Depth;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
dstIO[i] = srcIO[i];
|
||||
}
|
||||
}
|
||||
else if (dst.Format.IsU8() && src.Format.IsFloat())
|
||||
{
|
||||
LockIO dstIO = new LockIO(dst);
|
||||
LockIOF srcIO = new LockIOF(src);
|
||||
|
||||
int count = dst.Width * dst.Height * dst.Depth;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
dstIO[i] = (QColor)srcIO[i];
|
||||
}
|
||||
}
|
||||
else if (dst.Format.IsFloat() && src.Format.IsU8())
|
||||
{
|
||||
LockIOF dstIO = new LockIOF(dst);
|
||||
LockIO srcIO = new LockIO(src);
|
||||
|
||||
int count = dst.Width * dst.Height * dst.Depth;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
dstIO[i] = (QColorF)srcIO[i];
|
||||
}
|
||||
}
|
||||
else if (dst.Format.IsFloat() && src.Format.IsFloat())
|
||||
{
|
||||
LockIOF dstIO = new LockIOF(dst);
|
||||
LockIOF srcIO = new LockIOF(src);
|
||||
|
||||
int count = dst.Width * dst.Height * dst.Depth;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
dstIO[i] = srcIO[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Congratulations you have broken image formats!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
69
Dashboard/Media/Color/ImageBuffer.cs
Normal file
69
Dashboard/Media/Color/ImageBuffer.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dashboard.Media.Color
|
||||
{
|
||||
public class QImageBuffer : QImage
|
||||
{
|
||||
private byte[] buffer;
|
||||
GCHandle handle;
|
||||
private bool isSdf = false;
|
||||
|
||||
public override QImageFormat InternalFormat { get; }
|
||||
public override int Width { get; }
|
||||
public override int Height { get; }
|
||||
public override int Depth { get; }
|
||||
public override bool IsSdf => isSdf;
|
||||
|
||||
public QImageBuffer(QImageFormat format, int width, int height, int depth = 1)
|
||||
{
|
||||
InternalFormat = format;
|
||||
Width = width;
|
||||
Height = height;
|
||||
Depth = depth;
|
||||
|
||||
buffer = new byte[width * height * depth];
|
||||
}
|
||||
~QImageBuffer()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
private QImageLock Lock()
|
||||
{
|
||||
if (handle.IsAllocated) handle.Free();
|
||||
handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
|
||||
IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
|
||||
return new QImageLock(InternalFormat, Width, Height, Depth, ptr);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (handle.IsAllocated) handle.Free();
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public override void LockBits2d(out QImageLock imageLock, QImageLockOptions options)
|
||||
{
|
||||
imageLock = Lock();
|
||||
}
|
||||
|
||||
public override void LockBits3d(out QImageLock imageLock, QImageLockOptions options)
|
||||
{
|
||||
imageLock = Lock();
|
||||
}
|
||||
|
||||
public override void LockBits3d(out QImageLock imageLock, QImageLockOptions options, int depth)
|
||||
{
|
||||
imageLock = Lock();
|
||||
}
|
||||
|
||||
public override void UnlockBits()
|
||||
{
|
||||
handle.Free();
|
||||
}
|
||||
|
||||
public void SetSdf(bool value = true) => isSdf = value;
|
||||
}
|
||||
}
|
150
Dashboard/Media/Color/LockIO.cs
Normal file
150
Dashboard/Media/Color/LockIO.cs
Normal file
@ -0,0 +1,150 @@
|
||||
using System;
|
||||
|
||||
namespace Dashboard.Media.Color
|
||||
{
|
||||
public unsafe struct LockIO
|
||||
{
|
||||
public QImageLock Lock { get; }
|
||||
public int Width => Lock.Width;
|
||||
public int Height => Lock.Height;
|
||||
public int Depth => Depth;
|
||||
public QImageFormat Format => Lock.Format;
|
||||
|
||||
public LockIO(QImageLock imageLock)
|
||||
{
|
||||
if (!imageLock.Format.IsU8())
|
||||
throw new Exception("Can only read/write U8 format images");
|
||||
|
||||
Lock = imageLock;
|
||||
}
|
||||
|
||||
public QColor this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
int chan = Format.Channels();
|
||||
byte *ptr = (byte*)Lock.ImagePtr + chan * index;
|
||||
|
||||
switch (Format)
|
||||
{
|
||||
default:
|
||||
case QImageFormat.RedU8: return new QColor(ptr[0], 0, 0, 255);
|
||||
case QImageFormat.AlphaU8: return new QColor(0, 0, 0, ptr[0]);
|
||||
case QImageFormat.RaU8: return new QColor(ptr[0], 0, 0, ptr[1]);
|
||||
case QImageFormat.RgbU8: return new QColor(ptr[0], ptr[1], ptr[2], 255);
|
||||
case QImageFormat.RgbaU8: return new QColor(ptr[0], ptr[1], ptr[2], ptr[3]);
|
||||
}
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
int chan = Format.Channels();
|
||||
byte *ptr = (byte*)Lock.ImagePtr + chan * index;
|
||||
|
||||
switch (Format)
|
||||
{
|
||||
default:
|
||||
case QImageFormat.RedU8:
|
||||
ptr[0] = value.R;
|
||||
break;
|
||||
case QImageFormat.AlphaU8:
|
||||
ptr[0] = value.A;
|
||||
break;
|
||||
case QImageFormat.RaU8:
|
||||
ptr[0] = value.R;
|
||||
ptr[1] = value.A;
|
||||
break;
|
||||
case QImageFormat.RgbU8:
|
||||
ptr[0] = value.R;
|
||||
ptr[1] = value.G;
|
||||
ptr[2] = value.B;
|
||||
break;
|
||||
case QImageFormat.RgbaU8:
|
||||
ptr[0] = value.R;
|
||||
ptr[1] = value.G;
|
||||
ptr[2] = value.B;
|
||||
ptr[3] = value.A;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
public QColor this[int x, int y, int z = 0]
|
||||
{
|
||||
get => this[x + y * Width + z * Width * Height];
|
||||
set => this[x + y * Width + z * Width * Height] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe struct LockIOF
|
||||
{
|
||||
public QImageLock Lock { get; }
|
||||
public int Width => Lock.Width;
|
||||
public int Height => Lock.Height;
|
||||
public int Depth => Depth;
|
||||
public QImageFormat Format => Lock.Format;
|
||||
|
||||
public LockIOF(QImageLock imageLock)
|
||||
{
|
||||
if (!imageLock.Format.IsFloat())
|
||||
throw new Exception("Can only read/write U8 format images");
|
||||
|
||||
Lock = imageLock;
|
||||
}
|
||||
|
||||
public QColorF this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
int chan = Format.Channels();
|
||||
float *ptr = (float*)Lock.ImagePtr + chan * index;
|
||||
|
||||
switch (Format)
|
||||
{
|
||||
default:
|
||||
case QImageFormat.RedU8: return new QColorF(ptr[0], 0, 0, 255);
|
||||
case QImageFormat.AlphaU8: return new QColorF(0, 0, 0, ptr[0]);
|
||||
case QImageFormat.RaU8: return new QColorF(ptr[0], 0, 0, ptr[1]);
|
||||
case QImageFormat.RgbU8: return new QColorF(ptr[0], ptr[1], ptr[2], 255);
|
||||
case QImageFormat.RgbaU8: return new QColorF(ptr[0], ptr[1], ptr[2], ptr[3]);
|
||||
}
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
int chan = Format.Channels();
|
||||
float *ptr = (float*)Lock.ImagePtr + chan * index;
|
||||
|
||||
switch (Format)
|
||||
{
|
||||
default:
|
||||
case QImageFormat.RedU8:
|
||||
ptr[0] = value.R;
|
||||
break;
|
||||
case QImageFormat.AlphaU8:
|
||||
ptr[0] = value.A;
|
||||
break;
|
||||
case QImageFormat.RaU8:
|
||||
ptr[0] = value.R;
|
||||
ptr[1] = value.A;
|
||||
break;
|
||||
case QImageFormat.RgbU8:
|
||||
ptr[0] = value.R;
|
||||
ptr[1] = value.G;
|
||||
ptr[2] = value.B;
|
||||
break;
|
||||
case QImageFormat.RgbaU8:
|
||||
ptr[0] = value.R;
|
||||
ptr[1] = value.G;
|
||||
ptr[2] = value.B;
|
||||
ptr[3] = value.A;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
public QColorF this[int x, int y, int z = 0]
|
||||
{
|
||||
get => this[x + y * Width + z * Width * Height];
|
||||
set => this[x + y * Width + z * Width * Height] = value;
|
||||
}
|
||||
}
|
||||
}
|
71
Dashboard/Media/Extensions.cs
Normal file
71
Dashboard/Media/Extensions.cs
Normal file
@ -0,0 +1,71 @@
|
||||
namespace Dashboard.Media
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static bool IsU8(this QImageFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case QImageFormat.AlphaU8:
|
||||
case QImageFormat.RedU8:
|
||||
case QImageFormat.RaU8:
|
||||
case QImageFormat.RgbU8:
|
||||
case QImageFormat.RgbaU8:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsFloat(this QImageFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case QImageFormat.AlphaF:
|
||||
case QImageFormat.RedF:
|
||||
case QImageFormat.RaF:
|
||||
case QImageFormat.RgbF:
|
||||
case QImageFormat.RgbaF:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static int BytesPerPixel(this QImageFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case QImageFormat.AlphaU8: return sizeof(byte);
|
||||
case QImageFormat.RedU8: return sizeof(byte);
|
||||
case QImageFormat.RaU8: return 2 * sizeof(byte);
|
||||
case QImageFormat.RgbU8: return 3 * sizeof(byte);
|
||||
case QImageFormat.RgbaU8: return 4 * sizeof(byte);
|
||||
case QImageFormat.AlphaF: return sizeof(float);
|
||||
case QImageFormat.RedF: return sizeof(float);
|
||||
case QImageFormat.RaF: return 2 * sizeof(float);
|
||||
case QImageFormat.RgbF: return 3 * sizeof(float);
|
||||
case QImageFormat.RgbaF: return 4 * sizeof(float);
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static int Channels(this QImageFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case QImageFormat.AlphaU8: return 1;
|
||||
case QImageFormat.RedU8: return 1;
|
||||
case QImageFormat.RaU8: return 2;
|
||||
case QImageFormat.RgbU8: return 3;
|
||||
case QImageFormat.RgbaU8: return 4;
|
||||
case QImageFormat.AlphaF: return 1;
|
||||
case QImageFormat.RedF: return 1;
|
||||
case QImageFormat.RaF: return 2;
|
||||
case QImageFormat.RgbF: return 3;
|
||||
case QImageFormat.RgbaF: return 4;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
184
Dashboard/Media/Font/FontAtlas.cs
Normal file
184
Dashboard/Media/Font/FontAtlas.cs
Normal file
@ -0,0 +1,184 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dashboard.Media.Color;
|
||||
|
||||
namespace Dashboard.Media.Font
|
||||
{
|
||||
public struct FontAtlasGlyphInfo
|
||||
{
|
||||
public int Codepoint;
|
||||
public QImage Image;
|
||||
public QRectangle UVs;
|
||||
}
|
||||
|
||||
public class FontAtlas
|
||||
{
|
||||
private readonly int width, height;
|
||||
private readonly List<AtlasPage> atlases = new List<AtlasPage>();
|
||||
private readonly Dictionary<int, FontAtlasGlyphInfo> glyphs = new Dictionary<int, FontAtlasGlyphInfo>();
|
||||
private int index = 0;
|
||||
private AtlasPage? last = null;
|
||||
private bool isSdf = false;
|
||||
private int expansion;
|
||||
|
||||
public bool IsSdf
|
||||
{
|
||||
get => isSdf;
|
||||
set
|
||||
{
|
||||
foreach (AtlasPage page in atlases)
|
||||
{
|
||||
((QImageBuffer)page.Image).SetSdf(value);
|
||||
}
|
||||
isSdf = value;
|
||||
}
|
||||
}
|
||||
|
||||
public FontAtlas(int width, int height, bool isSdf, int expansion = 4)
|
||||
{
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
IsSdf = isSdf;
|
||||
this.expansion = expansion;
|
||||
}
|
||||
|
||||
public bool GetGlyph(int codepoint, out FontAtlasGlyphInfo info)
|
||||
{
|
||||
return glyphs.TryGetValue(codepoint, out info);
|
||||
}
|
||||
|
||||
public void PutGlyph(int codepoint, QImageLock source, out FontAtlasGlyphInfo info)
|
||||
{
|
||||
info = new FontAtlasGlyphInfo() { Codepoint = codepoint };
|
||||
|
||||
if (last == null || !last.WouldFit(source))
|
||||
{
|
||||
AddPage();
|
||||
}
|
||||
|
||||
last!.PutGlyph(source, ref info);
|
||||
}
|
||||
|
||||
private void AddPage()
|
||||
{
|
||||
index++;
|
||||
|
||||
if (index < atlases.Count)
|
||||
{
|
||||
last = atlases[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
last = new AtlasPage(width, height, expansion);
|
||||
((QImageBuffer)last.Image).SetSdf(IsSdf);
|
||||
atlases.Add(last);
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
// Trim any pages that were not used yet.
|
||||
for (int i = atlases.Count -1; i >= 0; i--)
|
||||
{
|
||||
if (atlases[i].PointerX != 0 && atlases[i].PointerY != 0)
|
||||
{
|
||||
for (int j = i + 1; j < atlases.Count; j++)
|
||||
{
|
||||
atlases[j].Dispose();
|
||||
}
|
||||
|
||||
if (i != atlases.Count - 1)
|
||||
atlases.RemoveRange(i+1, atlases.Count - i - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (atlases.Count > 0)
|
||||
{
|
||||
last = atlases[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
last = null;
|
||||
}
|
||||
index = -1;
|
||||
|
||||
glyphs.Clear();
|
||||
}
|
||||
|
||||
private class AtlasPage : IDisposable
|
||||
{
|
||||
public QImage Image;
|
||||
public int PointerX, PointerY;
|
||||
public int RowHeight;
|
||||
public int Expansion;
|
||||
|
||||
public bool IsFull => PointerX > Image.Width || PointerY > Image.Height;
|
||||
|
||||
public AtlasPage(int width, int height, int expansion)
|
||||
{
|
||||
Image = new QImageBuffer(QImageFormat.AlphaU8, width, height);
|
||||
Expansion = expansion;
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void PutGlyph(QImageLock src, ref FontAtlasGlyphInfo prototype)
|
||||
{
|
||||
if (IsFull)
|
||||
throw new Exception("Page is full!");
|
||||
|
||||
Image.LockBits2d(out QImageLock dst, QImageLockOptions.Default);
|
||||
src.CopyTo(dst, PointerX, PointerY);
|
||||
Image.UnlockBits();
|
||||
|
||||
QVec2 min = new QVec2((float)PointerX/Image.Width, (float)PointerY/Image.Height);
|
||||
QVec2 size = new QVec2((float)src.Width/Image.Width, (float)src.Height/Image.Height);
|
||||
|
||||
prototype.Image = Image;
|
||||
prototype.UVs = new QRectangle(min + size, min);
|
||||
|
||||
AdvanceColumn(src.Width, src.Height);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
RowHeight = PointerX = PointerY = 0;
|
||||
}
|
||||
|
||||
public void AdvanceRow()
|
||||
{
|
||||
PointerX = 0;
|
||||
PointerY += RowHeight + Expansion;
|
||||
|
||||
RowHeight = 0;
|
||||
}
|
||||
|
||||
public void AdvanceColumn(int width, int height)
|
||||
{
|
||||
RowHeight = Math.Max(RowHeight, height);
|
||||
PointerX += width + Expansion;
|
||||
|
||||
if (PointerX > Image.Width)
|
||||
{
|
||||
AdvanceRow();
|
||||
}
|
||||
}
|
||||
|
||||
private bool isDisposed = false;
|
||||
public void Dispose()
|
||||
{
|
||||
if (isDisposed)
|
||||
return;
|
||||
|
||||
Image?.Dispose();
|
||||
|
||||
isDisposed = true;
|
||||
}
|
||||
|
||||
internal bool WouldFit(QImageLock source)
|
||||
{
|
||||
return !IsFull || PointerX + source.Width > Image.Width || PointerY + source.Height > Image.Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
238
Dashboard/Media/Font/FontFace.cs
Normal file
238
Dashboard/Media/Font/FontFace.cs
Normal file
@ -0,0 +1,238 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Dashboard.Media.Font
|
||||
{
|
||||
public readonly struct FontFace : IEquatable<FontFace>
|
||||
{
|
||||
public string Family { get; }
|
||||
public FontSlant Slant { get; }
|
||||
public FontWeight Weight { get; }
|
||||
public FontStretch Stretch { get; }
|
||||
|
||||
public FontFace(string family, FontSlant slant, FontWeight weight, FontStretch stretch)
|
||||
{
|
||||
Family = family;
|
||||
Slant = slant;
|
||||
Weight = weight;
|
||||
Stretch = stretch;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(Family);
|
||||
|
||||
if (Slant != FontSlant.Normal)
|
||||
{
|
||||
builder.Append(' ');
|
||||
builder.Append(Slant);
|
||||
}
|
||||
if (Stretch != FontStretch.Normal)
|
||||
{
|
||||
builder.Append(' ');
|
||||
builder.Append(Stretch);
|
||||
}
|
||||
if (Weight != FontWeight.Normal)
|
||||
{
|
||||
builder.Append(' ');
|
||||
builder.Append(Weight);
|
||||
}
|
||||
|
||||
if (Slant == FontSlant.Normal &&
|
||||
Stretch == FontStretch.Normal &&
|
||||
Weight == FontWeight.Normal)
|
||||
{
|
||||
builder.Append(" Regular");
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Family, Slant, Weight, Stretch);
|
||||
}
|
||||
|
||||
public static bool operator==(FontFace a, FontFace b)
|
||||
{
|
||||
return (a.Slant == b.Slant) &&
|
||||
(a.Weight == b.Weight) &&
|
||||
(a.Stretch == b.Stretch) &&
|
||||
(a.Family == a.Family);
|
||||
}
|
||||
|
||||
public static bool operator!=(FontFace a, FontFace b)
|
||||
{
|
||||
return (a.Slant != b.Slant) ||
|
||||
(a.Weight != b.Weight) ||
|
||||
(a.Stretch != b.Stretch) ||
|
||||
(a.Family != b.Family);
|
||||
}
|
||||
|
||||
public bool Equals(FontFace other)
|
||||
{
|
||||
return this == other;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return (obj?.GetType() == typeof(FontFace)) &&
|
||||
this == (FontFace)obj;
|
||||
}
|
||||
|
||||
public static FontFace Parse(string family, string style)
|
||||
{
|
||||
FontSlant slant = FontSlant.Normal;
|
||||
FontWeight weight = FontWeight.Normal;
|
||||
FontStretch stretch = FontStretch.Normal;
|
||||
|
||||
string[] tokens = style.Split(' ');
|
||||
|
||||
foreach (string token in tokens)
|
||||
{
|
||||
/**/ if (TryParseSlant(token, out FontSlant xslant)) slant = xslant;
|
||||
else if (TryParseWeight(token, out FontWeight xweight)) weight = xweight;
|
||||
else if (TryParseStretch(token, out FontStretch xstretch)) stretch = xstretch;
|
||||
}
|
||||
|
||||
return new FontFace(family, slant, weight, stretch);
|
||||
}
|
||||
|
||||
public static FontFace Parse(string face)
|
||||
{
|
||||
StringBuilder family = new StringBuilder();
|
||||
FontSlant slant = FontSlant.Normal;
|
||||
FontWeight weight = FontWeight.Normal;
|
||||
FontStretch stretch = FontStretch.Normal;
|
||||
|
||||
string[] tokens = face.Split(' ');
|
||||
foreach (string token in tokens)
|
||||
{
|
||||
string xtoken = token.ToLower();
|
||||
|
||||
if (xtoken == "regular" || xtoken == "normal")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (TryParseSlant(xtoken, out FontSlant xslant)) slant = xslant;
|
||||
else if (TryParseWeight(xtoken, out FontWeight xweight)) weight = xweight;
|
||||
else if (TryParseStretch(xtoken, out FontStretch xstretch)) stretch = xstretch;
|
||||
else
|
||||
{
|
||||
family.Append(token);
|
||||
}
|
||||
}
|
||||
|
||||
return new FontFace(family.ToString(), slant, weight, stretch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to convert a token that represents a font slant into its enum.
|
||||
/// </summary>
|
||||
/// <param name="token">The token to interpret.</param>
|
||||
/// <param name="slant">The resulting slant.</param>
|
||||
/// <returns>True if it matched any.</returns>
|
||||
public static bool TryParseSlant(string token, out FontSlant slant)
|
||||
{
|
||||
switch (token.ToLower())
|
||||
{
|
||||
case "italic":
|
||||
slant = FontSlant.Italic;
|
||||
return true;
|
||||
case "oblique":
|
||||
slant = FontSlant.Oblique;
|
||||
return true;
|
||||
default:
|
||||
slant = FontSlant.Normal;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to convert a token that represents a font weight into its enum.
|
||||
/// </summary>
|
||||
/// <param name="token">The token to interpret.</param>
|
||||
/// <param name="weight">The resulting weight.</param>
|
||||
/// <returns>True if it matched any.</returns>
|
||||
public static bool TryParseWeight(string token, out FontWeight weight)
|
||||
{
|
||||
switch (token.ToLower())
|
||||
{
|
||||
case "thin":
|
||||
weight = FontWeight.Thin;
|
||||
return true;
|
||||
case "extralight":
|
||||
case "ultralight":
|
||||
weight = FontWeight._200;
|
||||
return true;
|
||||
case "light":
|
||||
case "demilight":
|
||||
case "semilight":
|
||||
weight = FontWeight._300;
|
||||
return true;
|
||||
case "demibold":
|
||||
case "semibold":
|
||||
weight = FontWeight._600;
|
||||
return true;
|
||||
case "bold":
|
||||
weight = FontWeight._700;
|
||||
return true;
|
||||
case "extrabold":
|
||||
case "ultrabold":
|
||||
weight = FontWeight._800;
|
||||
return true;
|
||||
case "heavy":
|
||||
case "extrablack":
|
||||
case "black":
|
||||
case "ultrablack":
|
||||
weight = FontWeight._900;
|
||||
return true;
|
||||
default:
|
||||
weight = FontWeight.Normal;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to convert a token that represents a font stretch into its enum.
|
||||
/// </summary>
|
||||
/// <param name="token">The token to interpret.</param>
|
||||
/// <param name="stretch">The resulting stretch.</param>
|
||||
/// <returns>True if it matched any.</returns>
|
||||
public static bool TryParseStretch(string token, out FontStretch stretch)
|
||||
{
|
||||
switch (token.ToLower())
|
||||
{
|
||||
case "ultracondensed":
|
||||
stretch = FontStretch.UltraCondensed;
|
||||
return true;
|
||||
case "extracondensed":
|
||||
stretch = FontStretch.ExtraCondensed;
|
||||
return true;
|
||||
case "condensed":
|
||||
stretch = FontStretch.Condensed;
|
||||
return true;
|
||||
case "semicondensed":
|
||||
case "demicondensed":
|
||||
stretch = FontStretch.SemiCondensed;
|
||||
return true;
|
||||
case "semiexpanded":
|
||||
case "demiexpanded":
|
||||
stretch = FontStretch.SemiExpanded;
|
||||
return true;
|
||||
case "expanded":
|
||||
stretch = FontStretch.Expanded;
|
||||
return true;
|
||||
case "extraexpanded":
|
||||
stretch = FontStretch.ExtraExpanded;
|
||||
return true;
|
||||
case "ultraexpanded":
|
||||
stretch = FontStretch.UltraExpanded;
|
||||
return true;
|
||||
default:
|
||||
stretch = FontStretch.Normal;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
Dashboard/Media/Font/FontSlant.cs
Normal file
9
Dashboard/Media/Font/FontSlant.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Dashboard.Media.Font
|
||||
{
|
||||
public enum FontSlant
|
||||
{
|
||||
Normal = 0,
|
||||
Italic = 1,
|
||||
Oblique = 2,
|
||||
}
|
||||
}
|
18
Dashboard/Media/Font/FontStretch.cs
Normal file
18
Dashboard/Media/Font/FontStretch.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace Dashboard.Media.Font
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration of font stretch values.
|
||||
/// </summary>
|
||||
public enum FontStretch
|
||||
{
|
||||
UltraCondensed = 500,
|
||||
ExtraCondensed = 625,
|
||||
Condensed = 750,
|
||||
SemiCondensed = 875,
|
||||
Normal = 1000,
|
||||
SemiExpanded = 1125,
|
||||
Expanded = 1250,
|
||||
ExtraExpanded = 1500,
|
||||
UltraExpanded = 2000,
|
||||
}
|
||||
}
|
22
Dashboard/Media/Font/FontWeight.cs
Normal file
22
Dashboard/Media/Font/FontWeight.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace Dashboard.Media.Font
|
||||
{
|
||||
public enum FontWeight
|
||||
{
|
||||
_100 = 100,
|
||||
_200 = 200,
|
||||
_300 = 300,
|
||||
_400 = 400,
|
||||
_500 = 500,
|
||||
_600 = 600,
|
||||
_700 = 700,
|
||||
_800 = 800,
|
||||
_900 = 900,
|
||||
|
||||
Thin = _100,
|
||||
Normal = _400,
|
||||
Bold = _700,
|
||||
Heavy = _900,
|
||||
}
|
||||
}
|
26
Dashboard/Media/Font/SystemFontFamily.cs
Normal file
26
Dashboard/Media/Font/SystemFontFamily.cs
Normal file
@ -0,0 +1,26 @@
|
||||
namespace Dashboard.Media.Font
|
||||
{
|
||||
public enum SystemFontFamily
|
||||
{
|
||||
/// <summary>
|
||||
/// A font with serifs, like Times New Roman.
|
||||
/// </summary>
|
||||
Serif,
|
||||
/// <summary>
|
||||
/// A font without serifs, like Helvetica or Arial.
|
||||
/// </summary>
|
||||
Sans,
|
||||
/// <summary>
|
||||
/// A monospace font like Courier New.
|
||||
/// </summary>
|
||||
Monospace,
|
||||
/// <summary>
|
||||
/// A cursive font like Lucida Handwriting.
|
||||
/// </summary>
|
||||
Cursive,
|
||||
/// <summary>
|
||||
/// An immature font like Comic Sans or Papyrus, nghehehehe.
|
||||
/// </summary>
|
||||
Fantasy
|
||||
}
|
||||
}
|
19
Dashboard/Media/ImageFormat.cs
Normal file
19
Dashboard/Media/ImageFormat.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace Dashboard.Media
|
||||
{
|
||||
public enum QImageFormat
|
||||
{
|
||||
Undefined,
|
||||
RedU8,
|
||||
RgbU8,
|
||||
RgbaU8,
|
||||
RedF,
|
||||
RgbF,
|
||||
RgbaF,
|
||||
AlphaU8,
|
||||
AlphaF,
|
||||
RaU8,
|
||||
RaF,
|
||||
}
|
||||
}
|
22
Dashboard/Media/MediaLoader.cs
Normal file
22
Dashboard/Media/MediaLoader.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Dashboard.Media
|
||||
{
|
||||
public enum MediaHint
|
||||
{
|
||||
None,
|
||||
Image,
|
||||
Font
|
||||
}
|
||||
|
||||
public interface MediaLoader
|
||||
{
|
||||
IDisposable GetMedia(object key, MediaHint hint);
|
||||
}
|
||||
|
||||
public interface MediaLoader<T> : MediaLoader
|
||||
{
|
||||
IDisposable GetMedia(T key, MediaHint hint);
|
||||
}
|
||||
}
|
129
Dashboard/Media/QFont.cs
Normal file
129
Dashboard/Media/QFont.cs
Normal file
@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dashboard.Media;
|
||||
using Dashboard.Media.Font;
|
||||
|
||||
namespace Dashboard.Media
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract class that represents a font.
|
||||
/// </summary>
|
||||
public abstract class QFont : IDisposable
|
||||
{
|
||||
public abstract FontFace Face { get; }
|
||||
public string Family => Face.Family;
|
||||
public FontSlant Slant => Face.Slant;
|
||||
public FontWeight Weight => Face.Weight;
|
||||
public FontStretch Stretch => Face.Stretch;
|
||||
|
||||
public abstract bool HasRune(int rune);
|
||||
protected abstract QImage Render(out QGlyphMetrics metrics, int codepoint, float size, in FontRasterizerOptions options);
|
||||
|
||||
|
||||
private readonly Dictionary<float, SizedFontCollection> _atlasses = new Dictionary<float, SizedFontCollection>();
|
||||
|
||||
public void Get(int codepoint, float size, out FontGlyph glyph)
|
||||
{
|
||||
SizedFontCollection? collection;
|
||||
|
||||
if (!_atlasses.TryGetValue(size, out collection))
|
||||
{
|
||||
collection = new SizedFontCollection(size);
|
||||
_atlasses.Add(size, collection);
|
||||
}
|
||||
|
||||
collection.Get(codepoint, out glyph, this);
|
||||
}
|
||||
|
||||
// IDisposable
|
||||
private bool isDisposed = false;
|
||||
private void DisposePrivate(bool disposing)
|
||||
{
|
||||
if (isDisposed) return;
|
||||
|
||||
Dispose(disposing);
|
||||
|
||||
isDisposed = true;
|
||||
}
|
||||
protected virtual void Dispose(bool disposing) { }
|
||||
public void Dispose() => DisposePrivate(true);
|
||||
|
||||
private class SizedFontCollection
|
||||
{
|
||||
public float Size { get; }
|
||||
private readonly Dictionary<int, FontGlyph> glyphs = new Dictionary<int, FontGlyph>();
|
||||
private readonly FontAtlas atlas;
|
||||
|
||||
public SizedFontCollection(float size)
|
||||
{
|
||||
Size = size;
|
||||
|
||||
DashboardApplication.Current.Platform.GetMaximumImage(out int height, out int width);
|
||||
|
||||
// Do no allow to create a texture that is greater than 16 square characters at 200 DPI.
|
||||
width = Math.Min(width, (int)(size * 200 * 16));
|
||||
height = Math.Min(height, (int)(size * 200 * 16));
|
||||
// width = height = 256;
|
||||
|
||||
atlas = new FontAtlas(width, height, DashboardApplication.Current.FontProvider.RasterizerOptions.Sdf);
|
||||
}
|
||||
|
||||
public void Get(int codepoint, out FontGlyph glyph, QFont font)
|
||||
{
|
||||
if (glyphs.TryGetValue(codepoint, out glyph))
|
||||
return;
|
||||
|
||||
QImage image = font.Render(
|
||||
out QGlyphMetrics metrics,
|
||||
codepoint,
|
||||
Size,
|
||||
DashboardApplication.Current.FontProvider.RasterizerOptions);
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
image.LockBits2d(out QImageLock l, QImageLockOptions.Default);
|
||||
atlas.PutGlyph(codepoint, l, out FontAtlasGlyphInfo glyphInfo);
|
||||
image.UnlockBits();
|
||||
image.Dispose();
|
||||
|
||||
glyph = new FontGlyph(codepoint, glyphInfo.Image, metrics, glyphInfo.UVs);
|
||||
}
|
||||
else
|
||||
{
|
||||
glyph = new FontGlyph(codepoint, null, metrics, default);
|
||||
}
|
||||
|
||||
glyphs[codepoint] = glyph;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct FontGlyph
|
||||
{
|
||||
public readonly int CodePoint;
|
||||
public readonly QImage? Image;
|
||||
public readonly QGlyphMetrics Metrics;
|
||||
public readonly QRectangle UVs;
|
||||
|
||||
public FontGlyph(int codepoint, QImage? image, in QGlyphMetrics metrics, in QRectangle uvs)
|
||||
{
|
||||
CodePoint = codepoint;
|
||||
Image = image;
|
||||
Metrics = metrics;
|
||||
UVs = uvs;
|
||||
}
|
||||
}
|
||||
|
||||
public struct FontRasterizerOptions
|
||||
{
|
||||
public float Resolution { get; set; }
|
||||
public bool Sdf { get; set; }
|
||||
|
||||
public static readonly FontRasterizerOptions Default = new FontRasterizerOptions()
|
||||
{
|
||||
Resolution = 96.0f,
|
||||
Sdf = false
|
||||
};
|
||||
}
|
||||
}
|
47
Dashboard/Media/QGlyphMetrics.cs
Normal file
47
Dashboard/Media/QGlyphMetrics.cs
Normal file
@ -0,0 +1,47 @@
|
||||
namespace Dashboard.Media
|
||||
{
|
||||
/// <summary>
|
||||
/// Glyph properties with metrics based on FreeType glyph metrics.
|
||||
/// </summary>
|
||||
public struct QGlyphMetrics
|
||||
{
|
||||
/// <summary>
|
||||
/// The code point for the character.
|
||||
/// </summary>
|
||||
public int Rune { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the glyph in units.
|
||||
/// </summary>
|
||||
public QVec2 Size { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Bearing vector for horizontal layout.
|
||||
/// </summary>
|
||||
public QVec2 HorizontalBearing { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Bearing vector for vertical layout.
|
||||
/// </summary>
|
||||
public QVec2 VerticalBearing { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Advance vector for vertical and horizontal layouts.
|
||||
/// </summary>
|
||||
public QVec2 Advance { get; }
|
||||
|
||||
public QGlyphMetrics(
|
||||
int character,
|
||||
QVec2 size,
|
||||
QVec2 horizontalBearing,
|
||||
QVec2 verticalBearing,
|
||||
QVec2 advance)
|
||||
{
|
||||
Rune = character;
|
||||
Size = size;
|
||||
HorizontalBearing = horizontalBearing;
|
||||
VerticalBearing = verticalBearing;
|
||||
Advance = advance;
|
||||
}
|
||||
}
|
||||
}
|
120
Dashboard/Media/QImage.cs
Normal file
120
Dashboard/Media/QImage.cs
Normal file
@ -0,0 +1,120 @@
|
||||
using System;
|
||||
namespace Dashboard.Media
|
||||
{
|
||||
public abstract class QImage : IDisposable
|
||||
{
|
||||
public abstract int Width { get; }
|
||||
public abstract int Height { get; }
|
||||
public abstract int Depth { get; }
|
||||
public abstract QImageFormat InternalFormat { get; }
|
||||
public virtual int MipMapLevels => 0;
|
||||
public virtual bool Premultiplied => false;
|
||||
public virtual bool IsSdf => false;
|
||||
|
||||
public abstract void LockBits2d(out QImageLock imageLock, QImageLockOptions options);
|
||||
public abstract void LockBits3d(out QImageLock imageLock, QImageLockOptions options);
|
||||
public abstract void LockBits3d(out QImageLock imageLock, QImageLockOptions options, int depth);
|
||||
public abstract void UnlockBits();
|
||||
|
||||
// IDisposable
|
||||
private bool isDisposed = false;
|
||||
private void DisposePrivate(bool disposing)
|
||||
{
|
||||
if (isDisposed) return;
|
||||
|
||||
Dispose(disposing);
|
||||
|
||||
isDisposed = true;
|
||||
}
|
||||
protected virtual void Dispose(bool disposing) { }
|
||||
public void Dispose() => DisposePrivate(true);
|
||||
}
|
||||
|
||||
public struct QImageLockOptions
|
||||
{
|
||||
public QImageFormat Format { get; }
|
||||
public bool Premultiply { get; }
|
||||
public int MipLevel { get; }
|
||||
public static QImageLockOptions Default { get; } = new QImageLockOptions(QImageFormat.RgbaU8, true, 0);
|
||||
|
||||
public QImageLockOptions(QImageFormat format, bool premultiply, int level)
|
||||
{
|
||||
Format = format;
|
||||
Premultiply = premultiply;
|
||||
MipLevel = level;
|
||||
}
|
||||
}
|
||||
|
||||
public struct QImageLock
|
||||
{
|
||||
public QImageFormat Format { get; }
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
public int Depth { get; }
|
||||
public IntPtr ImagePtr { get; }
|
||||
|
||||
public QImageLock(QImageFormat format, int width, int height, int depth, IntPtr ptr)
|
||||
{
|
||||
Format = format;
|
||||
Width = width;
|
||||
Height = height;
|
||||
Depth = depth;
|
||||
ImagePtr = ptr;
|
||||
}
|
||||
|
||||
public unsafe void CopyTo(QImageLock destination, int x, int y)
|
||||
{
|
||||
if (
|
||||
Width + x > destination.Width ||
|
||||
Height + y > destination.Height)
|
||||
{
|
||||
throw new Exception("Image falls outside the bounds of the destination.");
|
||||
}
|
||||
else if (Format != destination.Format)
|
||||
{
|
||||
throw new Exception("Image formats must be the same.");
|
||||
}
|
||||
|
||||
int bpp = Format.BytesPerPixel();
|
||||
for (int i = 0; i < Height; i++)
|
||||
{
|
||||
IntPtr srcPtr = (IntPtr)((long)ImagePtr + i * Width * bpp);
|
||||
|
||||
long dstPos = x + i * destination.Width;
|
||||
IntPtr dstPtr = (IntPtr)((long)destination.ImagePtr + dstPos * bpp);
|
||||
|
||||
Buffer.MemoryCopy((void*)srcPtr, (void*)dstPtr, Width * bpp, Width * bpp);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void ExtractFrom(QImageLock destination, int x, int y, int width, int height)
|
||||
{
|
||||
if (
|
||||
width != destination.Width ||
|
||||
height != destination.Height)
|
||||
{
|
||||
throw new Exception("Destination is not the same size as the subregion.");
|
||||
}
|
||||
else if (x + width > Width || y + height > Height)
|
||||
{
|
||||
throw new Exception("The subregion is larger than this image.");
|
||||
}
|
||||
else if (Format != destination.Format)
|
||||
{
|
||||
throw new Exception("Image formats must be the same.");
|
||||
}
|
||||
|
||||
int bpp = Format.BytesPerPixel();
|
||||
for (int i = 0; i < height; i++)
|
||||
{
|
||||
long srcPos = x + y * i;
|
||||
IntPtr srcPtr = (IntPtr)((long)ImagePtr + srcPos * bpp);
|
||||
|
||||
long dstPos = i * destination.Width;
|
||||
IntPtr dstPtr = (IntPtr)((long)destination.ImagePtr + dstPos * bpp);
|
||||
|
||||
Buffer.MemoryCopy((void*)srcPtr, (void*)dstPtr, width * bpp, width * bpp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
74
Dashboard/Mouse.cs
Normal file
74
Dashboard/Mouse.cs
Normal file
@ -0,0 +1,74 @@
|
||||
using System;
|
||||
|
||||
namespace Dashboard
|
||||
{
|
||||
public enum MouseButton : byte
|
||||
{
|
||||
Primary = 1 << 0,
|
||||
Secondary = 1 << 1,
|
||||
Tertiary = 1 << 2,
|
||||
Auxilliary1 = 1 << 3,
|
||||
Auxilliary2 = 1 << 4,
|
||||
Auxilliary3 = 1 << 5,
|
||||
Auxilliary4 = 1 << 6,
|
||||
Auxilliary5 = 1 << 7,
|
||||
}
|
||||
|
||||
public struct MouseState
|
||||
{
|
||||
public readonly QVec2 AbsolutePosition;
|
||||
public readonly MouseButton ButtonsDown;
|
||||
|
||||
public MouseState(QVec2 position, MouseButton down)
|
||||
{
|
||||
AbsolutePosition = position;
|
||||
ButtonsDown = down;
|
||||
}
|
||||
}
|
||||
|
||||
public class MouseButtonEventArgs : EventArgs
|
||||
{
|
||||
public QVec2 AbsolutePosition { get; }
|
||||
public MouseButton Buttons { get; }
|
||||
|
||||
public MouseButtonEventArgs(QVec2 position, MouseButton buttons)
|
||||
{
|
||||
AbsolutePosition = position;
|
||||
Buttons = buttons;
|
||||
}
|
||||
|
||||
public QVec2 RelativePosition(QVec2 origin)
|
||||
{
|
||||
return AbsolutePosition - origin;
|
||||
}
|
||||
|
||||
// public QVec2 RelativePosition(Controls.Control control)
|
||||
// {
|
||||
// return AbsolutePosition - control.AbsoluteBounds.Min;
|
||||
// }
|
||||
}
|
||||
|
||||
public class MouseMoveEventArgs : EventArgs
|
||||
{
|
||||
public QVec2 AbsolutePosition { get; }
|
||||
public QVec2 LastPosition { get; }
|
||||
public QVec2 Motion { get; }
|
||||
|
||||
public MouseMoveEventArgs(QVec2 position, QVec2 lastPosition)
|
||||
{
|
||||
AbsolutePosition = position;
|
||||
LastPosition = lastPosition;
|
||||
Motion = position - lastPosition;
|
||||
}
|
||||
|
||||
public QVec2 RelativePosition(QVec2 origin)
|
||||
{
|
||||
return AbsolutePosition - origin;
|
||||
}
|
||||
|
||||
// public QVec2 RelativePosition(Controls.Control control)
|
||||
// {
|
||||
// return AbsolutePosition - control.AbsoluteBounds.Min;
|
||||
// }
|
||||
}
|
||||
}
|
77
Dashboard/OpenGL/GL.Buffer.cs
Normal file
77
Dashboard/OpenGL/GL.Buffer.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Dashboard.OpenGL
|
||||
{
|
||||
public unsafe static partial class GL
|
||||
{
|
||||
private delegate void BufferDataProc(GLEnum target, int size, void* data, GLEnum usageHint);
|
||||
|
||||
private static GenObjectsProc? _genBuffers;
|
||||
private static GenObjectsProc? _deleteBuffers;
|
||||
private static BindSlottedProc? _bindBuffer;
|
||||
private static BufferDataProc? _bufferData;
|
||||
|
||||
private static void LoadBuffer()
|
||||
{
|
||||
_genBuffers = GetProcAddress<GenObjectsProc>("glGenBuffers");
|
||||
_deleteBuffers = GetProcAddress<GenObjectsProc>("glDeleteBuffers");
|
||||
_bindBuffer = GetProcAddress<BindSlottedProc>("glBindBuffer");
|
||||
_bufferData = GetProcAddress<BufferDataProc>("glBufferData");
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void GenBuffers(int count, out int buffers)
|
||||
{
|
||||
fixed (int *ptr = &buffers)
|
||||
_genBuffers!(count, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void GenBuffers(int[] buffers) => GenBuffers(buffers.Length, out buffers[0]);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static int GenBuffer()
|
||||
{
|
||||
GenBuffers(1, out int i);
|
||||
return i;
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void DeleteBuffers(int count, ref int buffers)
|
||||
{
|
||||
fixed (int *ptr = &buffers)
|
||||
_deleteBuffers!(count, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void DeleteBuffers(int[] buffers) => DeleteBuffers(buffers.Length, ref buffers[0]);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void DeleteBuffer(int buffer) => DeleteBuffers(1, ref buffer);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void BindBuffer(GLEnum target, int buffer)
|
||||
{
|
||||
_bindBuffer!(target, buffer);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void BufferData(GLEnum target, int size, IntPtr data, GLEnum usageHint) =>
|
||||
_bufferData!(target, size, (void*)data, usageHint);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void BufferData<T>(GLEnum target, int size, ref T data, GLEnum usageHint)
|
||||
where T : unmanaged
|
||||
{
|
||||
fixed (T* ptr = &data)
|
||||
_bufferData!(target, size, ptr, usageHint);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void BufferData<T>(GLEnum target, int size, T[] data, GLEnum usageHint)
|
||||
where T : unmanaged =>
|
||||
BufferData(target, size, ref data[0], usageHint);
|
||||
|
||||
}
|
||||
}
|
96
Dashboard/OpenGL/GL.Program.cs
Normal file
96
Dashboard/OpenGL/GL.Program.cs
Normal file
@ -0,0 +1,96 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using static Dashboard.OpenGL.GLEnum;
|
||||
|
||||
namespace Dashboard.OpenGL
|
||||
{
|
||||
public unsafe static partial class GL
|
||||
{
|
||||
private delegate int CreateProgramProc();
|
||||
private delegate void UseProgramProc(int program);
|
||||
private delegate void AttachShaderProc(int program, int shader);
|
||||
private delegate void DetachShaderProc(int program, int shader);
|
||||
private delegate void LinkProgramProc(int program);
|
||||
private delegate void GetProgramProc(int program, GLEnum pname, int *value);
|
||||
private delegate void GetProgramInfoLogProc(int program, int maxLength, int * length, byte *infoLog);
|
||||
private delegate void DeleteProgramProc(int program);
|
||||
private delegate int GetShaderLocationProc(int program, byte *name);
|
||||
|
||||
private static CreateProgramProc? _createProgram;
|
||||
private static UseProgramProc? _useProgram;
|
||||
private static AttachShaderProc? _attachShader;
|
||||
private static DetachShaderProc? _detachShader;
|
||||
private static LinkProgramProc? _linkProgram;
|
||||
private static GetProgramProc? _getProgram;
|
||||
private static GetProgramInfoLogProc? _getProgramInfoLog;
|
||||
private static DeleteProgramProc? _deleteProgram;
|
||||
private static GetShaderLocationProc? _getUniformLocation;
|
||||
private static GetShaderLocationProc? _getAttribLocation;
|
||||
|
||||
private static void LoadProgram()
|
||||
{
|
||||
_createProgram = GetProcAddress<CreateProgramProc>("glCreateProgram");
|
||||
_useProgram = GetProcAddress<UseProgramProc>("glUseProgram");
|
||||
_attachShader = GetProcAddress<AttachShaderProc>("glAttachShader");
|
||||
_detachShader = GetProcAddress<DetachShaderProc>("glDetachShader");
|
||||
_linkProgram = GetProcAddress<LinkProgramProc>("glLinkProgram");
|
||||
_getProgram = GetProcAddress<GetProgramProc>("glGetProgramiv");
|
||||
_getProgramInfoLog = GetProcAddress<GetProgramInfoLogProc>("glGetProgramInfoLog");
|
||||
_deleteProgram = GetProcAddress<DeleteProgramProc>("glDeleteProgram");
|
||||
_getUniformLocation = GetProcAddress<GetShaderLocationProc>("glGetUniformLocation");
|
||||
_getAttribLocation = GetProcAddress<GetShaderLocationProc>("glGetAttribLocation");
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static int CreateProgram() => _createProgram!();
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void UseProgram(int program) => _useProgram!(program);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void AttachShader(int program, int shader) => _attachShader!(program, shader);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void DetachShader(int program, int shader) => _detachShader!(program, shader);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void LinkProgram(int program) => _linkProgram!(program);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void GetProgram(int program, GLEnum pname, out int value)
|
||||
{
|
||||
value = default;
|
||||
fixed (int* ptr = &value)
|
||||
_getProgram!(program, pname, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static string GetProgramInfoLog(int program)
|
||||
{
|
||||
GetProgram(program, GL_INFO_LOG_LENGTH, out int length);
|
||||
byte[] infoLog = new byte[length];
|
||||
|
||||
fixed (byte *ptr = infoLog)
|
||||
_getProgramInfoLog!(program, length, &length, ptr);
|
||||
|
||||
return Encoding.UTF8.GetString(infoLog);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void DeleteProgram(int program) => _deleteProgram!(program);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static int GetUniformLocation(int program, string name)
|
||||
{
|
||||
fixed(byte* ptr = Encoding.UTF8.GetBytes(name))
|
||||
return _getUniformLocation!(program, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static int GetAttribLocation(int program, string name)
|
||||
{
|
||||
fixed(byte* ptr = Encoding.UTF8.GetBytes(name))
|
||||
return _getAttribLocation!(program, ptr);
|
||||
}
|
||||
}
|
||||
}
|
110
Dashboard/OpenGL/GL.Shader.cs
Normal file
110
Dashboard/OpenGL/GL.Shader.cs
Normal file
@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using static Dashboard.OpenGL.GLEnum;
|
||||
|
||||
namespace Dashboard.OpenGL
|
||||
{
|
||||
public unsafe static partial class GL
|
||||
{
|
||||
private delegate int CreateShaderProc(GLEnum type);
|
||||
private delegate void ShaderSourceProc(int shader, int count, byte** strings, int* length);
|
||||
private delegate void CompileShaderProc(int shader);
|
||||
private delegate void GetShaderProc(int shader, GLEnum pname, int* value);
|
||||
private delegate void GetShaderInfoLogProc(int shader, int maxLength, int* length, byte* infoLog);
|
||||
private delegate void DeleteShaderProc(int id);
|
||||
|
||||
private static CreateShaderProc? _createShader;
|
||||
private static ShaderSourceProc? _shaderSource;
|
||||
private static CompileShaderProc? _compileShader;
|
||||
private static GetShaderProc? _getShader;
|
||||
private static GetShaderInfoLogProc? _getShaderInfoLog;
|
||||
private static DeleteShaderProc? _deleteShader;
|
||||
|
||||
private static void LoadShader()
|
||||
{
|
||||
_createShader = GetProcAddress<CreateShaderProc>("glCreateShader");
|
||||
_shaderSource = GetProcAddress<ShaderSourceProc>("glShaderSource");
|
||||
_compileShader = GetProcAddress<CompileShaderProc>("glCompileShader");
|
||||
_getShader = GetProcAddress<GetShaderProc>("glGetShaderiv");
|
||||
_getShaderInfoLog = GetProcAddress<GetShaderInfoLogProc>("glGetShaderInfoLog");
|
||||
_deleteShader = GetProcAddress<DeleteShaderProc>("glDeleteShader");
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static int CreateShader(GLEnum type) => _createShader!(type);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void ShaderSource(int shader, string source)
|
||||
{
|
||||
byte[] sourceUTF8 = Encoding.UTF8.GetBytes(source);
|
||||
int length = sourceUTF8.Length;
|
||||
|
||||
fixed (byte* ptr = &sourceUTF8[0])
|
||||
{
|
||||
_shaderSource!(shader, 1, &ptr, &length);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void ShaderSource(int shader, string[] sources)
|
||||
{
|
||||
int count = sources.Length;
|
||||
byte*[] pointers = new byte*[count];
|
||||
int[] lengths = new int[count];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
byte[] decoded = Encoding.UTF8.GetBytes(sources[i]);
|
||||
int length = lengths[i] = decoded.Length;
|
||||
IntPtr memory = Marshal.AllocHGlobal(decoded.Length);
|
||||
|
||||
Marshal.Copy(decoded, 0, memory, length);
|
||||
pointers[i] = (byte*)memory;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
fixed (byte** ptr = &pointers[0])
|
||||
fixed (int * len = &lengths[0])
|
||||
{
|
||||
_shaderSource!(shader, count, ptr, len);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Marshal.FreeHGlobal((IntPtr)pointers[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void CompileShader(int shader) => _compileShader!(shader);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void GetShader(int shader, GLEnum pname, out int value)
|
||||
{
|
||||
value = default;
|
||||
fixed (int *ptr = &value)
|
||||
_getShader!(shader, pname, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static string GetShaderInfoLog(int shader)
|
||||
{
|
||||
GetShader(shader, GL_INFO_LOG_LENGTH, out int length);
|
||||
byte[] infoLog = new byte[length];
|
||||
|
||||
fixed (byte *ptr = infoLog)
|
||||
_getShaderInfoLog!(shader, length, &length, ptr);
|
||||
|
||||
return Encoding.UTF8.GetString(infoLog);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void DeleteShader(int shader) => _deleteShader!(shader);
|
||||
}
|
||||
}
|
187
Dashboard/OpenGL/GL.Texture.cs
Normal file
187
Dashboard/OpenGL/GL.Texture.cs
Normal file
@ -0,0 +1,187 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Dashboard.OpenGL
|
||||
{
|
||||
public unsafe static partial class GL
|
||||
{
|
||||
private delegate void ActiveTextureProc(GLEnum unit);
|
||||
private delegate void PixelStoreiProc(GLEnum pname, int param);
|
||||
private delegate void PixelStorefProc(GLEnum pname, float param);
|
||||
private delegate void TexImage2DProc(GLEnum target, int level, GLEnum internalFormat, int width, int height, int border, GLEnum format, GLEnum pixelType, void *data);
|
||||
private delegate void TexImage3DProc(GLEnum target, int level, GLEnum internalFormat, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, void* data);
|
||||
private delegate void TexSubImage2DProc(GLEnum target, int level, int x, int y, int width, int height, GLEnum format, GLEnum pixelType, void *data);
|
||||
private delegate void TexSubImage3DProc(GLEnum target, int level, int x, int y, int z, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, void* data);
|
||||
private delegate void TexParameteriProc(GLEnum target, GLEnum pname, int value);
|
||||
private delegate void TexParameterfProc(GLEnum target, GLEnum pname, float value);
|
||||
private delegate void TexParameterivProc(GLEnum target, GLEnum pname, int* value);
|
||||
private delegate void TexParameterfvProc(GLEnum target, GLEnum pname, float* value);
|
||||
private delegate void GenerateMipmapProc(GLEnum target);
|
||||
|
||||
private static GenObjectsProc? _genTextures;
|
||||
private static GenObjectsProc? _deleteTextures;
|
||||
private static BindSlottedProc? _bindTexture;
|
||||
private static ActiveTextureProc? _activeTexture;
|
||||
private static PixelStoreiProc? _pixelStorei;
|
||||
private static PixelStorefProc? _pixelStoref;
|
||||
private static TexImage2DProc? _texImage2D;
|
||||
private static TexImage3DProc? _texImage3D;
|
||||
private static TexSubImage2DProc? _texSubImage2D;
|
||||
private static TexSubImage3DProc? _texSubImage3D;
|
||||
private static TexParameteriProc? _texParameteri;
|
||||
private static TexParameterfProc? _texParameterf;
|
||||
private static TexParameterivProc? _texParameteriv;
|
||||
private static TexParameterfvProc? _texParameterfv;
|
||||
private static GenerateMipmapProc? _generateMipmap;
|
||||
|
||||
private static void LoadTexture()
|
||||
{
|
||||
_genTextures = GetProcAddress<GenObjectsProc>("glGenTextures");
|
||||
_deleteTextures = GetProcAddress<GenObjectsProc>("glDeleteTextures");
|
||||
_bindTexture = GetProcAddress<BindSlottedProc>("glBindTexture");
|
||||
_activeTexture = GetProcAddress<ActiveTextureProc>("glActiveTexture");
|
||||
_pixelStorei = GetProcAddress<PixelStoreiProc>("glPixelStorei");
|
||||
_pixelStoref = GetProcAddress<PixelStorefProc>("glPixelStoref");
|
||||
_texImage2D = GetProcAddress<TexImage2DProc>("glTexImage2D");
|
||||
_texImage3D = GetProcAddress<TexImage3DProc>("glTexImage3D");
|
||||
_texSubImage2D = GetProcAddress<TexSubImage2DProc>("glTexSubImage2D");
|
||||
_texSubImage3D = GetProcAddress<TexSubImage3DProc>("glTexSubImage3D");
|
||||
_texParameteri = GetProcAddress<TexParameteriProc>("glTexParameteri");
|
||||
_texParameterf = GetProcAddress<TexParameterfProc>("glTexParameterf");
|
||||
_texParameteriv = GetProcAddress<TexParameterivProc>("glTexParameteriv");
|
||||
_texParameterfv = GetProcAddress<TexParameterfvProc>("glTexParameterfv");
|
||||
_generateMipmap = GetProcAddress<GenerateMipmapProc>("glGenerateMipmap");
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void GenTextures(int count, out int textures)
|
||||
{
|
||||
fixed (int *ptr = &textures)
|
||||
_genTextures!(count, ptr);
|
||||
}
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static int GenTexture()
|
||||
{
|
||||
GenTextures(1, out int i);
|
||||
return i;
|
||||
}
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void GenTextures(int[] textures) => GenTextures(textures.Length, out textures[0]);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void DeleteTextures(int count, in int textures)
|
||||
{
|
||||
fixed (int* ptr = &textures)
|
||||
_deleteTextures!(count, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void DeleteTexture(int i) => DeleteTextures(1, i);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void DeleteTextures(int[] textures) => DeleteTextures(textures.Length, in textures[0]);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void BindTexture(GLEnum target, int texture) => _bindTexture!(target, texture);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void ActiveTexture(GLEnum unit) => _activeTexture!(unit);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void PixelStore(GLEnum pname, int value) => _pixelStorei!(pname, value);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void PixelStore(GLEnum pname, float value) => _pixelStoref!(pname, value);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void TexImage2D(GLEnum target, int level, GLEnum internalFormat, int width, int height, int border, GLEnum format, GLEnum pixelType, IntPtr data) =>
|
||||
_texImage2D!(target, level, internalFormat, width, height, border, format, pixelType, (void*)data);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void TexImage2D<T>(GLEnum target, int level, GLEnum internalFormat, int width, int height, int border, GLEnum format, GLEnum pixelType, in T data) where T : unmanaged
|
||||
{
|
||||
fixed(T *ptr = &data)
|
||||
_texImage2D!(target, level, internalFormat, width, height, border, format, pixelType, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void TexImage2D<T>(GLEnum target, int level, GLEnum internalFormat, int width, int height, int border, GLEnum format, GLEnum pixelType, T[] data) where T : unmanaged =>
|
||||
TexImage2D(target, level, internalFormat, width, height, border, format, pixelType, in data[0]);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void TexImage3D(GLEnum target, int level, GLEnum internalFormat, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, void* data) =>
|
||||
_texImage3D!(target, level, internalFormat, width, height, depth, border, format, pixelType, data);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void TexImage3D<T>(GLEnum target, int level, GLEnum internalFormat, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, in T data)
|
||||
where T : unmanaged
|
||||
{
|
||||
fixed (T* ptr = &data)
|
||||
_texImage3D!(target, level, internalFormat, width, height, depth, border, format, pixelType, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void TexImage3D<T>(GLEnum target, int level, GLEnum internalFormat, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, T[] data)
|
||||
where T : unmanaged =>
|
||||
TexImage3D(target, level, internalFormat, width, height, depth, border, format, pixelType, in data[0]);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void TexSubImage2D(GLEnum target, int level, int x, int y, int width, int height, GLEnum format, GLEnum pixelType, IntPtr data) =>
|
||||
_texSubImage2D!(target, level, x, y, width, height,format, pixelType, (void*)data);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void TexSubImage2d<T>(GLEnum target, int level, int x, int y, int width, int height, GLEnum format, GLEnum pixelType, in T data) where T : unmanaged
|
||||
{
|
||||
fixed(T *ptr = &data)
|
||||
_texSubImage2D!(target, level, x, y, width, height, format, pixelType, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void TexSubImage2D<T>(GLEnum target, int level, int x, int y, int width, int height, GLEnum format, GLEnum pixelType, T[] data) where T : unmanaged =>
|
||||
TexSubImage2d<T>(target, level, x, y, width, height, format, pixelType, in data[0]);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void TexSubImage3D(GLEnum target, int level, int x, int y, int z, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, void* data) =>
|
||||
_texSubImage3D!(target, level, x, y, z, width, height, depth, border, format, pixelType, data);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void TexSubImage3D<T>(GLEnum target, int level, int x, int y, int z, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, in T data)
|
||||
where T : unmanaged
|
||||
{
|
||||
fixed (T* ptr = &data)
|
||||
_texSubImage3D!(target, level, x, y, z, width, height, depth, border, format, pixelType, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void TexSubImage3D<T>(GLEnum target, int level, int x, int y, int z, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, T[] data)
|
||||
where T : unmanaged =>
|
||||
TexSubImage3D(target, level, x, y, z, width, height, depth, border, format, pixelType, in data[0]);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void TexParameter(GLEnum target, GLEnum pname, int value) => _texParameteri!(target, pname, value);
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void TexParameter(GLEnum target, GLEnum pname, GLEnum value) => _texParameteri!(target, pname, (int)value);
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void TexParameter(GLEnum target, GLEnum pname, float value) => _texParameterf!(target, pname, value);
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void TexParameter(GLEnum target, GLEnum pname, ref int values)
|
||||
{
|
||||
fixed (int *ptr = &values)
|
||||
_texParameteriv!(target, pname, ptr);
|
||||
}
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void TexParameter(GLEnum target, GLEnum pname, int[] values) => TexParameter(target, pname, ref values[0]);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void TexParameter(GLEnum target, GLEnum pname, ref float values)
|
||||
{
|
||||
fixed (float *ptr = &values)
|
||||
_texParameterfv!(target, pname, ptr);
|
||||
}
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void TexParameter(GLEnum target, GLEnum pname, float[] values) => TexParameter(target, pname, ref values[0]);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void GenerateMipmap(GLEnum target) => _generateMipmap!(target);
|
||||
}
|
||||
}
|
222
Dashboard/OpenGL/GL.Uniform.cs
Normal file
222
Dashboard/OpenGL/GL.Uniform.cs
Normal file
@ -0,0 +1,222 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Dashboard.OpenGL
|
||||
{
|
||||
public unsafe static partial class GL
|
||||
{
|
||||
private delegate void Uniform1fProc(int location, float x);
|
||||
private delegate void Uniform2fProc(int location, float x, float y);
|
||||
private delegate void Uniform3fProc(int location, float x, float y, float z);
|
||||
private delegate void Uniform4fProc(int location, float x, float y, float z, float w);
|
||||
private delegate void Uniform1iProc(int location, int x);
|
||||
private delegate void Uniform2iProc(int location, int x, int y);
|
||||
private delegate void Uniform3iProc(int location, int x, int y, int z);
|
||||
private delegate void Uniform4iProc(int location, int x, int y, int z, int w);
|
||||
private delegate void UniformNfvProc(int location, int count, float* value);
|
||||
private delegate void UniformNivProc(int location, int count, int* value);
|
||||
private delegate void UniformMatrixNxNfvProc(int location, int count, bool transpose, float *value);
|
||||
|
||||
private static Uniform1fProc? _uniform1f;
|
||||
private static Uniform2fProc? _uniform2f;
|
||||
private static Uniform3fProc? _uniform3f;
|
||||
private static Uniform4fProc? _uniform4f;
|
||||
private static Uniform1iProc? _uniform1i;
|
||||
private static Uniform2iProc? _uniform2i;
|
||||
private static Uniform3iProc? _uniform3i;
|
||||
private static Uniform4iProc? _uniform4i;
|
||||
private static UniformNfvProc? _uniform1fv;
|
||||
private static UniformNfvProc? _uniform2fv;
|
||||
private static UniformNfvProc? _uniform3fv;
|
||||
private static UniformNfvProc? _uniform4fv;
|
||||
private static UniformNivProc? _uniform1iv;
|
||||
private static UniformNivProc? _uniform2iv;
|
||||
private static UniformNivProc? _uniform3iv;
|
||||
private static UniformNivProc? _uniform4iv;
|
||||
private static UniformMatrixNxNfvProc? _uniformMatrix2fv;
|
||||
private static UniformMatrixNxNfvProc? _uniformMatrix3fv;
|
||||
private static UniformMatrixNxNfvProc? _uniformMatrix4fv;
|
||||
|
||||
public static void LoadUniform()
|
||||
{
|
||||
_uniform1f = GetProcAddress<Uniform1fProc>("glUniform1f");
|
||||
_uniform2f = GetProcAddress<Uniform2fProc>("glUniform2f");
|
||||
_uniform3f = GetProcAddress<Uniform3fProc>("glUniform3f");
|
||||
_uniform4f = GetProcAddress<Uniform4fProc>("glUniform4f");
|
||||
_uniform1i = GetProcAddress<Uniform1iProc>("glUniform1i");
|
||||
_uniform2i = GetProcAddress<Uniform2iProc>("glUniform2i");
|
||||
_uniform3i = GetProcAddress<Uniform3iProc>("glUniform3i");
|
||||
_uniform4i = GetProcAddress<Uniform4iProc>("glUniform4i");
|
||||
_uniform1fv = GetProcAddress<UniformNfvProc>("glUniform1fv");
|
||||
_uniform2fv = GetProcAddress<UniformNfvProc>("glUniform2fv");
|
||||
_uniform3fv = GetProcAddress<UniformNfvProc>("glUniform3fv");
|
||||
_uniform4fv = GetProcAddress<UniformNfvProc>("glUniform4fv");
|
||||
_uniform1iv = GetProcAddress<UniformNivProc>("glUniform1iv");
|
||||
_uniform2iv = GetProcAddress<UniformNivProc>("glUniform2iv");
|
||||
_uniform3iv = GetProcAddress<UniformNivProc>("glUniform3iv");
|
||||
_uniform4iv = GetProcAddress<UniformNivProc>("glUniform4iv");
|
||||
_uniformMatrix2fv = GetProcAddress<UniformMatrixNxNfvProc>("glUniformMatrix2fv");
|
||||
_uniformMatrix3fv = GetProcAddress<UniformMatrixNxNfvProc>("glUniformMatrix3fv");
|
||||
_uniformMatrix4fv = GetProcAddress<UniformMatrixNxNfvProc>("glUniformMatrix4fv");
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Uniform1(int location, float x)
|
||||
{
|
||||
_uniform1f!(location, x);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Uniform2(int location, float x, float y)
|
||||
{
|
||||
_uniform2f!(location, x, y);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Uniform3(int location, float x, float y, float z)
|
||||
{
|
||||
_uniform3f!(location, x, y, z);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Uniform4(int location, float x, float y, float z, float w)
|
||||
{
|
||||
_uniform4f!(location, x, y, z, w);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Uniform1(int location, int x)
|
||||
{
|
||||
_uniform1i!(location, x);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Uniform2(int location, int x, int y)
|
||||
{
|
||||
_uniform2i!(location, x, y);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Uniform3(int location, int x, int y, int z)
|
||||
{
|
||||
_uniform3i!(location, x, y, z);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Uniform4(int location, int x, int y, int z, int w)
|
||||
{
|
||||
_uniform4i!(location, x, y, z, w);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Uniform1(int location, int count, ref float first)
|
||||
{
|
||||
fixed(float *ptr = &first)
|
||||
_uniform1fv!(location, count, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Uniform2(int location, int count, ref float first)
|
||||
{
|
||||
fixed(float *ptr = &first)
|
||||
_uniform2fv!(location, count, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Uniform3(int location, int count, ref float first)
|
||||
{
|
||||
fixed(float *ptr = &first)
|
||||
_uniform3fv!(location, count, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Uniform4(int location, int count, ref float first)
|
||||
{
|
||||
fixed(float *ptr = &first)
|
||||
_uniform4fv!(location, count, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Uniform1(int location, int count, ref int first)
|
||||
{
|
||||
fixed(int *ptr = &first)
|
||||
_uniform1iv!(location, count, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Uniform2(int location, int count, ref int first)
|
||||
{
|
||||
fixed(int *ptr = &first)
|
||||
_uniform2iv!(location, count, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Uniform3(int location, int count, ref int first)
|
||||
{
|
||||
fixed(int *ptr = &first)
|
||||
_uniform3iv!(location, count, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Uniform4(int location, int count, ref int first)
|
||||
{
|
||||
fixed(int *ptr = &first)
|
||||
_uniform4iv!(location, count, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void UniformMatrix2(int location, bool transpose, ref float m11)
|
||||
{
|
||||
fixed (float* ptr = &m11)
|
||||
_uniformMatrix2fv!(location, 1, transpose, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void UniformMatrix3(int location, bool transpose, ref float m11)
|
||||
{
|
||||
fixed (float* ptr = &m11)
|
||||
_uniformMatrix3fv!(location, 1, transpose, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void UniformMatrix4(int location, bool transpose, ref float m11)
|
||||
{
|
||||
fixed (float* ptr = &m11)
|
||||
_uniformMatrix4fv!(location, 1, transpose, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void UniformMatrix2(int location, int count, bool transpose, ref float m11)
|
||||
{
|
||||
fixed (float* ptr = &m11)
|
||||
_uniformMatrix2fv!(location, count, transpose, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void UniformMatrix3(int location, int count, bool transpose, ref float m11)
|
||||
{
|
||||
fixed (float* ptr = &m11)
|
||||
_uniformMatrix3fv!(location, count, transpose, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void UniformMatrix4(int location, int count, bool transpose, ref float m11)
|
||||
{
|
||||
fixed (float* ptr = &m11)
|
||||
_uniformMatrix4fv!(location, count, transpose, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void UniformMatrix4(int location, bool transpose, in QMat4 m4)
|
||||
{
|
||||
fixed (float* ptr = &m4.M11)
|
||||
_uniformMatrix4fv!(location, 1, transpose, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void UniformMatrix4(int location, int count, bool transpose, ref QMat4 m4)
|
||||
{
|
||||
fixed (float* ptr = &m4.M11)
|
||||
_uniformMatrix4fv!(location, count, transpose, ptr);
|
||||
}
|
||||
}
|
||||
}
|
78
Dashboard/OpenGL/GL.VertexArray.cs
Normal file
78
Dashboard/OpenGL/GL.VertexArray.cs
Normal file
@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Dashboard.OpenGL
|
||||
{
|
||||
public unsafe static partial class GL
|
||||
{
|
||||
private delegate void EnableVertexAttribArrayProc(int location);
|
||||
private delegate void VertexAttribPointerProc(int location, int size, GLEnum type, bool normalized, int stride, IntPtr offset);
|
||||
private delegate void VertexAttribIPointerProc(int location, int size, GLEnum type, int stride, IntPtr offset);
|
||||
|
||||
private static GenObjectsProc? _genVertexArrays;
|
||||
private static GenObjectsProc? _deleteVertexArrays;
|
||||
private static BindObjectProc? _bindVertexArray;
|
||||
private static EnableVertexAttribArrayProc? _enableVertexAttribArray;
|
||||
private static EnableVertexAttribArrayProc? _disableVertexAttribArray;
|
||||
private static VertexAttribPointerProc? _vertexAttribPointer;
|
||||
private static VertexAttribIPointerProc? _vertexAttribIPointer;
|
||||
|
||||
private static void LoadVertexArrays()
|
||||
{
|
||||
_genVertexArrays = GetProcAddress<GenObjectsProc>("glGenVertexArrays");
|
||||
_deleteVertexArrays = GetProcAddress<GenObjectsProc>("glDeleteVertexArrays");
|
||||
_bindVertexArray = GetProcAddress<BindObjectProc>("glBindVertexArray");
|
||||
_enableVertexAttribArray = GetProcAddress<EnableVertexAttribArrayProc>("glEnableVertexAttribArray");
|
||||
_disableVertexAttribArray = GetProcAddress<EnableVertexAttribArrayProc>("glDisableVertexAttribArray");
|
||||
_vertexAttribPointer = GetProcAddress<VertexAttribPointerProc>("glVertexAttribPointer");
|
||||
_vertexAttribIPointer = GetProcAddress<VertexAttribIPointerProc>("glVertexAttribIPointer");
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void GenVertexArrays(int count, out int vertexArrays)
|
||||
{
|
||||
fixed (int *ptr = &vertexArrays)
|
||||
_genVertexArrays!(count, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static int GenVertexArray()
|
||||
{
|
||||
GenVertexArrays(1, out int i);
|
||||
return i;
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void GenVertexArrays(int[] vertexArrays) => GenVertexArrays(vertexArrays.Length, out vertexArrays[0]);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void DeleteVertexArrays(int count, ref int vertexArrays)
|
||||
{
|
||||
fixed (int *ptr = &vertexArrays)
|
||||
_deleteVertexArrays!(count, ptr);
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void DeleteVertexArrays(int[] vertexArrays) => DeleteVertexArrays(vertexArrays.Length, ref vertexArrays[0]);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void DeleteVertexArray(int vertexArray) => DeleteVertexArrays(1, ref vertexArray);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void BindVertexArray(int vertexArray) => _bindVertexArray!(vertexArray);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void EnableVertexAttribArray(int location) => _enableVertexAttribArray!(location);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void DisableVertexAttribArray(int location) => _disableVertexAttribArray!(location);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void VertexAttribPointer(int location, int size, GLEnum type, bool normalized, int stride, int offset) =>
|
||||
_vertexAttribPointer!(location, size, type, normalized, stride, (IntPtr)offset);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void VertexAttribIPointer(int location, int size, GLEnum type, int stride, int offset) =>
|
||||
_vertexAttribIPointer!(location, size, type, stride, (IntPtr)offset);
|
||||
}
|
||||
}
|
128
Dashboard/OpenGL/GL.cs
Normal file
128
Dashboard/OpenGL/GL.cs
Normal file
@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dashboard.OpenGL
|
||||
{
|
||||
public delegate IntPtr GetProcAddressProc(string procName);
|
||||
|
||||
public unsafe static partial class GL
|
||||
{
|
||||
private delegate void GenObjectsProc(int count, int *ids);
|
||||
private delegate void BindObjectProc(int id);
|
||||
private delegate void BindSlottedProc(GLEnum target, int id);
|
||||
private delegate void GLEnum1Proc(GLEnum x);
|
||||
private delegate void GLEnum2Proc(GLEnum x, GLEnum y);
|
||||
private delegate void GLI4Proc(int x, int y, int z, int w);
|
||||
private delegate void GLF4Proc(float x, float y, float z, float w);
|
||||
private delegate void DrawElementsProc(GLEnum primitive, int size, GLEnum type, void *offset);
|
||||
private delegate void DrawArraysProc(GLEnum primitive, int first, int offset);
|
||||
private delegate void GetIntegervProc(GLEnum pname, int *data);
|
||||
private delegate void GetFloatvProc(GLEnum pname, float *data);
|
||||
private delegate byte* GetStringProc(GLEnum pname);
|
||||
|
||||
private const short AggressiveInlining = (short)MethodImplOptions.AggressiveInlining;
|
||||
|
||||
private static GetProcAddressProc? _getProcAddress;
|
||||
private static GLEnum1Proc? _enable;
|
||||
private static GLEnum1Proc? _disable;
|
||||
private static GLEnum2Proc? _blendFunc;
|
||||
private static GLEnum1Proc? _depthFunc;
|
||||
private static GLEnum1Proc? _clear;
|
||||
private static GLI4Proc? _viewport;
|
||||
private static GLI4Proc? _scissor;
|
||||
private static GLF4Proc? _clearColor;
|
||||
private static DrawElementsProc? _drawElements;
|
||||
private static DrawArraysProc? _drawArrays;
|
||||
private static GetIntegervProc? _getIntegerv;
|
||||
private static GetFloatvProc? _getFloatv;
|
||||
private static GetStringProc? _getString;
|
||||
|
||||
private static T GetProcAddress<T>(string procName)
|
||||
where T : Delegate
|
||||
{
|
||||
IntPtr funcptr = _getProcAddress!(procName);
|
||||
return Marshal.GetDelegateForFunctionPointer<T>(funcptr);
|
||||
}
|
||||
|
||||
public static void LoadBindings(GetProcAddressProc getProcAddress)
|
||||
{
|
||||
_getProcAddress = getProcAddress;
|
||||
|
||||
_enable = GetProcAddress<GLEnum1Proc>("glEnable");
|
||||
_disable = GetProcAddress<GLEnum1Proc>("glDisable");
|
||||
_blendFunc = GetProcAddress<GLEnum2Proc>("glBlendFunc");
|
||||
_depthFunc = GetProcAddress<GLEnum1Proc>("glDepthFunc");
|
||||
_clear = GetProcAddress<GLEnum1Proc>("glClear");
|
||||
_viewport = GetProcAddress<GLI4Proc>("glViewport");
|
||||
_scissor = GetProcAddress<GLI4Proc>("glScissor");
|
||||
_clearColor = GetProcAddress<GLF4Proc>("glClearColor");
|
||||
_drawElements = GetProcAddress<DrawElementsProc>("glDrawElements");
|
||||
_drawArrays = GetProcAddress<DrawArraysProc>("glDrawArrays");
|
||||
_getIntegerv = GetProcAddress<GetIntegervProc>("glGetIntegerv");
|
||||
_getFloatv = GetProcAddress<GetFloatvProc>("glGetFloatv");
|
||||
_getString = GetProcAddress<GetStringProc>("glGetString");
|
||||
|
||||
LoadBuffer();
|
||||
LoadProgram();
|
||||
LoadShader();
|
||||
LoadTexture();
|
||||
LoadUniform();
|
||||
LoadVertexArrays();
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Enable(GLEnum cap) => _enable!(cap);
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Disable(GLEnum cap) => _disable!(cap);
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void BlendFunc(GLEnum src, GLEnum dst) => _blendFunc!(src, dst);
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void DepthFunc(GLEnum func) => _depthFunc!(func);
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Clear(GLEnum buffer_bits) => _clear!(buffer_bits);
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Viewport(int x, int y, int w, int h) => _viewport!(x, y, w, h);
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Scissor(int x, int y, int w, int h) => _scissor!(x, y, w, h);
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void ClearColor(float r, float g, float b, float a) => _clearColor!(r, g, b, a);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void DrawElements(GLEnum primitive, int count, GLEnum type, int offset) => _drawElements!(primitive, count, type, (void*)offset);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void DrawArrays(GLEnum primitive, int offset, int count) => _drawArrays!(primitive, offset, count);
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Get(GLEnum pname, out int value)
|
||||
{
|
||||
value = default;
|
||||
fixed(int* ptr = &value)
|
||||
{
|
||||
_getIntegerv!(pname, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static void Get(GLEnum pname, out float value)
|
||||
{
|
||||
value = default;
|
||||
fixed (float* ptr = &value)
|
||||
{
|
||||
_getFloatv!(pname, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(AggressiveInlining)]
|
||||
public static string GetString(GLEnum pname)
|
||||
{
|
||||
int length;
|
||||
byte* str = _getString!(pname);
|
||||
|
||||
for (length = 0; str[length] == 0 || length < 256; length++);
|
||||
|
||||
return System.Text.Encoding.UTF8.GetString(str, length);
|
||||
}
|
||||
}
|
||||
}
|
460
Dashboard/OpenGL/GL21Driver.cs
Normal file
460
Dashboard/OpenGL/GL21Driver.cs
Normal file
@ -0,0 +1,460 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using Dashboard.VertexGenerator;
|
||||
using static Dashboard.OpenGL.GLEnum;
|
||||
using Dashboard.Media;
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Dashboard.OpenGL
|
||||
{
|
||||
public class GL21Driver : IDisposable
|
||||
{
|
||||
private int program;
|
||||
private int v2Position;
|
||||
private int fZIndex;
|
||||
private int v2TexPos;
|
||||
private int fTexLayer;
|
||||
private int v4Color;
|
||||
private int m4Transforms;
|
||||
private int fMaxZ;
|
||||
private int iEnableSdf;
|
||||
private int iEnableTexture;
|
||||
private int iAlphaDiscard;
|
||||
private int fSdfThreshold;
|
||||
private int tx2d;
|
||||
private int tx2darray;
|
||||
|
||||
private bool isDiposed;
|
||||
private readonly Dictionary<DrawQueue, DrawData> data = new Dictionary<DrawQueue, DrawData>();
|
||||
private readonly TextureManager textures = new TextureManager();
|
||||
|
||||
public bool IsInit { get; private set; } = false;
|
||||
public event Action<GL21Driver>? OnGCDispose;
|
||||
|
||||
public GL21Driver()
|
||||
{
|
||||
}
|
||||
|
||||
~GL21Driver()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
if (IsInit) return;
|
||||
|
||||
int vs = CreateShader(GL_VERTEX_SHADER, "Dashboard.res.gl21.vert");
|
||||
int fs = CreateShader(GL_FRAGMENT_SHADER, "Dashboard.res.gl21.frag");
|
||||
|
||||
program = GL.CreateProgram();
|
||||
GL.AttachShader(program, vs);
|
||||
GL.AttachShader(program, fs);
|
||||
|
||||
GL.LinkProgram(program);
|
||||
|
||||
if (CheckProgram(program, out string msg) == false)
|
||||
{
|
||||
GraphicsException ex = new GraphicsException("Could not link shader program.");
|
||||
ex.Data.Add("Program Info Log", msg);
|
||||
}
|
||||
|
||||
GL.DeleteShader(vs);
|
||||
GL.DeleteShader(fs);
|
||||
|
||||
v2Position = GL.GetAttribLocation(program, nameof(v2Position));
|
||||
fZIndex = GL.GetAttribLocation(program, nameof(fZIndex));
|
||||
v2TexPos = GL.GetAttribLocation(program, nameof(v2TexPos));
|
||||
fTexLayer = GL.GetAttribLocation(program, nameof(fTexLayer));
|
||||
v4Color = GL.GetAttribLocation(program, nameof(v4Color));
|
||||
|
||||
m4Transforms = GL.GetUniformLocation(program, nameof(m4Transforms));
|
||||
fMaxZ = GL.GetUniformLocation(program, nameof(fMaxZ));
|
||||
fSdfThreshold = GL.GetUniformLocation(program, nameof(fSdfThreshold));
|
||||
iEnableSdf = GL.GetUniformLocation(program, nameof(iEnableSdf));
|
||||
iEnableTexture = GL.GetUniformLocation(program, nameof(iEnableTexture));
|
||||
iAlphaDiscard = GL.GetUniformLocation(program, nameof(iAlphaDiscard));
|
||||
tx2d = GL.GetUniformLocation(program, nameof(tx2d));
|
||||
tx2darray = GL.GetUniformLocation(program, nameof(tx2darray));
|
||||
|
||||
IsInit = true;
|
||||
}
|
||||
|
||||
private void AssertInit()
|
||||
{
|
||||
if (!IsInit) throw new InvalidOperationException("Initialize the driver first.");
|
||||
}
|
||||
|
||||
public void Draw(DrawQueue queue, in QRectangle view)
|
||||
{
|
||||
AssertInit();
|
||||
|
||||
if (!data.TryGetValue(queue, out DrawData? draw))
|
||||
{
|
||||
draw = new DrawData(this, queue);
|
||||
data.Add(queue, draw);
|
||||
}
|
||||
|
||||
// This already binds the vertex array for me.
|
||||
draw.PrepareFrame();
|
||||
|
||||
|
||||
QVec2 size = view.Size;
|
||||
QMat4.Orthographic(out QMat4 viewMatrix, view);
|
||||
|
||||
GL.Viewport(0, 0, (int)view.Size.X, (int)view.Size.Y);
|
||||
GL.UseProgram(program);
|
||||
GL.Uniform1(fMaxZ, (float)(queue.ZDepth+1));
|
||||
GL.Uniform1(fSdfThreshold, 0.5f);
|
||||
GL.Uniform1(tx2d, 0);
|
||||
GL.Enable(GL_BLEND);
|
||||
GL.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
GL.Enable(GL_SCISSOR_TEST);
|
||||
GL.Enable(GL_DEPTH_TEST);
|
||||
GL.DepthFunc(GL_LESS);
|
||||
|
||||
foreach (DrawCall call in queue)
|
||||
{
|
||||
GL.Scissor(
|
||||
(int)MathF.Round(call.Bounds.Min.X),
|
||||
(int)MathF.Round(size.Y - call.Bounds.Max.Y),
|
||||
(int)MathF.Round(call.Bounds.Size.X),
|
||||
(int)MathF.Round(call.Bounds.Size.Y));
|
||||
QMat4.Translation(out QMat4 modelMatrix, call.Bounds.Min.X, call.Bounds.Min.Y, 0);
|
||||
QMat4 modelView = viewMatrix * modelMatrix;
|
||||
GL.UniformMatrix4(m4Transforms, false, in modelView);
|
||||
|
||||
GL.ActiveTexture(GL_TEXTURE0);
|
||||
GL.BindTexture(GL_TEXTURE_2D, 0);
|
||||
if (call.Texture != null)
|
||||
{
|
||||
GL.Uniform1(iEnableSdf, call.Texture.IsSdf ? 1 : 0);
|
||||
GL.Uniform1(iAlphaDiscard, 1);
|
||||
|
||||
if (call.Texture.Depth > 1)
|
||||
{
|
||||
GL.Uniform1(iEnableTexture, 3);
|
||||
GL.ActiveTexture(GL_TEXTURE1);
|
||||
GL.BindTexture(GL_TEXTURE_2D_ARRAY, textures.GetTexture(call.Texture));
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.Uniform1(iEnableTexture, 2);
|
||||
GL.ActiveTexture(GL_TEXTURE0);
|
||||
GL.BindTexture(GL_TEXTURE_2D, textures.GetTexture(call.Texture));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.Uniform1(iEnableTexture, 0);
|
||||
}
|
||||
|
||||
GL.DrawElements(GL_TRIANGLES, call.Count, GL_UNSIGNED_INT, sizeof(int)*call.Start);
|
||||
}
|
||||
|
||||
GL.Disable(GL_SCISSOR_TEST);
|
||||
GL.Disable(GL_DEPTH_TEST);
|
||||
GL.Disable(GL_BLEND);
|
||||
}
|
||||
|
||||
public void ClearDrawQueue(DrawQueue queue)
|
||||
{
|
||||
AssertInit();
|
||||
|
||||
if (!data.TryGetValue(queue, out DrawData? draw))
|
||||
return;
|
||||
draw.Dispose();
|
||||
data.Remove(queue);
|
||||
}
|
||||
|
||||
private static int CreateShader(GLEnum type, string name)
|
||||
{
|
||||
StreamReader source = new StreamReader(typeof(GL21Driver).Assembly.GetManifestResourceStream(name) ?? throw new Exception("Resource not found."));
|
||||
string text = source.ReadToEnd();
|
||||
source.Dispose();
|
||||
|
||||
int shader = GL.CreateShader(type);
|
||||
GL.ShaderSource(shader, text);
|
||||
GL.CompileShader(shader);
|
||||
|
||||
if (CheckShader(shader, out string msg) == false)
|
||||
{
|
||||
GraphicsException ex = new GraphicsException($"Failed to compile {type} shader stage.");
|
||||
ex.Data.Add("Shader Stage", type);
|
||||
ex.Data.Add("Shader Info Log", msg);
|
||||
ex.Data.Add("Shader Source", text);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
private static bool CheckShader(int shader, out string message)
|
||||
{
|
||||
message = string.Empty;
|
||||
|
||||
GL.GetShader(shader, GL_COMPILE_STATUS, out int i);
|
||||
|
||||
if (i != (int)GL_TRUE)
|
||||
{
|
||||
message = GL.GetShaderInfoLog(shader);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool CheckProgram(int program, out string message)
|
||||
{
|
||||
message = string.Empty;
|
||||
|
||||
GL.GetProgram(program, GL_LINK_STATUS, out int i);
|
||||
|
||||
if (i != (int)GL_OK)
|
||||
{
|
||||
message = GL.GetProgramInfoLog(program);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (isDiposed) return;
|
||||
|
||||
if (!IsInit)
|
||||
{
|
||||
isDiposed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!disposing)
|
||||
{
|
||||
if (OnGCDispose == null)
|
||||
{
|
||||
throw new Exception("This object must strictly be disposed from the owning thread, not GC");
|
||||
}
|
||||
else
|
||||
{
|
||||
OnGCDispose(this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
GL.DeleteProgram(program);
|
||||
|
||||
foreach (DrawData datum in data.Values)
|
||||
{
|
||||
datum.Dispose();
|
||||
}
|
||||
|
||||
isDiposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
private class DrawData : IDisposable
|
||||
{
|
||||
public DrawQueue Queue { get; }
|
||||
public int VertexArray { get; }
|
||||
|
||||
private readonly GL21Driver driver;
|
||||
private int vbo1, vbo2;
|
||||
private int ebo1, ebo2;
|
||||
|
||||
public DrawData(GL21Driver driver, DrawQueue queue)
|
||||
{
|
||||
Queue = queue;
|
||||
this.driver = driver;
|
||||
VertexArray = GL.GenVertexArray();
|
||||
GL.GenBuffers(1, out vbo1);
|
||||
GL.GenBuffers(1, out vbo2);
|
||||
GL.GenBuffers(1, out ebo1);
|
||||
GL.GenBuffers(1, out ebo2);
|
||||
isDisposed = false;
|
||||
}
|
||||
|
||||
public void PrepareFrame()
|
||||
{
|
||||
int vbo, ebo;
|
||||
vbo = Swap(ref vbo1, ref vbo2);
|
||||
ebo = Swap(ref ebo1, ref ebo2);
|
||||
|
||||
if (Queue.VertexCount == 0 || Queue.ElementCount == 0)
|
||||
return;
|
||||
|
||||
GL.BindVertexArray(VertexArray);
|
||||
GL.BindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||
GL.BufferData(GL_ARRAY_BUFFER, QuikVertex.Stride * Queue.VertexCount, Queue.VertexArray, GL_STREAM_DRAW);
|
||||
|
||||
GL.VertexAttribPointer(driver.v2Position, 2, GL_FLOAT, false, QuikVertex.Stride, QuikVertex.PositionOffset);
|
||||
GL.VertexAttribPointer(driver.fZIndex, 1, GL_UNSIGNED_INT, false, QuikVertex.Stride, QuikVertex.ZIndexOffset);
|
||||
GL.VertexAttribPointer(driver.v2TexPos, 2, GL_FLOAT, false, QuikVertex.Stride, QuikVertex.TextureCoordinatesOffset);
|
||||
GL.VertexAttribPointer(driver.fTexLayer, 1, GL_FLOAT, false, QuikVertex.Stride, QuikVertex.TextureLayerOffset);
|
||||
GL.VertexAttribPointer(driver.v4Color, 4, GL_UNSIGNED_BYTE, true, QuikVertex.Stride, QuikVertex.ColorOffset);
|
||||
GL.EnableVertexAttribArray(driver.v2Position);
|
||||
GL.EnableVertexAttribArray(driver.fZIndex);
|
||||
GL.EnableVertexAttribArray(driver.v2TexPos);
|
||||
GL.EnableVertexAttribArray(driver.v4Color);
|
||||
GL.EnableVertexAttribArray(driver.fTexLayer);
|
||||
|
||||
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, Queue.ElementCount * sizeof(int), Queue.ElementArray, GL_STREAM_DRAW);
|
||||
|
||||
int Swap(ref int a, ref int b)
|
||||
{
|
||||
a ^= b;
|
||||
b ^= a;
|
||||
a ^= b;
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
private bool isDisposed;
|
||||
public void Dispose()
|
||||
{
|
||||
if (isDisposed) return;
|
||||
|
||||
GL.DeleteVertexArray(VertexArray);
|
||||
GL.DeleteBuffer(vbo1);
|
||||
GL.DeleteBuffer(vbo2);
|
||||
GL.DeleteBuffer(ebo1);
|
||||
GL.DeleteBuffer(ebo2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class TextureManager : IDisposable
|
||||
{
|
||||
private readonly Dictionary<QImage, int> textures = new Dictionary<QImage, int>();
|
||||
private readonly HashSet<QImage> imagesNotUsed = new HashSet<QImage>();
|
||||
private bool isDisposed = false;
|
||||
|
||||
public void BeginFrame()
|
||||
{
|
||||
if (imagesNotUsed.Count > 0)
|
||||
{
|
||||
foreach (QImage image in imagesNotUsed)
|
||||
{
|
||||
GL.DeleteTexture(textures[image]);
|
||||
}
|
||||
|
||||
imagesNotUsed.Clear();
|
||||
}
|
||||
|
||||
foreach (QImage image in textures.Keys)
|
||||
{
|
||||
imagesNotUsed.Add(image);
|
||||
}
|
||||
}
|
||||
|
||||
public int GetTexture(QImage image)
|
||||
{
|
||||
if (textures.TryGetValue(image, out int texture))
|
||||
{
|
||||
return texture;
|
||||
}
|
||||
|
||||
if (image.Depth > 1)
|
||||
{
|
||||
texture = UploadTexture3d(image);
|
||||
}
|
||||
else
|
||||
{
|
||||
texture = UploadTexture2d(image);
|
||||
}
|
||||
|
||||
return textures[image] = texture;
|
||||
}
|
||||
|
||||
public int UploadTexture3d(QImage image3d)
|
||||
{
|
||||
int texture = GL.GenTexture();
|
||||
|
||||
GL.BindTexture(GL_TEXTURE_2D_ARRAY, texture);
|
||||
|
||||
image3d.LockBits3d(out QImageLock lck, QImageLockOptions.Default);
|
||||
GL.TexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, lck.Width, lck.Height, lck.Depth, 0, s_InternalFormat[lck.Format], s_PixelType[lck.Format], lck.ImagePtr);
|
||||
image3d.UnlockBits();
|
||||
|
||||
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
public int UploadTexture2d(QImage image2d)
|
||||
{
|
||||
int texture = GL.GenTexture();
|
||||
|
||||
GL.BindTexture(GL_TEXTURE_2D, texture);
|
||||
|
||||
image2d.LockBits2d(out QImageLock lck, QImageLockOptions.Default);
|
||||
GL.TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, lck.Width, lck.Height, 0, s_InternalFormat[lck.Format], s_PixelType[lck.Format], lck.ImagePtr);
|
||||
image2d.UnlockBits();
|
||||
|
||||
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
switch (image2d.InternalFormat)
|
||||
{
|
||||
case QImageFormat.RedU8:
|
||||
case QImageFormat.RedF:
|
||||
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED);
|
||||
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_RED);
|
||||
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
|
||||
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ONE);
|
||||
break;
|
||||
case QImageFormat.AlphaU8:
|
||||
case QImageFormat.AlphaF:
|
||||
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_ONE);
|
||||
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_ONE);
|
||||
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_ONE);
|
||||
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ALPHA);
|
||||
break;
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (isDisposed)
|
||||
return;
|
||||
|
||||
isDisposed = true;
|
||||
|
||||
int[] ids = textures.Values.ToArray();
|
||||
GL.DeleteTextures(ids);
|
||||
}
|
||||
|
||||
private static readonly Dictionary<QImageFormat, GLEnum> s_InternalFormat = new Dictionary<QImageFormat, GLEnum>()
|
||||
{
|
||||
[QImageFormat.AlphaF] = GL_ALPHA,
|
||||
[QImageFormat.AlphaU8] = GL_ALPHA,
|
||||
[QImageFormat.RedF] = GL_RED,
|
||||
[QImageFormat.RedU8] = GL_RED,
|
||||
[QImageFormat.RgbF] = GL_RGB,
|
||||
[QImageFormat.RgbU8] = GL_RGB,
|
||||
[QImageFormat.RgbaU8] = GL_RGBA,
|
||||
[QImageFormat.RgbaF] = GL_RGBA,
|
||||
};
|
||||
|
||||
private static readonly Dictionary<QImageFormat, GLEnum> s_PixelType = new Dictionary<QImageFormat, GLEnum>()
|
||||
{
|
||||
[QImageFormat.AlphaF] = GL_FLOAT,
|
||||
[QImageFormat.RedF] = GL_FLOAT,
|
||||
[QImageFormat.RgbF] = GL_FLOAT,
|
||||
[QImageFormat.RgbaF] = GL_FLOAT,
|
||||
[QImageFormat.AlphaU8] = GL_UNSIGNED_BYTE,
|
||||
[QImageFormat.RedU8] = GL_UNSIGNED_BYTE,
|
||||
[QImageFormat.RgbU8] = GL_UNSIGNED_BYTE,
|
||||
[QImageFormat.RgbaU8] = GL_UNSIGNED_BYTE,
|
||||
};
|
||||
}
|
||||
}
|
94
Dashboard/OpenGL/GLEnum.cs
Normal file
94
Dashboard/OpenGL/GLEnum.cs
Normal file
@ -0,0 +1,94 @@
|
||||
namespace Dashboard.OpenGL
|
||||
{
|
||||
public enum GLEnum : int
|
||||
{
|
||||
GL_OK = 0,
|
||||
GL_TRUE = 1,
|
||||
GL_FALSE = 0,
|
||||
GL_ONE = 1,
|
||||
GL_ZERO = 0,
|
||||
GL_MAJOR_VERSION = 0x821B,
|
||||
GL_MINOR_VERSION = 0x821C,
|
||||
GL_VENDOR = 0x1F00,
|
||||
GL_RENDERER = 0x1F01,
|
||||
GL_VERSION = 0x1F02,
|
||||
GL_EXTENSIONS = 0x1F03,
|
||||
GL_MAX_TEXTURE_SIZE = 0x0D33,
|
||||
GL_MAX_3D_TEXTURE_SIZE = 0x8073,
|
||||
GL_MAX_ARRAY_TEXTURE_LAYERS = 0x88FF,
|
||||
|
||||
GL_MULTISAMPLE = 0x809D,
|
||||
GL_BLEND = 0x0BE2,
|
||||
|
||||
GL_COLOR_BUFFER_BIT = 0x00004000,
|
||||
GL_DEPTH_BUFFER_BIT = 0x00000100,
|
||||
|
||||
GL_SRC_ALPHA = 0x0302,
|
||||
GL_ONE_MINUS_SRC_ALPHA = 0x0303,
|
||||
|
||||
GL_VERTEX_SHADER = 0x8B31,
|
||||
GL_FRAGMENT_SHADER = 0x8B30,
|
||||
GL_INFO_LOG_LENGTH = 0x8B84,
|
||||
GL_COMPILE_STATUS = 0x8B81,
|
||||
GL_LINK_STATUS = 0x8B82,
|
||||
|
||||
GL_UNSIGNED_BYTE = 0x1401,
|
||||
GL_UNSIGNED_SHORT = 0x1403,
|
||||
GL_UNSIGNED_INT = 0x1405,
|
||||
GL_FLOAT = 0x1406,
|
||||
|
||||
GL_RED = 0x1903,
|
||||
GL_GREEN = 0x1904,
|
||||
GL_BLUE = 0x1905,
|
||||
GL_ALPHA = 0x1906,
|
||||
GL_RGB = 0x1907,
|
||||
GL_RGBA = 0x1908,
|
||||
|
||||
GL_ARRAY_BUFFER = 0x8892,
|
||||
GL_ELEMENT_ARRAY_BUFFER = 0x8893,
|
||||
|
||||
GL_STREAM_DRAW = 0x88E0,
|
||||
|
||||
GL_TEXTURE0 = 0x84C0,
|
||||
GL_TEXTURE1 = GL_TEXTURE0 + 1,
|
||||
GL_TEXTURE2 = GL_TEXTURE0 + 2,
|
||||
GL_TEXTURE3 = GL_TEXTURE0 + 3,
|
||||
GL_TEXTURE4 = GL_TEXTURE0 + 4,
|
||||
GL_TEXTURE5 = GL_TEXTURE0 + 5,
|
||||
GL_TEXTURE6 = GL_TEXTURE0 + 6,
|
||||
|
||||
GL_TEXTURE_2D = 0x0DE1,
|
||||
GL_TEXTURE_2D_ARRAY = 0x8C1A,
|
||||
GL_UNPACK_ALIGNMENT = 0x0CF5,
|
||||
|
||||
GL_TEXTURE_MAG_FILTER = 0x2800,
|
||||
GL_TEXTURE_MIN_FILTER = 0x2801,
|
||||
GL_NEAREST = 0x2600,
|
||||
GL_LINEAR = 0x2601,
|
||||
GL_NEAREST_MIPMAP_NEAREST = 0x2700,
|
||||
GL_LINEAR_MIPMAP_NEAREST = 0x2701,
|
||||
GL_NEAREST_MIPMAP_LINEAR = 0x2702,
|
||||
GL_LINEAR_MIPMAP_LINEAR = 0x2703,
|
||||
|
||||
GL_TEXTURE_WRAP_S = 0x2802,
|
||||
GL_TEXTURE_WRAP_T = 0x2803,
|
||||
GL_CLAMP_TO_EDGE = 0x812F,
|
||||
GL_CLAMP_TO_BORDER = 0x812D,
|
||||
GL_MIRRORED_REPEAT = 0x8370,
|
||||
GL_MIRROR_CLAMP_TO_EDGE = 0x8743,
|
||||
GL_CLAMP = 0x2900,
|
||||
GL_REPEAT = 0x2901,
|
||||
|
||||
GL_TRIANGLES = 0x0004,
|
||||
|
||||
GL_SCISSOR_TEST = 0x0C11,
|
||||
GL_DEPTH_TEST = 0x0B71,
|
||||
|
||||
GL_TEXTURE_SWIZZLE_R = 0x8E42,
|
||||
GL_TEXTURE_SWIZZLE_G = 0x8E43,
|
||||
GL_TEXTURE_SWIZZLE_B = 0x8E44,
|
||||
GL_TEXTURE_SWIZZLE_A = 0x8E45,
|
||||
|
||||
GL_LESS = 0x0201,
|
||||
}
|
||||
}
|
46
Dashboard/OpenGL/GraphicsException.cs
Normal file
46
Dashboard/OpenGL/GraphicsException.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using static Dashboard.OpenGL.GLEnum;
|
||||
|
||||
namespace Dashboard.OpenGL
|
||||
{
|
||||
[System.Serializable]
|
||||
public class GraphicsException : System.Exception
|
||||
{
|
||||
public GraphicsException()
|
||||
{
|
||||
AddExtraData();
|
||||
}
|
||||
|
||||
public GraphicsException(string message) : base(message)
|
||||
{
|
||||
AddExtraData();
|
||||
}
|
||||
|
||||
public GraphicsException(string message, System.Exception inner) : base(message, inner)
|
||||
{
|
||||
AddExtraData();
|
||||
}
|
||||
|
||||
protected GraphicsException(
|
||||
System.Runtime.Serialization.SerializationInfo info,
|
||||
System.Runtime.Serialization.StreamingContext context) : base(info, context)
|
||||
{
|
||||
AddExtraData();
|
||||
}
|
||||
|
||||
private void AddExtraData()
|
||||
{
|
||||
GL.Get(GL_MAJOR_VERSION, out int major);
|
||||
GL.Get(GL_MINOR_VERSION, out int minor);
|
||||
|
||||
string version = GL.GetString(GL_VERSION);
|
||||
string vendor = GL.GetString(GL_VENDOR);
|
||||
string renderer = GL.GetString(GL_RENDERER);
|
||||
|
||||
Data.Add("OpenGL Version", new Version(major, minor));
|
||||
Data.Add("OpenGL Version String", version);
|
||||
Data.Add("OpenGL Vendor", vendor);
|
||||
Data.Add("OpenGL Renderer", renderer);
|
||||
}
|
||||
}
|
||||
}
|
91
Dashboard/PAL/Dash.cs
Normal file
91
Dashboard/PAL/Dash.cs
Normal file
@ -0,0 +1,91 @@
|
||||
using Dashboard.CommandMachine;
|
||||
using Dashboard.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Dashboard.PAL
|
||||
{
|
||||
/// <summary>
|
||||
/// An abstraction layer over the UI input and output.
|
||||
/// </summary>
|
||||
public class Dash
|
||||
{
|
||||
private readonly IDashHandle handle;
|
||||
private readonly IDashboardPlatform platform;
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => platform.PortGetTitle(handle);
|
||||
set => platform.PortSetTitle(handle, value);
|
||||
}
|
||||
|
||||
public QVec2 Size
|
||||
{
|
||||
get => platform.PortGetSize(handle);
|
||||
set => platform.PortSetSize(handle, value);
|
||||
}
|
||||
|
||||
public QVec2 Position
|
||||
{
|
||||
get => platform.PortGetPosition(handle);
|
||||
set => platform.PortSetPosition(handle, value);
|
||||
}
|
||||
|
||||
public UIBase? UIElement { get; set; }
|
||||
|
||||
public bool IsValid => platform.PortIsValid(handle);
|
||||
|
||||
public event EventHandler EventRaised
|
||||
{
|
||||
add
|
||||
{
|
||||
platform.PortSubscribeEvent(handle, value);
|
||||
}
|
||||
remove
|
||||
{
|
||||
platform.PortUnsubscribeEvent(handle, value);
|
||||
}
|
||||
}
|
||||
|
||||
public Dash(IDashboardPlatform platform)
|
||||
{
|
||||
this.platform = platform;
|
||||
handle = platform.CreatePort();
|
||||
}
|
||||
|
||||
bool isDisposed = false;
|
||||
public void Dispose()
|
||||
{
|
||||
if (isDisposed) return;
|
||||
|
||||
platform.DestroyPort(handle);
|
||||
isDisposed = true;
|
||||
}
|
||||
|
||||
public void Focus()
|
||||
{
|
||||
platform.PortFocus(handle);
|
||||
}
|
||||
|
||||
public void Paint(CommandList? list = null)
|
||||
{
|
||||
if (UIElement == null)
|
||||
return;
|
||||
|
||||
list ??= new CommandList();
|
||||
|
||||
list.Clear();
|
||||
UIElement.Bounds = new QRectangle(Size, new QVec2(0,0));
|
||||
UIElement.Paint(list);
|
||||
platform.PortPaint(handle, list);
|
||||
}
|
||||
|
||||
public void Show(bool shown = true)
|
||||
{
|
||||
platform.PortShow(handle, shown);
|
||||
}
|
||||
}
|
||||
}
|
61
Dashboard/PAL/IDashboardPlatform.cs
Normal file
61
Dashboard/PAL/IDashboardPlatform.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using Dashboard.CommandMachine;
|
||||
using Dashboard.Media;
|
||||
|
||||
namespace Dashboard.PAL
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty interface to statically type Quik port handles.
|
||||
/// </summary>
|
||||
public interface IDashHandle
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The primary primary platform abstraction interface for dashboard hosts.
|
||||
/// </summary>
|
||||
public interface IDashboardPlatform : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The title of the application.
|
||||
/// </summary>
|
||||
string? Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The default icon for the application.
|
||||
/// </summary>
|
||||
QImage? Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The event raised when an event is received.
|
||||
/// </summary>
|
||||
event EventHandler? EventRaised;
|
||||
|
||||
/// <summary>
|
||||
/// Raise the events that have been enqueued.
|
||||
/// </summary>
|
||||
/// <param name="block">True to block until a new event arrives.</param>
|
||||
void ProcessEvents(bool block);
|
||||
|
||||
/// <summary>
|
||||
/// Create a window.
|
||||
/// </summary>
|
||||
/// <returns>The window instance.</returns>
|
||||
IDashHandle CreatePort();
|
||||
void DestroyPort(IDashHandle port);
|
||||
string PortGetTitle(IDashHandle port);
|
||||
void PortSetTitle(IDashHandle port, string title);
|
||||
QVec2 PortGetSize(IDashHandle port);
|
||||
void PortSetSize(IDashHandle port, QVec2 size);
|
||||
QVec2 PortGetPosition(IDashHandle port);
|
||||
void PortSetPosition(IDashHandle port, QVec2 position);
|
||||
bool PortIsValid(IDashHandle port);
|
||||
void PortSubscribeEvent(IDashHandle port, EventHandler handler);
|
||||
void PortUnsubscribeEvent(IDashHandle port, EventHandler handler);
|
||||
void PortFocus(IDashHandle port);
|
||||
void PortShow(IDashHandle port, bool shown = true);
|
||||
void PortPaint(IDashHandle port, CommandList commands);
|
||||
void GetMaximumImage(out int width, out int height);
|
||||
void GetMaximumImage(out int width, out int height, out int depth);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user