Compare commits
57 Commits
49257574f4
...
dashboard2
| Author | SHA1 | Date | |
|---|---|---|---|
| e2e60bb480 | |||
| 2d312b2a9f | |||
| 121115b108 | |||
| 4f51b4c09c | |||
| 1100fac94b | |||
| e46a13249b | |||
| df8aeca1db | |||
| 293ef241f1 | |||
| 4f7edd42d0 | |||
| 362165f911 | |||
| 008882cb81 | |||
| a6dbba19b4 | |||
| 6665a023c8 | |||
| 3b20c53088 | |||
| bda8e173c9 | |||
| 1b5e13eb10 | |||
| 97f8a29f19 | |||
| abdb78eae7 | |||
| 99a853f35d | |||
| 3d0f15af45 | |||
| 01d7f27dc7 | |||
| 7bf7b6c53c | |||
| fe96499512 | |||
| 18cccf1e41 | |||
| 019ac6f4ae | |||
| 449c398f05 | |||
| 1457e32095 | |||
| 9dc6175f8b | |||
| faaadbf5b1 | |||
| 764b2bff8b | |||
| aaa79d1878 | |||
| f9e374db50 | |||
| 5bcc2d8214 | |||
| 804a3979fd | |||
| d299084db5 | |||
| d20d929515 | |||
| 3b9fa5c9fb | |||
| 027ebe8dbd | |||
| 36dbcac8d9 | |||
| 7c3141822a | |||
| e30e50e860 | |||
| 4f67e0fb75 | |||
| 5cba1ab7db | |||
| 9ca309bd52 | |||
| 043060db66 | |||
| 66c5eecc26 | |||
| 9ee6c1180d | |||
| c4fe4841fe | |||
| 2932b3b85e | |||
| 6e8888df48 | |||
| edc85c3f24 | |||
| 50eda46b13 | |||
| c538dbd56b | |||
| 1dcf167022 | |||
| 2690c5bec0 | |||
| 1c3c730e82 | |||
| 2c957a0c1a |
+39
-14
@@ -1,14 +1,15 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.Diagnostics;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Dashboard
|
namespace Dashboard
|
||||||
{
|
{
|
||||||
|
[DebuggerDisplay("\\{{Min}; {Max}\\}")]
|
||||||
public readonly record struct Box2d(Vector2 Min, Vector2 Max)
|
public readonly record struct Box2d(Vector2 Min, Vector2 Max)
|
||||||
{
|
{
|
||||||
public float Left => Min.X;
|
public float Left => Min.X;
|
||||||
public float Right => Max.X;
|
|
||||||
public float Top => Min.Y;
|
public float Top => Min.Y;
|
||||||
|
public float Right => Max.X;
|
||||||
public float Bottom => Max.Y;
|
public float Bottom => Max.Y;
|
||||||
|
|
||||||
public Vector2 Size => Max - Min;
|
public Vector2 Size => Max - Min;
|
||||||
@@ -24,19 +25,43 @@ namespace Dashboard
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Box2d FromPositionAndSize(Vector2 position, Vector2 size, Origin anchor = Origin.Center)
|
public static Box2d FromPositionAndSize(Vector2 position, Vector2 size, Origin anchor = Origin.Center) =>
|
||||||
{
|
// Hoping the redundent additions+muls become conditional fma instructions.
|
||||||
Vector2 half = size * 0.5f;
|
anchor switch
|
||||||
switch (anchor)
|
|
||||||
{
|
{
|
||||||
case Origin.Center:
|
Origin.Left => new Box2d(
|
||||||
return new Box2d(position - half, position + half);
|
position + (size * new Vector2(0.0f, -0.5f)),
|
||||||
case Origin.TopLeft:
|
position + (size * new Vector2(1.0f, 0.5f))
|
||||||
return new Box2d(position, position + size);
|
),
|
||||||
default:
|
Origin.TopLeft => new Box2d(position, position + size),
|
||||||
throw new NotImplementedException();
|
Origin.Top => new Box2d(
|
||||||
}
|
position + (size * new Vector2(-0.5f, 0f)),
|
||||||
}
|
position + (size * new Vector2(0.5f, 1f))),
|
||||||
|
Origin.TopRight => new Box2d(
|
||||||
|
position + (size * new Vector2(-1.0f, 0.0f)),
|
||||||
|
position + (size * new Vector2(0.0f, 1.0f))
|
||||||
|
),
|
||||||
|
Origin.Right => new Box2d(
|
||||||
|
position + (size * new Vector2(-1.0f, -0.5f)),
|
||||||
|
position + (size * new Vector2(0.0f, 0.5f))
|
||||||
|
),
|
||||||
|
Origin.BottomRight => new Box2d(
|
||||||
|
position + (size * new Vector2(-1.0f, -1.0f)),
|
||||||
|
position + (size * new Vector2(0.0f, 0.0f))
|
||||||
|
),
|
||||||
|
Origin.Bottom => new Box2d(
|
||||||
|
position + (size * new Vector2(-0.5f, -1.0f)),
|
||||||
|
position + (size * new Vector2(0.5f, 0.0f))
|
||||||
|
),
|
||||||
|
Origin.BottomLeft => new Box2d(
|
||||||
|
position + (size * new Vector2(0.0f, -1.0f)),
|
||||||
|
position + (size * new Vector2(1.0f, 0.0f))
|
||||||
|
),
|
||||||
|
Origin.Center => new Box2d(
|
||||||
|
position + (size * new Vector2(-0.5f, -0.5f)),
|
||||||
|
position + (size * new Vector2(0.5f, 0.5f))),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(anchor)),
|
||||||
|
};
|
||||||
|
|
||||||
public static Box2d Union(Box2d left, Box2d right)
|
public static Box2d Union(Box2d left, Box2d right)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
using System.Drawing;
|
using System.Diagnostics;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Dashboard
|
namespace Dashboard
|
||||||
{
|
{
|
||||||
|
[DebuggerDisplay("\\{{Min}; {Max}\\}")]
|
||||||
public readonly record struct Box3d(Vector3 Min, Vector3 Max)
|
public readonly record struct Box3d(Vector3 Min, Vector3 Max)
|
||||||
{
|
{
|
||||||
public float Left => Min.X;
|
public float Left => Min.X;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Dashboard
|
namespace Dashboard.Collections
|
||||||
{
|
{
|
||||||
public class HashList<T> : IReadOnlyList<T>
|
public class HashList<T> : IReadOnlyList<T>
|
||||||
where T : notnull
|
where T : notnull
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
|
||||||
|
namespace Dashboard.Collections
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class for better type access performance.
|
||||||
|
/// </summary>
|
||||||
|
public record TypeAtom
|
||||||
|
{
|
||||||
|
|
||||||
|
public Type Type { get; }
|
||||||
|
public int Id { get; }
|
||||||
|
public ImmutableHashSet<TypeAtom> Ancestors { get; }
|
||||||
|
|
||||||
|
// Makes it so TypeAtom doesn't get pissed at me
|
||||||
|
private protected TypeAtom()
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeAtom(Type type)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
|
||||||
|
HashSet<TypeAtom> ancestors = new HashSet<TypeAtom>();
|
||||||
|
FindAncestors(Type, ancestors);
|
||||||
|
ancestors.Add(this);
|
||||||
|
Ancestors = ancestors.ToImmutableHashSet();
|
||||||
|
|
||||||
|
lock (s_lockObject)
|
||||||
|
{
|
||||||
|
Id = s_counter++;
|
||||||
|
s_atoms.Add(Id, this);
|
||||||
|
s_types.Add(Type, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly object s_lockObject = new object();
|
||||||
|
private static readonly Dictionary<int, TypeAtom> s_atoms = new Dictionary<int, TypeAtom>();
|
||||||
|
private static readonly Dictionary<Type, TypeAtom> s_types = new Dictionary<Type, TypeAtom>();
|
||||||
|
|
||||||
|
private static int s_counter = 0;
|
||||||
|
|
||||||
|
public static TypeAtom? Get(int id) => s_atoms.GetValueOrDefault(id);
|
||||||
|
public static TypeAtom Get(Type type)
|
||||||
|
{
|
||||||
|
if (s_types.TryGetValue(type, out TypeAtom? id))
|
||||||
|
return id;
|
||||||
|
|
||||||
|
// Type is not registered, try to acquire lock.
|
||||||
|
lock (s_lockObject)
|
||||||
|
{
|
||||||
|
// Maybe somebody else registered this type whilst acquiring the lock.
|
||||||
|
if (s_types.TryGetValue(type, out id))
|
||||||
|
return id;
|
||||||
|
|
||||||
|
// Register the type if applicable and leave.
|
||||||
|
return new TypeAtom(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FindAncestors(Type type, HashSet<TypeAtom> destination)
|
||||||
|
{
|
||||||
|
// Traverse the object tree for all possible aliases.
|
||||||
|
if (type.BaseType != null)
|
||||||
|
{
|
||||||
|
foreach (TypeAtom ancestor in Get(type.BaseType).Ancestors)
|
||||||
|
{
|
||||||
|
destination.Add(ancestor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Type trait in type.GetInterfaces())
|
||||||
|
{
|
||||||
|
TypeAtom atom = Get(trait);
|
||||||
|
destination.Add(atom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class for better type access performance.
|
||||||
|
/// </summary>
|
||||||
|
public sealed record TypeAtom<T> : TypeAtom
|
||||||
|
{
|
||||||
|
public static TypeAtom Atom { get; } = Get(typeof(T));
|
||||||
|
public new static int Id => Atom.Id;
|
||||||
|
public new static Type Type => Atom.Type;
|
||||||
|
public new static ImmutableHashSet<TypeAtom> Ancestors => Atom.Ancestors;
|
||||||
|
|
||||||
|
private TypeAtom() { }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace Dashboard.Collections
|
||||||
|
{
|
||||||
|
public class TypeDictionary<T>(bool hierarchical = false) : IEnumerable<T>
|
||||||
|
{
|
||||||
|
private readonly Dictionary<int, T> _objects = new Dictionary<int, T>();
|
||||||
|
|
||||||
|
public bool Contains<T2>() where T2 : T => _objects.ContainsKey(TypeAtom<T2>.Id);
|
||||||
|
|
||||||
|
public bool Add<T2>(T2 value) where T2 : T
|
||||||
|
{
|
||||||
|
TypeAtom atom = TypeAtom<T2>.Atom;
|
||||||
|
|
||||||
|
if (!_objects.TryAdd(atom.Id, value))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!hierarchical)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
foreach (TypeAtom ancestor in atom.Ancestors)
|
||||||
|
{
|
||||||
|
_objects[ancestor.Id] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T2 Get<T2>() where T2 : T => TryGet(out T2? value) ? value : throw new KeyNotFoundException();
|
||||||
|
|
||||||
|
public void Set<T2>(T2 value) where T2 : T
|
||||||
|
{
|
||||||
|
TypeAtom atom = TypeAtom<T2>.Atom;
|
||||||
|
_objects[atom.Id] = value;
|
||||||
|
|
||||||
|
if (!hierarchical)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (TypeAtom ancestor in atom.Ancestors)
|
||||||
|
{
|
||||||
|
_objects[ancestor.Id] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove<T2>() where T2 : T => Remove<T>(out _);
|
||||||
|
|
||||||
|
public bool Remove<T2>([NotNullWhen(true)] out T2? value) where T2 : T
|
||||||
|
{
|
||||||
|
TypeAtom atom = TypeAtom<T2>.Atom;
|
||||||
|
|
||||||
|
if (!_objects.Remove(atom.Id, out T? subValue))
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = (T2?)subValue;
|
||||||
|
|
||||||
|
if (hierarchical)
|
||||||
|
{
|
||||||
|
foreach (TypeAtom ancestor in atom.Ancestors)
|
||||||
|
{
|
||||||
|
_objects.Remove(ancestor.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGet<T2>([NotNullWhen(true)] out T2? value) where T2 : T
|
||||||
|
{
|
||||||
|
if (!_objects.TryGetValue(TypeAtom<T2>.Id, out T? subValue))
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = (T2?)subValue;
|
||||||
|
return value != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear() => _objects.Clear();
|
||||||
|
|
||||||
|
public IEnumerator<T> GetEnumerator() => _objects.Values.Distinct().GetEnumerator();
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TypeDictionary<TKey, TValue>(bool hierarchical = false) : IEnumerable<KeyValuePair<TypeAtom, TValue>>
|
||||||
|
{
|
||||||
|
private readonly Dictionary<int, TValue> _objects = new Dictionary<int, TValue>();
|
||||||
|
|
||||||
|
public bool Contains<TKey2>() where TKey2 : TKey => _objects.ContainsKey(TypeAtom<TKey2>.Id);
|
||||||
|
|
||||||
|
public bool Add<TKey2>(TValue value) where TKey2 : TKey
|
||||||
|
{
|
||||||
|
TypeAtom atom = TypeAtom<TKey2>.Atom;
|
||||||
|
|
||||||
|
if (!_objects.TryAdd(atom.Id, value))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!hierarchical)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
foreach (TypeAtom ancestor in atom.Ancestors)
|
||||||
|
{
|
||||||
|
_objects[ancestor.Id] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TValue Get<TKey2>() where TKey2 : TKey => (TValue)_objects[TypeAtom<TKey2>.Id]!;
|
||||||
|
|
||||||
|
public void Set<TKey2>(TValue value) where TKey2 : TKey
|
||||||
|
{
|
||||||
|
TypeAtom atom = TypeAtom<TKey2>.Atom;
|
||||||
|
_objects[atom.Id] = value;
|
||||||
|
|
||||||
|
if (!hierarchical)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (TypeAtom ancestor in atom.Ancestors)
|
||||||
|
{
|
||||||
|
_objects[ancestor.Id] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove<TKey2>() where TKey2 : TKey => Remove<TKey2>(out _);
|
||||||
|
public bool Remove<TKey2>([MaybeNullWhen(false)] out TValue? value) where TKey2 : TKey
|
||||||
|
{
|
||||||
|
TypeAtom atom = TypeAtom<TKey2>.Atom;
|
||||||
|
|
||||||
|
if (!_objects.Remove(atom.Id, out value))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hierarchical)
|
||||||
|
{
|
||||||
|
foreach (TypeAtom ancestor in atom.Ancestors)
|
||||||
|
{
|
||||||
|
_objects.Remove(ancestor.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGet<TKey2>([NotNullWhen(true)] out TValue? value) where TKey2 : TKey => _objects.TryGetValue(TypeAtom<TKey2>.Id, out value);
|
||||||
|
|
||||||
|
public void Clear() => _objects.Clear();
|
||||||
|
|
||||||
|
public IEnumerator<KeyValuePair<TypeAtom, TValue>> GetEnumerator() => _objects.Select(x => new KeyValuePair<TypeAtom, TValue>(TypeAtom.Get(x.Key)!, x.Value)).GetEnumerator();
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace Dashboard.Collections
|
||||||
|
{
|
||||||
|
public class TypeHashSet(bool hierarchical = false) : IEnumerable<TypeAtom>
|
||||||
|
{
|
||||||
|
private readonly HashSet<int> _set = new HashSet<int>();
|
||||||
|
|
||||||
|
public bool Contains<T>() => _set.Contains(TypeAtom<T>.Id);
|
||||||
|
|
||||||
|
public bool Set<T>()
|
||||||
|
{
|
||||||
|
if (!_set.Add(TypeAtom<T>.Id))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (hierarchical)
|
||||||
|
foreach (TypeAtom ancestor in TypeAtom<T>.Ancestors)
|
||||||
|
{
|
||||||
|
_set.Add(ancestor.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Reset<T>()
|
||||||
|
{
|
||||||
|
if (!_set.Remove(TypeAtom<T>.Id))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (hierarchical)
|
||||||
|
foreach (TypeAtom ancestor in TypeAtom<T>.Ancestors)
|
||||||
|
{
|
||||||
|
_set.Remove(ancestor.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear() => _set.Clear();
|
||||||
|
|
||||||
|
public IEnumerator<TypeAtom> GetEnumerator() => _set.Select(x => TypeAtom.Get(x)!).GetEnumerator();
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<RootNamespace>Dashboard</RootNamespace>
|
<RootNamespace>Dashboard</RootNamespace>
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Dashboard.Drawing
|
||||||
|
{
|
||||||
|
public abstract class Brush : INotifyPropertyChanged, ICloneable
|
||||||
|
{
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
public virtual object Clone()
|
||||||
|
{
|
||||||
|
return MemberwiseClone();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void SetField<T>(ref T field, T value, [CallerMemberName] string property = "")
|
||||||
|
{
|
||||||
|
field = value;
|
||||||
|
OnPropertyChanged(property);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnPropertyChanged(string property)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class SolidColorBrush(Color color) : Brush
|
||||||
|
{
|
||||||
|
public Color Color { get; } = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImageBrush(Image image) : Brush
|
||||||
|
{
|
||||||
|
public Image Image
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetField(ref field, value);
|
||||||
|
} = image;
|
||||||
|
|
||||||
|
public Box2d TextureCoordinates
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetField(ref field, value);
|
||||||
|
} = new Box2d(0, 0, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NinePatchImageBrush(Image image) : Brush
|
||||||
|
{
|
||||||
|
public Image Image
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetField(ref field, value);
|
||||||
|
} = image;
|
||||||
|
|
||||||
|
public Box2d CenterCoordinates
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetField(ref field, value);
|
||||||
|
} = new Box2d(0, 0, 1, 1);
|
||||||
|
|
||||||
|
public Extents Extents
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetField(ref field, value);
|
||||||
|
} = Extents.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GradientBrush : Brush
|
||||||
|
{
|
||||||
|
public Gradient Gradient
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Unsubscribe(field);
|
||||||
|
Subscribe(value);
|
||||||
|
|
||||||
|
SetField(ref field, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GradientBrush(Gradient gradient)
|
||||||
|
{
|
||||||
|
Gradient = gradient;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Subscribe(Gradient gradient)
|
||||||
|
{
|
||||||
|
gradient.PropertyChanged += GradientPropertyChanged;
|
||||||
|
gradient.CollectionChanged += GradientCollectionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Unsubscribe(Gradient? gradient)
|
||||||
|
{
|
||||||
|
gradient?.PropertyChanged -= GradientPropertyChanged;
|
||||||
|
gradient?.CollectionChanged -= GradientCollectionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GradientCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(Gradient));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GradientPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(Gradient));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Dashboard.Drawing
|
||||||
|
{
|
||||||
|
public enum MeshPrimitive
|
||||||
|
{
|
||||||
|
Point,
|
||||||
|
Line,
|
||||||
|
Triangle,
|
||||||
|
TriangleFan,
|
||||||
|
TriangleStrip,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum WindingOrder
|
||||||
|
{
|
||||||
|
Clockwise,
|
||||||
|
Counterclockwise,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FaceCulling
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Front,
|
||||||
|
Back,
|
||||||
|
Both,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum VertexAttributeType
|
||||||
|
{
|
||||||
|
UnsignedByte,
|
||||||
|
UnsignedShort,
|
||||||
|
UnsignedInt,
|
||||||
|
Byte,
|
||||||
|
Short,
|
||||||
|
Int,
|
||||||
|
Half,
|
||||||
|
Float,
|
||||||
|
Double,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum IndexType
|
||||||
|
{
|
||||||
|
UnsignedShort,
|
||||||
|
UnsignedInt,
|
||||||
|
UnsignedLong,
|
||||||
|
Short,
|
||||||
|
Int,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum BlendFunction
|
||||||
|
{
|
||||||
|
One,
|
||||||
|
Zero,
|
||||||
|
SourceAlpha,
|
||||||
|
OneMinusSourceAlpha,
|
||||||
|
DestinationAlpha,
|
||||||
|
OneMinusDestinationAlpha,
|
||||||
|
SourceColor,
|
||||||
|
OneMinusSourceColor,
|
||||||
|
DestinationColor,
|
||||||
|
OneMinusDestinationColor,
|
||||||
|
ConstantColor,
|
||||||
|
OneMinusConstantColor,
|
||||||
|
ConstantAlpha,
|
||||||
|
OneMinusConstantAlpha,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum BlendEquation
|
||||||
|
{
|
||||||
|
Add,
|
||||||
|
Subtract,
|
||||||
|
ReverseSubtract,
|
||||||
|
Min,
|
||||||
|
Max,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TestFunction
|
||||||
|
{
|
||||||
|
Never,
|
||||||
|
LessThan,
|
||||||
|
LessThanOrEqual,
|
||||||
|
Equal,
|
||||||
|
NotEqual,
|
||||||
|
GreaterThanOrEqual,
|
||||||
|
Greater,
|
||||||
|
Always,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum StencilOperation
|
||||||
|
{
|
||||||
|
Keep,
|
||||||
|
Zero,
|
||||||
|
Replace,
|
||||||
|
Increment,
|
||||||
|
UncheckedIncrement,
|
||||||
|
Decrement,
|
||||||
|
UncheckedDecrement,
|
||||||
|
Invert,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum UniformType
|
||||||
|
{
|
||||||
|
U1,
|
||||||
|
U2,
|
||||||
|
U3,
|
||||||
|
U4,
|
||||||
|
I1,
|
||||||
|
I2,
|
||||||
|
I3,
|
||||||
|
I4,
|
||||||
|
F1,
|
||||||
|
F2,
|
||||||
|
F3,
|
||||||
|
F4,
|
||||||
|
Mat2,
|
||||||
|
Mat3,
|
||||||
|
Mat4,
|
||||||
|
Buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct DepthMode(bool Test, bool Mask, TestFunction Function)
|
||||||
|
{
|
||||||
|
public static readonly DepthMode Off = new DepthMode(false, false, TestFunction.Always);
|
||||||
|
public static readonly DepthMode WriteOnly = new DepthMode(false, true, TestFunction.LessThan);
|
||||||
|
public static readonly DepthMode Equal = new DepthMode(true, false, TestFunction.Equal);
|
||||||
|
public static readonly DepthMode Enabled = new DepthMode(true, true, TestFunction.LessThan);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct BlendFactor(BlendFunction Source, BlendFunction Destination)
|
||||||
|
{
|
||||||
|
public static readonly BlendFactor Default =
|
||||||
|
new BlendFactor(BlendFunction.SourceAlpha, BlendFunction.OneMinusSourceAlpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct BlendMode(bool Enabled, BlendFactor Color, BlendFactor Alpha)
|
||||||
|
{
|
||||||
|
[MemberNotNullWhen(false, nameof(Unified), nameof(UnifiedEquation))]
|
||||||
|
public bool IsSeparate => Color != Alpha;
|
||||||
|
public BlendFactor? Unified => IsSeparate ? null : Color;
|
||||||
|
public BlendEquation? UnifiedEquation => IsSeparate ? null : ColorEquation;
|
||||||
|
public BlendEquation ColorEquation { get; init; } = BlendEquation.Add;
|
||||||
|
public BlendEquation AlphaEquation { get; init; } = BlendEquation.Add;
|
||||||
|
public Color Constant { get; init; } = System.Drawing.Color.Black;
|
||||||
|
|
||||||
|
public BlendMode(BlendFunction source, BlendFunction destination)
|
||||||
|
: this(true, new BlendFactor(source, destination), new BlendFactor(source, destination))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly BlendMode Off = new BlendMode(false, BlendFactor.Default, BlendFactor.Default);
|
||||||
|
|
||||||
|
public static readonly BlendMode Normal =
|
||||||
|
new BlendMode(true, BlendFactor.Default, BlendFactor.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct StencilMode(bool Enabled, TestFunction Function)
|
||||||
|
{
|
||||||
|
public int Reference { get; init; } = 0;
|
||||||
|
public int Mask { get; init; } = ~0;
|
||||||
|
public StencilOperation Pass { get; init; } = StencilOperation.Keep;
|
||||||
|
public StencilOperation DepthFail { get; init; } = StencilOperation.Keep;
|
||||||
|
public StencilOperation Fail { get; init; } = StencilOperation.Keep;
|
||||||
|
|
||||||
|
public static readonly StencilMode Off = new StencilMode(false, TestFunction.Always);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record struct VertexAttribute(
|
||||||
|
int Location,
|
||||||
|
int Components,
|
||||||
|
VertexAttributeType Type,
|
||||||
|
long Offset,
|
||||||
|
long Stride)
|
||||||
|
{
|
||||||
|
public int Divisor { get; init; } = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record VertexSpecification(
|
||||||
|
ImmutableList<VertexAttribute> Attributes,
|
||||||
|
ImmutableList<IBuffer?> VertexBuffers
|
||||||
|
)
|
||||||
|
{
|
||||||
|
public IBuffer? ElementBuffer { get; init; } = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record struct UniformDescriptor(int Location, UniformType Type, long Offset, long Size);
|
||||||
|
|
||||||
|
public struct PipelineState()
|
||||||
|
{
|
||||||
|
public Box2d ViewportRegion { get; init; } = new Box2d(Vector2.Zero, Vector2.PositiveInfinity);
|
||||||
|
public Box2d ScissorRegion { get; init; } = new Box2d(Vector2.PositiveInfinity, Vector2.PositiveInfinity);
|
||||||
|
public WindingOrder FrontFace { get; init; } = WindingOrder.Counterclockwise;
|
||||||
|
public FaceCulling CullMode { get; init; } = FaceCulling.Back;
|
||||||
|
public BlendMode BlendMode { get; init; } = BlendMode.Normal;
|
||||||
|
public DepthMode DepthMode { get; init; } = DepthMode.Enabled;
|
||||||
|
public StencilMode StencilMode { get; init; } = StencilMode.Off;
|
||||||
|
public bool RedMask { get; init; } = true;
|
||||||
|
public bool GreenMask { get; init; } = true;
|
||||||
|
public bool BlueMask { get; init; } = true;
|
||||||
|
public bool AlphaMask { get; init; } = true;
|
||||||
|
public float PointSize { get; init; }= 1.0f;
|
||||||
|
public float LineWidth { get; init; }= 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct DrawCall(MeshPrimitive primitive, VertexSpecification vertexSpec, int first, int count)
|
||||||
|
{
|
||||||
|
public IShader? ShaderPipeline { get; init; }
|
||||||
|
public PipelineState PipelineState { get; init; } = new PipelineState();
|
||||||
|
public VertexSpecification VertexSpecification { get; init; } = vertexSpec;
|
||||||
|
|
||||||
|
public MeshPrimitive Primitive { get; init; } = primitive;
|
||||||
|
public int First { get; init; } = first;
|
||||||
|
public int Count { get; init; } = count;
|
||||||
|
public int BaseVertex { get; init; } = 0;
|
||||||
|
public long Offset { get; init; } = 0;
|
||||||
|
public bool EnableRestart { get; init; } = false;
|
||||||
|
public long RestartIndex { get; init; } = -1;
|
||||||
|
|
||||||
|
public Box2d ViewportRegion => PipelineState.ViewportRegion;
|
||||||
|
public Box2d ScissorRegion => PipelineState.ScissorRegion;
|
||||||
|
public WindingOrder FrontFace => PipelineState.FrontFace;
|
||||||
|
public FaceCulling CullMode => PipelineState.CullMode;
|
||||||
|
public BlendMode BlendMode => PipelineState.BlendMode;
|
||||||
|
public DepthMode DepthMode => PipelineState.DepthMode;
|
||||||
|
public StencilMode StencilMode => PipelineState.StencilMode;
|
||||||
|
public bool RedMask => PipelineState.RedMask;
|
||||||
|
public bool GreenMask => PipelineState.GreenMask;
|
||||||
|
public bool BlueMask => PipelineState.BlueMask;
|
||||||
|
public bool AlphaMask => PipelineState.AlphaMask;
|
||||||
|
public float PointSize => PipelineState.PointSize;
|
||||||
|
public float LineWidth => PipelineState.LineWidth;
|
||||||
|
|
||||||
|
public List<ITexture?> Textures { get; init; } = [];
|
||||||
|
public List<UniformDescriptor> Uniforms { get; init; } = [];
|
||||||
|
public IndexType IndexType { get; init; } = IndexType.UnsignedShort;
|
||||||
|
|
||||||
|
public ReadOnlyMemory<byte> UniformData { get; init; } = ReadOnlyMemory<byte>.Empty;
|
||||||
|
public IBuffer? UniformBuffer { get; init; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net.Mime;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
|
||||||
|
namespace Dashboard.Drawing
|
||||||
|
{
|
||||||
|
public class Font(IFont iFont) : IFont
|
||||||
|
{
|
||||||
|
public IFont Base => iFont;
|
||||||
|
public string Family => iFont.Family;
|
||||||
|
public FontWeight Weight => iFont.Weight;
|
||||||
|
public FontSlant Slant => iFont.Slant;
|
||||||
|
public FontStretch Stretch => iFont.Stretch;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
iFont.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Font Create(Stream stream)
|
||||||
|
{
|
||||||
|
IFont iFont = Application.Current.ExtensionRequire<IFontLoader>().Load(stream);
|
||||||
|
|
||||||
|
return new Font(iFont);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Font Create(FontInfo info)
|
||||||
|
{
|
||||||
|
IFont iFont = Application.Current.ExtensionRequire<IFontLoader>().Load(info);
|
||||||
|
|
||||||
|
return new Font(iFont);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Font Create(string path)
|
||||||
|
{
|
||||||
|
IFont iFont = Application.Current.ExtensionRequire<IFontLoader>().Load(path);
|
||||||
|
|
||||||
|
return new Font(iFont);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Specialized;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Dashboard
|
namespace Dashboard.Drawing
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enumeration of the kinds of gradients available.
|
/// Enumeration of the kinds of gradients available.
|
||||||
@@ -25,29 +27,41 @@ namespace Dashboard
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="Position">The position of the gradient stop. Must be [0,1].</param>
|
/// <param name="Position">The position of the gradient stop. Must be [0,1].</param>
|
||||||
/// <param name="Color">The color value for the stop.</param>
|
/// <param name="Color">The color value for the stop.</param>
|
||||||
public record struct GradientStop(float Position, Color Color);
|
public readonly record struct GradientStop(float Position, Color Color);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a linear gradient.
|
/// Represents a linear gradient.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public struct Gradient : ICollection<GradientStop>, ICloneable, IEquatable<Gradient>
|
public class Gradient : ICollection<GradientStop>, ICloneable, IEquatable<Gradient>, INotifyPropertyChanged, INotifyCollectionChanged
|
||||||
{
|
{
|
||||||
private readonly List<GradientStop> _stops = new List<GradientStop>();
|
private readonly List<GradientStop> _stops = new List<GradientStop>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gradient type.
|
/// Gradient type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GradientType Type { get; set; } = GradientType.Axial;
|
public GradientType Type
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetField(ref field, value);
|
||||||
|
} = GradientType.Axial;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// First gradient control point.
|
/// First gradient control point.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Vector2 C0 { get; set; } = Vector2.Zero;
|
public Vector2 C0
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetField(ref field, value);
|
||||||
|
} = Vector2.Zero;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Second gradient control point.
|
/// Second gradient control point.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Vector2 C1 { get; set; } = Vector2.One;
|
public Vector2 C1
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetField(ref field, value);
|
||||||
|
} = Vector2.One;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Number of stops in a gradient.
|
/// Number of stops in a gradient.
|
||||||
@@ -69,6 +83,10 @@ namespace Dashboard
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
public event NotifyCollectionChangedEventHandler? CollectionChanged;
|
||||||
|
|
||||||
|
|
||||||
public Gradient()
|
public Gradient()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -164,11 +182,13 @@ namespace Dashboard
|
|||||||
index = _stops.Count;
|
index = _stops.Count;
|
||||||
|
|
||||||
_stops.Insert(index, item);
|
_stops.Insert(index, item);
|
||||||
|
OnCollectionChanged(NotifyCollectionChangedAction.Add);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
_stops.Clear();
|
_stops.Clear();
|
||||||
|
OnCollectionChanged(NotifyCollectionChangedAction.Reset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Contains(GradientStop item)
|
public bool Contains(GradientStop item)
|
||||||
@@ -183,30 +203,51 @@ namespace Dashboard
|
|||||||
|
|
||||||
public bool Remove(GradientStop item)
|
public bool Remove(GradientStop item)
|
||||||
{
|
{
|
||||||
return _stops.Remove(item);
|
if (!_stops.Remove(item))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
OnCollectionChanged(NotifyCollectionChangedAction.Remove);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveAt(int index)
|
public void RemoveAt(int index)
|
||||||
{
|
{
|
||||||
_stops.RemoveAt(index);
|
_stops.RemoveAt(index);
|
||||||
|
OnCollectionChanged(NotifyCollectionChangedAction.Remove);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
{
|
{
|
||||||
HashCode code = new HashCode();
|
HashCode code = new HashCode();
|
||||||
|
|
||||||
code.Add(Count);
|
code.Add(Count);
|
||||||
|
code.Add(Type);
|
||||||
|
code.Add(C0);
|
||||||
|
code.Add(C1);
|
||||||
|
|
||||||
foreach (GradientStop item in this)
|
foreach (GradientStop item in this)
|
||||||
code.Add(item.GetHashCode());
|
code.Add(item.GetHashCode());
|
||||||
return code.ToHashCode();
|
return code.ToHashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Equals(Gradient other)
|
public bool Equals(Gradient? other)
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
|
other != null &&
|
||||||
Type == other.Type &&
|
Type == other.Type &&
|
||||||
C0 == other.C0 &&
|
C0 == other.C0 &&
|
||||||
C1 == other.C1 &&
|
C1 == other.C1 &&
|
||||||
_stops.Equals(other._stops);
|
_stops.SequenceEqual(other._stops);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool NotEquals(Gradient? other)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
other == null ||
|
||||||
|
Type != other.Type ||
|
||||||
|
C0 != other.C0 ||
|
||||||
|
C1 != other.C1 ||
|
||||||
|
!_stops.SequenceEqual(other._stops);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
public override bool Equals(object? obj)
|
||||||
@@ -214,14 +255,30 @@ namespace Dashboard
|
|||||||
return obj is Gradient other && Equals(other);
|
return obj is Gradient other && Equals(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool operator ==(Gradient left, Gradient right)
|
public static bool operator ==(Gradient? left, Gradient? right)
|
||||||
{
|
{
|
||||||
return left.Equals(right);
|
return left?.Equals(right) ?? right == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool operator !=(Gradient left, Gradient right)
|
public static bool operator !=(Gradient? left, Gradient? right)
|
||||||
{
|
{
|
||||||
return !left.Equals(right);
|
return left?.NotEquals(right) ?? right != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void SetField<T>(ref T field, T value, [CallerMemberName] string name = "")
|
||||||
|
{
|
||||||
|
field = value;
|
||||||
|
OnPropertyChanged(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnPropertyChanged(string property)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnCollectionChanged(NotifyCollectionChangedAction action)
|
||||||
|
{
|
||||||
|
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
namespace Dashboard.Drawing
|
||||||
|
{
|
||||||
|
public enum BufferAccessPattern
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No hint given. Not recommended.
|
||||||
|
/// </summary>
|
||||||
|
DontCare,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For buffers which have a long lifetime, but the contents are initialized once.
|
||||||
|
/// </summary>
|
||||||
|
Static,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For buffers which have a long lifetime, but the contents can be updated every frame.
|
||||||
|
/// </summary>
|
||||||
|
Stream,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For buffers which have a short lifetime, used for streaming resources to the graphics hardware.
|
||||||
|
/// </summary>
|
||||||
|
Upload,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For buffers which have a short lifetime, used for streaming resources from the graphics hardware.
|
||||||
|
/// </summary>
|
||||||
|
Download,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public interface IBuffer : IDisposable
|
||||||
|
{
|
||||||
|
public BufferAccessPattern AccessPattern { get; }
|
||||||
|
public long Size { get; }
|
||||||
|
|
||||||
|
void Reallocate(long newSize);
|
||||||
|
Span<T> Map<T>(long offset, int count = -1) where T : unmanaged;
|
||||||
|
void Unmap();
|
||||||
|
void Read<T>(long offset, Span<T> span) where T : unmanaged;
|
||||||
|
void Write<T>(long offset, ReadOnlySpan<T> span) where T : unmanaged;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
|
||||||
|
namespace Dashboard.Drawing
|
||||||
|
{
|
||||||
|
public interface IDeviceContextBase : IDeviceContextExtension
|
||||||
|
{
|
||||||
|
Box2d ClipRegion { get; }
|
||||||
|
Box2d ScissorRegion { get; }
|
||||||
|
Matrix4x4 Transforms { get; }
|
||||||
|
float Scale { get; }
|
||||||
|
float ScaleOverride { get; set; }
|
||||||
|
|
||||||
|
void ResetClip();
|
||||||
|
void PushClip(Box2d clipRegion);
|
||||||
|
void PopClip();
|
||||||
|
|
||||||
|
void ResetScissor();
|
||||||
|
void PushScissor(Box2d scissorRegion);
|
||||||
|
void PopScissor();
|
||||||
|
|
||||||
|
void ResetTransforms();
|
||||||
|
void PushTransforms(in Matrix4x4 matrix);
|
||||||
|
void PopTransforms();
|
||||||
|
|
||||||
|
void ClearColor(Color color);
|
||||||
|
void ClearDepth();
|
||||||
|
|
||||||
|
int IncrementZ();
|
||||||
|
int DecrementZ();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using Dashboard.Pal;
|
||||||
|
|
||||||
|
namespace Dashboard.Drawing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This extension provides direct access to the backend rendering pipeline without writing with a higher level
|
||||||
|
/// abstraction than the rendering backend.
|
||||||
|
/// </summary>
|
||||||
|
public interface IDirectRendering : IDeviceContextExtension
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Create a shader program.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type of the shader create info object.</typeparam>
|
||||||
|
/// <param name="createInfo">An object which describes how the shader is created.</param>
|
||||||
|
/// <returns>A shader program object.</returns>
|
||||||
|
/// <seealso cref="SpirvShaderCreateInfo"/>
|
||||||
|
/// <seealso cref="GlslShaderCreateInfo"/>
|
||||||
|
/// <seealso cref="GlslComputeShaderCreateInfo"/>
|
||||||
|
IShader CreateShader<T>(T createInfo) where T : IShaderCreateInfo;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a texture object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">Type of texture to create.</param>
|
||||||
|
/// <returns>An empty texture object.</returns>
|
||||||
|
ITexture CreateTexture(TextureType type);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a buffer object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pattern">Buffer access pattern hint.</param>
|
||||||
|
/// <param name="size">Initial capacity of the buffer in bytes.</param>
|
||||||
|
/// <returns>An empty buffer object.</returns>
|
||||||
|
IBuffer CreateBuffer(BufferAccessPattern pattern, long size);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enqueue a draw call.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="drawCall">The draw call to enqueue.</param>
|
||||||
|
void Draw(DrawCall drawCall);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete("Use IDirectRendering instead.")]
|
||||||
|
public interface ITextureExtension : IDeviceContextExtension
|
||||||
|
{
|
||||||
|
/// <inheritdoc cref="IDirectRendering.CreateTexture(TextureType)"/>
|
||||||
|
ITexture CreateTexture(TextureType type);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using Dashboard.Pal;
|
||||||
|
|
||||||
|
namespace Dashboard.Drawing
|
||||||
|
{
|
||||||
|
public interface IFont : IDisposable
|
||||||
|
{
|
||||||
|
string Family { get; }
|
||||||
|
FontWeight Weight { get; }
|
||||||
|
FontSlant Slant { get; }
|
||||||
|
FontStretch Stretch { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record struct FontInfo(string Family, FontWeight Weight = FontWeight.Normal,
|
||||||
|
FontSlant Slant = FontSlant.Normal, FontStretch Stretch = FontStretch.Normal) : IFont
|
||||||
|
{
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IFontLoader : IApplicationExtension
|
||||||
|
{
|
||||||
|
IFont Load(FontInfo info);
|
||||||
|
IFont Load(string path);
|
||||||
|
IFont Load(Stream stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
using Dashboard.Pal;
|
||||||
|
|
||||||
|
namespace Dashboard.Drawing
|
||||||
|
{
|
||||||
|
public record ImageData(
|
||||||
|
TextureType Type,
|
||||||
|
PixelFormat Format,
|
||||||
|
int Width,
|
||||||
|
int Height,
|
||||||
|
byte[] Bitmap)
|
||||||
|
{
|
||||||
|
public int Depth { get; init; } = 1;
|
||||||
|
public int Levels { get; init; } = 1;
|
||||||
|
public bool Premultiplied { get; init; } = false;
|
||||||
|
public int Alignment { get; init; } = 4;
|
||||||
|
|
||||||
|
public long GetLevelOffset(int level)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfLessThan(level, 0, nameof(level));
|
||||||
|
ArgumentOutOfRangeException.ThrowIfGreaterThan(level, Levels, nameof(level));
|
||||||
|
ArgumentOutOfRangeException.ThrowIfGreaterThan(level, Math.ILogB(Math.Max(Width, Height)));
|
||||||
|
|
||||||
|
long offset = 0;
|
||||||
|
|
||||||
|
long row = Width * Format switch
|
||||||
|
{
|
||||||
|
PixelFormat.R8I => 1,
|
||||||
|
PixelFormat.R16F => 2,
|
||||||
|
PixelFormat.Rg8I => 2,
|
||||||
|
PixelFormat.Rg16F => 4,
|
||||||
|
PixelFormat.Rgb8I => 3,
|
||||||
|
PixelFormat.Rgb16F => 6,
|
||||||
|
PixelFormat.Rgba8I => 4,
|
||||||
|
PixelFormat.Rgba16F => 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
row += Alignment - (row % Alignment);
|
||||||
|
long plane = row * Height;
|
||||||
|
long volume = plane * Depth;
|
||||||
|
|
||||||
|
for (int i = 0; i < level; i++)
|
||||||
|
{
|
||||||
|
if (Depth == 1)
|
||||||
|
{
|
||||||
|
offset += plane / (1 << i) / (1 << i);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
offset += volume / (1 << i) / (1 << i) / (1 << i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IImageLoader : IApplicationExtension
|
||||||
|
{
|
||||||
|
public ImageData LoadImageData(Stream stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
using System.Data;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Dashboard.Layout;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
|
||||||
|
namespace Dashboard.Drawing
|
||||||
|
{
|
||||||
|
public record struct RectangleDrawInfo(Vector2 Position, Box2d Box, Brush Fill, Brush? Border = null);
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit, Size = Size)]
|
||||||
|
public struct ImmediateVertex()
|
||||||
|
{
|
||||||
|
[field: FieldOffset(PosOffset)]
|
||||||
|
public Vector3 Position { get; init; }= Vector3.Zero;
|
||||||
|
|
||||||
|
[field: FieldOffset(TexCoordsOffset)]
|
||||||
|
public Vector2 TexCoords { get; init; } = Vector2.Zero;
|
||||||
|
|
||||||
|
[field: FieldOffset(ColorOffset)]
|
||||||
|
public Vector4 Color { get; init; } = Vector4.One;
|
||||||
|
|
||||||
|
public ImmediateVertex(Vector3 position, Vector2 texCoords, Vector4 color) : this()
|
||||||
|
{
|
||||||
|
Position = position;
|
||||||
|
TexCoords = texCoords;
|
||||||
|
Color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmediateVertex(Vector3 position, Vector2 texCoords, Color color)
|
||||||
|
: this(
|
||||||
|
position,
|
||||||
|
texCoords,
|
||||||
|
new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public const int Size = 16 * sizeof(float);
|
||||||
|
public const int PosOffset = 0 * sizeof(float);
|
||||||
|
public const int TexCoordsOffset = 4 * sizeof(float);
|
||||||
|
public const int ColorOffset = 8 * sizeof(float);
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ImmediateDrawCall(MeshPrimitive primitive, ReadOnlyMemory<ImmediateVertex> vertices)
|
||||||
|
{
|
||||||
|
public MeshPrimitive Primitive { get; init; } = primitive;
|
||||||
|
public ReadOnlyMemory<ImmediateVertex> Vertices { get; init; } = vertices;
|
||||||
|
public PipelineState PipelineState { get; init; } = new PipelineState();
|
||||||
|
public Matrix4x4 Transforms { get; init; } = Matrix4x4.Identity;
|
||||||
|
public ITexture? Texture { get; init; } = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IImmediateMode : IDeviceContextExtension
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enqueue a draw with the immediate mode shader pipeline.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="call">Information about this draw call.</param>
|
||||||
|
void DrawImmediate(ImmediateDrawCall call);
|
||||||
|
|
||||||
|
// TODO: move these to their own extension?
|
||||||
|
void Line(Vector2 a, Vector2 b, float width, float depth, Vector4 color);
|
||||||
|
void Rectangle(Box2d rectangle, float depth, Vector4 color);
|
||||||
|
void Rectangle(in RectangleDrawInfo rectangle);
|
||||||
|
void Image(Box2d rectangle, Box2d uv, float depth, ITexture texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
|
||||||
|
namespace Dashboard.Drawing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Base interface for creating shader pipelines.
|
||||||
|
/// </summary>
|
||||||
|
public interface IShaderCreateInfo
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct ShaderMappingProperty(string Name, int Index);
|
||||||
|
|
||||||
|
public interface IShader : IDisposable
|
||||||
|
{
|
||||||
|
public ImmutableList<ShaderMappingProperty> Attributes { get; }
|
||||||
|
public ImmutableList<ShaderMappingProperty> Uniforms { get; }
|
||||||
|
public ImmutableList<ShaderMappingProperty> Blocks { get; }
|
||||||
|
public ImmutableList<ShaderMappingProperty> Textures { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
|
||||||
|
namespace Dashboard.Drawing
|
||||||
|
{
|
||||||
|
|
||||||
|
public interface ITextRenderer : IDeviceContextExtension
|
||||||
|
{
|
||||||
|
Box2d MeasureText(IFont font, float size, string text);
|
||||||
|
void DrawText(Vector2 position, Vector4 color, float size, IFont font, string text);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
|
||||||
|
namespace Dashboard.Drawing
|
||||||
|
{
|
||||||
|
public enum TextureType
|
||||||
|
{
|
||||||
|
Texture1D,
|
||||||
|
Texture2D,
|
||||||
|
Texture2DArray,
|
||||||
|
Texture2DCube,
|
||||||
|
Texture3D,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TextureFilter
|
||||||
|
{
|
||||||
|
Nearest,
|
||||||
|
Linear,
|
||||||
|
NearestMipmapNearest,
|
||||||
|
LinearMipmapNearest,
|
||||||
|
NearestMipmapLinear,
|
||||||
|
LinearMipmapLinear,
|
||||||
|
Anisotropic,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TextureRepeat
|
||||||
|
{
|
||||||
|
Repeat,
|
||||||
|
MirroredRepeat,
|
||||||
|
ClampToEdge,
|
||||||
|
ClampToBorder,
|
||||||
|
MirrorClampToEdge,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CubeMapFace
|
||||||
|
{
|
||||||
|
PositiveX,
|
||||||
|
PositiveY,
|
||||||
|
PositiveZ,
|
||||||
|
NegativeX,
|
||||||
|
NegativeY,
|
||||||
|
NegativeZ,
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ITexture : IDisposable
|
||||||
|
{
|
||||||
|
public TextureType Type { get; }
|
||||||
|
public PixelFormat Format { get; }
|
||||||
|
|
||||||
|
public int Width { get; }
|
||||||
|
public int Height { get; }
|
||||||
|
public int Depth { get; }
|
||||||
|
public int Levels { get; }
|
||||||
|
|
||||||
|
public bool Premultiplied { get; set; }
|
||||||
|
|
||||||
|
public ColorSwizzle Swizzle { get; set; }
|
||||||
|
|
||||||
|
public TextureFilter MinifyFilter { get; set; }
|
||||||
|
|
||||||
|
public TextureFilter MagnifyFilter { get; set; }
|
||||||
|
|
||||||
|
public Color BorderColor { get; set; }
|
||||||
|
|
||||||
|
public TextureRepeat RepeatS { get; set; }
|
||||||
|
|
||||||
|
public TextureRepeat RepeatT { get; set; }
|
||||||
|
|
||||||
|
public TextureRepeat RepeatR { get; set; }
|
||||||
|
public int Anisotropy { get; set; }
|
||||||
|
|
||||||
|
void SetStorage(PixelFormat format, int width, int height, int depth, int levels);
|
||||||
|
void Read<T>(Span<T> buffer, int level = 0, int align = 0) where T : unmanaged;
|
||||||
|
void Write<T>(PixelFormat format, ReadOnlySpan<T> buffer, int level = 0, int align = 4) where T : unmanaged;
|
||||||
|
void Premultiply();
|
||||||
|
void Unmultiply();
|
||||||
|
void GenerateMipmaps();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
|
||||||
|
namespace Dashboard.Drawing
|
||||||
|
{
|
||||||
|
public class Image(ImageData data) : IDisposable
|
||||||
|
{
|
||||||
|
protected readonly ConditionalWeakTable<DeviceContext, ITexture> Textures =
|
||||||
|
new ConditionalWeakTable<DeviceContext, ITexture>();
|
||||||
|
|
||||||
|
public virtual TextureType Type => data.Type;
|
||||||
|
public PixelFormat Format { get; } = data.Format;
|
||||||
|
public int Width { get; } = data.Width;
|
||||||
|
public int Height { get; } = data.Height;
|
||||||
|
public int Depth { get; } = data.Depth;
|
||||||
|
public int Levels { get; } = data.Levels;
|
||||||
|
public bool Premultiplied { get; } = data.Premultiplied;
|
||||||
|
|
||||||
|
public bool IsDisposed { get; private set; } = false;
|
||||||
|
|
||||||
|
~Image()
|
||||||
|
{
|
||||||
|
InvokeDispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual ITexture InternTexture(DeviceContext dc)
|
||||||
|
{
|
||||||
|
if (Textures.TryGetValue(dc, out ITexture? texture))
|
||||||
|
return texture;
|
||||||
|
|
||||||
|
IDirectRendering ext = dc.ExtensionRequire<IDirectRendering>();
|
||||||
|
texture = ext.CreateTexture(Type);
|
||||||
|
texture.SetStorage(Format, Width, Height, Depth, Levels);
|
||||||
|
for (int i = 0; i < Levels; i++)
|
||||||
|
{
|
||||||
|
texture.Write<byte>(Format, data.Bitmap.AsSpan()[(int)data.GetLevelOffset(i)..], level: i, align: data.Alignment);
|
||||||
|
}
|
||||||
|
texture.Premultiplied = Premultiplied;
|
||||||
|
texture.GenerateMipmaps();
|
||||||
|
Textures.Add(dc, texture);
|
||||||
|
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InvokeDispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (IsDisposed)
|
||||||
|
return;
|
||||||
|
IsDisposed = true;
|
||||||
|
|
||||||
|
Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
foreach ((DeviceContext dc, ITexture texture) in Textures)
|
||||||
|
{
|
||||||
|
texture.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() => InvokeDispose(true);
|
||||||
|
|
||||||
|
public static Image Load(Stream stream)
|
||||||
|
{
|
||||||
|
IImageLoader imageLoader = Application.Current.ExtensionRequire<IImageLoader>();
|
||||||
|
return new Image(imageLoader.LoadImageData(stream));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
namespace Dashboard.Drawing
|
||||||
|
{
|
||||||
|
public record GlslShaderCreateInfo() : IShaderCreateInfo
|
||||||
|
{
|
||||||
|
public required string VertexShader { get; init; }
|
||||||
|
public string? GeometryShader { get; init; } = null;
|
||||||
|
public string? TesselationControl { get; init; } = null;
|
||||||
|
public string? TesselationEvaluation { get; init; } = null;
|
||||||
|
public string? FragmentShader { get; init; } = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record GlslComputeShaderCreateInfo(string Source) : IShaderCreateInfo
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public record SpirvShaderCreateInfo(byte[] Binary) : IShaderCreateInfo
|
||||||
|
{
|
||||||
|
public int Format { get; init; } = Spirv;
|
||||||
|
|
||||||
|
public SpirvShaderCreateInfo(Stream stream) : this(GetBinary(stream)) { }
|
||||||
|
|
||||||
|
private static byte[] GetBinary(Stream stream)
|
||||||
|
{
|
||||||
|
//FIXME: this might actually be copying twice.
|
||||||
|
if (stream is MemoryStream ms)
|
||||||
|
{
|
||||||
|
return ms.ToArray();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using MemoryStream memory = new MemoryStream();
|
||||||
|
stream.CopyTo(memory);
|
||||||
|
return memory.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const int Spirv = 38225;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
namespace Dashboard.Drawing
|
||||||
|
{
|
||||||
|
public enum ShaderStage
|
||||||
|
{
|
||||||
|
Vertex,
|
||||||
|
Geometry,
|
||||||
|
TesselationEvaluation,
|
||||||
|
TesselationControl,
|
||||||
|
Fragment,
|
||||||
|
Compute,
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class ShaderBuilder(string language)
|
||||||
|
{
|
||||||
|
public string Language { get; } = language;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
namespace Dashboard.Events
|
||||||
|
{
|
||||||
|
[Flags]
|
||||||
|
public enum ModifierKeys
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
LeftBitPos = 8,
|
||||||
|
RightBitPos = 16,
|
||||||
|
|
||||||
|
Shift = (1 << 0),
|
||||||
|
Control = (1 << 1),
|
||||||
|
Alt = (1 << 2),
|
||||||
|
Meta = (1 << 3),
|
||||||
|
|
||||||
|
NumLock = (1 << 4),
|
||||||
|
CapsLock = (1 << 5),
|
||||||
|
ScrollLock = (1 << 6),
|
||||||
|
|
||||||
|
LeftShift = (Shift << LeftBitPos),
|
||||||
|
LeftControl = (Control << LeftBitPos),
|
||||||
|
LeftAlt = (Alt << LeftBitPos),
|
||||||
|
LeftMeta = (Meta << LeftBitPos),
|
||||||
|
|
||||||
|
RightShift = (Shift << RightBitPos),
|
||||||
|
RightControl = (Control << RightBitPos),
|
||||||
|
RightAlt = (Alt << RightBitPos),
|
||||||
|
RightMeta = (Meta << RightBitPos),
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum KeyCode
|
||||||
|
{
|
||||||
|
// TODO: Keycode table. Keycodes are subject to change upstream. SeeAlso: Extensions.cs@KeyCode ToDashboard(this Key key)
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ScanCode
|
||||||
|
{
|
||||||
|
// This scan code table is based on the Win32 scan code table.
|
||||||
|
// See https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input
|
||||||
|
Error = 0xFF,
|
||||||
|
SystemPowerDown = 0xE05E,
|
||||||
|
SystemSleep = 0xE05F,
|
||||||
|
SystemWakeUp = 0xE063,
|
||||||
|
|
||||||
|
A = 0x1E,
|
||||||
|
B = 0x30,
|
||||||
|
C = 0x2E,
|
||||||
|
D = 0x20,
|
||||||
|
E = 0x12,
|
||||||
|
F = 0x21,
|
||||||
|
G = 0x22,
|
||||||
|
H = 0x23,
|
||||||
|
I = 0x17,
|
||||||
|
J = 0x24,
|
||||||
|
K = 0x25,
|
||||||
|
L = 0x26,
|
||||||
|
M = 0x32,
|
||||||
|
N = 0x31,
|
||||||
|
O = 0x18,
|
||||||
|
P = 0x19,
|
||||||
|
Q = 0x10,
|
||||||
|
R = 0x13,
|
||||||
|
S = 0x1F,
|
||||||
|
T = 0x14,
|
||||||
|
U = 0x16,
|
||||||
|
V = 0x2F,
|
||||||
|
W = 0x11,
|
||||||
|
X = 0x2D,
|
||||||
|
Y = 0x15,
|
||||||
|
Z = 0x2C,
|
||||||
|
|
||||||
|
D1 = 0x02,
|
||||||
|
D2 = 0x03,
|
||||||
|
D3 = 0x04,
|
||||||
|
D4 = 0x05,
|
||||||
|
D5 = 0x06,
|
||||||
|
D6 = 0x07,
|
||||||
|
D7 = 0x08,
|
||||||
|
D8 = 0x09,
|
||||||
|
D9 = 0x0A,
|
||||||
|
D0 = 0x0B,
|
||||||
|
|
||||||
|
Return = 0x1C,
|
||||||
|
Esc = 0x01,
|
||||||
|
Delete = 0x0E,
|
||||||
|
Tab = 0x0F,
|
||||||
|
Space = 0x39,
|
||||||
|
Dash = 0x0C,
|
||||||
|
Equals = 0x0D,
|
||||||
|
LBracket = 0x1A,
|
||||||
|
RBracket = 0x1B,
|
||||||
|
Backslash = 0x2B,
|
||||||
|
Semicolon = 0x27,
|
||||||
|
Apostrophe = 0x28,
|
||||||
|
Grave = 0x29,
|
||||||
|
Comma = 0x33,
|
||||||
|
Period = 0x34,
|
||||||
|
ForwardSlash = 0x35,
|
||||||
|
CapsLock = 0x3A,
|
||||||
|
F1 = 0x3B,
|
||||||
|
F2 = 0x3C,
|
||||||
|
F3 = 0x3D,
|
||||||
|
F4 = 0x3E,
|
||||||
|
F5 = 0x3F,
|
||||||
|
F6 = 0x40,
|
||||||
|
F7 = 0x41,
|
||||||
|
F8 = 0x42,
|
||||||
|
F9 = 0x43,
|
||||||
|
F10 = 0x44,
|
||||||
|
F11 = 0x45,
|
||||||
|
F12 = 0x46,
|
||||||
|
PrintScr = 0xE037,
|
||||||
|
ScrollLock = 0x46,
|
||||||
|
Pause = 0xE046,
|
||||||
|
Insert = 0xE052,
|
||||||
|
Home = 0xE047,
|
||||||
|
PageUp = 0xE049,
|
||||||
|
Backspace = 0xE053,
|
||||||
|
End = 0xE04F,
|
||||||
|
PageDown = 0xE051,
|
||||||
|
RightArrow = 0xE04D,
|
||||||
|
LeftArrow = 0xE04B,
|
||||||
|
DownArrow = 0xE050,
|
||||||
|
UpArrow = 0xE048,
|
||||||
|
NumLock = 0xE045,
|
||||||
|
NumDiv = 0xE035,
|
||||||
|
NumMul = 0x37,
|
||||||
|
NumSub = 0x4A,
|
||||||
|
NumAdd = 0x4E,
|
||||||
|
NumEnter = 0xE01C,
|
||||||
|
Num1 = 0x4F,
|
||||||
|
Num2 = 0x50,
|
||||||
|
Num3 = 0x51,
|
||||||
|
Num4 = 0x4B,
|
||||||
|
Num5 = 0x4C,
|
||||||
|
Num6 = 0x4D,
|
||||||
|
Num7 = 0x47,
|
||||||
|
Num8 = 0x48,
|
||||||
|
Num9 = 0x49,
|
||||||
|
Num0 = 0x52,
|
||||||
|
NumDecimal = 0x53,
|
||||||
|
NumBackslash = 0x56,
|
||||||
|
NumEquals = 0x59,
|
||||||
|
Application = 0xE05D,
|
||||||
|
F13 = 0x64,
|
||||||
|
F14 = 0x65,
|
||||||
|
F15 = 0x66,
|
||||||
|
F16 = 0x67,
|
||||||
|
F17 = 0x68,
|
||||||
|
F18 = 0x69,
|
||||||
|
F19 = 0x6A,
|
||||||
|
F20 = 0x6B,
|
||||||
|
F21 = 0x6C,
|
||||||
|
F22 = 0x6D,
|
||||||
|
F23 = 0x6E,
|
||||||
|
F24 = 0x76,
|
||||||
|
NumComma = 0x7E,
|
||||||
|
International1 = 0x73,
|
||||||
|
International2 = 0x70,
|
||||||
|
International3 = 0x7D,
|
||||||
|
International4 = 0x79,
|
||||||
|
International5 = 0x7B,
|
||||||
|
International6 = 0x5C,
|
||||||
|
International7,
|
||||||
|
International8,
|
||||||
|
International9,
|
||||||
|
Lang1 = 0x72,
|
||||||
|
Lang2 = 0x71,
|
||||||
|
Lang3 = 0x78,
|
||||||
|
Lang4 = 0x77,
|
||||||
|
Lang5,
|
||||||
|
Lang6,
|
||||||
|
Lang7,
|
||||||
|
Lang8,
|
||||||
|
Lang9,
|
||||||
|
LCtrl = 0x1D,
|
||||||
|
LShift = 0x2A,
|
||||||
|
LWin = 0xE05B,
|
||||||
|
LAlt = 0x38,
|
||||||
|
RAlt = 0xE038,
|
||||||
|
RWin = 0xE05C,
|
||||||
|
RCtrl = 0xE01D,
|
||||||
|
RShift = 0x36,
|
||||||
|
NextTrack = 0xE019,
|
||||||
|
PrevTrack = 0xE010,
|
||||||
|
Stop = 0xE024,
|
||||||
|
PlayPause = 0xE022,
|
||||||
|
VolUp = 0xE030,
|
||||||
|
VolDown = 0xE029,
|
||||||
|
Configuration = 0xE06D,
|
||||||
|
Email = 0xE06C,
|
||||||
|
Calculator = 0xE021,
|
||||||
|
Browser = 0xE06B,
|
||||||
|
Search = 0xE065,
|
||||||
|
HomePage = 0xE032,
|
||||||
|
Back = 0xE06A,
|
||||||
|
Forward = 0xE69,
|
||||||
|
Halt = 0xE068,
|
||||||
|
Refresh = 0xE67,
|
||||||
|
Bookmarks = 0xE066,
|
||||||
|
|
||||||
|
NonUsSlashBar = 0xFF01,
|
||||||
|
Mute,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class KeyboardButtonEventArgs(KeyCode keyCode, ScanCode scanCode, ModifierKeys modifierKeys, bool up)
|
||||||
|
: UiEventArgs(up ? UiEventType.KeyUp : UiEventType.KeyDown)
|
||||||
|
{
|
||||||
|
public KeyCode KeyCode { get; } = keyCode;
|
||||||
|
public ScanCode ScanCode { get; } = scanCode;
|
||||||
|
public ModifierKeys ModifierKeys { get; } = modifierKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TextInputEventArgs(string text) : UiEventArgs(UiEventType.TextEdit)
|
||||||
|
{
|
||||||
|
public string Text { get; } = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TextEditEventArgs(string candidate, int cursor, int length) : UiEventArgs(UiEventType.TextEdit)
|
||||||
|
{
|
||||||
|
public string Candidate { get; } = candidate;
|
||||||
|
public int Cursor { get; } = cursor;
|
||||||
|
public int Length { get; } = length;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Dashboard.Events
|
||||||
|
{
|
||||||
|
[Flags]
|
||||||
|
public enum MouseButtons
|
||||||
|
{
|
||||||
|
M1 = 1 << 0,
|
||||||
|
M2 = 1 << 1,
|
||||||
|
M3 = 1 << 2,
|
||||||
|
M4 = 1 << 3,
|
||||||
|
M5 = 1 << 4,
|
||||||
|
M6 = 1 << 5,
|
||||||
|
M7 = 1 << 6,
|
||||||
|
M8 = 1 << 7,
|
||||||
|
|
||||||
|
Left = M1,
|
||||||
|
Right = M2,
|
||||||
|
Middle = M3,
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class MouseMoveEventArgs(Vector2 clientPosition, Vector2 delta) : UiEventArgs(UiEventType.MouseMove)
|
||||||
|
{
|
||||||
|
public Vector2 ClientPosition { get; } = clientPosition;
|
||||||
|
public Vector2 Delta { get; } = delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class MouseButtonEventArgs(Vector2 clientPosition, MouseButtons buttons, ModifierKeys modifierKeys, bool up)
|
||||||
|
: UiEventArgs(up ? UiEventType.MouseButtonUp : UiEventType.MouseButtonDown)
|
||||||
|
{
|
||||||
|
public ModifierKeys ModifierKeys { get; } = modifierKeys;
|
||||||
|
public Vector2 ClientPosition { get; } = clientPosition;
|
||||||
|
public MouseButtons Buttons { get; } = buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class MouseScrollEventArgs(Vector2 clientPosition, Vector2 scrollDelta)
|
||||||
|
: UiEventArgs(UiEventType.MouseScroll)
|
||||||
|
{
|
||||||
|
public Vector2 ClientPosition { get; } = clientPosition;
|
||||||
|
public Vector2 ScrollDelta { get; } = scrollDelta;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
namespace Dashboard.Events
|
||||||
|
{
|
||||||
|
public class TickEventArgs : UiEventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Animation delta time in seconds.
|
||||||
|
/// </summary>
|
||||||
|
public float Delta { get; }
|
||||||
|
|
||||||
|
public TickEventArgs(float delta) : base(UiEventType.AnimationTick)
|
||||||
|
{
|
||||||
|
Delta = delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
|
||||||
|
namespace Dashboard.Events
|
||||||
|
{
|
||||||
|
public enum UiEventType
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
AnimationTick, // Generic timer event.
|
||||||
|
Paint, // Generic paint event.
|
||||||
|
|
||||||
|
// Text input related events.
|
||||||
|
KeyDown, // Keyboard key down.
|
||||||
|
KeyUp, // Keyboard key up.
|
||||||
|
TextInput, // Non-IME text event.
|
||||||
|
TextEdit, // IME text event.
|
||||||
|
TextCandidates, // IME text candidate list.
|
||||||
|
TextLanguage, // Keyboard language changed event.
|
||||||
|
|
||||||
|
// Mouse & touch related events
|
||||||
|
MouseButtonDown, // Mouse button down.
|
||||||
|
MouseButtonUp, // Mouse button up.
|
||||||
|
MouseMove, // Mouse moved.
|
||||||
|
MouseScroll, // Mouse scrolled.
|
||||||
|
|
||||||
|
// Reserved event names
|
||||||
|
StylusEnter, // The stylus has entered the hover region.
|
||||||
|
StylusLeave, // The stylus has left the hover region.
|
||||||
|
StylusMove, // The stylus has moved.
|
||||||
|
StylusDown, // The stylus is touching.
|
||||||
|
StylusUp, // The stylus is no longer touching.
|
||||||
|
StylusButtonUp, // Stylus button up.
|
||||||
|
StylusButtonDown, // Stylus button down.
|
||||||
|
StylusAxes, // Extra stylus axes data.
|
||||||
|
|
||||||
|
// Window & Control Events
|
||||||
|
ControlInvalidateVisual, // Force rendering the control again.
|
||||||
|
ControlStateChanged, // Control state changed.
|
||||||
|
ControlMoved, // Control moved.
|
||||||
|
ControlResized, // Control resized.
|
||||||
|
ControlEnter, // The pointing device entered the control.
|
||||||
|
ControlLeave, // The pointing device left the control.
|
||||||
|
ControlFocusGet, // The control acquired focus.
|
||||||
|
ControlFocusLost, // The control lost focus.
|
||||||
|
WindowClose, // The window closed.
|
||||||
|
|
||||||
|
UserRangeStart = 1 << 12,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UiEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public UiEventType Type { get; }
|
||||||
|
|
||||||
|
public UiEventArgs(UiEventType type)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly UiEventArgs None = new UiEventArgs(UiEventType.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PaintEventArgs(DeviceContext dc) : UiEventArgs(UiEventType.Paint)
|
||||||
|
{
|
||||||
|
public DeviceContext DeviceContext { get; } = dc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ControlMovedEventArgs : UiEventArgs
|
||||||
|
{
|
||||||
|
public Vector2 OldPosition { get; }
|
||||||
|
public Vector2 NewPosition { get; }
|
||||||
|
|
||||||
|
public ControlMovedEventArgs(Vector2 oldPosition, Vector2 newPosition) : base(UiEventType.ControlMoved)
|
||||||
|
{
|
||||||
|
OldPosition = oldPosition;
|
||||||
|
NewPosition = newPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ResizeEventArgs() : UiEventArgs(UiEventType.ControlResized)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Dashboard.Events
|
||||||
|
{
|
||||||
|
public class WindowCloseEvent() : UiEventArgs(UiEventType.WindowClose)
|
||||||
|
{
|
||||||
|
public bool Cancel { get; set; } = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Dashboard
|
||||||
|
{
|
||||||
|
[DebuggerDisplay("{Value}")]
|
||||||
|
public readonly record struct Extents(Vector4 Value)
|
||||||
|
{
|
||||||
|
public float Left => Value.X;
|
||||||
|
public float Top => Value.Y;
|
||||||
|
public float Right => Value.Z;
|
||||||
|
public float Bottom => Value.W;
|
||||||
|
|
||||||
|
public float ExtentWidth => Left + Right;
|
||||||
|
|
||||||
|
public float ExtentHeight => Top + Bottom;
|
||||||
|
|
||||||
|
public Vector2 ExtentSize => new(ExtentWidth, ExtentHeight);
|
||||||
|
|
||||||
|
public Extents(float left, float top, float right, float bottom)
|
||||||
|
: this(new Vector4(left, top, right, bottom))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Extents(float value) : this(new Vector4(value))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly Extents None = new Extents(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ namespace Dashboard
|
|||||||
_400 = 400,
|
_400 = 400,
|
||||||
_500 = 500,
|
_500 = 500,
|
||||||
_600 = 600,
|
_600 = 600,
|
||||||
|
_700 = 700,
|
||||||
_800 = 800,
|
_800 = 800,
|
||||||
_900 = 900,
|
_900 = 900,
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace Dashboard
|
namespace Dashboard
|
||||||
@@ -6,57 +6,81 @@ namespace Dashboard
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Pixel format for images.
|
/// Pixel format for images.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum PixelFormat
|
[Flags]
|
||||||
|
public enum PixelFormat : byte
|
||||||
{
|
{
|
||||||
R8I,
|
None = 0,
|
||||||
Rg8I,
|
|
||||||
Rgb8I,
|
R8I = R | I8,
|
||||||
Rgba8I,
|
Rg8I = Rg | I8,
|
||||||
R16F,
|
Rgb8I = Rgb | I8,
|
||||||
Rg816F,
|
Rgba8I = Rgba | I8,
|
||||||
Rgb16F,
|
R16F = R | F16,
|
||||||
Rgba16F,
|
Rg16F = Rg | F16,
|
||||||
|
Rgb16F = Rgb | F16,
|
||||||
|
Rgba16F = Rgba | F16,
|
||||||
|
|
||||||
|
// Channels (hide these from editors)
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Advanced)]
|
||||||
|
R = 0x01,
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Advanced)]
|
||||||
|
Rg = 0x02,
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Advanced)]
|
||||||
|
Rgb = 0x03,
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Advanced)]
|
||||||
|
Rgba = 0x04,
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Advanced)]
|
||||||
|
A = 0x05,
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Advanced)]
|
||||||
|
ColorMask = 0x0F,
|
||||||
|
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Advanced)]
|
||||||
|
I8 = 0x10,
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Advanced)]
|
||||||
|
F16 = 0x20,
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Advanced)]
|
||||||
|
TypeMask = 0xF0,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Color channels for images.
|
/// Color channels for images.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum ColorChannel
|
public enum ColorChannel : byte
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An invalid swizzle mask.
|
||||||
|
/// </summary>
|
||||||
|
Unknown = 0b00000,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The zero channel. Used for swizzle masks.
|
/// The zero channel. Used for swizzle masks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Zero = 0,
|
Zero = 0b10000,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The one channel. Used for swizzle masks.
|
/// The one channel. Used for swizzle masks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
One = 1,
|
One = 0b11110,
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// An invalid swizzle mask.
|
|
||||||
/// </summary>
|
|
||||||
Unknown = 2,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The red channel.
|
/// The red channel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Red = 4,
|
Red = 0b11000,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The green channel.
|
/// The green channel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Green = 5,
|
Green = 0b10100,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The blue channel.
|
/// The blue channel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Blue = 6,
|
Blue = 0b10010,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The alpha channel.
|
/// The alpha channel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Alpha = 7,
|
Alpha = 0b10001,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Dashboard.Layout
|
||||||
|
{
|
||||||
|
public class ContainerLayoutInfo : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
public FlowDirection FlowDirection
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetField(ref field, value);
|
||||||
|
} = FlowDirection.Row;
|
||||||
|
|
||||||
|
public Extents Padding
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetField(ref field, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
public virtual void ValidateLayout(ILayoutContainer container)
|
||||||
|
{
|
||||||
|
if (container.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ItemSolution[] items = [.. container.Select(x => new ItemSolution(x))];
|
||||||
|
|
||||||
|
// TODO: is this the actual size budget available at draw time?
|
||||||
|
Vector2 budget = container.Layout.ComputedBox.Size - Padding.ExtentSize;
|
||||||
|
|
||||||
|
IEnumerable<ItemSolution> growItems = items.Where(x => x.DisplayMode == DisplayMode.Grow);
|
||||||
|
|
||||||
|
Vector2 denominator = growItems.Any()
|
||||||
|
? growItems.Select(x => x.Layout.Size + x.Layout.Margin.ExtentSize)
|
||||||
|
.Aggregate((a, b) => a + b)
|
||||||
|
: Vector2.One;
|
||||||
|
|
||||||
|
// TODO: The layout engine assumes row layout atm. Implement other layouts.
|
||||||
|
|
||||||
|
for (int i = 0; i < items.Length; i++)
|
||||||
|
items[i].Measure(budget, budget/denominator);
|
||||||
|
|
||||||
|
float fixedWidth = items
|
||||||
|
.Where(x => x.DisplayMode != DisplayMode.Grow)
|
||||||
|
.Sum(x => x.DesiredSize.X);
|
||||||
|
|
||||||
|
float remaining = budget.X - fixedWidth;
|
||||||
|
float star = remaining / denominator.X;
|
||||||
|
|
||||||
|
Vector2 pen = container.Layout.ComputedBox.Min + new Vector2(Padding.Left, Padding.Top);
|
||||||
|
for (int i = 0; i < items.Length; i++)
|
||||||
|
{
|
||||||
|
items[i].Measure(budget, new Vector2(star, budget.Y));
|
||||||
|
items[i].Position = pen;
|
||||||
|
pen.X += items[i].DesiredSize.X;
|
||||||
|
|
||||||
|
items[i].Finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ItemSolution(ILayoutItem item)
|
||||||
|
{
|
||||||
|
public ILayoutItem Item { get; } = item;
|
||||||
|
public Vector2 InstrinsicSize { get; } = item.CalculateIntrinsicSize();
|
||||||
|
public Vector2 DesiredSize { get; private set; }
|
||||||
|
|
||||||
|
public readonly LayoutInfo Layout => Item.Layout;
|
||||||
|
public readonly DisplayMode DisplayMode => Item.Layout.DisplayMode;
|
||||||
|
|
||||||
|
public Vector2 Position { get; set; }
|
||||||
|
|
||||||
|
public void Measure(Vector2 budget, Vector2 coefficient)
|
||||||
|
{
|
||||||
|
switch (DisplayMode)
|
||||||
|
{
|
||||||
|
case DisplayMode.None:
|
||||||
|
DesiredSize = Vector2.Zero;
|
||||||
|
break;
|
||||||
|
case DisplayMode.Fit:
|
||||||
|
DesiredSize = InstrinsicSize;
|
||||||
|
break;
|
||||||
|
case DisplayMode.Fixed:
|
||||||
|
DesiredSize = Layout.Size;
|
||||||
|
break;
|
||||||
|
case DisplayMode.Grow:
|
||||||
|
DesiredSize = Layout.Size * coefficient;
|
||||||
|
break;
|
||||||
|
case DisplayMode.Relative:
|
||||||
|
DesiredSize = Layout.Size * budget;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
DesiredSize = Vector2.Clamp(DesiredSize, Layout.MinimumSize, Layout.MaximumSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Finish()
|
||||||
|
{
|
||||||
|
Layout.ComputedBox = new Box2d(Position, Position + DesiredSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
|
||||||
|
field = value;
|
||||||
|
OnPropertyChanged(propertyName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Dashboard.Layout
|
||||||
|
{
|
||||||
|
public enum ContainerMode
|
||||||
|
{
|
||||||
|
Basic,
|
||||||
|
Flex,
|
||||||
|
Grid,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Dashboard.Layout
|
||||||
|
{
|
||||||
|
public enum DisplayMode
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Fit,
|
||||||
|
Grow,
|
||||||
|
Relative,
|
||||||
|
Fixed,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Dashboard.Layout
|
||||||
|
{
|
||||||
|
public enum FlowDirection
|
||||||
|
{
|
||||||
|
Row,
|
||||||
|
Column,
|
||||||
|
RowReverse,
|
||||||
|
ColumnReverse,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Dashboard.Layout
|
||||||
|
{
|
||||||
|
public interface ILayoutItem : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
public LayoutInfo Layout { get; }
|
||||||
|
|
||||||
|
public Vector2 CalculateIntrinsicSize();
|
||||||
|
public Vector2 CalculateSize(Vector2 limits);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ILayoutContainer : ILayoutItem, IReadOnlyList<ILayoutItem>
|
||||||
|
{
|
||||||
|
public ContainerLayoutInfo ContainerLayout { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Dashboard.Layout
|
||||||
|
{
|
||||||
|
public class LayoutInfo : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the control display.
|
||||||
|
/// </summary>
|
||||||
|
public DisplayMode DisplayMode
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetField(ref field, value);
|
||||||
|
} = DisplayMode.Fit;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes how overflows are handled.
|
||||||
|
/// </summary>
|
||||||
|
public OverflowMode OverflowMode
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetField(ref field, value);
|
||||||
|
} = OverflowMode.Hidden;
|
||||||
|
|
||||||
|
public Vector2 Size
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetField(ref field, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 MaximumSize
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetField(ref field, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 MinimumSize
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetField(ref field, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Extents Margin
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetField(ref field, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Box2d ComputedBox
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set => SetField(ref field, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
|
||||||
|
field = value;
|
||||||
|
OnPropertyChanged(propertyName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Dashboard.Layout
|
||||||
|
{
|
||||||
|
public enum OverflowMode
|
||||||
|
{
|
||||||
|
Hidden,
|
||||||
|
Overflow,
|
||||||
|
ScrollHorizontal,
|
||||||
|
ScrollVertical,
|
||||||
|
ScrollBoth,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dashboard.Layout;
|
||||||
|
|
||||||
|
namespace Dashboard
|
||||||
|
{
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public static class LayoutExtensions
|
||||||
|
{
|
||||||
|
extension<T>(T item)
|
||||||
|
where T : ILayoutItem
|
||||||
|
{
|
||||||
|
public DisplayMode DisplayMode
|
||||||
|
{
|
||||||
|
get => item.Layout.DisplayMode;
|
||||||
|
set => item.Layout.DisplayMode = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OverflowMode OverflowMode
|
||||||
|
{
|
||||||
|
get => item.Layout.OverflowMode;
|
||||||
|
set => item.Layout.OverflowMode = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 Size
|
||||||
|
{
|
||||||
|
get => item.Layout.Size;
|
||||||
|
set => item.Layout.Size = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Width
|
||||||
|
{
|
||||||
|
get => item.Size.X;
|
||||||
|
set => item.Size = new Vector2(value, item.Size.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Height
|
||||||
|
{
|
||||||
|
get => item.Size.Y;
|
||||||
|
set => item.Size = new Vector2(item.Size.X, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 MaximumSize
|
||||||
|
{
|
||||||
|
get => item.Layout.MaximumSize;
|
||||||
|
set => item.Layout.MaximumSize = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float MaximumWidth
|
||||||
|
{
|
||||||
|
get => item.MaximumSize.X;
|
||||||
|
set => item.MaximumSize = new Vector2(value, item.MaximumSize.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float MaximumHeight
|
||||||
|
{
|
||||||
|
get => item.MaximumSize.Y;
|
||||||
|
set => item.MaximumSize = new Vector2(item.MaximumSize.X, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 MinimumSize
|
||||||
|
{
|
||||||
|
get => item.Layout.MinimumSize;
|
||||||
|
set => item.Layout.MinimumSize = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float MinimumWidth
|
||||||
|
{
|
||||||
|
get => item.MinimumSize.X;
|
||||||
|
set => item.MinimumSize = new Vector2(value, item.MinimumSize.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float MinimumHeight
|
||||||
|
{
|
||||||
|
get => item.MinimumSize.Y;
|
||||||
|
set => item.MinimumSize = new Vector2(item.MinimumSize.X, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Extents Margin
|
||||||
|
{
|
||||||
|
get => item.Layout.Margin;
|
||||||
|
set => item.Layout.Margin = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float MarginLeft
|
||||||
|
{
|
||||||
|
get => item.Layout.Margin.Left;
|
||||||
|
set => item.Layout.Margin = new Extents(value, item.MarginTop, item.MarginRight, item.MarginBottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float MarginTop
|
||||||
|
{
|
||||||
|
get => item.Layout.Margin.Top;
|
||||||
|
set => item.Layout.Margin = new Extents(item.MarginLeft, value, item.MarginRight, item.MarginBottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float MarginRight
|
||||||
|
{
|
||||||
|
get => item.Layout.Margin.Right;
|
||||||
|
set => item.Layout.Margin = new Extents(item.MarginLeft, item.MarginTop, value, item.MarginBottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float MarginBottom
|
||||||
|
{
|
||||||
|
get => item.Layout.Margin.Bottom;
|
||||||
|
set => item.Layout.Margin = new Extents(item.MarginLeft, item.MarginTop, item.MarginRight, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Box2d ComputedBox
|
||||||
|
{
|
||||||
|
get => item.Layout.ComputedBox;
|
||||||
|
set => item.Layout.ComputedBox = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension<T> (T item)
|
||||||
|
where T : ILayoutContainer
|
||||||
|
{
|
||||||
|
public FlowDirection FlowDirection
|
||||||
|
{
|
||||||
|
get => item.ContainerLayout.FlowDirection;
|
||||||
|
set => item.ContainerLayout.FlowDirection = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Extents Padding
|
||||||
|
{
|
||||||
|
get => item.ContainerLayout.Padding;
|
||||||
|
set => item.ContainerLayout.Padding = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
namespace Dashboard
|
||||||
|
{
|
||||||
|
public enum LayoutUnit : short
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Does not specify a unit.
|
||||||
|
/// </summary>
|
||||||
|
Auto,
|
||||||
|
/// <summary>
|
||||||
|
/// The default unit. A size of a single picture element.
|
||||||
|
/// </summary>
|
||||||
|
Pixel = 1,
|
||||||
|
/// <summary>
|
||||||
|
/// 1/72th of an inch traditional in graphics design.
|
||||||
|
/// </summary>
|
||||||
|
Point = 2,
|
||||||
|
/// <summary>
|
||||||
|
/// The universal length unit for small distances.
|
||||||
|
/// </summary>
|
||||||
|
Millimeter = 3,
|
||||||
|
/// <summary>
|
||||||
|
/// An inverse proportional unit with respect to the container size.
|
||||||
|
/// </summary>
|
||||||
|
Star = 4,
|
||||||
|
/// <summary>
|
||||||
|
/// A directly proportional unit with respect to the container size.
|
||||||
|
/// </summary>
|
||||||
|
Percent = 5,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Dashboard
|
||||||
|
{
|
||||||
|
public record struct LayoutUnits(LayoutUnit All)
|
||||||
|
{
|
||||||
|
public LayoutUnit XUnit
|
||||||
|
{
|
||||||
|
get => (LayoutUnit)(((int)All & 0xF) >> 0);
|
||||||
|
set => All = (LayoutUnit)(((int)All & ~(0xF << 0)) | ((int)value << 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit YUnit
|
||||||
|
{
|
||||||
|
get => (LayoutUnit)(((int)All & 0xF) >> 4);
|
||||||
|
set => All = (LayoutUnit)(((int)All & ~(0xF << 4)) | ((int)value << 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit ZUnit
|
||||||
|
{
|
||||||
|
get => (LayoutUnit)(((int)All & 0xF) >> 8);
|
||||||
|
set => All = (LayoutUnit)(((int)All & ~(0xF << 8)) | ((int)value << 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit WUnit
|
||||||
|
{
|
||||||
|
get => (LayoutUnit)(((int)All & 0xF) >> 12);
|
||||||
|
set => All = (LayoutUnit)(((int)All & ~(0xF << 12)) | ((int)value << 12));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record struct Metric(LayoutUnit Units, float Value)
|
||||||
|
{
|
||||||
|
public float Compute(float dpi, float rel, float star)
|
||||||
|
{
|
||||||
|
switch (Units)
|
||||||
|
{
|
||||||
|
case LayoutUnit.Auto:
|
||||||
|
return -1;
|
||||||
|
case LayoutUnit.Millimeter:
|
||||||
|
float mm2Px = dpi / 25.4f;
|
||||||
|
return Value * mm2Px;
|
||||||
|
case LayoutUnit.Pixel:
|
||||||
|
return Value;
|
||||||
|
case LayoutUnit.Point:
|
||||||
|
float pt2Px = 72 / dpi;
|
||||||
|
return Value * pt2Px;
|
||||||
|
case LayoutUnit.Percent:
|
||||||
|
return rel * Value;
|
||||||
|
case LayoutUnit.Star:
|
||||||
|
return star * Value;
|
||||||
|
default:
|
||||||
|
throw new Exception("Unrecognized unit.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record struct Metric2D(LayoutUnits Units, Vector2 Value)
|
||||||
|
{
|
||||||
|
public float X
|
||||||
|
{
|
||||||
|
get => Value.X;
|
||||||
|
set => Value = new Vector2(value, Value.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit XUnits
|
||||||
|
{
|
||||||
|
get => Units.XUnit;
|
||||||
|
set => Units = Units with { XUnit = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Y
|
||||||
|
{
|
||||||
|
get => Value.Y;
|
||||||
|
set => Value = new Vector2(Value.X, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayoutUnit YUnits
|
||||||
|
{
|
||||||
|
get => Units.YUnit;
|
||||||
|
set => Units = Units with { YUnit = value };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record struct BoxMetric(LayoutUnit Units, Box2d Value);
|
||||||
|
|
||||||
|
public record struct AdvancedMetric(LayoutUnit Unit, float Value)
|
||||||
|
{
|
||||||
|
public AdvancedMetric Convert(LayoutUnit target, float dpi, float rel, int stars)
|
||||||
|
{
|
||||||
|
if (Unit == target)
|
||||||
|
return this;
|
||||||
|
|
||||||
|
float pixels = Unit switch {
|
||||||
|
LayoutUnit.Pixel => Value,
|
||||||
|
LayoutUnit.Point => Value * (72f / dpi),
|
||||||
|
LayoutUnit.Millimeter => Value * (28.3464566929f / dpi),
|
||||||
|
LayoutUnit.Star => Value * rel / stars,
|
||||||
|
LayoutUnit.Percent => Value * rel / 100,
|
||||||
|
_ => throw new Exception(),
|
||||||
|
};
|
||||||
|
|
||||||
|
float value = target switch {
|
||||||
|
LayoutUnit.Pixel => pixels,
|
||||||
|
LayoutUnit.Point => Value * (dpi / 72f),
|
||||||
|
// MeasurementUnit.Millimeter =>
|
||||||
|
};
|
||||||
|
|
||||||
|
return new AdvancedMetric(target, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Value} {Unit.ToShortString()}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryParse(ReadOnlySpan<char> str, out AdvancedMetric metric)
|
||||||
|
{
|
||||||
|
metric = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AdvancedMetric Parse(ReadOnlySpan<char> str) =>
|
||||||
|
TryParse(str, out AdvancedMetric metric)
|
||||||
|
? metric
|
||||||
|
: throw new Exception($"Could not parse the value '{str}'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
namespace Dashboard
|
||||||
|
{
|
||||||
|
public static class MeasurementExtensions
|
||||||
|
{
|
||||||
|
public static bool IsRelative(this LayoutUnit unit) => unit switch {
|
||||||
|
LayoutUnit.Star or LayoutUnit.Percent => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
public static bool IsAbsolute(this LayoutUnit unit) => !IsRelative(unit);
|
||||||
|
|
||||||
|
public static string ToShortString(this LayoutUnit unit) => unit switch {
|
||||||
|
LayoutUnit.Pixel => "px",
|
||||||
|
LayoutUnit.Point => "pt",
|
||||||
|
LayoutUnit.Millimeter => "mm",
|
||||||
|
LayoutUnit.Star => "*",
|
||||||
|
LayoutUnit.Percent => "%",
|
||||||
|
_ => throw new Exception("Unknown unit."),
|
||||||
|
};
|
||||||
|
|
||||||
|
public static bool WithinTolerance(this float value, float reference, float absTol, float relTol)
|
||||||
|
=> value.CompareTolerance(reference, absTol, relTol) == 0;
|
||||||
|
|
||||||
|
public static int CompareTolerance(this float value, float reference, float absTol, float relTol)
|
||||||
|
{
|
||||||
|
float tolerance = Math.Max(absTol, Math.Abs(reference) * relTol);
|
||||||
|
float difference = value - reference;
|
||||||
|
return difference < -tolerance ? -1 : difference > tolerance ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
using Dashboard.Collections;
|
||||||
|
using Dashboard.Windowing;
|
||||||
|
using BindingFlags = System.Reflection.BindingFlags;
|
||||||
|
|
||||||
|
namespace Dashboard.Pal
|
||||||
|
{
|
||||||
|
public abstract class Application : IContextBase<Application, IApplicationExtension>
|
||||||
|
{
|
||||||
|
public abstract string DriverName { get; }
|
||||||
|
public abstract string DriverVendor { get; }
|
||||||
|
public abstract Version DriverVersion { get; }
|
||||||
|
|
||||||
|
public virtual string AppTitle { get; set; } = "Dashboard Application";
|
||||||
|
|
||||||
|
public bool IsInitialized { get; private set; } = false;
|
||||||
|
public bool IsDisposed { get; private set; } = false;
|
||||||
|
public IContextDebugger? Debugger { get; set; }
|
||||||
|
|
||||||
|
protected CancellationToken? CancellationToken { get; private set; }
|
||||||
|
protected bool Quit { get; set; } = false;
|
||||||
|
|
||||||
|
private readonly TypeDictionary<IApplicationExtension> _extensions =
|
||||||
|
new TypeDictionary<IApplicationExtension>(true);
|
||||||
|
private readonly TypeDictionary<IApplicationExtension, Func<IApplicationExtension>> _preloadedExtensions =
|
||||||
|
new TypeDictionary<IApplicationExtension, Func<IApplicationExtension>>(true);
|
||||||
|
|
||||||
|
public event EventHandler<DeviceContext>? DeviceContextCreated;
|
||||||
|
|
||||||
|
public Application()
|
||||||
|
{
|
||||||
|
Current = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
~Application()
|
||||||
|
{
|
||||||
|
InvokeDispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
if (IsInitialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IsInitialized = true;
|
||||||
|
|
||||||
|
InitializeInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void InitializeInternal()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal virtual void OnDeviceContextCreated(DeviceContext dc)
|
||||||
|
{
|
||||||
|
DeviceContextCreated?.Invoke(this, dc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void RunEvents(bool wait)
|
||||||
|
{
|
||||||
|
if (!IsInitialized)
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Run() => Run(true, System.Threading.CancellationToken.None);
|
||||||
|
|
||||||
|
public void Run(bool wait) => Run(wait, System.Threading.CancellationToken.None);
|
||||||
|
|
||||||
|
public void Run(bool waitForEvents, CancellationToken token)
|
||||||
|
{
|
||||||
|
CancellationToken = token;
|
||||||
|
CancellationToken.Value.Register(() => Quit = true);
|
||||||
|
|
||||||
|
Initialize();
|
||||||
|
|
||||||
|
while (!Quit && !token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
RunEvents(waitForEvents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Window API
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a window. It could be a virtual window, or a physical window.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A window.</returns>
|
||||||
|
public abstract IWindow CreateWindow();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Always creates a physical window.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A physical window.</returns>
|
||||||
|
public abstract IPhysicalWindow CreatePhysicalWindow();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a physical window with a window manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A physical window with the given window manager.</returns>
|
||||||
|
public IPhysicalWindow CreatePhysicalWindow(IWindowManager wm)
|
||||||
|
{
|
||||||
|
IPhysicalWindow window = CreatePhysicalWindow();
|
||||||
|
window.WindowManager = wm;
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IWindow CreateDialogWindow(IWindow? parent = null)
|
||||||
|
{
|
||||||
|
if (parent is IVirtualWindow virtualWindow)
|
||||||
|
{
|
||||||
|
IWindow? window = virtualWindow.WindowManager?.CreateWindow();
|
||||||
|
|
||||||
|
if (window != null)
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreatePhysicalWindow();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public bool IsExtensionAvailable<T>() where T : IApplicationExtension
|
||||||
|
{
|
||||||
|
return _extensions.Contains<T>() || _preloadedExtensions.Contains<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ExtensionPreload<T>(Func<IApplicationExtension> loader) where T : IApplicationExtension
|
||||||
|
{
|
||||||
|
return _preloadedExtensions.Add<T>(loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ExtensionPreload<T>() where T : IApplicationExtension, new()
|
||||||
|
{
|
||||||
|
return _preloadedExtensions.Add<T>(() => new T());
|
||||||
|
}
|
||||||
|
|
||||||
|
public T ExtensionRequire<T>() where T : IApplicationExtension
|
||||||
|
{
|
||||||
|
T? extension = default;
|
||||||
|
|
||||||
|
if (_extensions.TryGet(out extension))
|
||||||
|
return extension;
|
||||||
|
|
||||||
|
lock (_extensions)
|
||||||
|
{
|
||||||
|
if (_extensions.TryGet(out extension))
|
||||||
|
return extension;
|
||||||
|
|
||||||
|
if (_preloadedExtensions.Remove<T>(out Func<IApplicationExtension>? loader))
|
||||||
|
{
|
||||||
|
extension = (T)loader!();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
extension = Activator.CreateInstance<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
_extensions.Add(extension);
|
||||||
|
extension.Require(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ExtensionLoad<T>(T instance) where T : IApplicationExtension
|
||||||
|
{
|
||||||
|
if (_extensions.Contains(instance))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_extensions.Add(instance);
|
||||||
|
instance.Require(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
if (!isDisposing) return;
|
||||||
|
|
||||||
|
Quit = true;
|
||||||
|
foreach (IApplicationExtension extension in _extensions)
|
||||||
|
{
|
||||||
|
extension.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InvokeDispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
if (IsDisposed) return;
|
||||||
|
IsDisposed = true;
|
||||||
|
|
||||||
|
Dispose(isDisposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() => InvokeDispose(true);
|
||||||
|
|
||||||
|
[field: ThreadStatic]
|
||||||
|
public static Application Current
|
||||||
|
{
|
||||||
|
get => field ?? throw new InvalidOperationException("There is currently no current application.");
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Dashboard.Collections;
|
||||||
|
using Dashboard.Windowing;
|
||||||
|
|
||||||
|
namespace Dashboard.Pal
|
||||||
|
{
|
||||||
|
public abstract class DeviceContext : IContextBase<DeviceContext, IDeviceContextExtension>, IDeviceContext
|
||||||
|
{
|
||||||
|
private readonly TypeDictionary<IDeviceContextExtension> _extensions =
|
||||||
|
new TypeDictionary<IDeviceContextExtension>(true);
|
||||||
|
private readonly TypeDictionary<IDeviceContextExtension, Func<IDeviceContextExtension>> _preloadedExtensions =
|
||||||
|
new TypeDictionary<IDeviceContextExtension, Func<IDeviceContextExtension>>(true);
|
||||||
|
|
||||||
|
private readonly Dictionary<string, object> _attributes = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
|
||||||
|
public Application Application { get; }
|
||||||
|
public IWindow? Window { get; }
|
||||||
|
public abstract string DriverName { get; }
|
||||||
|
public abstract string DriverVendor { get; }
|
||||||
|
public abstract Version DriverVersion { get; }
|
||||||
|
public abstract ISwapGroup SwapGroup { get; }
|
||||||
|
public abstract Vector2 FramebufferSize { get; }
|
||||||
|
public virtual bool DoubleBuffered { get; } = false;
|
||||||
|
|
||||||
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional debugging object for your pleasure.
|
||||||
|
/// </summary>
|
||||||
|
public IContextDebugger? Debugger { get; set; }
|
||||||
|
|
||||||
|
protected DeviceContext(Application app, IWindow? window)
|
||||||
|
{
|
||||||
|
Application = app;
|
||||||
|
Window = window;
|
||||||
|
app.OnDeviceContextCreated(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
~DeviceContext()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Begin()
|
||||||
|
{
|
||||||
|
foreach (IDeviceContextExtension extension in _extensions)
|
||||||
|
{
|
||||||
|
extension.Begin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// public abstract void Paint(object renderbuffer);
|
||||||
|
|
||||||
|
public virtual void End()
|
||||||
|
{
|
||||||
|
foreach (IDeviceContextExtension extension in _extensions)
|
||||||
|
{
|
||||||
|
extension.End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsExtensionAvailable<T>() where T : IDeviceContextExtension
|
||||||
|
{
|
||||||
|
return _extensions.Contains<T>() || _preloadedExtensions.Contains<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ExtensionPreload<T>(Func<IDeviceContextExtension> loader) where T : IDeviceContextExtension
|
||||||
|
{
|
||||||
|
return _preloadedExtensions.Add<T>(loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ExtensionPreload<T>() where T : IDeviceContextExtension, new()
|
||||||
|
{
|
||||||
|
return _preloadedExtensions.Add<T>(() => new T());
|
||||||
|
}
|
||||||
|
|
||||||
|
public T ExtensionRequire<T>() where T : IDeviceContextExtension
|
||||||
|
{
|
||||||
|
T? extension = default;
|
||||||
|
|
||||||
|
if (_extensions.TryGet(out extension))
|
||||||
|
return extension;
|
||||||
|
|
||||||
|
lock (_extensions)
|
||||||
|
{
|
||||||
|
if (_extensions.TryGet(out extension))
|
||||||
|
return extension;
|
||||||
|
|
||||||
|
if (_preloadedExtensions.Remove<T>(out Func<IDeviceContextExtension>? loader))
|
||||||
|
{
|
||||||
|
extension = (T)loader!();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
extension = Activator.CreateInstance<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
_extensions.Add(extension);
|
||||||
|
extension.Require(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ExtensionLoad<T>(T instance) where T : IDeviceContextExtension
|
||||||
|
{
|
||||||
|
if (_extensions.Contains(instance))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_extensions.Add(instance);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetAttribute(string name, object? v)
|
||||||
|
{
|
||||||
|
if (v != null)
|
||||||
|
_attributes[name] = v;
|
||||||
|
else
|
||||||
|
_attributes.Remove(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetAttribute<T>(string name, T v) => SetAttribute(name, (object?)v);
|
||||||
|
|
||||||
|
public object? GetAttribute(string name)
|
||||||
|
{
|
||||||
|
return _attributes.GetValueOrDefault(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T? GetAttribute<T>(string name)
|
||||||
|
{
|
||||||
|
object? o = GetAttribute(name);
|
||||||
|
if (o != null)
|
||||||
|
return (T?)o;
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implement your dispose in this function.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isDisposing">True if disposing, false otherwise.</param>
|
||||||
|
protected virtual void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
if (!isDisposing) return;
|
||||||
|
|
||||||
|
foreach (IDeviceContextExtension extension in _extensions)
|
||||||
|
{
|
||||||
|
extension.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InvokeDispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
if (IsDisposed) return;
|
||||||
|
IsDisposed = true;
|
||||||
|
|
||||||
|
Dispose(isDisposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() => InvokeDispose(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Dashboard.Pal
|
||||||
|
{
|
||||||
|
public interface IApplicationExtension : IContextExtensionBase<Application>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
namespace Dashboard.Pal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Information about this context interface.
|
||||||
|
/// </summary>
|
||||||
|
public interface IContextInterfaceInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Name of this driver.
|
||||||
|
/// </summary>
|
||||||
|
string DriverName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The vendor for this driver.
|
||||||
|
/// </summary>
|
||||||
|
string DriverVendor { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The version of this driver.
|
||||||
|
/// </summary>
|
||||||
|
Version DriverVersion { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The base context interface.
|
||||||
|
/// </summary>
|
||||||
|
public interface IContextBase : IContextInterfaceInfo, IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The debugger for this context.
|
||||||
|
/// </summary>
|
||||||
|
IContextDebugger? Debugger { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The base context interface.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TContext">The context type.</typeparam>
|
||||||
|
/// <typeparam name="TExtension">The extension type, if used.</typeparam>
|
||||||
|
public interface IContextBase<TContext, in TExtension> : IContextBase
|
||||||
|
where TContext : IContextBase<TContext, TExtension>
|
||||||
|
where TExtension : IContextExtensionBase<TContext>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Is such an extension available?
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The extension to check.</typeparam>
|
||||||
|
/// <returns>True if the extension is available.</returns>
|
||||||
|
bool IsExtensionAvailable<T>() where T : TExtension;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Preload extensions, to be lazy loaded when required.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The extension to preload.</typeparam>
|
||||||
|
/// <returns>
|
||||||
|
/// True if the extension was added to the preload set. Otherwise, already loaded or another extension
|
||||||
|
/// exists which provides this.
|
||||||
|
/// </returns>
|
||||||
|
bool ExtensionPreload<T>() where T : TExtension, new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Preload extensions, to be lazy loaded when required.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="loader">The loader delegate.</param>
|
||||||
|
/// <typeparam name="T">The extension to preload.</typeparam>
|
||||||
|
/// <returns>
|
||||||
|
/// True if the extension was added to the preload set. Otherwise, already loaded or another extension
|
||||||
|
/// exists which provides this.
|
||||||
|
/// </returns>
|
||||||
|
bool ExtensionPreload<T>(Func<TExtension> loader) where T : TExtension;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Require an extension.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The extension to require.</typeparam>
|
||||||
|
/// <returns>The extension instance.</returns>
|
||||||
|
T ExtensionRequire<T>() where T : TExtension;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load an extension.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="instance">The extension instance.</param>
|
||||||
|
/// <typeparam name="T">The extension to require.</typeparam>
|
||||||
|
/// <returns>True if the extension was loaded, false if there was already one.</returns>
|
||||||
|
bool ExtensionLoad<T>(T instance) where T : TExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base interface for all context extensions.
|
||||||
|
/// </summary>
|
||||||
|
public interface IContextExtensionBase : IContextInterfaceInfo, IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The context that loaded this extension.
|
||||||
|
/// </summary>
|
||||||
|
IContextBase Context { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Require this extension.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context that required this extension.</param>
|
||||||
|
void Require(IContextBase context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base interface for all context extensions.
|
||||||
|
/// </summary>
|
||||||
|
public interface IContextExtensionBase<TContext> : IContextExtensionBase
|
||||||
|
where TContext : IContextBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The context that loaded this extension.
|
||||||
|
/// </summary>
|
||||||
|
new TContext Context { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Require this extension.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The context that required this extension.</param>
|
||||||
|
void Require(TContext context);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Dashboard.Pal
|
||||||
|
{
|
||||||
|
public interface IContextDebugger : IDisposable
|
||||||
|
{
|
||||||
|
void LogDebug(string message);
|
||||||
|
void LogInfo(string message);
|
||||||
|
void LogWarning(string message);
|
||||||
|
void LogError(string message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Dashboard.Pal
|
||||||
|
{
|
||||||
|
public interface IDeviceContextExtension : IContextExtensionBase<DeviceContext>
|
||||||
|
{
|
||||||
|
void Begin() {}
|
||||||
|
void End() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
namespace Dashboard.Windowing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for a class that composites multiple windows together.
|
||||||
|
/// </summary>
|
||||||
|
public interface ICompositor : IDisposable
|
||||||
|
{
|
||||||
|
void Composite(IPhysicalWindow window, IEnumerable<IVirtualWindow> windows);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for classes that implement a window manager.
|
||||||
|
/// </summary>
|
||||||
|
public interface IWindowManager : IEnumerable<IVirtualWindow>, IEventListener
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The physical window that this window manager is associated with.
|
||||||
|
/// </summary>
|
||||||
|
IPhysicalWindow PhysicalWindow { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The compositor that will composite all virtual windows.
|
||||||
|
/// </summary>
|
||||||
|
ICompositor Compositor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The window that is currently focused.
|
||||||
|
/// </summary>
|
||||||
|
IVirtualWindow? FocusedWindow { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a virtual window.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Virtual window handle.</returns>
|
||||||
|
IVirtualWindow CreateWindow();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Focus a virtual window, if it is owned by this window manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="window">The window to focus.</param>
|
||||||
|
void Focus(IVirtualWindow window);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Dashboard.Windowing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generic interface for the rendering system present in a <see cref="IPhysicalWindow"/>
|
||||||
|
/// </summary>
|
||||||
|
public interface IDeviceContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The swap group for this device context.
|
||||||
|
/// </summary>
|
||||||
|
ISwapGroup SwapGroup { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The size of the window framebuffer in pixels.
|
||||||
|
/// </summary>
|
||||||
|
Vector2 FramebufferSize { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Dashboard.Windowing
|
||||||
|
{
|
||||||
|
public interface IEventListener
|
||||||
|
{
|
||||||
|
event EventHandler? EventRaised;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send an event to this windowing object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender">The object which generated the event.</param>
|
||||||
|
/// <param name="args">The event arguments sent.</param>
|
||||||
|
void SendEvent(object? sender, EventArgs args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Dashboard.Windowing
|
||||||
|
{
|
||||||
|
public interface IForm : IEventListener, IDisposable
|
||||||
|
{
|
||||||
|
public IWindow Window { get; }
|
||||||
|
public string Title { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Dashboard.Windowing
|
||||||
|
{
|
||||||
|
public interface IPaintable
|
||||||
|
{
|
||||||
|
event EventHandler Painting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Paint this paintable object.
|
||||||
|
/// </summary>
|
||||||
|
void Paint();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
namespace Dashboard.Windowing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface that is used to swap the buffers of windows
|
||||||
|
/// </summary>
|
||||||
|
public interface ISwapGroup
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The swap interval for this swap group.
|
||||||
|
/// </summary>
|
||||||
|
public int SwapInterval { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Swap buffers.
|
||||||
|
/// </summary>
|
||||||
|
void Swap();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
|
||||||
|
namespace Dashboard.Windowing
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Base class of all Dashboard windows.
|
||||||
|
/// </summary>
|
||||||
|
public interface IWindow : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The application for this window.
|
||||||
|
/// </summary>
|
||||||
|
Application Application { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the window.
|
||||||
|
/// </summary>
|
||||||
|
string Title { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The size of the window that includes the window extents.
|
||||||
|
/// </summary>
|
||||||
|
SizeF OuterSize { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The size of the window that excludes the window extents.
|
||||||
|
/// </summary>
|
||||||
|
SizeF ClientSize { get; set; }
|
||||||
|
|
||||||
|
IForm? Form { get; set; }
|
||||||
|
|
||||||
|
public event EventHandler? EventRaised;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribe to events from this window.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The event listener instance.</param>
|
||||||
|
/// <returns>An unsubscription token.</returns>
|
||||||
|
public void SubscribeEvent(IEventListener listener);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsubscribe from events in from this window.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The event listener to unsubscribe.</param>
|
||||||
|
public void UnsubscribeEvent(IEventListener listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for all Dashboard windows that are DPI-aware.
|
||||||
|
/// </summary>
|
||||||
|
public interface IDpiAwareWindow : IWindow
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// DPI of the window.
|
||||||
|
/// </summary>
|
||||||
|
float Dpi { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scale of the window.
|
||||||
|
/// </summary>
|
||||||
|
float Scale { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An object that represents a window in a virtual space, usually another window or a rendering system.
|
||||||
|
/// </summary>
|
||||||
|
public interface IVirtualWindow : IWindow, IEventListener
|
||||||
|
{
|
||||||
|
IWindowManager? WindowManager { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An object that represents a native operating system window.
|
||||||
|
/// </summary>
|
||||||
|
public interface IPhysicalWindow : IWindow
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The device context for this window.
|
||||||
|
/// </summary>
|
||||||
|
DeviceContext DeviceContext { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True if the window is double buffered.
|
||||||
|
/// </summary>
|
||||||
|
public bool DoubleBuffered { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The window manager for this physical window.
|
||||||
|
/// </summary>
|
||||||
|
public IWindowManager? WindowManager { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,179 +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());
|
|
||||||
AddExecutor(new TextCommandExecutor());
|
|
||||||
}
|
|
||||||
|
|
||||||
~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,26 +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="BlurgText" Version="0.1.0-nightly-19" />
|
|
||||||
<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" />
|
|
||||||
<EmbeddedResource Include="Executors\text.vert" />
|
|
||||||
<EmbeddedResource Include="Executors\text.frag" />
|
|
||||||
</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,243 +0,0 @@
|
|||||||
using System.Reflection;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using BlurgText;
|
|
||||||
using Dashboard.Drawing.OpenGL.Text;
|
|
||||||
using OpenTK.Graphics.OpenGL;
|
|
||||||
using OpenTK.Mathematics;
|
|
||||||
|
|
||||||
namespace Dashboard.Drawing.OpenGL.Executors
|
|
||||||
{
|
|
||||||
public class TextCommandExecutor : ICommandExecutor, IInitializer
|
|
||||||
{
|
|
||||||
public IEnumerable<string> Extensions { get; } = new[] { "DB_Text" };
|
|
||||||
public IContextExecutor Executor { get; private set; }
|
|
||||||
private BlurgEngine Engine => Executor.ResourcePool.GetResourceManager<BlurgEngine>();
|
|
||||||
public bool IsInitialized { get; private set; }
|
|
||||||
|
|
||||||
private DrawCallRecorder _recorder;
|
|
||||||
private int _program = 0;
|
|
||||||
private int _transformsLocation = -1;
|
|
||||||
private int _atlasLocation = -1;
|
|
||||||
private int _borderWidthLocation = -1;
|
|
||||||
private int _borderColorLocation = -1;
|
|
||||||
private int _fillColorLocation = -1;
|
|
||||||
|
|
||||||
public TextCommandExecutor()
|
|
||||||
{
|
|
||||||
Executor = null!;
|
|
||||||
_recorder = new DrawCallRecorder(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Initialize()
|
|
||||||
{
|
|
||||||
if (IsInitialized)
|
|
||||||
return;
|
|
||||||
IsInitialized = true;
|
|
||||||
|
|
||||||
Assembly self = typeof(TextCommandExecutor).Assembly;
|
|
||||||
|
|
||||||
using Stream vsource = self.GetManifestResourceStream("Dashboard.Drawing.OpenGL.Executors.text.vert")!;
|
|
||||||
using Stream fsource = self.GetManifestResourceStream("Dashboard.Drawing.OpenGL.Executors.text.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",
|
|
||||||
});
|
|
||||||
GL.DeleteShader(vs);
|
|
||||||
GL.DeleteShader(fs);
|
|
||||||
|
|
||||||
_transformsLocation = GL.GetUniformLocation(_program, "m4Transforms");
|
|
||||||
_atlasLocation = GL.GetUniformLocation(_program, "txAtlas");
|
|
||||||
_borderWidthLocation = GL.GetUniformLocation(_program, "fBorderWidth");
|
|
||||||
_borderColorLocation = GL.GetUniformLocation(_program, "v4BorderColor");
|
|
||||||
_fillColorLocation = GL.GetUniformLocation(_program, "v4FillColor");
|
|
||||||
|
|
||||||
_recorder.Initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetContextExecutor(IContextExecutor executor)
|
|
||||||
{
|
|
||||||
Executor = executor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void BeginFrame()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void BeginDraw()
|
|
||||||
{
|
|
||||||
_recorder.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EndDraw()
|
|
||||||
{
|
|
||||||
GL.UseProgram(_program);
|
|
||||||
GL.Enable(EnableCap.Blend);
|
|
||||||
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
|
||||||
_recorder.Execute();
|
|
||||||
GL.Disable(EnableCap.Blend);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EndFrame()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ProcessCommand(ICommandFrame frame)
|
|
||||||
{
|
|
||||||
switch (frame.Command.Name)
|
|
||||||
{
|
|
||||||
case "Text":
|
|
||||||
DrawText(frame);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawText(ICommandFrame frame)
|
|
||||||
{
|
|
||||||
TextCommandArgs args = frame.GetParameter<TextCommandArgs>();
|
|
||||||
DbBlurgFont font = Engine.InternFont(args.Font);
|
|
||||||
|
|
||||||
BlurgColor color;
|
|
||||||
switch (args.TextBrush)
|
|
||||||
{
|
|
||||||
case SolidBrush solid:
|
|
||||||
color = new BlurgColor()
|
|
||||||
{
|
|
||||||
R = solid.Color.R,
|
|
||||||
G = solid.Color.G,
|
|
||||||
B = solid.Color.B,
|
|
||||||
A = solid.Color.A,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
color = new BlurgColor() { R = 255, G = 0, B = 255, A = 255 };
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
BlurgResult? result = Engine.Blurg.BuildString(font.Font, font.Size, color, args.Text);
|
|
||||||
|
|
||||||
if (result == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Vector3 position = new Vector3(args.Position.X, args.Position.Y, args.Position.Z);
|
|
||||||
ExecuteBlurgResult(result, position);
|
|
||||||
|
|
||||||
result.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ExecuteBlurgResult(BlurgResult result, Vector3 position)
|
|
||||||
{
|
|
||||||
Matrix4 transforms = Executor.TransformStack.Top;
|
|
||||||
|
|
||||||
for (int i = 0; i < result.Count; i++)
|
|
||||||
{
|
|
||||||
BlurgRect rect = result[i];
|
|
||||||
|
|
||||||
int texture = (int)rect.UserData;
|
|
||||||
Vector4 color = new Vector4(rect.Color.R / 255f, rect.Color.G / 255f, rect.Color.B / 255f,
|
|
||||||
rect.Color.A / 255f);
|
|
||||||
|
|
||||||
if (i == 0)
|
|
||||||
{
|
|
||||||
_recorder.Begin(PrimitiveType.Triangles, new Call()
|
|
||||||
{
|
|
||||||
Texture = texture,
|
|
||||||
FillColor = color,
|
|
||||||
Transforms = transforms,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
_recorder.CurrentCall.Texture != texture ||
|
|
||||||
_recorder.CurrentCall.FillColor != color)
|
|
||||||
{
|
|
||||||
_recorder.End();
|
|
||||||
Call call = new Call()
|
|
||||||
{
|
|
||||||
Texture = texture,
|
|
||||||
FillColor = color,
|
|
||||||
Transforms = transforms,
|
|
||||||
};
|
|
||||||
_recorder.Begin(PrimitiveType.Triangles, call);
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector3 p00 = new Vector3(rect.X, rect.Y, 0) + position;
|
|
||||||
Vector3 p10 = p00 + new Vector3(rect.Width, 0, 0);
|
|
||||||
Vector3 p11 = p00 + new Vector3(rect.Width, rect.Height, 0);
|
|
||||||
Vector3 p01 = p00 + new Vector3(0, rect.Height, 0);
|
|
||||||
|
|
||||||
Vector2 uv00 = new Vector2(rect.U0, rect.V0);
|
|
||||||
Vector2 uv10 = new Vector2(rect.U1, rect.V0);
|
|
||||||
Vector2 uv11 = new Vector2(rect.U1, rect.V1);
|
|
||||||
Vector2 uv01 = new Vector2(rect.U0, rect.V1);
|
|
||||||
|
|
||||||
_recorder.Vertex(p00, uv00);
|
|
||||||
_recorder.Vertex(p10, uv10);
|
|
||||||
_recorder.Vertex(p11, uv11);
|
|
||||||
|
|
||||||
_recorder.Vertex(p00, uv00);
|
|
||||||
_recorder.Vertex(p11, uv11);
|
|
||||||
_recorder.Vertex(p01, uv01);
|
|
||||||
}
|
|
||||||
_recorder.End();
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct Call
|
|
||||||
{
|
|
||||||
public Matrix4 Transforms = Matrix4.Identity;
|
|
||||||
public int Texture = 0;
|
|
||||||
public float BorderWidth = 0f;
|
|
||||||
public Vector4 FillColor = Vector4.One;
|
|
||||||
public Vector4 BorderColor = new Vector4(0,0,0,1);
|
|
||||||
|
|
||||||
public Call()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(float))]
|
|
||||||
private struct Vertex
|
|
||||||
{
|
|
||||||
[FieldOffset(0)]
|
|
||||||
public Vector3 Position;
|
|
||||||
[FieldOffset(4 * sizeof(float))]
|
|
||||||
public Vector2 TexCoords;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DrawCallRecorder : DrawCallRecorder<Call, Vertex>
|
|
||||||
{
|
|
||||||
private TextCommandExecutor Executor { get; }
|
|
||||||
|
|
||||||
public DrawCallRecorder(TextCommandExecutor executor)
|
|
||||||
{
|
|
||||||
Executor = executor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Vertex(Vector3 position, Vector2 texCoords)
|
|
||||||
{
|
|
||||||
Vertex(new Vertex(){Position = position, TexCoords = texCoords});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void PrepareCall(in Call call)
|
|
||||||
{
|
|
||||||
Matrix4 transforms = call.Transforms;
|
|
||||||
GL.UniformMatrix4f(Executor._transformsLocation, 1, true, ref transforms);
|
|
||||||
GL.Uniform1f(Executor._borderWidthLocation, call.BorderWidth);
|
|
||||||
GL.Uniform4f(Executor._borderColorLocation, 1, in call.BorderColor);
|
|
||||||
GL.Uniform4f(Executor._fillColorLocation, 1, in call.FillColor);
|
|
||||||
GL.Uniform1i(Executor._atlasLocation, 0);
|
|
||||||
GL.ActiveTexture(TextureUnit.Texture0);
|
|
||||||
GL.BindTexture(TextureTarget.Texture2d, call.Texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void SetVertexFormat()
|
|
||||||
{
|
|
||||||
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, VertexSize, 0);
|
|
||||||
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, VertexSize, 4*sizeof(float));
|
|
||||||
GL.EnableVertexAttribArray(0);
|
|
||||||
GL.EnableVertexAttribArray(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
#ifndef _GRADIENT_GLSL_
|
|
||||||
#define _GRADIENT_GLSL_
|
|
||||||
|
|
||||||
#define DB_GRADIENT_MAX 16
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -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,39 +0,0 @@
|
|||||||
using Dashboard.Drawing.OpenGL.Text;
|
|
||||||
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);
|
|
||||||
|
|
||||||
Typesetter.Backend = BlurgEngine.Global;
|
|
||||||
}
|
|
||||||
|
|
||||||
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,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,188 +0,0 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Numerics;
|
|
||||||
using BlurgText;
|
|
||||||
using OpenTK.Graphics.OpenGL;
|
|
||||||
using OPENGL = OpenTK.Graphics.OpenGL;
|
|
||||||
|
|
||||||
namespace Dashboard.Drawing.OpenGL.Text
|
|
||||||
{
|
|
||||||
public class BlurgEngine : IResourceManager, IGLDisposable, ITypeSetter
|
|
||||||
{
|
|
||||||
public string Name { get; } = "BlurgEngine";
|
|
||||||
public Blurg Blurg { get; }
|
|
||||||
public bool SystemFontsEnabled { get; }
|
|
||||||
|
|
||||||
private readonly List<int> _textures = new List<int>();
|
|
||||||
|
|
||||||
public BlurgEngine() : this(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private BlurgEngine(bool global)
|
|
||||||
{
|
|
||||||
if (global)
|
|
||||||
Blurg = new Blurg(AllocateTextureGlobal, UpdateTextureGlobal);
|
|
||||||
else
|
|
||||||
Blurg = new Blurg(AllocateTexture, UpdateTexture);
|
|
||||||
|
|
||||||
SystemFontsEnabled = Blurg.EnableSystemFonts();
|
|
||||||
}
|
|
||||||
|
|
||||||
~BlurgEngine()
|
|
||||||
{
|
|
||||||
Dispose(false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SizeF MeasureString(IFont font, string value)
|
|
||||||
{
|
|
||||||
return MeasureStringInternal(InternFont(font), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private SizeF MeasureStringInternal(DbBlurgFont font, string value)
|
|
||||||
{
|
|
||||||
Vector2 v = Blurg.MeasureString(font.Font, font.Size, value);
|
|
||||||
return new SizeF(v.X, v.Y);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IFont LoadFont(Stream stream)
|
|
||||||
{
|
|
||||||
string path;
|
|
||||||
Stream dest;
|
|
||||||
for (int i = 0;; i++)
|
|
||||||
{
|
|
||||||
path = Path.GetTempFileName();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
dest = File.Open(path, FileMode.CreateNew, FileAccess.Write, FileShare.None);
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
if (i < 3)
|
|
||||||
continue;
|
|
||||||
else
|
|
||||||
throw new Exception("Could not open a temporary file for writing the font.", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
stream.CopyTo(dest);
|
|
||||||
dest.Dispose();
|
|
||||||
|
|
||||||
DbBlurgFont font = (DbBlurgFont)LoadFont(path);
|
|
||||||
File.Delete(path);
|
|
||||||
return font;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IFont LoadFont(string path)
|
|
||||||
{
|
|
||||||
BlurgFont? font = Blurg.AddFontFile(path) ?? throw new Exception("Failed to load the font file.");
|
|
||||||
return new DbBlurgFont(Blurg, font, 12f);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IFont LoadFont(NamedFont font)
|
|
||||||
{
|
|
||||||
// Ignore the stretch argument.
|
|
||||||
bool italic = font.Slant != FontSlant.Normal;
|
|
||||||
BlurgFont? loaded = Blurg.QueryFont(font.Family, new BlurgText.FontWeight((int)font.Weight), italic);
|
|
||||||
|
|
||||||
if (loaded != null)
|
|
||||||
return new DbBlurgFont(Blurg, loaded, 12f);
|
|
||||||
else
|
|
||||||
throw new Exception("Font not found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public DbBlurgFont InternFont(IFont font)
|
|
||||||
{
|
|
||||||
if (font is NamedFont named)
|
|
||||||
{
|
|
||||||
return (DbBlurgFont)LoadFont(named);
|
|
||||||
}
|
|
||||||
else if (font is DbBlurgFont dblurg)
|
|
||||||
{
|
|
||||||
if (dblurg.Owner != Blurg)
|
|
||||||
{
|
|
||||||
throw new Exception();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return dblurg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new Exception("Unsupported font resource.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateTexture(IntPtr texture, IntPtr buffer, int x, int y, int width, int height)
|
|
||||||
{
|
|
||||||
GL.BindTexture(TextureTarget.Texture2d, (int)texture);
|
|
||||||
GL.TexSubImage2D(TextureTarget.Texture2d, 0, x, y, width, height, OPENGL.PixelFormat.Rgba, PixelType.UnsignedByte, buffer);
|
|
||||||
// GL.TexSubImage2D(TextureTarget.Texture2d, 0, x, y, width, height, OPENGL.PixelFormat.Red, PixelType.Byte, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr AllocateTexture(int width, int height)
|
|
||||||
{
|
|
||||||
int texture = GL.GenTexture();
|
|
||||||
|
|
||||||
GL.BindTexture(TextureTarget.Texture2d, texture);
|
|
||||||
GL.TexImage2D(TextureTarget.Texture2d, 0, InternalFormat.Rgba, width, height, 0, OPENGL.PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
|
|
||||||
// GL.TexImage2D(TextureTarget.Texture2d, 0, InternalFormat.R8, width, height, 0, OPENGL.PixelFormat.Red, PixelType.Byte, IntPtr.Zero);
|
|
||||||
|
|
||||||
GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
|
|
||||||
GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
|
|
||||||
// GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureSwizzleR, (int)TextureSwizzle.One);
|
|
||||||
// GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureSwizzleG, (int)TextureSwizzle.One);
|
|
||||||
// GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureSwizzleB, (int)TextureSwizzle.One);
|
|
||||||
// GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureSwizzleA, (int)TextureSwizzle.Red);
|
|
||||||
|
|
||||||
_textures.Add(texture);
|
|
||||||
|
|
||||||
return texture;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool _isDisposed = false;
|
|
||||||
|
|
||||||
private void Dispose(bool disposing, bool safeExit)
|
|
||||||
{
|
|
||||||
if (_isDisposed)
|
|
||||||
return;
|
|
||||||
_isDisposed = true;
|
|
||||||
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
Blurg.Dispose();
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (safeExit)
|
|
||||||
{
|
|
||||||
foreach (int texture in _textures)
|
|
||||||
ContextCollector.Global.DeleteTexture(texture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() => Dispose(true, true);
|
|
||||||
|
|
||||||
public void Dispose(bool safeExit) => Dispose(true, safeExit);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The global Blurg engine implements the needed methods for command queues to work.
|
|
||||||
/// </summary>
|
|
||||||
public static BlurgEngine Global { get; } = new BlurgEngine(true);
|
|
||||||
|
|
||||||
private static void UpdateTextureGlobal(IntPtr userdata, IntPtr buffer, int x, int y, int width, int height)
|
|
||||||
{
|
|
||||||
// Report the user error.
|
|
||||||
Debug.WriteLine("Attempt to create or update a texture from the global BlurgEngine.", "Dashboard/BlurgEngine");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IntPtr AllocateTextureGlobal(int width, int height)
|
|
||||||
{
|
|
||||||
Debug.WriteLine("Attempt to create or update a texture from the global BlurgEngine.", "Dashboard/BlurgEngine");
|
|
||||||
return IntPtr.Zero;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
namespace Dashboard.Drawing.OpenGL.Text
|
|
||||||
{
|
|
||||||
public class BlurgFontExtension : IDrawExtension
|
|
||||||
{
|
|
||||||
public string Name { get; } = "BLURG_Font";
|
|
||||||
public IReadOnlyList<IDrawExtension> Requires { get; } = new [] { FontExtension.Instance };
|
|
||||||
public IReadOnlyList<IDrawCommand> Commands { get; } = new IDrawCommand[] { };
|
|
||||||
|
|
||||||
private BlurgFontExtension() {}
|
|
||||||
|
|
||||||
public static readonly BlurgFontExtension Instance = new BlurgFontExtension();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using BlurgText;
|
|
||||||
|
|
||||||
namespace Dashboard.Drawing.OpenGL.Text
|
|
||||||
{
|
|
||||||
public class DbBlurgFont : IFont
|
|
||||||
{
|
|
||||||
public IDrawExtension Kind { get; } = BlurgFontExtension.Instance;
|
|
||||||
public Blurg Owner { get; }
|
|
||||||
public BlurgFont Font { get; }
|
|
||||||
public float Size { get; }
|
|
||||||
public string Family => Font.FamilyName;
|
|
||||||
public FontWeight Weight => (FontWeight)Font.Weight.Value;
|
|
||||||
public FontSlant Slant => Font.Italic ? FontSlant.Italic : FontSlant.Normal;
|
|
||||||
public FontStretch Stretch => FontStretch.Normal;
|
|
||||||
|
|
||||||
public DbBlurgFont(Blurg owner, BlurgFont font, float size)
|
|
||||||
{
|
|
||||||
Owner = owner;
|
|
||||||
Font = font;
|
|
||||||
Size = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DbBlurgFont WithSize(float size)
|
|
||||||
{
|
|
||||||
return new DbBlurgFont(Owner, Font, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,13 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>disable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Dashboard.Common\Dashboard.Common.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -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,141 +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);
|
|
||||||
SizeF size = Typesetter.MeasureString(font, text);
|
|
||||||
controller.EnsureBounds(new Box2d(position.X, position.Y, position.X + size.Width, position.Y + size.Height), 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);
|
|
||||||
SizeF size = Typesetter.MeasureString(font, text);
|
|
||||||
controller.EnsureBounds(new Box2d(position.X, position.Y, position.X + size.Width, position.Y + size.Height), 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,52 +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; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct NamedFont : IFont
|
|
||||||
{
|
|
||||||
public IDrawExtension Kind { get; } = Instance;
|
|
||||||
|
|
||||||
public string Family { get; }
|
|
||||||
public float Size { get; }
|
|
||||||
public FontWeight Weight { get; }
|
|
||||||
public FontSlant Slant { get; }
|
|
||||||
public FontStretch Stretch { get; }
|
|
||||||
|
|
||||||
public NamedFont(string family, float size, FontWeight weight = FontWeight.Normal,
|
|
||||||
FontSlant slant = FontSlant.Normal, FontStretch stretch = FontStretch.Normal)
|
|
||||||
{
|
|
||||||
Family = family;
|
|
||||||
Size = size;
|
|
||||||
Weight = weight;
|
|
||||||
Slant = slant;
|
|
||||||
Stretch = Stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly IDrawExtension Instance = new Extension();
|
|
||||||
|
|
||||||
private class Extension : DrawExtension
|
|
||||||
{
|
|
||||||
public Extension() : base("DB_Font_Named", [FontExtension.Instance])
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.IO;
|
|
||||||
using System.Reflection.PortableExecutable;
|
|
||||||
|
|
||||||
namespace Dashboard.Drawing
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Interface for registered typesetters.
|
|
||||||
/// </summary>
|
|
||||||
public interface ITypeSetter
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Name of the typesetter.
|
|
||||||
/// </summary>
|
|
||||||
string Name { get; }
|
|
||||||
|
|
||||||
SizeF MeasureString(IFont font, string value);
|
|
||||||
|
|
||||||
IFont LoadFont(Stream stream);
|
|
||||||
IFont LoadFont(string path);
|
|
||||||
IFont LoadFont(NamedFont font);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Class for typesetting related functions.
|
|
||||||
/// </summary>
|
|
||||||
public static class Typesetter
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The typesetting backend for this instance.
|
|
||||||
/// </summary>
|
|
||||||
public static ITypeSetter Backend { get; set; } = new UndefinedTypeSetter();
|
|
||||||
|
|
||||||
public static string Name => Backend.Name;
|
|
||||||
|
|
||||||
public static SizeF MeasureString(IFont font, string value)
|
|
||||||
{
|
|
||||||
return Backend.MeasureString(font, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IFont LoadFont(Stream stream)
|
|
||||||
{
|
|
||||||
return Backend.LoadFont(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IFont LoadFont(string path)
|
|
||||||
{
|
|
||||||
return Backend.LoadFont(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IFont LoadFont(FileInfo file)
|
|
||||||
{
|
|
||||||
return Backend.LoadFont(file.FullName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IFont LoadFont(NamedFont font)
|
|
||||||
{
|
|
||||||
return Backend.LoadFont(font);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IFont LoadFont(string family, float size, FontWeight weight = FontWeight.Normal,
|
|
||||||
FontSlant slant = FontSlant.Normal, FontStretch stretch = FontStretch.Normal)
|
|
||||||
{
|
|
||||||
return LoadFont(new NamedFont(family, size, weight, slant, stretch));
|
|
||||||
}
|
|
||||||
|
|
||||||
private class UndefinedTypeSetter : ITypeSetter
|
|
||||||
{
|
|
||||||
public string Name { get; } = "Undefined";
|
|
||||||
|
|
||||||
[DoesNotReturn]
|
|
||||||
private void Except()
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("No typesetting backend is loaded.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public SizeF MeasureString(IFont font, string value)
|
|
||||||
{
|
|
||||||
Except();
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IFont LoadFont(Stream stream)
|
|
||||||
{
|
|
||||||
Except();
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IFont LoadFont(string path)
|
|
||||||
{
|
|
||||||
Except();
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IFont LoadFont(NamedFont font)
|
|
||||||
{
|
|
||||||
Except();
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>disable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Dashboard.Drawing\Dashboard.Drawing.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Text;
|
|
||||||
using Dashboard.Drawing;
|
|
||||||
|
|
||||||
namespace Dashboard.ImmediateUI
|
|
||||||
{
|
|
||||||
public class DimUIConfig
|
|
||||||
{
|
|
||||||
public Vector2 Margin = new Vector2(8, 4);
|
|
||||||
public Vector2 Padding = new Vector2(4);
|
|
||||||
public required IFont Font { get; init; }
|
|
||||||
|
|
||||||
public IBrush TextBrush = new SolidBrush(Color.Black);
|
|
||||||
public IBrush DisabledText = new SolidBrush(Color.Gray);
|
|
||||||
|
|
||||||
public float ButtonBorderSize = 2f;
|
|
||||||
public IBrush ButtonBorderBrush = new SolidBrush(Color.SteelBlue);
|
|
||||||
public IBrush ButtonFillBrush = new SolidBrush(Color.SlateGray);
|
|
||||||
public IBrush? ButtonShadowBrush = new SolidBrush(Color.FromArgb(32, Color.LightSteelBlue));
|
|
||||||
public float ButtonShadowOffset = 2f;
|
|
||||||
|
|
||||||
public float InputBorderSize = 2f;
|
|
||||||
public IBrush InputPlaceholderTextBrush = new SolidBrush(Color.SteelBlue);
|
|
||||||
public IBrush InputBorderBrush = new SolidBrush(Color.SlateGray);
|
|
||||||
public IBrush InputFillBrush = new SolidBrush(Color.LightGray);
|
|
||||||
public IBrush? InputShadowBrush = new SolidBrush(Color.FromArgb(32, Color.LightSteelBlue));
|
|
||||||
public float InputShadowOffset = -2f;
|
|
||||||
|
|
||||||
public float MenuBorderSize = 2f;
|
|
||||||
public IBrush MenuBorderBrush = new SolidBrush(Color.SteelBlue);
|
|
||||||
public IBrush MenuFillBrush = new SolidBrush(Color.SlateGray);
|
|
||||||
public IBrush? MenuShadowBrush = new SolidBrush(Color.FromArgb(32, Color.LightSteelBlue));
|
|
||||||
public float MenuShadowOffset = 2f;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DimUI
|
|
||||||
{
|
|
||||||
private readonly DimUIConfig _config;
|
|
||||||
private Vector2 _pen;
|
|
||||||
private Box2d _bounds;
|
|
||||||
private bool _firstLine = false;
|
|
||||||
private bool _sameLine = false;
|
|
||||||
private float _z = -1;
|
|
||||||
private float _lineHeight;
|
|
||||||
private DrawQueue _queue;
|
|
||||||
|
|
||||||
public DimUI(DimUIConfig config)
|
|
||||||
{
|
|
||||||
_config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MemberNotNull(nameof(_queue))]
|
|
||||||
public void Begin(Box2d bounds, DrawQueue queue)
|
|
||||||
{
|
|
||||||
_bounds = bounds;
|
|
||||||
_pen = _bounds.Min;
|
|
||||||
_queue = queue;
|
|
||||||
_firstLine = true;
|
|
||||||
_lineHeight = 0;
|
|
||||||
_z = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SameLine()
|
|
||||||
{
|
|
||||||
_sameLine = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Line()
|
|
||||||
{
|
|
||||||
if (!_firstLine && !_sameLine)
|
|
||||||
{
|
|
||||||
_pen = new Vector2(_bounds.Left, _pen.Y + _lineHeight);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_firstLine = false;
|
|
||||||
_sameLine = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_pen.X += _config.Margin.X;
|
|
||||||
_lineHeight = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private float Z()
|
|
||||||
{
|
|
||||||
return _z += 0.001f;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Text(string text)
|
|
||||||
{
|
|
||||||
Line();
|
|
||||||
|
|
||||||
SizeF sz = Typesetter.MeasureString(_config.Font, text);
|
|
||||||
float z = Z();
|
|
||||||
float h = _config.Margin.Y * 2 + sz.Height;
|
|
||||||
_queue.Text(new Vector3(_pen + new Vector2(0, _config.Margin.X), z), _config.TextBrush, text, _config.Font);
|
|
||||||
|
|
||||||
_lineHeight = Math.Max(_lineHeight, h);
|
|
||||||
_pen.X += sz.Width;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DrawBox(
|
|
||||||
Vector2 position,
|
|
||||||
Vector2 size,
|
|
||||||
IBrush fill,
|
|
||||||
IBrush border, float borderWidth,
|
|
||||||
IBrush? shadow, float offset)
|
|
||||||
{
|
|
||||||
float z = Z();
|
|
||||||
|
|
||||||
if (shadow != null)
|
|
||||||
{
|
|
||||||
if (offset >= 0)
|
|
||||||
{
|
|
||||||
_queue.Rect(position + new Vector2(offset), position + size + new Vector2(offset + borderWidth), z, shadow);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Inset shadows are draw a bit weirdly.
|
|
||||||
_queue.Rect(position, position + new Vector2(offset, size.Y), z, shadow);
|
|
||||||
_queue.Rect(position + new Vector2(offset, 0), position + new Vector2(size.X - offset, offset), z, shadow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_queue.Rect(position, position + size, z, fill, border, borderWidth, BorderKind.Outset);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Button(string label)
|
|
||||||
{
|
|
||||||
Line();
|
|
||||||
|
|
||||||
SizeF sz = Typesetter.MeasureString(_config.Font, label);
|
|
||||||
|
|
||||||
float h = _config.Margin.Y * 2 + _config.Padding.Y * 2 + sz.Height;
|
|
||||||
DrawBox(
|
|
||||||
_pen + new Vector2(0, _config.Margin.Y),
|
|
||||||
new Vector2(sz.Width + 2 * _config.Padding.X, sz.Height + 2 * _config.Padding.Y),
|
|
||||||
_config.ButtonFillBrush,
|
|
||||||
_config.ButtonBorderBrush,
|
|
||||||
_config.ButtonBorderSize,
|
|
||||||
_config.ButtonShadowBrush,
|
|
||||||
_config.ButtonShadowOffset);
|
|
||||||
|
|
||||||
float z = Z();
|
|
||||||
|
|
||||||
_queue.Text(new Vector3(_pen + new Vector2(_config.Padding.X, _config.Margin.Y + _config.Padding.Y), z),
|
|
||||||
_config.TextBrush, label, _config.Font);
|
|
||||||
_lineHeight = Math.Max(_lineHeight, h);
|
|
||||||
|
|
||||||
_pen.X += sz.Width + 2 * _config.Padding.X;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Input(string placeholder, StringBuilder value)
|
|
||||||
{
|
|
||||||
Line();
|
|
||||||
|
|
||||||
IBrush textBrush;
|
|
||||||
string str;
|
|
||||||
if (value.Length == 0)
|
|
||||||
{
|
|
||||||
textBrush = _config.DisabledText;
|
|
||||||
str = placeholder;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
textBrush = _config.TextBrush;
|
|
||||||
str = value.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
SizeF sz = Typesetter.MeasureString(_config.Font, str);
|
|
||||||
|
|
||||||
float h = _config.Margin.Y * 2 + _config.Padding.Y * 2 + sz.Height;
|
|
||||||
DrawBox(
|
|
||||||
_pen + new Vector2(0, _config.Margin.Y),
|
|
||||||
new Vector2(sz.Width + 2 * _config.Padding.X, sz.Height + 2 * _config.Padding.Y),
|
|
||||||
_config.InputFillBrush,
|
|
||||||
_config.InputBorderBrush,
|
|
||||||
_config.InputBorderSize,
|
|
||||||
_config.InputShadowBrush,
|
|
||||||
_config.InputShadowOffset);
|
|
||||||
|
|
||||||
float z = Z();
|
|
||||||
|
|
||||||
_queue.Text(new Vector3(_pen + new Vector2(_config.Padding.X, _config.Margin.Y + _config.Padding.Y), z),
|
|
||||||
textBrush, str, _config.Font);
|
|
||||||
_lineHeight = Math.Max(_lineHeight, h);
|
|
||||||
|
|
||||||
_pen.X += sz.Width + 2 * _config.Padding.X;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void BeginMenu()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool MenuItem(string name)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EndMenu()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Id(ReadOnlySpan<char> str)
|
|
||||||
{
|
|
||||||
// Uses the FVN-1A algorithm in 32-bit mode.
|
|
||||||
const int PRIME = 0x01000193;
|
|
||||||
const int BASIS = unchecked((int)0x811c9dc5);
|
|
||||||
|
|
||||||
int hash = BASIS;
|
|
||||||
for (int i = 0; i < str.Length; i++)
|
|
||||||
{
|
|
||||||
hash ^= str[i] & 0xFF;
|
|
||||||
hash *= PRIME;
|
|
||||||
hash ^= str[i] >> 8;
|
|
||||||
hash *= PRIME;
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Finish()
|
|
||||||
{
|
|
||||||
// TODO:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
namespace Dashboard.Drawing.OpenGL
|
namespace Dashboard.OpenGL
|
||||||
{
|
{
|
||||||
public class ContextCollector : IDisposable
|
public class ContextCollector : IDisposable
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="OpenTK.Graphics" Version="[5.0.0-pre.*,5.1)" />
|
||||||
|
<ProjectReference Include="..\Dashboard.Common\Dashboard.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Drawing\immediate.frag" />
|
||||||
|
<EmbeddedResource Include="Drawing\immediate.frag" />
|
||||||
|
<None Remove="Drawing\immediate.vert" />
|
||||||
|
<EmbeddedResource Include="Drawing\immediate.vert" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dashboard.Drawing;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
using Dashboard.Windowing;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using OpenTK.Graphics.Wgl;
|
||||||
|
using OpenTK.Mathematics;
|
||||||
|
using ColorBuffer = OpenTK.Graphics.OpenGL.ColorBuffer;
|
||||||
|
using Vector2 = System.Numerics.Vector2;
|
||||||
|
|
||||||
|
namespace Dashboard.OpenGL.Drawing
|
||||||
|
{
|
||||||
|
public class DeviceContextBase : IDeviceContextBase
|
||||||
|
{
|
||||||
|
private readonly Stack<Matrix4x4> _transforms = new Stack<Matrix4x4>();
|
||||||
|
private readonly Stack<Box2d> _clipRegions = new Stack<Box2d>();
|
||||||
|
private readonly Stack<Box2d> _scissorRegions = new Stack<Box2d>();
|
||||||
|
private int _z = 0;
|
||||||
|
|
||||||
|
public DeviceContext Context { get; private set; } = null!;
|
||||||
|
IContextBase IContextExtensionBase.Context => Context;
|
||||||
|
public string DriverName => "Dashboard OpenGL Device Context";
|
||||||
|
public string DriverVendor => "Dashboard";
|
||||||
|
public Version DriverVersion => new Version(0, 1);
|
||||||
|
|
||||||
|
public Box2d ClipRegion => _clipRegions.Peek();
|
||||||
|
public Box2d ScissorRegion => _scissorRegions.Peek();
|
||||||
|
public Matrix4x4 Transforms => _transforms.Peek();
|
||||||
|
public float Scale => ScaleOverride > 0 ? ScaleOverride : (Context.Window as IDpiAwareWindow)?.Scale ?? 1;
|
||||||
|
public float ScaleOverride { get; set; } = -1f;
|
||||||
|
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Require(DeviceContext context)
|
||||||
|
{
|
||||||
|
Context = context;
|
||||||
|
|
||||||
|
ResetClip();
|
||||||
|
ResetScissor();
|
||||||
|
ResetTransforms();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void IContextExtensionBase.Require(IContextBase context) => Require((DeviceContext)context);
|
||||||
|
|
||||||
|
public void ResetClip()
|
||||||
|
{
|
||||||
|
_clipRegions.Clear();
|
||||||
|
|
||||||
|
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
|
||||||
|
_clipRegions.Push(new Box2d(Vector2.Zero, size));
|
||||||
|
|
||||||
|
SetClip(ClipRegion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PushClip(Box2d clipRegion)
|
||||||
|
{
|
||||||
|
clipRegion = new Box2d(ClipRegion.Min.X + clipRegion.Min.X, ClipRegion.Min.Y + clipRegion.Min.Y,
|
||||||
|
Math.Min(ClipRegion.Max.X, ClipRegion.Min.X + clipRegion.Max.X),
|
||||||
|
Math.Min(ClipRegion.Max.Y, ClipRegion.Max.Y + clipRegion.Max.Y));
|
||||||
|
_clipRegions.Push(clipRegion);
|
||||||
|
|
||||||
|
SetClip(clipRegion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PopClip()
|
||||||
|
{
|
||||||
|
_clipRegions.Pop();
|
||||||
|
SetClip(ClipRegion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetScissor()
|
||||||
|
{
|
||||||
|
GL.Disable(EnableCap.ScissorTest);
|
||||||
|
_scissorRegions.Clear();
|
||||||
|
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
|
||||||
|
_scissorRegions.Push(new Box2d(Vector2.Zero, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PushScissor(Box2d scissorRegion)
|
||||||
|
{
|
||||||
|
GL.Enable(EnableCap.ScissorTest);
|
||||||
|
|
||||||
|
// scissorRegion = new RectangleF(scissorRegion.X + scissorRegion.X, scissorRegion.Y + scissorRegion.Y,
|
||||||
|
// Math.Min(ScissorRegion.Right - scissorRegion.X, scissorRegion.Width),
|
||||||
|
// Math.Min(ScissorRegion.Bottom - scissorRegion.Y, scissorRegion.Height));
|
||||||
|
_scissorRegions.Push(scissorRegion);
|
||||||
|
|
||||||
|
SetScissor(scissorRegion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PopScissor()
|
||||||
|
{
|
||||||
|
if (_scissorRegions.Count == 1)
|
||||||
|
GL.Disable(EnableCap.ScissorTest);
|
||||||
|
|
||||||
|
_scissorRegions.Pop();
|
||||||
|
SetScissor(ClipRegion);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetClip(Box2d rect)
|
||||||
|
{
|
||||||
|
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
|
||||||
|
GL.Viewport(
|
||||||
|
(int)Math.Round(rect.Min.X),
|
||||||
|
(int)Math.Round(size.Y - rect.Min.Y - rect.Size.Y),
|
||||||
|
(int)Math.Round(rect.Size.X),
|
||||||
|
(int)Math.Round(rect.Size.Y));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetScissor(Box2d rect)
|
||||||
|
{
|
||||||
|
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
|
||||||
|
GL.Scissor(
|
||||||
|
(int)Math.Round(rect.Min.X),
|
||||||
|
(int)Math.Round(size.Y - rect.Min.Y - rect.Size.Y),
|
||||||
|
(int)Math.Round(rect.Size.X),
|
||||||
|
(int)Math.Round(rect.Size.Y));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetTransforms()
|
||||||
|
{
|
||||||
|
Vector2 size = ((GLDeviceContext)Context).GLContext.FramebufferSize;
|
||||||
|
Matrix4x4 m = Matrix4x4.CreateOrthographicOffCenterLeftHanded(0, size.X, size.Y, 0, 1, -1);
|
||||||
|
|
||||||
|
_transforms.Clear();
|
||||||
|
_transforms.Push(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PushTransforms(in Matrix4x4 matrix)
|
||||||
|
{
|
||||||
|
Matrix4x4 result = matrix * Transforms;
|
||||||
|
_transforms.Push(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PopTransforms()
|
||||||
|
{
|
||||||
|
_transforms.Pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearColor(Color color)
|
||||||
|
{
|
||||||
|
GL.ClearColor(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
|
||||||
|
GL.Clear(ClearBufferMask.ColorBufferBit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearDepth()
|
||||||
|
{
|
||||||
|
GL.Clear(ClearBufferMask.DepthBufferBit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int IncrementZ()
|
||||||
|
{
|
||||||
|
return ++_z;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int DecrementZ()
|
||||||
|
{
|
||||||
|
return --_z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dashboard.Drawing;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
|
namespace Dashboard.OpenGL.Drawing
|
||||||
|
{
|
||||||
|
public class DirectRendering : IDirectRendering, ITextureExtension
|
||||||
|
{
|
||||||
|
public GLDeviceContext Context { get; private set; } = null!;
|
||||||
|
public string DriverName { get; } = "Dashboard OpenGL";
|
||||||
|
public string DriverVendor { get; } = "Dashboard";
|
||||||
|
public Version DriverVersion { get; } = new Version(0, 1);
|
||||||
|
IContextBase IContextExtensionBase.Context => Context;
|
||||||
|
DeviceContext IContextExtensionBase<DeviceContext>.Context => Context;
|
||||||
|
|
||||||
|
public bool SupportsArbTextureStorage { get; private set; }
|
||||||
|
public bool SupportsAnisotropy { get; private set; }
|
||||||
|
|
||||||
|
private int _vao = -1;
|
||||||
|
private List<GLTexture> _textures = new List<GLTexture>();
|
||||||
|
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Require(DeviceContext context)
|
||||||
|
{
|
||||||
|
Context = (GLDeviceContext)context;
|
||||||
|
|
||||||
|
SupportsArbTextureStorage = Context.DriverVersion >= new Version(4, 2) ||
|
||||||
|
Context.IsGLExtensionAvailable("GL_ARB_texture_storage");
|
||||||
|
SupportsAnisotropy = Context.DriverVersion >= new Version() ||
|
||||||
|
Context.IsGLExtensionAvailable("GL_EXT_texture_filter_anisotropic") ||
|
||||||
|
Context.IsGLExtensionAvailable("GL_ARB_texture_filter_anisotropic");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Require(IContextBase context) => Require((DeviceContext)context);
|
||||||
|
|
||||||
|
public void Begin()
|
||||||
|
{
|
||||||
|
if (_vao == -1)
|
||||||
|
{
|
||||||
|
GL.GenVertexArray(out _vao);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void End()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public IShader CreateShader<T>(T createInfo) where T : IShaderCreateInfo
|
||||||
|
{
|
||||||
|
int program = createInfo switch
|
||||||
|
{
|
||||||
|
GlslShaderCreateInfo glsl => glsl.CreateProgram(),
|
||||||
|
GlslComputeShaderCreateInfo compute => compute.CreateProgram(),
|
||||||
|
SpirvShaderCreateInfo spirv => spirv.CreateProgram(),
|
||||||
|
_ => throw new Exception($"Unsupported shader pipeline type {createInfo.GetType()}."),
|
||||||
|
};
|
||||||
|
|
||||||
|
return new GLShader((GLDeviceContext)Context, program);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IBuffer CreateBuffer(BufferAccessPattern pattern, long size)
|
||||||
|
{
|
||||||
|
return GLBuffer.Create((GLDeviceContext)Context, pattern, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw(DrawCall drawCall)
|
||||||
|
{
|
||||||
|
Vector2 size = Context.FramebufferSize;
|
||||||
|
drawCall.SetAll(size, _vao, false);
|
||||||
|
|
||||||
|
if (drawCall.VertexSpecification.ElementBuffer is null)
|
||||||
|
{
|
||||||
|
GL.DrawArrays(drawCall.Primitive.OpenGL, drawCall.First, drawCall.Count);
|
||||||
|
}
|
||||||
|
else if (drawCall.BaseVertex == 0)
|
||||||
|
{
|
||||||
|
GL.DrawElements(drawCall.Primitive.OpenGL, drawCall.Count, drawCall.IndexType.OpenGL, (nint)drawCall.Offset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GL.DrawElementsBaseVertex(drawCall.Primitive.OpenGL, drawCall.Count, drawCall.IndexType.OpenGL, (nint)drawCall.Offset, drawCall.BaseVertex);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GLTexture CreateTexture(TextureType type)
|
||||||
|
{
|
||||||
|
GLTexture texture = new GLTexture(this, type);
|
||||||
|
lock (_textures) _textures.Add(texture);
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void TextureDisposed(GLTexture texture)
|
||||||
|
{
|
||||||
|
lock (_textures) _textures.Remove(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
ITexture ITextureExtension.CreateTexture(TextureType type) => CreateTexture(type);
|
||||||
|
|
||||||
|
ITexture IDirectRendering.CreateTexture(TextureType type)
|
||||||
|
{
|
||||||
|
return CreateTexture(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using Dashboard.Drawing;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
|
namespace Dashboard.OpenGL.Drawing
|
||||||
|
{
|
||||||
|
public class GLShader : IShader
|
||||||
|
{
|
||||||
|
public GLDeviceContext Context { get; }
|
||||||
|
public int Handle { get; }
|
||||||
|
|
||||||
|
public ImmutableList<ShaderMappingProperty> Attributes { get; }
|
||||||
|
|
||||||
|
public ImmutableList<ShaderMappingProperty> Uniforms { get; }
|
||||||
|
|
||||||
|
public ImmutableList<ShaderMappingProperty> Blocks { get; }
|
||||||
|
|
||||||
|
public ImmutableList<ShaderMappingProperty> Textures { get; }
|
||||||
|
|
||||||
|
public GLShader(GLDeviceContext context, int handle)
|
||||||
|
{
|
||||||
|
Context = context;
|
||||||
|
Handle = handle;
|
||||||
|
|
||||||
|
List<ShaderMappingProperty> list = new List<ShaderMappingProperty>();
|
||||||
|
List<ShaderMappingProperty> list2 = new List<ShaderMappingProperty>();
|
||||||
|
|
||||||
|
int count = GL.GetProgrami(handle, ProgramProperty.ActiveAttributes);
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
list.Add(GetAttribute(handle, i));
|
||||||
|
}
|
||||||
|
Attributes = list.ToImmutableList();
|
||||||
|
|
||||||
|
list.Clear();
|
||||||
|
count = GL.GetProgrami(handle, ProgramProperty.ActiveUniforms);
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var property = GetUniform(handle, i, out bool isSampler);
|
||||||
|
list.Add(property);
|
||||||
|
if (isSampler)
|
||||||
|
list2.Add(property);
|
||||||
|
}
|
||||||
|
Uniforms = list.ToImmutableList();
|
||||||
|
Textures = list2.ToImmutableList();
|
||||||
|
|
||||||
|
list.Clear();
|
||||||
|
count = GL.GetProgrami(handle, ProgramProperty.ActiveUniformBlocks);
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var property = GetBlock(handle, i);
|
||||||
|
list.Add(property);
|
||||||
|
}
|
||||||
|
Blocks = list.ToImmutableList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ShaderMappingProperty GetAttribute(int program, int index)
|
||||||
|
{
|
||||||
|
string name = GL.GetActiveAttrib(program, (uint)index, 260, out _, out _, out _);
|
||||||
|
int location = GL.GetAttribLocation(program, name);
|
||||||
|
|
||||||
|
return new ShaderMappingProperty(name, location);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ShaderMappingProperty GetUniform(int program, int index, out bool isSampler)
|
||||||
|
{
|
||||||
|
string name = GL.GetActiveUniform(program, (uint)index, 260, out _, out _, out OpenTK.Graphics.OpenGL.UniformType type);
|
||||||
|
int location = GL.GetUniformLocation(program, name);
|
||||||
|
|
||||||
|
isSampler = type switch
|
||||||
|
{
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.Sampler1D or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.Sampler1DArray or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.Sampler1DShadow or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.Sampler1DArrayShadow or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.IntSampler1D or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.IntSampler1DArray or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler1D or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler1DArray or
|
||||||
|
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.Sampler2D or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.Sampler2DShadow or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.Sampler2DRect or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.Sampler2DRectShadow or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.Sampler2DArray or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.Sampler2DArrayShadow or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.Sampler2DMultisample or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.Sampler2DMultisampleArray or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.IntSampler2D or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.IntSampler2DRect or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.IntSampler2DArray or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.IntSampler2DMultisample or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.IntSampler2DMultisampleArray or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler2D or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler2DRect or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler2DArray or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler2DMultisample or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler2DMultisampleArray or
|
||||||
|
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.Sampler3D or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.IntSampler3D or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler3D or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.SamplerCube or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.SamplerCubeShadow or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.SamplerCubeMapArray or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.SamplerCubeMapArrayShadow or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.IntSamplerCube or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.IntSamplerCubeMapArray or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSamplerCube or
|
||||||
|
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSamplerCubeMapArray
|
||||||
|
=> true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
return new ShaderMappingProperty(name, location);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ShaderMappingProperty GetBlock(int program, int index)
|
||||||
|
{
|
||||||
|
int size = 0;
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
GL.GetActiveUniformBlockiv(program, (uint)index, UniformBlockPName.UniformBlockDataSize, &size);
|
||||||
|
}
|
||||||
|
|
||||||
|
GL.GetActiveUniformBlockName(program, (uint)index, 1024, out _, out string name);
|
||||||
|
return new ShaderMappingProperty(name, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Context.Collector.DeleteProgram(Handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,499 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Dashboard.Drawing;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using OpenTK.Mathematics;
|
||||||
|
using UniformType = Dashboard.Drawing.UniformType;
|
||||||
|
using Vector2 = System.Numerics.Vector2;
|
||||||
|
|
||||||
|
namespace Dashboard.OpenGL.Drawing
|
||||||
|
{
|
||||||
|
public static class GLStateExtensions
|
||||||
|
{
|
||||||
|
extension(VertexAttributeType type)
|
||||||
|
{
|
||||||
|
public All OpenGL => type switch
|
||||||
|
{
|
||||||
|
VertexAttributeType.UnsignedByte => All.UnsignedByte,
|
||||||
|
VertexAttributeType.UnsignedShort => All.UnsignedShort,
|
||||||
|
VertexAttributeType.UnsignedInt => All.UnsignedInt,
|
||||||
|
VertexAttributeType.Byte => All.Byte,
|
||||||
|
VertexAttributeType.Short => All.Short,
|
||||||
|
VertexAttributeType.Int => All.Int,
|
||||||
|
VertexAttributeType.Half => All.HalfFloat,
|
||||||
|
VertexAttributeType.Float => All.Float,
|
||||||
|
VertexAttributeType.Double => All.Double,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(),
|
||||||
|
};
|
||||||
|
|
||||||
|
public VertexAttribType VertexAttribType => (VertexAttribType)type.OpenGL;
|
||||||
|
public VertexAttribPointerType VertexAttribPointerType => (VertexAttribPointerType)type.OpenGL;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension(TextureType type)
|
||||||
|
{
|
||||||
|
public TextureTarget OpenGL => type switch
|
||||||
|
{
|
||||||
|
TextureType.Texture1D => TextureTarget.Texture1D,
|
||||||
|
TextureType.Texture2D => TextureTarget.Texture2D,
|
||||||
|
TextureType.Texture2DArray => TextureTarget.Texture2DArray,
|
||||||
|
TextureType.Texture2DCube => TextureTarget.TextureCubeMap,
|
||||||
|
TextureType.Texture3D => TextureTarget.Texture3D,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
extension(BlendEquation eq)
|
||||||
|
{
|
||||||
|
public BlendEquationMode OpenGL => eq switch
|
||||||
|
{
|
||||||
|
BlendEquation.Add => BlendEquationMode.FuncAdd,
|
||||||
|
BlendEquation.Subtract => BlendEquationMode.FuncSubtract,
|
||||||
|
BlendEquation.Max => BlendEquationMode.Max,
|
||||||
|
BlendEquation.Min => BlendEquationMode.Min,
|
||||||
|
BlendEquation.ReverseSubtract => BlendEquationMode.FuncReverseSubtract,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
extension(BlendFunction mode)
|
||||||
|
{
|
||||||
|
public BlendingFactor OpenGL => mode switch
|
||||||
|
{
|
||||||
|
BlendFunction.One => BlendingFactor.One,
|
||||||
|
BlendFunction.Zero => BlendingFactor.Zero,
|
||||||
|
BlendFunction.SourceAlpha => BlendingFactor.SrcAlpha,
|
||||||
|
BlendFunction.OneMinusSourceAlpha => BlendingFactor.OneMinusSrcAlpha,
|
||||||
|
BlendFunction.DestinationAlpha => BlendingFactor.DstAlpha,
|
||||||
|
BlendFunction.OneMinusDestinationAlpha => BlendingFactor.OneMinusDstAlpha,
|
||||||
|
BlendFunction.SourceColor => BlendingFactor.SrcColor,
|
||||||
|
BlendFunction.OneMinusSourceColor => BlendingFactor.OneMinusSrcColor,
|
||||||
|
BlendFunction.DestinationColor => BlendingFactor.DstColor,
|
||||||
|
BlendFunction.OneMinusDestinationColor => BlendingFactor.OneMinusDstColor,
|
||||||
|
BlendFunction.ConstantColor => BlendingFactor.OneMinusConstantColor,
|
||||||
|
BlendFunction.OneMinusConstantColor => BlendingFactor.OneMinusConstantColor,
|
||||||
|
BlendFunction.ConstantAlpha => BlendingFactor.OneMinusConstantAlpha,
|
||||||
|
BlendFunction.OneMinusConstantAlpha => BlendingFactor.OneMinusConstantAlpha,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
extension(TestFunction test)
|
||||||
|
{
|
||||||
|
public All OpenGL => test switch
|
||||||
|
{
|
||||||
|
TestFunction.Always => All.Always,
|
||||||
|
TestFunction.Equal => All.Equal,
|
||||||
|
TestFunction.Greater => All.Greater,
|
||||||
|
TestFunction.GreaterThanOrEqual => All.Gequal,
|
||||||
|
TestFunction.LessThan => All.Less,
|
||||||
|
TestFunction.LessThanOrEqual => All.Lequal,
|
||||||
|
TestFunction.Never => All.Never,
|
||||||
|
TestFunction.NotEqual => All.Notequal,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(),
|
||||||
|
};
|
||||||
|
|
||||||
|
public DepthFunction DepthFunction => (DepthFunction)test.OpenGL;
|
||||||
|
public StencilFunction StencilFunction => (StencilFunction)test.OpenGL;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension(StencilOperation op)
|
||||||
|
{
|
||||||
|
public StencilOp OpenGL => op switch
|
||||||
|
{
|
||||||
|
StencilOperation.Keep => StencilOp.Keep,
|
||||||
|
StencilOperation.Zero => StencilOp.Zero,
|
||||||
|
StencilOperation.Replace => StencilOp.Replace,
|
||||||
|
StencilOperation.Increment => StencilOp.Incr,
|
||||||
|
StencilOperation.UncheckedIncrement => StencilOp.IncrWrap,
|
||||||
|
StencilOperation.Decrement => StencilOp.Decr,
|
||||||
|
StencilOperation.UncheckedDecrement => StencilOp.DecrWrap,
|
||||||
|
StencilOperation.Invert => StencilOp.Invert,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
extension(MeshPrimitive primitive)
|
||||||
|
{
|
||||||
|
public PrimitiveType OpenGL => primitive switch
|
||||||
|
{
|
||||||
|
MeshPrimitive.Line => PrimitiveType.Lines,
|
||||||
|
MeshPrimitive.Point => PrimitiveType.Points,
|
||||||
|
MeshPrimitive.Triangle => PrimitiveType.Triangles,
|
||||||
|
MeshPrimitive.TriangleFan => PrimitiveType.TriangleFan,
|
||||||
|
MeshPrimitive.TriangleStrip => PrimitiveType.TriangleStrip,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
extension(IndexType index)
|
||||||
|
{
|
||||||
|
public DrawElementsType OpenGL => index switch
|
||||||
|
{
|
||||||
|
IndexType.Int or IndexType.UnsignedInt => DrawElementsType.UnsignedInt,
|
||||||
|
IndexType.Short or IndexType.UnsignedShort => DrawElementsType.UnsignedShort,
|
||||||
|
IndexType.UnsignedLong => throw new NotSupportedException(),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
extension(BlendMode mode)
|
||||||
|
{
|
||||||
|
public void SetEnabled()
|
||||||
|
{
|
||||||
|
if (mode.Enabled)
|
||||||
|
GL.Enable(EnableCap.Blend);
|
||||||
|
else
|
||||||
|
GL.Disable(EnableCap.Blend);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetMode()
|
||||||
|
{
|
||||||
|
if (mode.IsSeparate)
|
||||||
|
{
|
||||||
|
GL.BlendEquationSeparate(mode.ColorEquation.OpenGL, mode.AlphaEquation.OpenGL);
|
||||||
|
GL.BlendFuncSeparate(
|
||||||
|
mode.Color.Source.OpenGL, mode.Color.Destination.OpenGL,
|
||||||
|
mode.Alpha.Source.OpenGL, mode.Alpha.Destination.OpenGL);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GL.BlendEquation(mode.UnifiedEquation.Value.OpenGL);
|
||||||
|
GL.BlendFunc(mode.Unified.Value.Source.OpenGL, mode.Unified.Value.Destination.OpenGL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetConstant()
|
||||||
|
{
|
||||||
|
GL.BlendColor(mode.Constant.R / 255f, mode.Constant.G / 255f,mode.Constant.B / 255f,mode.Constant.A / 255f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetAll()
|
||||||
|
{
|
||||||
|
mode.SetEnabled();
|
||||||
|
mode.SetConstant();
|
||||||
|
mode.SetMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension(DepthMode mode)
|
||||||
|
{
|
||||||
|
public void SetEnabled()
|
||||||
|
{
|
||||||
|
if (mode.Test)
|
||||||
|
GL.Enable(EnableCap.DepthTest);
|
||||||
|
else
|
||||||
|
GL.Disable(EnableCap.DepthTest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetMask() => GL.DepthMask(mode.Mask);
|
||||||
|
|
||||||
|
public void SetFunction() => GL.DepthFunc(mode.Function.DepthFunction);
|
||||||
|
|
||||||
|
public void SetAll()
|
||||||
|
{
|
||||||
|
mode.SetEnabled();
|
||||||
|
mode.SetMask();
|
||||||
|
mode.SetFunction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension(StencilMode mode)
|
||||||
|
{
|
||||||
|
public void SetEnabled()
|
||||||
|
{
|
||||||
|
if (mode.Enabled)
|
||||||
|
GL.Enable(EnableCap.StencilTest);
|
||||||
|
else
|
||||||
|
GL.Disable(EnableCap.StencilTest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetFunction() => GL.StencilFunc(mode.Function.StencilFunction, mode.Reference, (uint)mode.Mask);
|
||||||
|
|
||||||
|
public void SetOperations() => GL.StencilOp(mode.Fail.OpenGL, mode.DepthFail.OpenGL, mode.Pass.OpenGL);
|
||||||
|
|
||||||
|
public void SetAll()
|
||||||
|
{
|
||||||
|
mode.SetEnabled();
|
||||||
|
mode.SetFunction();
|
||||||
|
mode.SetOperations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension(PipelineState state)
|
||||||
|
{
|
||||||
|
public void SetViewport(Vector2 size)
|
||||||
|
{
|
||||||
|
Vector2 min = Vector2.Max(Vector2.Zero, state.ViewportRegion.Min);
|
||||||
|
Vector2 max = Vector2.Min(size, state.ViewportRegion.Max);
|
||||||
|
|
||||||
|
GL.Viewport(
|
||||||
|
(int)Math.Round(min.X),
|
||||||
|
(int)Math.Round(size.Y - max.Y),
|
||||||
|
(int)Math.Round(max.X - min.X),
|
||||||
|
(int)Math.Round(max.Y - min.Y));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: clamp to viewport size
|
||||||
|
public void SetScissor(Vector2 size)
|
||||||
|
{
|
||||||
|
if (state.ScissorRegion == new Box2d(Vector2.PositiveInfinity, Vector2.PositiveInfinity))
|
||||||
|
{
|
||||||
|
GL.Disable(EnableCap.ScissorTest);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GL.Enable(EnableCap.ScissorTest);
|
||||||
|
GL.Scissor(
|
||||||
|
(int)Math.Round(state.ViewportRegion.Left),
|
||||||
|
(int)Math.Round(size.Y - state.ViewportRegion.Top),
|
||||||
|
(int)Math.Round(state.ViewportRegion.Right - state.ViewportRegion.Left),
|
||||||
|
(int)Math.Round(state.ViewportRegion.Bottom - state.ViewportRegion.Top));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetFrontFace()
|
||||||
|
{
|
||||||
|
GL.FrontFace(state.FrontFace switch
|
||||||
|
{
|
||||||
|
WindingOrder.Clockwise => FrontFaceDirection.Cw,
|
||||||
|
WindingOrder.Counterclockwise => FrontFaceDirection.Ccw,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCullMode()
|
||||||
|
{
|
||||||
|
if (state.CullMode == FaceCulling.None)
|
||||||
|
{
|
||||||
|
GL.Disable(EnableCap.CullFace);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GL.Enable(EnableCap.CullFace);
|
||||||
|
|
||||||
|
GL.CullFace(state.CullMode switch
|
||||||
|
{
|
||||||
|
FaceCulling.Both => TriangleFace.FrontAndBack,
|
||||||
|
FaceCulling.Front => TriangleFace.Front,
|
||||||
|
FaceCulling.Back => TriangleFace.Back,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetBlendMode() => state.BlendMode.SetAll();
|
||||||
|
public void SetDepthMode() => state.DepthMode.SetAll();
|
||||||
|
public void SetStencilMode() => state.StencilMode.SetAll();
|
||||||
|
|
||||||
|
public void SetColorMask()
|
||||||
|
{
|
||||||
|
GL.ColorMask(state.RedMask, state.GreenMask, state.BlueMask, state.AlphaMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPointSize()
|
||||||
|
{
|
||||||
|
GL.PointSize(state.PointSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetLineWidth()
|
||||||
|
{
|
||||||
|
GL.LineWidth(state.LineWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension(DrawCall call)
|
||||||
|
{
|
||||||
|
public void UsePipeline() => GL.UseProgram((call.ShaderPipeline as GLShader)?.Handle ?? 0);
|
||||||
|
|
||||||
|
public void SetViewport(Vector2 size) => call.PipelineState.SetViewport(size);
|
||||||
|
|
||||||
|
public void SetScissor(Vector2 size) => call.PipelineState.SetViewport(size);
|
||||||
|
|
||||||
|
public void SetFrontFace() => call.PipelineState.SetFrontFace();
|
||||||
|
|
||||||
|
public void SetCullMode() => call.PipelineState.SetCullMode();
|
||||||
|
|
||||||
|
public void SetBlendMode() => call.PipelineState.BlendMode.SetAll();
|
||||||
|
public void SetDepthMode() => call.PipelineState.DepthMode.SetAll();
|
||||||
|
public void SetStencilMode() => call.PipelineState.StencilMode.SetAll();
|
||||||
|
|
||||||
|
public void SetColorMask() => call.PipelineState.SetColorMask();
|
||||||
|
|
||||||
|
public void SetPointSize() => call.PipelineState.SetPointSize();
|
||||||
|
|
||||||
|
public void SetLineWidth() => call.PipelineState.SetLineWidth();
|
||||||
|
|
||||||
|
public void SetPrimitiveRestart()
|
||||||
|
{
|
||||||
|
if (!call.EnableRestart)
|
||||||
|
{
|
||||||
|
GL.Disable(EnableCap.PrimitiveRestart);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GL.Enable(EnableCap.PrimitiveRestart);
|
||||||
|
GL.PrimitiveRestartIndex((uint)call.RestartIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UseTextures()
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
foreach (ITexture? texture in call.Textures)
|
||||||
|
{
|
||||||
|
GL.ActiveTexture((TextureUnit)((int)TextureUnit.Texture0 + i));
|
||||||
|
if (texture is GLTexture glTexture)
|
||||||
|
{
|
||||||
|
GL.BindTexture(glTexture.Type.OpenGL, glTexture.Handle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GL.BindTexture(TextureTarget.Texture2D, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateVertexArrays(int vao)
|
||||||
|
{
|
||||||
|
GL.BindVertexArray(vao);
|
||||||
|
|
||||||
|
VertexSpecification spec = call.VertexSpecification;
|
||||||
|
|
||||||
|
if (spec.ElementBuffer != null)
|
||||||
|
{
|
||||||
|
GL.BindBuffer(BufferTarget.ElementArrayBuffer, (spec.ElementBuffer as GLBuffer)?.Handle ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (int i = 0; i < spec.Attributes.Count; i++)
|
||||||
|
{
|
||||||
|
VertexAttribute attrib = spec.Attributes[i];
|
||||||
|
IBuffer? buffer = spec.VertexBuffers[i];
|
||||||
|
|
||||||
|
GL.BindBuffer(BufferTarget.ArrayBuffer, (buffer as GLBuffer)?.Handle ?? 0);
|
||||||
|
|
||||||
|
GL.VertexAttribPointer(
|
||||||
|
(uint)attrib.Location,
|
||||||
|
attrib.Components,
|
||||||
|
attrib.Type.VertexAttribPointerType,
|
||||||
|
false,
|
||||||
|
(int)attrib.Stride,
|
||||||
|
(nint)attrib.Offset);
|
||||||
|
GL.EnableVertexAttribArray((uint)attrib.Location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateUniforms(bool transpose = false)
|
||||||
|
{
|
||||||
|
int bufferIndex = 0;
|
||||||
|
foreach (UniformDescriptor uniform in call.Uniforms)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<byte> uniformData = call.UniformData.Span;
|
||||||
|
|
||||||
|
switch (uniform.Type)
|
||||||
|
{
|
||||||
|
case UniformType.I1:
|
||||||
|
{
|
||||||
|
ReadOnlySpan<int> span = CastSpan<int>(uniformData, uniform.Offset, uniform.Size);
|
||||||
|
GL.Uniform1i(uniform.Location, span.Length, span);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UniformType.I2:
|
||||||
|
{
|
||||||
|
ReadOnlySpan<Vector2i> span = CastSpan<Vector2i>(uniformData, uniform.Offset, uniform.Size);
|
||||||
|
GL.Uniform2i(uniform.Location, span.Length, span);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UniformType.I3:
|
||||||
|
{
|
||||||
|
ReadOnlySpan<Vector3i> span = CastSpan<Vector3i>(uniformData, uniform.Offset, uniform.Size);
|
||||||
|
GL.Uniform3i(uniform.Location, span.Length, span);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UniformType.I4:
|
||||||
|
{
|
||||||
|
ReadOnlySpan<Vector4i> span = CastSpan<Vector4i>(uniformData, uniform.Offset, uniform.Size);
|
||||||
|
GL.Uniform4i(uniform.Location, span.Length, span);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UniformType.F1:
|
||||||
|
{
|
||||||
|
ReadOnlySpan<float> span = CastSpan<float>(uniformData, uniform.Offset, uniform.Size);
|
||||||
|
GL.Uniform1f(uniform.Location, span.Length, span);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UniformType.F2:
|
||||||
|
{
|
||||||
|
ReadOnlySpan<Vector2> span = CastSpan<Vector2>(uniformData, uniform.Offset, uniform.Size);
|
||||||
|
GL.Uniform2f(uniform.Location, span.Length, span);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UniformType.F3:
|
||||||
|
{
|
||||||
|
ReadOnlySpan<Vector3> span = CastSpan<Vector3>(uniformData, uniform.Offset, uniform.Size);
|
||||||
|
GL.Uniform3f(uniform.Location, span.Length, span);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UniformType.F4:
|
||||||
|
{
|
||||||
|
ReadOnlySpan<Vector4> span = CastSpan<Vector4>(uniformData, uniform.Offset, uniform.Size);
|
||||||
|
GL.Uniform4f(uniform.Location, span.Length, span);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UniformType.Mat2:
|
||||||
|
{
|
||||||
|
ReadOnlySpan<Matrix2> span = CastSpan<Matrix2>(uniformData, uniform.Offset, uniform.Size);
|
||||||
|
GL.UniformMatrix2f(uniform.Location, span.Length, transpose, span);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UniformType.Mat3:
|
||||||
|
{
|
||||||
|
ReadOnlySpan<Matrix3> span = CastSpan<Matrix3>(uniformData, uniform.Offset, uniform.Size);
|
||||||
|
GL.UniformMatrix3f(uniform.Location, span.Length, transpose, span);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UniformType.Mat4:
|
||||||
|
{
|
||||||
|
ReadOnlySpan<Matrix4> span = CastSpan<Matrix4>(uniformData, uniform.Offset, uniform.Size);
|
||||||
|
GL.UniformMatrix4f(uniform.Location, span.Length, transpose, span);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UniformType.Buffer:
|
||||||
|
{
|
||||||
|
int buffer = (call.UniformBuffer as GLBuffer)?.Handle ?? 0;
|
||||||
|
int index = bufferIndex++;
|
||||||
|
GL.BindBufferRange(BufferTarget.UniformBuffer, (uint)index, buffer, (nint)uniform.Offset, (int)uniform.Size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
ReadOnlySpan<T> CastSpan<T>(ReadOnlySpan<byte> bytes, long offset, long size)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
return MemoryMarshal.Cast<byte, T>(bytes.Slice((int)offset, (int)size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetAll(Vector2 size, int vao, bool transpose = false)
|
||||||
|
{
|
||||||
|
call.UsePipeline();
|
||||||
|
call.SetViewport(size); call.SetScissor(size);
|
||||||
|
call.SetFrontFace();
|
||||||
|
call.SetCullMode();
|
||||||
|
call.SetBlendMode();
|
||||||
|
call.SetDepthMode();
|
||||||
|
call.SetStencilMode();
|
||||||
|
call.SetPrimitiveRestart();
|
||||||
|
call.SetColorMask();
|
||||||
|
call.SetPointSize();
|
||||||
|
call.SetLineWidth();
|
||||||
|
call.UseTextures();
|
||||||
|
call.UpdateUniforms(transpose);
|
||||||
|
call.UpdateVertexArrays(vao);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using Dashboard.Drawing;
|
||||||
|
using Dashboard.OpenGL.Drawing;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using OGL = OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
|
namespace Dashboard.OpenGL
|
||||||
|
{
|
||||||
|
public class GLTexture(DirectRendering extension, TextureType type) : ITexture
|
||||||
|
{
|
||||||
|
public int Handle { get; private set; } = 0;
|
||||||
|
public bool IsValid => Handle != 0;
|
||||||
|
|
||||||
|
public TextureType Type { get; } = type;
|
||||||
|
public PixelFormat Format { get; private set; } = PixelFormat.Rgba8I;
|
||||||
|
public ColorSwizzle Swizzle { get; set; } = ColorSwizzle.Default;
|
||||||
|
public TextureFilter MinifyFilter { get; set; } = TextureFilter.Linear;
|
||||||
|
public TextureFilter MagnifyFilter { get; set; } = TextureFilter.Linear;
|
||||||
|
public Color BorderColor { get; set; } = Color.White;
|
||||||
|
public TextureRepeat RepeatS { get; set; } = TextureRepeat.Repeat;
|
||||||
|
public TextureRepeat RepeatT { get; set; } = TextureRepeat.Repeat;
|
||||||
|
public TextureRepeat RepeatR { get; set; } = TextureRepeat.Repeat;
|
||||||
|
public int Anisotropy { get; set; } = 0;
|
||||||
|
|
||||||
|
public int Width { get; private set; } = 0;
|
||||||
|
public int Height { get; private set; } = 0;
|
||||||
|
public int Depth { get; private set; } = 0;
|
||||||
|
public int Levels { get; private set; } = 0;
|
||||||
|
public bool Premultiplied { get; set; } = false;
|
||||||
|
|
||||||
|
private TextureTarget Target { get; } = type switch
|
||||||
|
{
|
||||||
|
TextureType.Texture1D => TextureTarget.Texture1D,
|
||||||
|
TextureType.Texture2D => TextureTarget.Texture2D,
|
||||||
|
TextureType.Texture3D => TextureTarget.Texture3D,
|
||||||
|
TextureType.Texture2DArray => TextureTarget.Texture2DArray,
|
||||||
|
TextureType.Texture2DCube => TextureTarget.TextureCubeMap,
|
||||||
|
_ => throw new NotSupportedException()
|
||||||
|
};
|
||||||
|
|
||||||
|
private DirectRendering Extension { get; } = extension;
|
||||||
|
private GLDeviceContext Context => Extension.Context;
|
||||||
|
|
||||||
|
~GLTexture()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetStorage(PixelFormat format, int width, int height, int depth, int levels)
|
||||||
|
{
|
||||||
|
if (!Context.IsRenderThread)
|
||||||
|
{
|
||||||
|
Context.InvokeBeforeDraw(() => SetStorage(format, width, height, depth, levels)).Wait();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (levels == 0)
|
||||||
|
{
|
||||||
|
levels = Math.Max(Math.ILogB(width), Math.ILogB(height));
|
||||||
|
}
|
||||||
|
|
||||||
|
Bind();
|
||||||
|
SizedInternalFormat glFormat = GetFormat(format);
|
||||||
|
if (Extension.SupportsArbTextureStorage)
|
||||||
|
{
|
||||||
|
switch (Type)
|
||||||
|
{
|
||||||
|
case TextureType.Texture1D:
|
||||||
|
GL.TexStorage1D(Target, levels, glFormat, width);
|
||||||
|
break;
|
||||||
|
case TextureType.Texture2D:
|
||||||
|
GL.TexStorage2D(Target, levels, glFormat, width, height);
|
||||||
|
break;
|
||||||
|
case TextureType.Texture3D:
|
||||||
|
case TextureType.Texture2DArray:
|
||||||
|
case TextureType.Texture2DCube:
|
||||||
|
GL.TexStorage3D(Target, levels, glFormat, width, height, depth);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (Type)
|
||||||
|
{
|
||||||
|
case TextureType.Texture1D:
|
||||||
|
GL.TexImage1D(Target, 0, (InternalFormat)glFormat, width, 0, (OGL.PixelFormat)glFormat, PixelType.UnsignedByte, IntPtr.Zero);
|
||||||
|
break;
|
||||||
|
case TextureType.Texture2D:
|
||||||
|
GL.TexImage2D(Target, 0, (InternalFormat)glFormat, width, height, 0, (OGL.PixelFormat)glFormat, PixelType.UnsignedByte, IntPtr.Zero);
|
||||||
|
break;
|
||||||
|
case TextureType.Texture3D:
|
||||||
|
case TextureType.Texture2DArray:
|
||||||
|
case TextureType.Texture2DCube:
|
||||||
|
GL.TexImage3D(Target, 0, (InternalFormat)glFormat, width, height, depth, 0, (OGL.PixelFormat)glFormat, PixelType.UnsignedByte, IntPtr.Zero);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
Depth = depth;
|
||||||
|
Levels = levels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Read<T>(Span<T> buffer, int level = 0, int align = 0) where T : unmanaged
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ITexture.Write<T>(PixelFormat format, ReadOnlySpan<T> buffer, int level, int align) => Write(format, buffer, level, align, null);
|
||||||
|
|
||||||
|
public unsafe void Write<T>(PixelFormat format, ReadOnlySpan<T> buffer, int level = 0, int align = 4, TimeSpan? timeout = null) where T : unmanaged
|
||||||
|
{
|
||||||
|
if (!Context.IsRenderThread)
|
||||||
|
{
|
||||||
|
T[] bufferArray = buffer.ToArray();
|
||||||
|
Task task = Context.InvokeBeforeDraw(() => Write<T>(format, bufferArray, level, align));
|
||||||
|
|
||||||
|
if (timeout.HasValue)
|
||||||
|
task.Wait(timeout.Value);
|
||||||
|
else
|
||||||
|
task.Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
Bind();
|
||||||
|
OGL::PixelFormat glFormat = format switch
|
||||||
|
{
|
||||||
|
PixelFormat.R8I or PixelFormat.R16F => OGL.PixelFormat.Red,
|
||||||
|
PixelFormat.Rg8I or PixelFormat.Rg16F => OGL.PixelFormat.Rg,
|
||||||
|
PixelFormat.Rgb8I or PixelFormat.Rgb16F => OGL.PixelFormat.Rgb,
|
||||||
|
PixelFormat.Rgba8I or PixelFormat.Rgba16F => OGL.PixelFormat.Rgba,
|
||||||
|
_ => throw new NotSupportedException()
|
||||||
|
};
|
||||||
|
|
||||||
|
PixelType glType = format switch
|
||||||
|
{
|
||||||
|
PixelFormat.R8I or PixelFormat.Rg8I or PixelFormat.Rgb8I or PixelFormat.Rgba8I => PixelType.UnsignedByte,
|
||||||
|
PixelFormat.R16F or PixelFormat.Rg16F or PixelFormat.Rgb16F or PixelFormat.Rgba16F => PixelType.HalfFloat,
|
||||||
|
_ => throw new NotSupportedException()
|
||||||
|
};
|
||||||
|
|
||||||
|
GL.PixelStorei(PixelStoreParameter.UnpackAlignment, align);
|
||||||
|
fixed (T* ptr = buffer)
|
||||||
|
{
|
||||||
|
switch (Type)
|
||||||
|
{
|
||||||
|
case TextureType.Texture1D:
|
||||||
|
GL.TexSubImage1D(Target, level, 0, Width, glFormat, glType, ptr);
|
||||||
|
break;
|
||||||
|
case TextureType.Texture2D:
|
||||||
|
GL.TexSubImage2D(Target, level, 0, 0, Width, Height, glFormat, glType, ptr);
|
||||||
|
break;
|
||||||
|
case TextureType.Texture2DCube:
|
||||||
|
case TextureType.Texture3D:
|
||||||
|
case TextureType.Texture2DArray:
|
||||||
|
GL.TexSubImage3D(Target, level, 0, 0, 0, Width, Height, Depth, glFormat, glType, ptr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Premultiply()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unmultiply()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GenerateMipmaps()
|
||||||
|
{
|
||||||
|
if (!Context.IsRenderThread)
|
||||||
|
{
|
||||||
|
Context.InvokeBeforeDraw(GenerateMipmaps).Wait();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bind();
|
||||||
|
GL.GenerateMipmap(Target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (IsDisposed)
|
||||||
|
return;
|
||||||
|
IsDisposed = true;
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
if (Thread.CurrentThread != Context.RendererThread)
|
||||||
|
{
|
||||||
|
Context.Collector.DeleteTexture(Handle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GL.DeleteTexture(Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle = 0;
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Context.Collector.DeleteTexture(Handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
|
public void Dispose() => Dispose(false);
|
||||||
|
|
||||||
|
private void Bind()
|
||||||
|
{
|
||||||
|
if (Handle == 0)
|
||||||
|
{
|
||||||
|
Handle = GL.GenTexture();
|
||||||
|
}
|
||||||
|
|
||||||
|
GL.BindTexture(Target, Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SizedInternalFormat GetFormat(PixelFormat format)
|
||||||
|
{
|
||||||
|
return format switch
|
||||||
|
{
|
||||||
|
PixelFormat.R8I => SizedInternalFormat.R8,
|
||||||
|
PixelFormat.R16F => SizedInternalFormat.R16f,
|
||||||
|
PixelFormat.Rg8I => SizedInternalFormat.Rg8,
|
||||||
|
PixelFormat.Rg16F => SizedInternalFormat.Rg16f,
|
||||||
|
PixelFormat.Rgb8I => SizedInternalFormat.Rgb8,
|
||||||
|
PixelFormat.Rgb16F => SizedInternalFormat.Rgb16f,
|
||||||
|
PixelFormat.Rgba8I => SizedInternalFormat.Rgba8,
|
||||||
|
PixelFormat.Rgba16F => SizedInternalFormat.Rgba16f,
|
||||||
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PixelFormat GetFormat(SizedInternalFormat format)
|
||||||
|
{
|
||||||
|
return format switch
|
||||||
|
{
|
||||||
|
SizedInternalFormat.R8 => PixelFormat.R8I,
|
||||||
|
SizedInternalFormat.R16f => PixelFormat.R16F,
|
||||||
|
SizedInternalFormat.Rg8 => PixelFormat.Rg8I,
|
||||||
|
SizedInternalFormat.Rg16f => PixelFormat.Rg16F,
|
||||||
|
SizedInternalFormat.Rgb8 => PixelFormat.Rgb8I,
|
||||||
|
SizedInternalFormat.Rgb16f => PixelFormat.Rgb16F,
|
||||||
|
SizedInternalFormat.Rgba8 => PixelFormat.Rgba8I,
|
||||||
|
SizedInternalFormat.Rgba16f => PixelFormat.Rgba16F,
|
||||||
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Dashboard.Drawing;
|
||||||
|
using Dashboard.Pal;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
|
namespace Dashboard.OpenGL.Drawing
|
||||||
|
{
|
||||||
|
public class ImmediateMode : IImmediateMode
|
||||||
|
{
|
||||||
|
public string DriverName => "Dashboard OpenGL Immediate Mode";
|
||||||
|
public string DriverVendor => "Dashboard";
|
||||||
|
public Version DriverVersion { get; } = new Version(1, 0);
|
||||||
|
|
||||||
|
public DeviceContext Context { get; private set; } = null!;
|
||||||
|
|
||||||
|
private IDirectRendering _dr;
|
||||||
|
private GLShader _program;
|
||||||
|
|
||||||
|
private uint _program_apos;
|
||||||
|
private uint _program_atexcoord;
|
||||||
|
private uint _program_acolor;
|
||||||
|
private int _program_transforms;
|
||||||
|
private int _program_image;
|
||||||
|
private GLTexture _white;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Require(DeviceContext context)
|
||||||
|
{
|
||||||
|
Context = context;
|
||||||
|
_dr = context.ExtensionRequire<IDirectRendering>();
|
||||||
|
|
||||||
|
GlslShaderCreateInfo shader;
|
||||||
|
|
||||||
|
using (StreamReader vertex = new StreamReader(GetType().Assembly
|
||||||
|
.GetManifestResourceStream("Dashboard.OpenGL.Drawing.immediate.vert")!))
|
||||||
|
using (StreamReader fragment = new StreamReader(GetType().Assembly
|
||||||
|
.GetManifestResourceStream("Dashboard.OpenGL.Drawing.immediate.frag")!))
|
||||||
|
{
|
||||||
|
shader = new GlslShaderCreateInfo()
|
||||||
|
{
|
||||||
|
VertexShader = vertex.ReadToEnd(),
|
||||||
|
FragmentShader = fragment.ReadToEnd(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_program = (GLShader)_dr.CreateShader(shader);
|
||||||
|
|
||||||
|
_program_apos = (uint)GL.GetAttribLocation(_program.Handle, "aPos");
|
||||||
|
_program_atexcoord = (uint)GL.GetAttribLocation(_program.Handle, "aTexCoords");
|
||||||
|
_program_acolor = (uint)GL.GetAttribLocation(_program.Handle, "aColor");
|
||||||
|
|
||||||
|
_program_transforms = GL.GetUniformLocation(_program.Handle, "transforms");
|
||||||
|
_program_image = GL.GetUniformLocation(_program.Handle, "image");
|
||||||
|
|
||||||
|
_white = (GLTexture)_dr.CreateTexture(TextureType.Texture2D);
|
||||||
|
_white.SetStorage(PixelFormat.Rgb8I, 1, 1, 1, 1);
|
||||||
|
_white.Swizzle = new ColorSwizzle(ColorChannel.One, ColorChannel.One, ColorChannel.One, ColorChannel.One);
|
||||||
|
_white.MinifyFilter = TextureFilter.Nearest;
|
||||||
|
_white.MagnifyFilter = TextureFilter.Nearest;
|
||||||
|
|
||||||
|
// TODO: implement the API above instead of writing this manually.
|
||||||
|
GL.BindTexture(TextureTarget.Texture2D, _white.Handle);
|
||||||
|
GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, 1, 1, OpenTK.Graphics.OpenGL.PixelFormat.Rgb, PixelType.Byte, stackalloc byte[] { 0xFF, 0xFF, 0xFF, 0xFF });
|
||||||
|
GL.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleA, (int)All.One);
|
||||||
|
GL.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleR, (int)All.One);
|
||||||
|
GL.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleG, (int)All.One);
|
||||||
|
GL.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleB, (int)All.One);
|
||||||
|
GL.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
|
||||||
|
GL.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearColor(Color color)
|
||||||
|
{
|
||||||
|
GL.ClearColor(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
|
||||||
|
GL.Clear(ClearBufferMask.ColorBufferBit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Line(Vector2 a, Vector2 b, float width, float depth, Vector4 color)
|
||||||
|
{
|
||||||
|
Vector2 normal = Vector2.Normalize(b - a);
|
||||||
|
Vector2 tangent = new Vector2(-normal.Y, normal.X) * width;
|
||||||
|
Span<ImmediateVertex> vertices =
|
||||||
|
[
|
||||||
|
new ImmediateVertex(new Vector3(a-tangent, depth), Vector2.Zero, color),
|
||||||
|
new ImmediateVertex(new Vector3(b-tangent, depth), Vector2.Zero, color),
|
||||||
|
new ImmediateVertex(new Vector3(b+tangent, depth), Vector2.Zero, color),
|
||||||
|
new ImmediateVertex(new Vector3(a-tangent, depth), Vector2.Zero, color),
|
||||||
|
new ImmediateVertex(new Vector3(b+tangent, depth), Vector2.Zero, color),
|
||||||
|
new ImmediateVertex(new Vector3(a+tangent, depth), Vector2.Zero, color),
|
||||||
|
];
|
||||||
|
|
||||||
|
DrawImmediate(new ImmediateDrawCall(MeshPrimitive.Triangle, vertices.ToArray())
|
||||||
|
{
|
||||||
|
Transforms = Context.ExtensionRequire<IDeviceContextBase>().Transforms,
|
||||||
|
PipelineState = new PipelineState { CullMode = FaceCulling.None },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Rectangle(Box2d rectangle, float depth, Vector4 color)
|
||||||
|
{
|
||||||
|
Span<ImmediateVertex> vertices =
|
||||||
|
[
|
||||||
|
new ImmediateVertex(new Vector3(rectangle.Min.X, rectangle.Min.Y, depth), Vector2.Zero, color),
|
||||||
|
new ImmediateVertex(new Vector3(rectangle.Max.X, rectangle.Min.Y, depth), Vector2.Zero, color),
|
||||||
|
new ImmediateVertex(new Vector3(rectangle.Max.X, rectangle.Max.Y, depth), Vector2.Zero, color),
|
||||||
|
new ImmediateVertex(new Vector3(rectangle.Min.X, rectangle.Min.Y, depth), Vector2.Zero, color),
|
||||||
|
new ImmediateVertex(new Vector3(rectangle.Max.X, rectangle.Max.Y, depth), Vector2.Zero, color),
|
||||||
|
new ImmediateVertex(new Vector3(rectangle.Min.X, rectangle.Max.Y, depth), Vector2.Zero, color),
|
||||||
|
];
|
||||||
|
|
||||||
|
DrawImmediate(new ImmediateDrawCall(MeshPrimitive.Triangle, vertices.ToArray())
|
||||||
|
{
|
||||||
|
Transforms = Context.ExtensionRequire<IDeviceContextBase>().Transforms,
|
||||||
|
PipelineState = new PipelineState { CullMode = FaceCulling.None },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Rectangle(in RectangleDrawInfo rectangle)
|
||||||
|
{
|
||||||
|
// TODO: implement this better.
|
||||||
|
int z = Context.ExtensionRequire<IDeviceContextBase>().IncrementZ();
|
||||||
|
Color color = (rectangle.Fill as SolidColorBrush)?.Color ?? Color.LightGray;
|
||||||
|
Vector4 colorV = new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
|
||||||
|
Vector2 size = rectangle.Box.Size;
|
||||||
|
Box2d box = Box2d.FromPositionAndSize(rectangle.Position, size);
|
||||||
|
|
||||||
|
Rectangle(box, z, colorV);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Image(Box2d rectangle, Box2d uv, float depth, ITexture texture)
|
||||||
|
{
|
||||||
|
Span<ImmediateVertex> vertices =
|
||||||
|
[
|
||||||
|
new ImmediateVertex(new Vector3(rectangle.Min.X, rectangle.Min.Y, depth), new Vector2(uv.Min.X, uv.Min.Y), Vector4.One),
|
||||||
|
new ImmediateVertex(new Vector3(rectangle.Max.X, rectangle.Min.Y, depth), new Vector2(uv.Max.X, uv.Min.Y), Vector4.One),
|
||||||
|
new ImmediateVertex(new Vector3(rectangle.Max.X, rectangle.Max.Y, depth), new Vector2(uv.Max.X, uv.Max.Y), Vector4.One),
|
||||||
|
new ImmediateVertex(new Vector3(rectangle.Min.X, rectangle.Min.Y, depth), new Vector2(uv.Min.X, uv.Min.Y), Vector4.One),
|
||||||
|
new ImmediateVertex(new Vector3(rectangle.Max.X, rectangle.Max.Y, depth), new Vector2(uv.Max.X, uv.Max.Y), Vector4.One),
|
||||||
|
new ImmediateVertex(new Vector3(rectangle.Min.X, rectangle.Max.Y, depth), new Vector2(uv.Min.X, uv.Max.Y), Vector4.One),
|
||||||
|
];
|
||||||
|
|
||||||
|
DrawImmediate(new ImmediateDrawCall(MeshPrimitive.Triangle, vertices.ToArray())
|
||||||
|
{
|
||||||
|
Transforms = Context.ExtensionRequire<IDeviceContextBase>().Transforms,
|
||||||
|
PipelineState = new PipelineState { CullMode = FaceCulling.None },
|
||||||
|
Texture = texture,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
IContextBase IContextExtensionBase.Context => Context;
|
||||||
|
|
||||||
|
void IContextExtensionBase.Require(IContextBase context)
|
||||||
|
{
|
||||||
|
Require((DeviceContext)context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawImmediate(ImmediateDrawCall call)
|
||||||
|
{
|
||||||
|
// This is a terrible implementation as it stands but we can improve this immensely later on.
|
||||||
|
IDirectRendering dr = Context.ExtensionRequire<IDirectRendering>();
|
||||||
|
|
||||||
|
int size = call.Vertices.Length * ImmediateVertex.Size;
|
||||||
|
|
||||||
|
IBuffer buffer = dr.CreateBuffer(BufferAccessPattern.Stream, size);
|
||||||
|
buffer.Write(0, call.Vertices.Span);
|
||||||
|
|
||||||
|
VertexSpecification spec = CreateVertexSpec(buffer);
|
||||||
|
ReadOnlyMemory<byte> uniforms = MemoryMarshal.AsBytes(stackalloc Matrix4x4[] {call.Transforms}).ToArray();
|
||||||
|
|
||||||
|
DrawCall drawCall = new DrawCall(call.Primitive, spec, 0, call.Vertices.Length)
|
||||||
|
{
|
||||||
|
ShaderPipeline = _program,
|
||||||
|
PipelineState = call.PipelineState,
|
||||||
|
UniformData = uniforms,
|
||||||
|
Uniforms = [
|
||||||
|
new UniformDescriptor() {
|
||||||
|
Location = _program_transforms,
|
||||||
|
Type = Dashboard.Drawing.UniformType.Mat4,
|
||||||
|
Offset = 0,
|
||||||
|
Size = Unsafe.SizeOf<Matrix4x4>(),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
Textures = [call.Texture ?? _white]
|
||||||
|
};
|
||||||
|
|
||||||
|
dr.Draw(drawCall);
|
||||||
|
buffer.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private VertexSpecification CreateVertexSpec(IBuffer buffer)
|
||||||
|
{
|
||||||
|
return new VertexSpecification(
|
||||||
|
[
|
||||||
|
new VertexAttribute((int)_program_apos, 3, VertexAttributeType.Float, ImmediateVertex.PosOffset, ImmediateVertex.Size),
|
||||||
|
new VertexAttribute((int)_program_atexcoord, 2, VertexAttributeType.Float, ImmediateVertex.TexCoordsOffset, ImmediateVertex.Size),
|
||||||
|
new VertexAttribute((int)_program_acolor, 4, VertexAttributeType.Float, ImmediateVertex.ColorOffset, ImmediateVertex.Size)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
buffer,
|
||||||
|
buffer,
|
||||||
|
buffer
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Dashboard.Drawing;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
|
namespace Dashboard.OpenGL.Drawing
|
||||||
|
{
|
||||||
|
public static class KhronosShaderHelper
|
||||||
|
{
|
||||||
|
public static int CompileStage(ShaderType type, string source, out string? log)
|
||||||
|
{
|
||||||
|
log = null;
|
||||||
|
int shader = GL.CreateShader(type);
|
||||||
|
|
||||||
|
GL.ShaderSource(shader, source);
|
||||||
|
GL.CompileShader(shader);
|
||||||
|
|
||||||
|
GL.GetShaderi(shader, ShaderParameterName.CompileStatus, out int flag);
|
||||||
|
|
||||||
|
if (flag != 0)
|
||||||
|
return shader;
|
||||||
|
|
||||||
|
GL.GetShaderInfoLog(shader, out log);
|
||||||
|
GL.DeleteShader(shader);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int LinkStages(ReadOnlySpan<int> stages, out string? log)
|
||||||
|
{
|
||||||
|
log = null;
|
||||||
|
int program = GL.CreateProgram();
|
||||||
|
|
||||||
|
foreach (int stage in stages)
|
||||||
|
{
|
||||||
|
GL.AttachShader(program, stage);
|
||||||
|
}
|
||||||
|
|
||||||
|
GL.LinkProgram(program);
|
||||||
|
GL.GetProgrami(program, ProgramProperty.LinkStatus, out int flag);
|
||||||
|
|
||||||
|
if (flag != 0)
|
||||||
|
return program;
|
||||||
|
|
||||||
|
GL.GetProgramInfoLog(program, out log);
|
||||||
|
GL.DeleteProgram(program);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int CreateProgram(this GlslShaderCreateInfo glsl)
|
||||||
|
{
|
||||||
|
Span<int> stages = stackalloc int[5];
|
||||||
|
int failed = 0;
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
Compile(ShaderType.VertexShader, glsl.VertexShader, "Vertex", stages);
|
||||||
|
Compile(ShaderType.FragmentShader, glsl.FragmentShader, "Fragment", stages);
|
||||||
|
Compile(ShaderType.GeometryShader, glsl.GeometryShader, "Geometry", stages);
|
||||||
|
Compile(ShaderType.TessControlShader, glsl.TesselationControl, "Tesselation Control", stages);
|
||||||
|
Compile(ShaderType.TessEvaluationShader, glsl.TesselationEvaluation, "Tesselation Evaluation", stages);
|
||||||
|
|
||||||
|
if (failed > 0)
|
||||||
|
{
|
||||||
|
throw new Exception($"{failed} shader stage(s) failed to compile. See the exception data for details.")
|
||||||
|
{
|
||||||
|
Data =
|
||||||
|
{
|
||||||
|
["Log"] = builder.ToString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
int program = KhronosShaderHelper.LinkStages(stages[..count], out string? log);
|
||||||
|
if (program == 0)
|
||||||
|
{
|
||||||
|
throw new Exception($"Shader program failed to link. See the exception data for details.")
|
||||||
|
{
|
||||||
|
Data =
|
||||||
|
{
|
||||||
|
["Log"] = log,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return program;
|
||||||
|
|
||||||
|
void Compile(ShaderType type, string? source, string name, Span<int> stages)
|
||||||
|
{
|
||||||
|
if (source == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int stage = CompileStage(type, source, out string? log);
|
||||||
|
if (stage == 0)
|
||||||
|
{
|
||||||
|
failed++;
|
||||||
|
builder.Append($"=== {name} Shader Compile Log Begin ===\n{log}\n=== {name} Shader Compile Log End ===\n\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stages[count++] = stage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int CreateProgram(this GlslComputeShaderCreateInfo glsl)
|
||||||
|
{
|
||||||
|
int compute = CompileStage(ShaderType.ComputeShader, glsl.Source, out string? log);
|
||||||
|
if (compute == 0)
|
||||||
|
{
|
||||||
|
throw new Exception("Failed to compile compute shader. See the exception data for details.")
|
||||||
|
{
|
||||||
|
Data =
|
||||||
|
{
|
||||||
|
["Log"] = log,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
int program = KhronosShaderHelper.LinkStages([compute], out log);
|
||||||
|
|
||||||
|
if (program != 0)
|
||||||
|
return program;
|
||||||
|
|
||||||
|
throw new Exception("Failed to link compute shader. See the exception data for details.")
|
||||||
|
{
|
||||||
|
Data =
|
||||||
|
{
|
||||||
|
["Log"] = log,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int CreateProgram(this SpirvShaderCreateInfo spirv)
|
||||||
|
{
|
||||||
|
int program = GL.CreateProgram();
|
||||||
|
GL.ProgramBinary(program, (All)spirv.Format, spirv.Binary, spirv.Binary.Length);
|
||||||
|
|
||||||
|
GL.GetProgrami(program, ProgramProperty.LinkStatus, out int flag);
|
||||||
|
if (flag == 0)
|
||||||
|
return program;
|
||||||
|
|
||||||
|
GL.GetProgramInfoLog(program, out string? log);
|
||||||
|
GL.DeleteProgram(program);
|
||||||
|
|
||||||
|
throw new Exception("Failed to load program binary. See the exception data for details.")
|
||||||
|
{
|
||||||
|
Data =
|
||||||
|
{
|
||||||
|
["Log"] = log,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#version 130
|
||||||
|
|
||||||
|
uniform sampler2D image;
|
||||||
|
|
||||||
|
in vec2 vTexCoords;
|
||||||
|
in vec4 vColor;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
fragColor = vColor * texture(image, vTexCoords);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#version 130
|
||||||
|
|
||||||
|
uniform mat4 transforms;
|
||||||
|
|
||||||
|
in vec3 aPos;
|
||||||
|
in vec2 aTexCoords;
|
||||||
|
in vec4 aColor;
|
||||||
|
|
||||||
|
out vec2 vTexCoords;
|
||||||
|
out vec4 vColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 position = transforms * vec4(aPos, 1.0);
|
||||||
|
gl_Position = position;
|
||||||
|
|
||||||
|
vTexCoords = aTexCoords;
|
||||||
|
vColor = aColor;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user