49 Commits

Author SHA1 Message Date
themixedupstuff e2e60bb480 Implement INotifyPropertyChanged for brushes. 2026-09-08 21:39:20 +03:00
themixedupstuff 2d312b2a9f Implement INotifyPropertyChanged and INotifyCollectionChanged in Gradient. 2026-09-08 21:33:42 +03:00
themixedupstuff 121115b108 Add Extents.None. 2026-09-08 21:33:03 +03:00
themixedupstuff 4f51b4c09c Move HashList to the right namespace (even if unused) 2026-09-08 21:32:40 +03:00
themixedupstuff 1100fac94b Implement Control ownership and nice to have interfaces. 2026-09-07 22:40:58 +03:00
themixedupstuff e46a13249b Hide members from editors. 2026-09-07 22:10:58 +03:00
themixedupstuff df8aeca1db Implement missing origin calculations. 2026-09-07 22:02:08 +03:00
themixedupstuff 293ef241f1 Begind creating new layout system. 2026-09-07 21:51:29 +03:00
themixedupstuff 4f7edd42d0 Create new Extents type and add debugger display attributes. 2026-09-07 21:50:43 +03:00
themixedupstuff 362165f911 Push a fix for the upstream issue in ReFuel. 2026-08-24 22:01:20 +03:00
themixedupstuff 008882cb81 Move framework projects to their own folders. 2026-08-24 21:49:12 +03:00
themixedupstuff a6dbba19b4 Remove archaic code from the working tree. 2026-08-24 21:46:18 +03:00
themixedupstuff 6665a023c8 Refactor ImmediateMode rendering logic to use DrawImmediate. 2026-08-23 22:04:46 +03:00
themixedupstuff 3b20c53088 Refactor rendering interfaces and implementations
- Refactored DirectRendering class to support new IDirectRendering interface and manage textures directly.
- Enhanced ImmediateMode to utilize ImmediateVertex struct for immediate rendering calls.
- Updated Image class to use IDirectRendering for texture creation.
- Modified test application to demonstrate immediate mode rendering with new structures.
- Removed obsolete code related to previous rendering abstractions.
2026-08-23 21:44:05 +03:00
themixedupstuff bda8e173c9 Use new syntax to remove compile time warning from Application.Current. 2026-08-23 20:56:19 +03:00
themixedupstuff 1b5e13eb10 Add new SVG resources for dashboard components. 2026-08-23 17:42:43 +03:00
themixedupstuff 97f8a29f19 Refactor DrawCall to use VertexSpecification and update related rendering logic 2026-08-20 20:54:01 +03:00
themixedupstuff abdb78eae7 Move to blurgtext stable. 2026-08-11 10:12:11 +03:00
themixedupstuff 99a853f35d Use th new API in the demo. 2026-08-11 10:10:01 +03:00
themixedupstuff 3d0f15af45 Don't set the depth state twice. 2026-08-11 10:07:36 +03:00
themixedupstuff 01d7f27dc7 Fix the use of glCreateXXX (4.2) instead of glGenXXX (1.0). 2026-08-11 10:07:11 +03:00
themixedupstuff 7bf7b6c53c Add a configure blurg method to the extension factory interface. 2026-08-11 10:06:35 +03:00
themixedupstuff fe96499512 Implement the first version of the direct rendering API. 2026-08-02 16:44:17 +03:00
themixedupstuff 18cccf1e41 WIP direct rendering backend. 2026-08-01 17:38:59 +03:00
themixedupstuff 019ac6f4ae Add Begin() and End() functions to each extension for pre/post frame tasks. 2026-08-01 17:38:27 +03:00
themixedupstuff 449c398f05 Move modifier and scan code tables to an ext class. 2026-08-01 15:52:26 +03:00
themixedupstuff 1457e32095 Add a few more scan codes. 2026-06-07 15:33:29 +03:00
themixedupstuff 9dc6175f8b Fix typo in SubscribeEvent method. 2026-06-04 21:56:38 +03:00
themixedupstuff faaadbf5b1 Merge branch 'dashboard2' of https://git.mixedup.dev/ReFuel/Dashboard into HEAD 2026-05-17 21:46:46 +03:00
themixedupstuff 764b2bff8b Update to the latest OpenTK and BlurgText. 2026-05-17 21:45:58 +03:00
themixedupstuff aaa79d1878 Add Image member to image brushes. 2026-03-21 20:05:06 +03:00
themixedupstuff f9e374db50 Fix double initialization bug. 2026-03-21 20:04:50 +03:00
themixedupstuff 5bcc2d8214 Write partial scan code translation table. 2026-02-07 15:40:54 +03:00
themixedupstuff 804a3979fd Write out Win32 based Scancode table. 2026-02-07 15:40:38 +03:00
themixedupstuff d299084db5 Fix deafult font structure causing issues when passed to InternFont(). 2026-02-07 15:40:24 +03:00
themixedupstuff d20d929515 Call base method in Form class. 2026-02-02 23:16:17 +03:00
themixedupstuff 3b9fa5c9fb Implement a very botched input scancode mapping. 2026-02-02 23:16:05 +03:00
themixedupstuff 027ebe8dbd Bubble some OpenGL elements up to the DC impl. 2026-02-02 22:02:32 +03:00
themixedupstuff 36dbcac8d9 Make Pal2Application handle Toolkit.Init() instead. 2026-02-01 00:01:08 +03:00
themixedupstuff 7c3141822a Add image brushes as a construct, as well as move some resource types to Common assembly. 2026-01-05 22:34:34 +03:00
themixedupstuff e30e50e860 Unfinished work I want to document in the Git history. 2026-01-05 22:26:34 +03:00
themixedupstuff 4f67e0fb75 Create basic controls without much regards to layout rules. 2025-11-21 23:04:37 +03:00
themixedupstuff 5cba1ab7db Give label a proper implementation. 2025-11-19 20:12:52 +03:00
themixedupstuff 9ca309bd52 Remove all old project files. 2025-11-17 22:00:58 +03:00
themixedupstuff 043060db66 Rework the library architecture. 2025-11-17 21:59:07 +03:00
themixedupstuff 66c5eecc26 Move StbImage to its own assembly. 2025-11-14 22:31:54 +03:00
themixedupstuff 9ee6c1180d Add new functions to extension system. 2025-11-14 22:28:21 +03:00
themixedupstuff c4fe4841fe Work on creating a simple message box dialog. 2025-11-13 21:35:01 +03:00
themixedupstuff 2932b3b85e Implement new immediate mode. 2025-11-12 21:24:25 +03:00
154 changed files with 8251 additions and 4741 deletions
+39 -14
View File
@@ -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)
{ {
+2 -1
View File
@@ -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
@@ -82,7 +82,7 @@ namespace Dashboard.Collections
public void Clear() => _objects.Clear(); public void Clear() => _objects.Clear();
public IEnumerator<T> GetEnumerator() => _objects.Values.GetEnumerator(); public IEnumerator<T> GetEnumerator() => _objects.Values.Distinct().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
} }
+1 -1
View File
@@ -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>
+111
View File
@@ -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));
}
}
}
+242
View File
@@ -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; }
}
}
+42
View File
@@ -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));
} }
} }
} }
+43
View File
@@ -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);
}
}
+27
View File
@@ -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);
}
}
+61
View File
@@ -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);
}
}
+21
View File
@@ -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; }
}
}
+12
View File
@@ -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);
}
}
@@ -1,12 +1,7 @@
using System.Drawing; using System.Drawing;
namespace Dashboard.Pal namespace Dashboard.Drawing
{ {
public interface ITextureExtension : IDeviceContextExtension
{
ITexture CreateTexture(TextureType type);
}
public enum TextureType public enum TextureType
{ {
Texture1D, Texture1D,
+72
View File
@@ -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));
}
}
}
+39
View File
@@ -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;
}
}
+17
View File
@@ -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;
}
}
@@ -1,20 +0,0 @@
namespace Dashboard.Events
{
public class AnimationTickEventArgs : UiEventArgs
{
/// <summary>
/// Animation delta time in seconds.
/// </summary>
public float Delta { get; }
public AnimationTickEventArgs(float delta) : base(UiEventType.AnimationTick)
{
Delta = delta;
}
}
public interface IAnimationTickEvent
{
event EventHandler<AnimationTickEventArgs> AnimationTimerEvent;
}
}
+224
View File
@@ -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;
}
}
+12 -38
View File
@@ -20,50 +20,24 @@ namespace Dashboard.Events
Middle = M3, Middle = M3,
} }
public sealed class MouseMoveEventArgs : UiEventArgs public sealed class MouseMoveEventArgs(Vector2 clientPosition, Vector2 delta) : UiEventArgs(UiEventType.MouseMove)
{ {
public Vector2 ClientPosition { get; } public Vector2 ClientPosition { get; } = clientPosition;
public Vector2 Delta { get; } public Vector2 Delta { get; } = delta;
public MouseMoveEventArgs(Vector2 clientPosition, Vector2 delta)
: base(UiEventType.MouseMove)
{
ClientPosition = clientPosition;
Delta = delta;
}
} }
public sealed class MouseButtonEventArgs : UiEventArgs public sealed class MouseButtonEventArgs(Vector2 clientPosition, MouseButtons buttons, ModifierKeys modifierKeys, bool up)
: UiEventArgs(up ? UiEventType.MouseButtonUp : UiEventType.MouseButtonDown)
{ {
public Vector2 ClientPosition { get; } public ModifierKeys ModifierKeys { get; } = modifierKeys;
public MouseButtons Buttons { get; } public Vector2 ClientPosition { get; } = clientPosition;
public MouseButtons Buttons { get; } = buttons;
public MouseButtonEventArgs(Vector2 clientPosition, MouseButtons buttons, bool up)
: base(up ? UiEventType.MouseButtonUp : UiEventType.MouseButtonDown)
{
ClientPosition = clientPosition;
Buttons = buttons;
}
} }
public sealed class MouseScrollEventArgs : UiEventArgs public sealed class MouseScrollEventArgs(Vector2 clientPosition, Vector2 scrollDelta)
: UiEventArgs(UiEventType.MouseScroll)
{ {
public Vector2 ClientPosition { get; } public Vector2 ClientPosition { get; } = clientPosition;
public Vector2 ScrollDelta { get; } public Vector2 ScrollDelta { get; } = scrollDelta;
public MouseScrollEventArgs(Vector2 clientPosition, Vector2 scrollDelta)
: base(UiEventType.MouseScroll)
{
ClientPosition = clientPosition;
ScrollDelta = scrollDelta;
}
}
public interface IMouseEvents
{
event EventHandler<MouseMoveEventArgs> MouseMoved;
event EventHandler<MouseButtonEventArgs> MouseButtonDown;
event EventHandler<MouseButtonEventArgs> MouseButtonUp;
event EventHandler<MouseScrollEventArgs> MouseScroll;
} }
} }
+15
View File
@@ -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;
}
}
}
+9 -3
View File
@@ -1,4 +1,5 @@
using System.Numerics; using System.Numerics;
using Dashboard.Pal;
namespace Dashboard.Events namespace Dashboard.Events
{ {
@@ -6,6 +7,7 @@ namespace Dashboard.Events
{ {
None, None,
AnimationTick, // Generic timer event. AnimationTick, // Generic timer event.
Paint, // Generic paint event.
// Text input related events. // Text input related events.
KeyDown, // Keyboard key down. KeyDown, // Keyboard key down.
@@ -41,7 +43,7 @@ namespace Dashboard.Events
ControlFocusGet, // The control acquired focus. ControlFocusGet, // The control acquired focus.
ControlFocusLost, // The control lost focus. ControlFocusLost, // The control lost focus.
WindowClose, // The window closed. WindowClose, // The window closed.
UserRangeStart = 1 << 12, UserRangeStart = 1 << 12,
} }
@@ -57,6 +59,11 @@ namespace Dashboard.Events
public static readonly UiEventArgs None = new UiEventArgs(UiEventType.None); 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 class ControlMovedEventArgs : UiEventArgs
{ {
public Vector2 OldPosition { get; } public Vector2 OldPosition { get; }
@@ -69,8 +76,7 @@ namespace Dashboard.Events
} }
} }
public class ControlResizedEventArgs public class ResizeEventArgs() : UiEventArgs(UiEventType.ControlResized)
{ {
} }
} }
+7
View File
@@ -0,0 +1,7 @@
namespace Dashboard.Events
{
public class WindowCloseEvent() : UiEventArgs(UiEventType.WindowClose)
{
public bool Cancel { get; set; } = false;
}
}
+31
View File
@@ -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);
}
}
+1
View File
@@ -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,
+24 -15
View File
@@ -1,4 +1,4 @@
using System; using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
namespace Dashboard namespace Dashboard
@@ -7,7 +7,7 @@ namespace Dashboard
/// Pixel format for images. /// Pixel format for images.
/// </summary> /// </summary>
[Flags] [Flags]
public enum PixelFormat public enum PixelFormat : byte
{ {
None = 0, None = 0,
@@ -20,58 +20,67 @@ namespace Dashboard
Rgb16F = Rgb | F16, Rgb16F = Rgb | F16,
Rgba16F = Rgba | F16, Rgba16F = Rgba | F16,
// Channels // Channels (hide these from editors)
[EditorBrowsable(EditorBrowsableState.Advanced)]
R = 0x01, R = 0x01,
[EditorBrowsable(EditorBrowsableState.Advanced)]
Rg = 0x02, Rg = 0x02,
[EditorBrowsable(EditorBrowsableState.Advanced)]
Rgb = 0x03, Rgb = 0x03,
[EditorBrowsable(EditorBrowsableState.Advanced)]
Rgba = 0x04, Rgba = 0x04,
[EditorBrowsable(EditorBrowsableState.Advanced)]
A = 0x05, A = 0x05,
[EditorBrowsable(EditorBrowsableState.Advanced)]
ColorMask = 0x0F, ColorMask = 0x0F,
[EditorBrowsable(EditorBrowsableState.Advanced)]
I8 = 0x10, I8 = 0x10,
[EditorBrowsable(EditorBrowsableState.Advanced)]
F16 = 0x20, F16 = 0x20,
[EditorBrowsable(EditorBrowsableState.Advanced)]
TypeMask = 0xF0, 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;
}
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace Dashboard.Layout
{
public enum ContainerMode
{
Basic,
Flex,
Grid,
}
}
+11
View File
@@ -0,0 +1,11 @@
namespace Dashboard.Layout
{
public enum DisplayMode
{
None,
Fit,
Grow,
Relative,
Fixed,
}
}
+10
View File
@@ -0,0 +1,10 @@
namespace Dashboard.Layout
{
public enum FlowDirection
{
Row,
Column,
RowReverse,
ColumnReverse,
}
}
+18
View File
@@ -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; }
}
}
+73
View File
@@ -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;
}
}
}
+11
View File
@@ -0,0 +1,11 @@
namespace Dashboard.Layout
{
public enum OverflowMode
{
Hidden,
Overflow,
ScrollHorizontal,
ScrollVertical,
ScrollBoth,
}
}
+132
View File
@@ -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;
}
}
}
}
+30
View File
@@ -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,
}
}
+90 -48
View File
@@ -1,48 +1,108 @@
using System.Numerics;
namespace Dashboard namespace Dashboard
{ {
public enum MeasurementUnit public record struct LayoutUnits(LayoutUnit All)
{ {
/// <summary> public LayoutUnit XUnit
/// The default unit. A size of a single picture element. {
/// </summary> get => (LayoutUnit)(((int)All & 0xF) >> 0);
Pixel, set => All = (LayoutUnit)(((int)All & ~(0xF << 0)) | ((int)value << 0));
/// <summary> }
/// 1/72th of an inch traditional in graphics design.
/// </summary> public LayoutUnit YUnit
Point, {
/// <summary> get => (LayoutUnit)(((int)All & 0xF) >> 4);
/// The universal length unit for small distances. set => All = (LayoutUnit)(((int)All & ~(0xF << 4)) | ((int)value << 4));
/// </summary> }
Millimeter,
/// <summary> public LayoutUnit ZUnit
/// An inverse proportional unit with respect to the container size. {
/// </summary> get => (LayoutUnit)(((int)All & 0xF) >> 8);
Star, set => All = (LayoutUnit)(((int)All & ~(0xF << 8)) | ((int)value << 8));
/// <summary> }
/// A directly proportional unit with respect to the container size.
/// </summary> public LayoutUnit WUnit
Percent, {
get => (LayoutUnit)(((int)All & 0xF) >> 12);
set => All = (LayoutUnit)(((int)All & ~(0xF << 12)) | ((int)value << 12));
}
} }
public record struct AdvancedMetric(MeasurementUnit Unit, float Value) public record struct Metric(LayoutUnit Units, float Value)
{ {
public AdvancedMetric Convert(MeasurementUnit target, float dpi, float rel, int stars) 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) if (Unit == target)
return this; return this;
float pixels = Unit switch { float pixels = Unit switch {
MeasurementUnit.Pixel => Value, LayoutUnit.Pixel => Value,
MeasurementUnit.Point => Value * (72f / dpi), LayoutUnit.Point => Value * (72f / dpi),
MeasurementUnit.Millimeter => Value * (28.3464566929f / dpi), LayoutUnit.Millimeter => Value * (28.3464566929f / dpi),
MeasurementUnit.Star => Value * rel / stars, LayoutUnit.Star => Value * rel / stars,
MeasurementUnit.Percent => Value * rel / 100, LayoutUnit.Percent => Value * rel / 100,
_ => throw new Exception(), _ => throw new Exception(),
}; };
float value = target switch { float value = target switch {
MeasurementUnit.Pixel => pixels, LayoutUnit.Pixel => pixels,
MeasurementUnit.Point => Value * (dpi / 72f), LayoutUnit.Point => Value * (dpi / 72f),
// MeasurementUnit.Millimeter => // MeasurementUnit.Millimeter =>
}; };
@@ -65,22 +125,4 @@ namespace Dashboard
? metric ? metric
: throw new Exception($"Could not parse the value '{str}'."); : throw new Exception($"Could not parse the value '{str}'.");
} }
public static class MeasurementExtensions
{
public static bool IsRelative(this MeasurementUnit unit) => unit switch {
MeasurementUnit.Star or MeasurementUnit.Percent => true,
_ => false,
};
public static bool IsAbsolute(this MeasurementUnit unit) => !IsRelative(unit);
public static string ToShortString(this MeasurementUnit unit) => unit switch {
MeasurementUnit.Pixel => "px",
MeasurementUnit.Point => "pt",
MeasurementUnit.Millimeter => "mm",
MeasurementUnit.Star => "*",
MeasurementUnit.Percent => "%",
_ => throw new Exception("Unknown unit."),
};
}
} }
+30
View File
@@ -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;
}
}
}
@@ -1,9 +1,10 @@
using Dashboard.Collections; using Dashboard.Collections;
using Dashboard.Windowing; using Dashboard.Windowing;
using BindingFlags = System.Reflection.BindingFlags;
namespace Dashboard.Pal namespace Dashboard.Pal
{ {
public abstract class AppContext : IContextBase<AppContext, IAppContextExtension> public abstract class Application : IContextBase<Application, IApplicationExtension>
{ {
public abstract string DriverName { get; } public abstract string DriverName { get; }
public abstract string DriverVendor { get; } public abstract string DriverVendor { get; }
@@ -15,12 +16,22 @@ namespace Dashboard.Pal
public bool IsDisposed { get; private set; } = false; public bool IsDisposed { get; private set; } = false;
public IContextDebugger? Debugger { get; set; } public IContextDebugger? Debugger { get; set; }
private readonly TypeDictionary<IAppContextExtension> _extensions = protected CancellationToken? CancellationToken { get; private set; }
new TypeDictionary<IAppContextExtension>(true); protected bool Quit { get; set; } = false;
private readonly TypeDictionary<IAppContextExtension, Func<IAppContextExtension>> _preloadedExtensions =
new TypeDictionary<IAppContextExtension, Func<IAppContextExtension>>(true);
~AppContext() 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); InvokeDispose(false);
} }
@@ -39,21 +50,29 @@ namespace Dashboard.Pal
{ {
} }
protected internal virtual void OnDeviceContextCreated(DeviceContext dc)
{
DeviceContextCreated?.Invoke(this, dc);
}
public virtual void RunEvents(bool wait) public virtual void RunEvents(bool wait)
{ {
if (!IsInitialized) if (!IsInitialized)
Initialize(); Initialize();
} }
public void Run() => Run(true, CancellationToken.None); public void Run() => Run(true, System.Threading.CancellationToken.None);
public void Run(bool wait) => Run(wait, CancellationToken.None); public void Run(bool wait) => Run(wait, System.Threading.CancellationToken.None);
public void Run(bool waitForEvents, CancellationToken token) public void Run(bool waitForEvents, CancellationToken token)
{ {
InitializeInternal(); CancellationToken = token;
CancellationToken.Value.Register(() => Quit = true);
while (!token.IsCancellationRequested) Initialize();
while (!Quit && !token.IsCancellationRequested)
{ {
RunEvents(waitForEvents); RunEvents(waitForEvents);
} }
@@ -82,19 +101,37 @@ namespace Dashboard.Pal
window.WindowManager = wm; window.WindowManager = wm;
return window; 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 #endregion
public bool IsExtensionAvailable<T>() where T : IAppContextExtension public bool IsExtensionAvailable<T>() where T : IApplicationExtension
{ {
return _extensions.Contains<T>() || _preloadedExtensions.Contains<T>(); return _extensions.Contains<T>() || _preloadedExtensions.Contains<T>();
} }
public bool ExtensionPreload<T>() where T : IAppContextExtension, new() 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()); return _preloadedExtensions.Add<T>(() => new T());
} }
public T ExtensionRequire<T>() where T : IAppContextExtension, new() public T ExtensionRequire<T>() where T : IApplicationExtension
{ {
T? extension = default; T? extension = default;
@@ -106,13 +143,13 @@ namespace Dashboard.Pal
if (_extensions.TryGet(out extension)) if (_extensions.TryGet(out extension))
return extension; return extension;
if (_preloadedExtensions.Remove<T>(out Func<IAppContextExtension>? loader)) if (_preloadedExtensions.Remove<T>(out Func<IApplicationExtension>? loader))
{ {
extension = (T)loader!(); extension = (T)loader!();
} }
else else
{ {
extension = new T(); extension = Activator.CreateInstance<T>();
} }
_extensions.Add(extension); _extensions.Add(extension);
@@ -122,11 +159,22 @@ namespace Dashboard.Pal
return extension; 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) protected virtual void Dispose(bool isDisposing)
{ {
if (!isDisposing) return; if (!isDisposing) return;
foreach (IAppContextExtension extension in _extensions) Quit = true;
foreach (IApplicationExtension extension in _extensions)
{ {
extension.Dispose(); extension.Dispose();
} }
@@ -143,5 +191,12 @@ namespace Dashboard.Pal
} }
public void Dispose() => InvokeDispose(true); public void Dispose() => InvokeDispose(true);
[field: ThreadStatic]
public static Application Current
{
get => field ?? throw new InvalidOperationException("There is currently no current application.");
set;
}
} }
} }
+71 -5
View File
@@ -1,17 +1,27 @@
using System.Numerics;
using Dashboard.Collections; using Dashboard.Collections;
using Dashboard.Windowing;
namespace Dashboard.Pal namespace Dashboard.Pal
{ {
public abstract class DeviceContext : IContextBase<DeviceContext, IDeviceContextExtension> public abstract class DeviceContext : IContextBase<DeviceContext, IDeviceContextExtension>, IDeviceContext
{ {
private readonly TypeDictionary<IDeviceContextExtension> _extensions = private readonly TypeDictionary<IDeviceContextExtension> _extensions =
new TypeDictionary<IDeviceContextExtension>(true); new TypeDictionary<IDeviceContextExtension>(true);
private readonly TypeDictionary<IDeviceContextExtension, Func<IDeviceContextExtension>> _preloadedExtensions = private readonly TypeDictionary<IDeviceContextExtension, Func<IDeviceContextExtension>> _preloadedExtensions =
new TypeDictionary<IDeviceContextExtension, Func<IDeviceContextExtension>>(true); 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 DriverName { get; }
public abstract string DriverVendor { get; } public abstract string DriverVendor { get; }
public abstract Version DriverVersion { 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; } public bool IsDisposed { get; private set; }
@@ -20,28 +30,52 @@ namespace Dashboard.Pal
/// </summary> /// </summary>
public IContextDebugger? Debugger { get; set; } public IContextDebugger? Debugger { get; set; }
protected DeviceContext(Application app, IWindow? window)
{
Application = app;
Window = window;
app.OnDeviceContextCreated(this);
}
~DeviceContext() ~DeviceContext()
{ {
Dispose(false); Dispose(false);
} }
public virtual void Begin() { } public virtual void Begin()
{
foreach (IDeviceContextExtension extension in _extensions)
{
extension.Begin();
}
}
// public abstract void Paint(object renderbuffer); // public abstract void Paint(object renderbuffer);
public virtual void End() { } public virtual void End()
{
foreach (IDeviceContextExtension extension in _extensions)
{
extension.End();
}
}
public bool IsExtensionAvailable<T>() where T : IDeviceContextExtension public bool IsExtensionAvailable<T>() where T : IDeviceContextExtension
{ {
return _extensions.Contains<T>() || _preloadedExtensions.Contains<T>(); 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() public bool ExtensionPreload<T>() where T : IDeviceContextExtension, new()
{ {
return _preloadedExtensions.Add<T>(() => new T()); return _preloadedExtensions.Add<T>(() => new T());
} }
public T ExtensionRequire<T>() where T : IDeviceContextExtension, new() public T ExtensionRequire<T>() where T : IDeviceContextExtension
{ {
T? extension = default; T? extension = default;
@@ -59,7 +93,7 @@ namespace Dashboard.Pal
} }
else else
{ {
extension = new T(); extension = Activator.CreateInstance<T>();
} }
_extensions.Add(extension); _extensions.Add(extension);
@@ -69,6 +103,38 @@ namespace Dashboard.Pal
return extension; 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> /// <summary>
/// Implement your dispose in this function. /// Implement your dispose in this function.
/// </summary> /// </summary>
@@ -1,7 +0,0 @@
namespace Dashboard.Pal
{
public interface IAppContextExtension : IContextExtensionBase<AppContext>
{
}
}
@@ -0,0 +1,7 @@
namespace Dashboard.Pal
{
public interface IApplicationExtension : IContextExtensionBase<Application>
{
}
}
+20 -1
View File
@@ -58,12 +58,31 @@ namespace Dashboard.Pal
/// </returns> /// </returns>
bool ExtensionPreload<T>() where T : TExtension, new(); 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> /// <summary>
/// Require an extension. /// Require an extension.
/// </summary> /// </summary>
/// <typeparam name="T">The extension to require.</typeparam> /// <typeparam name="T">The extension to require.</typeparam>
/// <returns>The extension instance.</returns> /// <returns>The extension instance.</returns>
T ExtensionRequire<T>() where T : TExtension, new(); 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> /// <summary>
@@ -2,5 +2,7 @@ namespace Dashboard.Pal
{ {
public interface IDeviceContextExtension : IContextExtensionBase<DeviceContext> public interface IDeviceContextExtension : IContextExtensionBase<DeviceContext>
{ {
void Begin() {}
void End() {}
} }
} }
+1 -1
View File
@@ -11,7 +11,7 @@ namespace Dashboard.Windowing
/// <summary> /// <summary>
/// Interface for classes that implement a window manager. /// Interface for classes that implement a window manager.
/// </summary> /// </summary>
public interface IWindowManager : IEnumerable<IVirtualWindow>, IEventListener, IPaintable, IDisposable public interface IWindowManager : IEnumerable<IVirtualWindow>, IEventListener
{ {
/// <summary> /// <summary>
/// The physical window that this window manager is associated with. /// The physical window that this window manager is associated with.
+3 -2
View File
@@ -1,4 +1,5 @@
using System.Drawing; using System.Drawing;
using System.Numerics;
namespace Dashboard.Windowing namespace Dashboard.Windowing
{ {
@@ -15,6 +16,6 @@ namespace Dashboard.Windowing
/// <summary> /// <summary>
/// The size of the window framebuffer in pixels. /// The size of the window framebuffer in pixels.
/// </summary> /// </summary>
Size FramebufferSize { get; } Vector2 FramebufferSize { get; }
} }
} }
+4 -1
View File
@@ -2,10 +2,13 @@ namespace Dashboard.Windowing
{ {
public interface IEventListener public interface IEventListener
{ {
event EventHandler? EventRaised;
/// <summary> /// <summary>
/// Send an event to this windowing object. /// Send an event to this windowing object.
/// </summary> /// </summary>
/// <param name="sender">The object which generated the event.</param>
/// <param name="args">The event arguments sent.</param> /// <param name="args">The event arguments sent.</param>
void SendEvent(EventArgs args); void SendEvent(object? sender, EventArgs args);
} }
} }
+8
View File
@@ -0,0 +1,8 @@
namespace Dashboard.Windowing
{
public interface IForm : IEventListener, IDisposable
{
public IWindow Window { get; }
public string Title { get; }
}
}
+26 -7
View File
@@ -1,17 +1,18 @@
using System.Drawing; using System.Drawing;
using Dashboard.Events; using Dashboard.Pal;
namespace Dashboard.Windowing namespace Dashboard.Windowing
{ {
/// <summary> /// <summary>
/// Base class of all Dashboard windows. /// Base class of all Dashboard windows.
/// </summary> /// </summary>
public interface IWindow : public interface IWindow : IDisposable
IPaintable,
IDisposable,
IAnimationTickEvent,
IMouseEvents
{ {
/// <summary>
/// The application for this window.
/// </summary>
Application Application { get; }
/// <summary> /// <summary>
/// Name of the window. /// Name of the window.
/// </summary> /// </summary>
@@ -26,6 +27,23 @@ namespace Dashboard.Windowing
/// The size of the window that excludes the window extents. /// The size of the window that excludes the window extents.
/// </summary> /// </summary>
SizeF ClientSize { get; set; } 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> /// <summary>
@@ -49,6 +67,7 @@ namespace Dashboard.Windowing
/// </summary> /// </summary>
public interface IVirtualWindow : IWindow, IEventListener public interface IVirtualWindow : IWindow, IEventListener
{ {
IWindowManager? WindowManager { get; set; }
} }
/// <summary> /// <summary>
@@ -59,7 +78,7 @@ namespace Dashboard.Windowing
/// <summary> /// <summary>
/// The device context for this window. /// The device context for this window.
/// </summary> /// </summary>
IDeviceContext DeviceContext { get; } DeviceContext DeviceContext { get; }
/// <summary> /// <summary>
/// True if the window is double buffered. /// True if the window is double buffered.
-48
View File
@@ -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;
}
}
-184
View File
@@ -1,184 +0,0 @@
using System.Drawing;
using Dashboard.Drawing.OpenGL.Executors;
using Dashboard.OpenGL;
using OpenTK.Mathematics;
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, float scale = 1.0f)
{
BeginDraw();
if (scale != 1.0f)
TransformStack.Push(Matrix4.CreateScale(scale, scale, 1));
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,133 +0,0 @@
using Dashboard.OpenGL;
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,27 +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.*,5.1)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Dashboard.Drawing\Dashboard.Drawing.csproj" />
<ProjectReference Include="..\Dashboard.OpenGL\Dashboard.OpenGL.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,468 +0,0 @@
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Dashboard.OpenGL;
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;
}
-40
View File
@@ -1,40 +0,0 @@
using Dashboard.Drawing.OpenGL.Text;
using Dashboard.OpenGL;
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,90 +0,0 @@
using System.Runtime.InteropServices;
using Dashboard.OpenGL;
using OpenTK.Mathematics;
namespace Dashboard.Drawing.OpenGL
{
public class GradientUniformBuffer : IInitializer, IGLDisposable, IResourceManager
{
private bool _isDisposed;
private int _top = 0;
private readonly MappableBuffer<GradientUniformStruct> _buffer = new MappableBuffer<GradientUniformStruct>();
private readonly Dictionary<Gradient, Entry> _entries = new Dictionary<Gradient, Entry>();
public bool IsInitialized { get; private set; } = false;
public void Initialize()
{
if (IsInitialized)
return;
IsInitialized = true;
_buffer.Initialize();
}
public Entry InternGradient(Gradient gradient)
{
if (_entries.TryGetValue(gradient, out Entry entry))
return entry;
int count = gradient.Count;
int offset = _top;
_top += count;
_buffer.EnsureCapacity(_top);
_buffer.Map();
Span<GradientUniformStruct> span = _buffer.AsSpan()[offset.._top];
for (int i = 0; i < count; i++)
{
GradientStop stop = gradient[i];
span[i] = new GradientUniformStruct()
{
Position = stop.Position,
Color = new Vector4(
stop.Color.R / 255f,
stop.Color.G / 255f,
stop.Color.B / 255f,
stop.Color.A / 255f),
};
}
entry = new Entry(offset, count);
_entries.Add(gradient, entry);
return entry;
}
public void Clear()
{
_entries.Clear();
_top = 0;
}
public record struct Entry(int Offset, int Count);
public void Dispose() => Dispose(true);
public void Dispose(bool safeExit)
{
if (_isDisposed)
return;
_isDisposed = true;
_buffer.Dispose(safeExit);
}
string IResourceManager.Name { get; } = nameof(GradientUniformBuffer);
}
[StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(float))]
public struct GradientUniformStruct
{
[FieldOffset(0 * sizeof(float))]
public float Position;
[FieldOffset(4 * sizeof(float))]
public Vector4 Color;
}
}
-24
View File
@@ -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();
}
}
-9
View File
@@ -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; }
}
}
-168
View File
@@ -1,168 +0,0 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using Dashboard.OpenGL;
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,193 +0,0 @@
using System.Diagnostics;
using System.Drawing;
using System.Numerics;
using BlurgText;
using Dashboard.OpenGL;
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.OpenOrCreate, 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);
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) { Path = path };
}
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)
{
if (dblurg.Path == null)
return (DbBlurgFont)LoadFont(new NamedFont(dblurg.Family, dblurg.Size, dblurg.Weight,
dblurg.Slant,
dblurg.Stretch));
else
return (DbBlurgFont)LoadFont(dblurg.Path);
}
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,30 +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;
internal string? Path { get; init; }
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;
}
}
}
-52
View File
@@ -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>
-292
View File
@@ -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;
}
}
}
-107
View File
@@ -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);
}
}
}
-141
View File
@@ -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,
});
}
}
}
-368
View File
@@ -1,368 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
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 |= (b & 0x7F) << (7*i);
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>;
}
}
-52
View File
@@ -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])
{
}
}
}
}
-9
View File
@@ -1,9 +0,0 @@
using Dashboard.Windowing;
namespace Dashboard.Drawing
{
public interface IDrawQueuePaintable : IPaintable
{
DrawQueue DrawQueue { get; }
}
}
-13
View File
@@ -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; }
}
}
-14
View File
@@ -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);
}
}
-140
View File
@@ -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;
}
}
-104
View File
@@ -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>
-234
View File
@@ -1,234 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
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
{ {
+10 -1
View File
@@ -1,13 +1,22 @@
<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>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="OpenTK.Graphics" Version="[5.0.0-pre.*,5.1)" />
<ProjectReference Include="..\Dashboard.Common\Dashboard.Common.csproj" /> <ProjectReference Include="..\Dashboard.Common\Dashboard.Common.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Remove="Drawing\immediate.frag" />
<EmbeddedResource Include="Drawing\immediate.frag" />
<None Remove="Drawing\immediate.vert" />
<EmbeddedResource Include="Drawing\immediate.vert" />
</ItemGroup>
</Project> </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;
}
}
}
+113
View File
@@ -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);
}
}
}
+137
View File
@@ -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);
}
}
}
}
@@ -1,61 +1,12 @@
using System.Drawing; using System.Drawing;
using Dashboard.Pal; using Dashboard.Drawing;
using Dashboard.OpenGL.Drawing;
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
using OGL = OpenTK.Graphics.OpenGL; using OGL = OpenTK.Graphics.OpenGL;
namespace Dashboard.Drawing.OpenGL.Pal namespace Dashboard.OpenGL
{ {
public class GLTextureExtension : ITextureExtension, IContextExtensionBase<GLDeviceContext> public class GLTexture(DirectRendering extension, TextureType type) : ITexture
{
public string DriverName => "Dashboard OpenGL Texture Extension";
public string DriverVendor => "Dashboard";
public Version DriverVersion => new Version(0, 1, 0);
public GLDeviceContext Context { get; private set; } = null!;
public bool SupportsArbTextureStorage { get; private set; }
public bool SupportsAnisotropy { get; private set; }
IContextBase IContextExtensionBase.Context => Context;
DeviceContext IContextExtensionBase<DeviceContext>.Context => Context;
private List<GLTexture> _textures = new List<GLTexture>();
public void Dispose()
{
throw new NotImplementedException();
}
public void Require(GLDeviceContext context)
{
Context = 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(DeviceContext context) => Require((GLDeviceContext)context);
public void Require(IContextBase context) => Require((GLDeviceContext)context);
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);
}
public class GLTexture(GLTextureExtension extension, TextureType type) : ITexture
{ {
public int Handle { get; private set; } = 0; public int Handle { get; private set; } = 0;
public bool IsValid => Handle != 0; public bool IsValid => Handle != 0;
@@ -79,17 +30,22 @@ namespace Dashboard.Drawing.OpenGL.Pal
private TextureTarget Target { get; } = type switch private TextureTarget Target { get; } = type switch
{ {
TextureType.Texture1D => TextureTarget.Texture1d, TextureType.Texture1D => TextureTarget.Texture1D,
TextureType.Texture2D => TextureTarget.Texture2d, TextureType.Texture2D => TextureTarget.Texture2D,
TextureType.Texture3D => TextureTarget.Texture3d, TextureType.Texture3D => TextureTarget.Texture3D,
TextureType.Texture2DArray => TextureTarget.Texture2dArray, TextureType.Texture2DArray => TextureTarget.Texture2DArray,
TextureType.Texture2DCube => TextureTarget.TextureCubeMap, TextureType.Texture2DCube => TextureTarget.TextureCubeMap,
_ => throw new NotSupportedException() _ => throw new NotSupportedException()
}; };
private GLTextureExtension Extension { get; } = extension; private DirectRendering Extension { get; } = extension;
private GLDeviceContext Context => Extension.Context; private GLDeviceContext Context => Extension.Context;
~GLTexture()
{
Dispose(false);
}
public void SetStorage(PixelFormat format, int width, int height, int depth, int levels) public void SetStorage(PixelFormat format, int width, int height, int depth, int levels)
{ {
if (!Context.IsRenderThread) if (!Context.IsRenderThread)
@@ -98,6 +54,11 @@ namespace Dashboard.Drawing.OpenGL.Pal
return; return;
} }
if (levels == 0)
{
levels = Math.Max(Math.ILogB(width), Math.ILogB(height));
}
Bind(); Bind();
SizedInternalFormat glFormat = GetFormat(format); SizedInternalFormat glFormat = GetFormat(format);
if (Extension.SupportsArbTextureStorage) if (Extension.SupportsArbTextureStorage)
@@ -122,18 +83,23 @@ namespace Dashboard.Drawing.OpenGL.Pal
switch (Type) switch (Type)
{ {
case TextureType.Texture1D: case TextureType.Texture1D:
GL.TexImage1D(Target, 0, (InternalFormat)glFormat, width, 0, (OGL.PixelFormat)glFormat, PixelType.Byte, IntPtr.Zero); GL.TexImage1D(Target, 0, (InternalFormat)glFormat, width, 0, (OGL.PixelFormat)glFormat, PixelType.UnsignedByte, IntPtr.Zero);
break; break;
case TextureType.Texture2D: case TextureType.Texture2D:
GL.TexImage2D(Target, 0, (InternalFormat)glFormat, width, height, 0, (OGL.PixelFormat)glFormat, PixelType.Byte, IntPtr.Zero); GL.TexImage2D(Target, 0, (InternalFormat)glFormat, width, height, 0, (OGL.PixelFormat)glFormat, PixelType.UnsignedByte, IntPtr.Zero);
break; break;
case TextureType.Texture3D: case TextureType.Texture3D:
case TextureType.Texture2DArray: case TextureType.Texture2DArray:
case TextureType.Texture2DCube: case TextureType.Texture2DCube:
GL.TexImage3D(Target, 0, (InternalFormat)glFormat, width, height, depth, 0, (OGL.PixelFormat)glFormat, PixelType.Byte, IntPtr.Zero); GL.TexImage3D(Target, 0, (InternalFormat)glFormat, width, height, depth, 0, (OGL.PixelFormat)glFormat, PixelType.UnsignedByte, IntPtr.Zero);
break; 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 public void Read<T>(Span<T> buffer, int level = 0, int align = 0) where T : unmanaged
@@ -141,11 +107,19 @@ namespace Dashboard.Drawing.OpenGL.Pal
throw new NotImplementedException(); throw new NotImplementedException();
} }
public unsafe void Write<T>(PixelFormat format, ReadOnlySpan<T> buffer, int level = 0, int align = 4) where T : unmanaged 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) if (!Context.IsRenderThread)
{ {
throw new NotImplementedException(); 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(); Bind();
@@ -160,7 +134,7 @@ namespace Dashboard.Drawing.OpenGL.Pal
PixelType glType = format switch PixelType glType = format switch
{ {
PixelFormat.R8I or PixelFormat.Rg8I or PixelFormat.Rgb8I or PixelFormat.Rgba8I => PixelType.Byte, 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, PixelFormat.R16F or PixelFormat.Rg16F or PixelFormat.Rgb16F or PixelFormat.Rgba16F => PixelType.HalfFloat,
_ => throw new NotSupportedException() _ => throw new NotSupportedException()
}; };
@@ -207,11 +181,37 @@ namespace Dashboard.Drawing.OpenGL.Pal
GL.GenerateMipmap(Target); GL.GenerateMipmap(Target);
} }
public void Dispose() private void Dispose(bool disposing)
{ {
throw new NotImplementedException(); 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() private void Bind()
{ {
if (Handle == 0) if (Handle == 0)
+212
View File
@@ -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,
},
};
}
}
}
+12
View File
@@ -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);
}
+18
View File
@@ -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;
}
+98
View File
@@ -0,0 +1,98 @@
using System.Runtime.CompilerServices;
using Dashboard.Drawing;
using OpenTK.Graphics.OpenGL;
namespace Dashboard.OpenGL
{
public class GLBuffer(GLDeviceContext dc, BufferAccessPattern pattern, int handle, long size, long offset) : IBuffer
{
// TODO: At the moment, this class does no pooling. Convert it to a pool for better API usage.
public BufferAccessPattern AccessPattern { get; } = pattern;
public long Size { get; private set; } = size;
public long Offset { get; private set; } = offset;
public int Handle { get; private set; } = handle;
public void Reallocate(long newSize)
{
GL.GenBuffer(out int handle);
GL.BindBuffer(BufferTarget.ArrayBuffer, handle);
GL.BufferData(BufferTarget.ArrayBuffer, (nint)newSize, (nint)0, AccessPattern switch
{
BufferAccessPattern.Stream => BufferUsage.StreamDraw,
BufferAccessPattern.Static => BufferUsage.StaticDraw,
BufferAccessPattern.Download => BufferUsage.DynamicCopy,
BufferAccessPattern.Upload => BufferUsage.DynamicCopy,
_ => BufferUsage.DynamicDraw,
});
if (Handle != -1)
GL.DeleteBuffer(Handle);
Handle = handle;
Size = newSize;
Offset = 0;
}
public unsafe Span<T> Map<T>(long offset, int count = -1)
where T : unmanaged
{
ArgumentOutOfRangeException.ThrowIfNegative(offset);
if (count < 0)
count = (int)(Size / Unsafe.SizeOf<T>());
long absOffset = Offset + offset;
long absCount = count * Unsafe.SizeOf<T>();
ArgumentOutOfRangeException.ThrowIfGreaterThan(offset + absCount, Size);
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
nint ptr = (nint)GL.MapBuffer(BufferTarget.ArrayBuffer, BufferAccess.ReadWrite);
return new Span<T>((T*)(ptr + absOffset), count);
}
public void Unmap()
{
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
GL.UnmapBuffer(BufferTarget.ArrayBuffer);
}
public void Read<T>(long offset, Span<T> span) where T : unmanaged
{
long absCount = span.Length * Unsafe.SizeOf<T>();
ArgumentOutOfRangeException.ThrowIfGreaterThan(offset + absCount, Size);
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
GL.GetBufferSubData(BufferTarget.ArrayBuffer, (nint)(Offset + offset), (nint)absCount, span);
}
public void Write<T>(long offset, ReadOnlySpan<T> span) where T : unmanaged
{
long absCount = span.Length * Unsafe.SizeOf<T>();
ArgumentOutOfRangeException.ThrowIfGreaterThan(offset + absCount, Size);
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
GL.BufferSubData(BufferTarget.ArrayBuffer, (nint)(Offset + offset), (nint)absCount, span);
}
public void Dispose()
{
if (Handle == -1)
return;
dc.Collector.DeleteBufffer(Handle);
Handle = -1;
GC.SuppressFinalize(this);
}
public static GLBuffer Create(GLDeviceContext dc, BufferAccessPattern pattern, long size)
{
GLBuffer buffer = new GLBuffer(dc, pattern, -1, 0, 0);
buffer.Reallocate(size);
return buffer;
}
}
}
@@ -1,12 +1,15 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Immutable; using System.Collections.Immutable;
using Dashboard.OpenGL; using System.Numerics;
using Dashboard.Drawing;
using Dashboard.OpenGL.Drawing;
using Dashboard.Pal; using Dashboard.Pal;
using Dashboard.Windowing;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
namespace Dashboard.Drawing.OpenGL.Pal namespace Dashboard.OpenGL
{ {
internal class GLContextBindingsContext(IGLContext context) : IBindingsContext internal class GLContextBindingsContext(IGLContext context) : IBindingsContext
{ {
@@ -20,9 +23,14 @@ namespace Dashboard.Drawing.OpenGL.Pal
{ {
public IGLContext GLContext { get; } public IGLContext GLContext { get; }
public ContextCollector Collector { get; } = new ContextCollector();
public override string DriverName => "Dashboard OpenGL Device Context"; public override string DriverName => "Dashboard OpenGL Device Context";
public override string DriverVendor => "Dashboard"; public override string DriverVendor => "Dashboard";
public override Version DriverVersion => new Version(0, 1, 0); public override Version DriverVersion => new Version(0, 1, 0);
public override ISwapGroup SwapGroup { get; }
public override Vector2 FramebufferSize => GLContext.FramebufferSize;
public override bool DoubleBuffered { get; } = true;
public Version GLVersion { get; } public Version GLVersion { get; }
public string GLRenderer { get; } public string GLRenderer { get; }
@@ -35,9 +43,11 @@ namespace Dashboard.Drawing.OpenGL.Pal
private readonly ConcurrentQueue<Task> _beforeDrawActions = new ConcurrentQueue<Task>(); private readonly ConcurrentQueue<Task> _beforeDrawActions = new ConcurrentQueue<Task>();
private readonly ConcurrentQueue<Task> _afterDrawActions = new ConcurrentQueue<Task>(); private readonly ConcurrentQueue<Task> _afterDrawActions = new ConcurrentQueue<Task>();
public GLDeviceContext(IGLContext context) public GLDeviceContext(Application app, IWindow? window, IGLContext context, ISwapGroup swap) : base(app, window)
{ {
GLContext = context; GLContext = context;
SwapGroup = swap;
context.MakeCurrent(); context.MakeCurrent();
GLLoader.LoadBindings(new GLContextBindingsContext(context)); GLLoader.LoadBindings(new GLContextBindingsContext(context));
@@ -61,7 +71,9 @@ namespace Dashboard.Drawing.OpenGL.Pal
Extensions = extensions.ToImmutableHashSet(); Extensions = extensions.ToImmutableHashSet();
ExtensionPreload<GLTextureExtension>(); ExtensionPreload<DeviceContextBase>();
ExtensionPreload<ImmediateMode>();
ExtensionPreload<DirectRendering>();
} }
public bool IsGLExtensionAvailable(string name) public bool IsGLExtensionAvailable(string name)
@@ -130,6 +142,9 @@ namespace Dashboard.Drawing.OpenGL.Pal
base.Begin(); base.Begin();
GLContext.MakeCurrent(); GLContext.MakeCurrent();
IDeviceContextBase dc = ExtensionRequire<IDeviceContextBase>();
dc.ResetClip();
dc.ResetTransforms();
while (_beforeDrawActions.TryDequeue(out Task? action)) while (_beforeDrawActions.TryDequeue(out Task? action))
{ {
@@ -139,12 +154,12 @@ namespace Dashboard.Drawing.OpenGL.Pal
public override void End() public override void End()
{ {
base.End();
while (_afterDrawActions.TryDequeue(out Task? action)) while (_afterDrawActions.TryDequeue(out Task? action))
{ {
action.RunSynchronously(); action.RunSynchronously();
} }
base.End();
} }
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
+1 -6
View File
@@ -1,4 +1,4 @@
using System.Drawing; using System.Numerics;
using Dashboard.Windowing; using Dashboard.Windowing;
namespace Dashboard.OpenGL namespace Dashboard.OpenGL
@@ -14,11 +14,6 @@ namespace Dashboard.OpenGL
/// <remarks>-1 assigns no group.</remarks> /// <remarks>-1 assigns no group.</remarks>
public int ContextGroup { get; } public int ContextGroup { get; }
/// <summary>
/// The size of the framebuffer in pixels.
/// </summary>
public Size FramebufferSize { get; }
/// <summary> /// <summary>
/// Called when the context is disposed. /// Called when the context is disposed.
/// </summary> /// </summary>

Some files were not shown because too many files have changed in this diff Show More