65 Commits

Author SHA1 Message Date
3f1875252e Merge branch 'master' into blurg-test 2024-07-28 11:36:20 +03:00
65609bf685 Fix buffer size in QImageBuffer.cs 2024-07-28 11:33:12 +03:00
a95ddb46ad Add some blurg test code. 2024-07-28 11:16:07 +03:00
dadd9f137a Add utility for custom commands into command lists. 2024-07-26 23:48:48 +03:00
23fc485b2a Create Dashboard.BlurgText 2024-07-26 22:20:44 +03:00
c6c76aa8be Update dependencies. 2024-07-18 21:54:44 +03:00
6670667ff6 Fix some broken dependencies. 2024-07-18 20:34:39 +03:00
d06264e424 Rename some classes to fit the new names. 2024-07-18 20:28:02 +03:00
a1f4e6a4dc Rename from QUIK to Dashboard
Due to unforseen naming conflicts, the project has been rebranded under the ReFuel
umbrealla and will now be referred to as Dashboard from now on. Other changes will
occur to suit the library more for the engine whilst keeping the freestanding
nature of the library.

Rename folder.

Rename to Dashboard.OpenTK

Rename to Dashboard.Media.Defaults.

Do the last renames and path fixes.
2024-07-17 23:26:58 +03:00
470475092a Rename namespace to Dashboard. 2024-07-17 23:18:20 +03:00
1ee492ccd4 Push WIP changes before rename. 2024-07-17 23:12:07 +03:00
3b52649ad2 Change over to the new nuget package and change namespaces. 2024-06-24 22:28:41 +03:00
f8a4c73367 Fix load order in FallbackFontDatabase. 2024-06-21 11:59:04 +03:00
88e060a92b Update Stbi to new rebranded package. 2024-06-21 09:52:29 +03:00
1ccab1c85a Converted project to ^nullable enable. 2024-06-09 22:54:33 +03:00
55e9d775cd Some changes to the UI elements. 2024-06-09 22:06:13 +03:00
8fd00257d3 Fix GL21 driver bug. 2024-06-09 19:55:13 +03:00
82d2027cf3 Add measure string function to Typesetter. 2024-06-09 19:20:35 +03:00
9b16e015f6 Fix depth test issue in demo. 2024-06-06 22:33:55 +03:00
ff4a158cdb Update stbi. 2024-06-01 14:33:20 +03:00
29829fd299 Made font rendering easier. 2024-05-27 21:49:25 +03:00
19cc765955 Implement Label control. 2024-05-16 22:58:22 +03:00
ce2a569a20 Add texture swizzle parameters. 2024-05-16 22:57:55 +03:00
ab1849a891 Add missing commands. 2024-05-16 22:57:38 +03:00
3ae107c83e Fix translation matrix to be column major and implement multiplication. 2024-05-16 22:57:14 +03:00
bd69c0d93f Preliminary typesetting work. 2024-05-15 23:17:01 +03:00
279e619c3b Remove extra accidental copy of one file. 2024-05-15 23:16:37 +03:00
7cb47c721b Fix 90 degree rotation error in Image2D 2024-05-05 16:45:43 +03:00
9105b16df8 Add singleton support for QuikApplication. 2024-05-05 16:45:25 +03:00
3b73090f79 Fix error in the mouse button enum. 2024-05-05 16:45:12 +03:00
3484dce8c5 Create a font atlaser. 2024-05-01 16:53:30 +03:00
3418537b43 Add image copy functions to QImage. 2024-05-01 16:52:20 +03:00
d831c9b72d Added better font search stuff. 2024-05-01 15:34:21 +03:00
66644be699 Remove popz command in UIBase.cs 2024-05-01 14:15:47 +03:00
39dfca18f2 Remove the readme under lib/ 2024-05-01 14:13:16 +03:00
75a11adbe7 Remove old submodules from the repository. 2024-05-01 14:12:12 +03:00
21233c8173 Add fontconfig support. 2024-04-28 15:39:12 +03:00
ccb0c6ffe7 Add new font search criterea. 2024-04-28 13:45:46 +03:00
bc3dcff3ea Remove the old Quik texture API. 2024-04-28 13:44:40 +03:00
2aa1066a9d Fix one of the image command lists. 2024-04-14 23:21:31 +03:00
fe3c2d0550 Image support for vertex generator. 2024-04-14 23:21:08 +03:00
5aa9a2f4e6 Support for 3d images in the renderer. 2024-04-14 22:51:10 +03:00
4d5e0dd8f2 Add unit vectors to QVec2. 2024-04-14 22:35:54 +03:00
f07208ebe9 Replace textures with QImage. 2024-04-14 22:35:30 +03:00
b57d7bed41 Texture 3d entry points and enums. 2024-04-14 22:34:51 +03:00
a1573d3786 Merge branch 'master' of https://git.mixedup.dev/QUIK/Quik 2024-04-11 19:35:30 +03:00
20c126fb88 New Image3D command. 2024-04-11 19:33:42 +03:00
7ce474d92a Changes to platform abstraction layer. 2024-04-11 19:09:00 +03:00
2eb5663ee9 Renamed the classes in the command engine. 2024-04-11 19:06:58 +03:00
09ce8d3229 Add invalid cast exceptions to frame conversion operators. 2024-04-09 10:50:52 +03:00
themixedupstuff
98aee237bd Update README.md 2024-04-08 22:55:37 +02:00
themixedupstuff
d8beb2a274 Update README.md 2024-04-08 22:54:26 +02:00
themixedupstuff
a8d805b461 Update README.md 2024-04-08 22:54:02 +02:00
4dff6eba91 Math library changes and fixes. 2024-04-08 23:51:03 +03:00
9c9efc6eeb Convert command queue to a command list. 2024-04-08 23:50:05 +03:00
51e9018a22 These dependencies have been moved into their own repositories. 2024-04-06 23:36:32 +03:00
032a38e13b Small fixes on OpenTK support. 2024-03-04 21:41:16 +03:00
cea243a3b8 Finish backporting Freetype from in development quik. 2023-11-10 20:57:33 +03:00
6240f5921b Add new assets for the quik logo. 2023-10-17 22:26:14 +03:00
959788563f Fix small errors in driver. 2023-09-22 21:23:59 +03:00
118b50cee2 Add font rendering. 2023-09-22 19:30:17 +03:00
1f6a3a55e1 Ressurrect old freetype library. 2023-09-16 09:59:51 +03:00
ac0a70cefc Remove auto-generated freetype bindings. 2023-09-16 09:50:14 +03:00
845ed1c27a Rename Quik.Media.Stb to *.Defaults 2023-09-16 09:24:57 +03:00
4a35f18737 Add partial FreeType bindings. 2023-09-15 20:40:30 +03:00
158 changed files with 5261 additions and 3932 deletions

1
.gitignore vendored
View File

@@ -4,6 +4,7 @@ obj/
riderModule.iml
/_ReSharper.Caches/
.idea
.vs
.vscode
nuget_repo
**/out

6
.gitmodules vendored
View File

@@ -1,6 +0,0 @@
[submodule "lib/stb"]
path = lib/stb
url = https://github.com/nothings/stb.git
[submodule "lib/freetype"]
path = lib/freetype
url = https://gitlab.freedesktop.org/freetype/freetype.git

View File

@@ -0,0 +1,26 @@
using BlurgText;
using Dashboard.CommandMachine;
namespace Dashboard.BlurgText
{
public static class BlurgCommand
{
public static void PutBlurgText(this CommandList list, DashboardBlurg blurg, BlurgResult result, QVec2 origin)
{
for (int i = 0; i < result.Count; i++)
{
BlurgRect rect = result[i];
QRectangle pos = new QRectangle()
{
Min = origin + new QVec2(rect.X, rect.Y),
Size = new QVec2(rect.Width, rect.Height)
};
QRectangle uv = new QRectangle(rect.U1, rect.V1, rect.U0, rect.V0);
list.Image(blurg.Images[(int)rect.UserData], pos, uv);
}
}
}
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Dashboard\Dashboard.csproj" />
<PackageReference Include="BlurgText" Version="0.1.0-nightly-4" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,63 @@
using BlurgText;
using Dashboard.Media;
using Dashboard.Media.Color;
namespace Dashboard.BlurgText
{
public class DashboardBlurg : IDisposable
{
private readonly List<QImageBuffer> images = new List<QImageBuffer>();
public Blurg Blurg { get; }
public IReadOnlyList<QImageBuffer> Images => images.AsReadOnly();
public DashboardBlurg()
{
Blurg = new Blurg(TextureAllocationCallback, TextureUpdateCallback);
}
private nint TextureAllocationCallback(int width, int height)
{
QImageBuffer image = new QImageBuffer(QImageFormat.RgbaU8, width, height);
images.Add(image);
return images.Count - 1;
}
private void TextureUpdateCallback(nint userData, nint buffer, int x, int y, int width, int height)
{
if (width == 0 || height == 0)
return;
QImageLock src = new QImageLock(QImageFormat.RgbaU8, width, height, 1, buffer);
QImageBuffer image = images[(int)userData];
image.LockBits2d(out QImageLock dest, QImageLockOptions.Default);
src.CopyTo(dest, x, y);
image.UnlockBits();
}
private bool _isDisposed = false;
private void Dispose(bool disposing)
{
if (_isDisposed) return;
if (disposing)
{
Blurg.Dispose();
foreach (QImageBuffer image in images)
{
image.Dispose();
}
images.Clear();
}
_isDisposed = true;
}
public void Dispose() => Dispose(true);
}
}

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LanguageVersion>7.3</LanguageVersion>
<Nullable>disable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ReFuel.FreeType" Version="0.1.0-rc.5" />
<PackageReference Include="ReFuel.StbImage" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Dashboard\Dashboard.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,13 @@
namespace Dashboard.Media.Defaults
{
internal static class EnvironmentVariables
{
public const string SerifFont = "QUIK_SERIF_FONT";
public const string SansFont = "QUIK_SANS_FONT";
public const string MonospaceFont = "QUIK_MONOSPACE_FONT";
public const string CursiveFont = "QUIK_CURSIVE_FONT";
public const string FantasyFont = "QUIK_FANTASY_FONT";
public const string FallbackFontDatabase = "QUIK_FALLBACK_FONT_DB";
}
}

View File

@@ -0,0 +1,16 @@
using System;
using ReFuel.FreeType;
namespace Dashboard.Media.Defaults
{
public static class FTProvider
{
private static FTLibrary _ft;
public static FTLibrary Ft => _ft;
static FTProvider()
{
FT.InitFreeType(out _ft);
}
}
}

View File

@@ -0,0 +1,269 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json.Serialization;
using System.Text.Json;
using ReFuel.FreeType;
using Dashboard.Media.Font;
using Dashboard.PAL;
using Dashboard.Media.Defaults.Linux;
namespace Dashboard.Media.Defaults.Fallback
{
public class FallbackFontDatabase : IFontDataBase
{
private readonly string DbPath =
Environment.GetEnvironmentVariable(EnvironmentVariables.FallbackFontDatabase) ??
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "QUIK/fontdb.json");
private Dictionary<FontFace, FileInfo> FilesMap { get; } = new Dictionary<FontFace, FileInfo>();
private Dictionary<string, List<FontFace>> ByFamily { get; } = new Dictionary<string, List<FontFace>>();
private Dictionary<SystemFontFamily, FontFace> SystemFonts { get; } = new Dictionary<SystemFontFamily, FontFace>();
private List<FontFace> All { get; } = new List<FontFace>();
IEnumerable<FontFace> IFontDataBase.All => this.All;
public FallbackFontDatabase(bool rebuild = false)
{
// Load existing database if desired.
List<DbEntry> database;
if(!rebuild)
{
database = LoadDatabase();
}
else
{
database = new List<DbEntry>();
}
VerifyDatabase(database);
FlushDatabase(database);
database.ForEach(x => AddFont(x.Face, new FileInfo(x.FilePath)));
(FontFace, FileInfo) serif = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.SerifFont, LinuxFonts.DefaultSerifFamilies, this);
SystemFonts[SystemFontFamily.Serif] = serif.Item1;
(FontFace, FileInfo) sans = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.SansFont, LinuxFonts.DefaultSansFamilies, this);
SystemFonts[SystemFontFamily.Sans] = sans.Item1;
(FontFace, FileInfo) mono = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.MonospaceFont, LinuxFonts.DefaultMonospaceFamilies, this);
SystemFonts[SystemFontFamily.Monospace] = mono.Item1;
(FontFace, FileInfo) cursive = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.CursiveFont, LinuxFonts.DefaultCursiveFamilies, this);
SystemFonts[SystemFontFamily.Cursive] = cursive.Item1;
(FontFace, FileInfo) fantasy = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.FantasyFont, LinuxFonts.DefaultFantasyFamilies, this);
SystemFonts[SystemFontFamily.Fantasy] = fantasy.Item1;
}
public FileInfo FontFileInfo(FontFace face)
{
if (FilesMap.TryGetValue(face, out FileInfo info))
return info;
else
return null;
}
public Stream Open(FontFace face)
{
return FontFileInfo(face)?.OpenRead() ?? throw new FileNotFoundException();
}
public IEnumerable<FontFace> Search(FontFace prototype, FontMatchCriteria criteria = FontMatchCriteria.All)
{
// A bit scuffed and LINQ heavy but it should work.
IEnumerable<FontFace> candidates;
if (criteria.HasFlag(FontMatchCriteria.Family))
{
List<FontFace> siblings;
if (!ByFamily.TryGetValue(prototype.Family, out siblings))
{
return Enumerable.Empty<FontFace>();
}
candidates = siblings;
}
else
{
candidates = All;
}
return
candidates
.Where(x =>
implies(criteria.HasFlag(FontMatchCriteria.Slant), prototype.Slant == x.Slant) ||
implies(criteria.HasFlag(FontMatchCriteria.Weight), prototype.Weight == x.Weight) ||
implies(criteria.HasFlag(FontMatchCriteria.Stretch), prototype.Stretch == x.Stretch)
)
.OrderByDescending(x =>
(prototype.Slant == x.Slant ? 1 : 0) +
(prototype.Weight == x.Weight ? 1 : 0) +
(prototype.Stretch == x.Stretch ? 1 : 0) +
confidence(prototype.Family, x.Family) * 3
);
// a => b = a'+b
static bool implies(bool a, bool b)
{
return !a || b;
}
static int confidence(string target, string testee)
{
int i;
for (i = 0; i < target.Length && i < testee.Length && target[i] == testee[i]; i++);
return i;
}
}
public FontFace GetSystemFontFace(SystemFontFamily family)
{
return SystemFonts[family];
}
private void AddFont(FontFace face, FileInfo file)
{
if (!All.Contains(face))
All.Add(face);
FilesMap.TryAdd(face, file);
if (!ByFamily.TryGetValue(face.Family, out List<FontFace> siblings))
{
siblings = new List<FontFace>();
ByFamily.Add(face.Family, siblings);
}
if (!siblings.Contains(face))
siblings.Add(face);
}
private List<DbEntry> LoadDatabase()
{
FileInfo info = new FileInfo(DbPath);
if (!info.Exists)
return new List<DbEntry>();
using Stream str = info.OpenRead();
try
{
return JsonSerializer.Deserialize<List<DbEntry>>(str);
}
catch
{
return new List<DbEntry>();
}
}
private void VerifyDatabase(List<DbEntry> db)
{
// Very slow way to do this but how many fonts could a system have on average?
Dictionary<string, DbEntry> entires = new Dictionary<string, DbEntry>();
foreach (DbEntry entry in db)
{
FileInfo info = new FileInfo(entry.FilePath);
// Reprocess fonts that appear like this.
if (!info.Exists) continue;
else if (info.LastWriteTime > entry.AccessTime) continue;
}
string fontpath = null;
try
{
fontpath = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
if (string.IsNullOrEmpty(fontpath))
throw new Exception();
}
catch
{
foreach (string path in FontPaths)
{
if (Directory.Exists(path))
{
fontpath = path;
break;
}
}
// rip
if (string.IsNullOrEmpty(fontpath))
return;
}
SearchPathForFonts(entires, fontpath);
db.Clear();
db.AddRange(entires.Values);
}
private static void SearchPathForFonts(Dictionary<string, DbEntry> entries, string path)
{
DirectoryInfo dir = new DirectoryInfo(path);
foreach (FileInfo file in dir.EnumerateFiles())
{
SearchFileForFonts(entries, file);
}
foreach (DirectoryInfo directory in dir.EnumerateDirectories())
{
SearchPathForFonts(entries, directory.FullName);
}
}
private static void SearchFileForFonts(Dictionary<string, DbEntry> entries, FileInfo file)
{
if (entries.ContainsKey(file.FullName))
return;
if (FT.NewFace(FTProvider.Ft, file.FullName, 0, out FTFace face) != FTError.None)
return;
FontFace facename = FontFace.Parse(face.FamilyName, face.StyleName);
DbEntry entry = new DbEntry(facename, file.FullName);
entries.Add(file.FullName, entry);
FT.DoneFace(face);
}
private void FlushDatabase(List<DbEntry> db)
{
FileInfo info = new FileInfo(DbPath);
Directory.CreateDirectory(Path.GetDirectoryName(DbPath));
using Stream str = info.Open(FileMode.Create);
JsonSerializer.Serialize(str, db);
}
private static readonly string[] FontPaths = new string[] {
"/usr/share/fonts",
};
[JsonSerializable(typeof(DbEntry))]
private class DbEntry
{
[JsonIgnore] public FontFace Face => new FontFace(Family, Slant, Weight, Stretch);
public string Family { get; set; }
public FontSlant Slant { get; set; }
public FontWeight Weight { get; set; }
public FontStretch Stretch { get; set; }
public string FilePath { get; set; }
public DateTime AccessTime { get; set; }
public DbEntry() {}
public DbEntry(FontFace face, string path)
{
Family = face.Family;
Slant = face.Slant;
Weight = face.Weight;
Stretch = face.Stretch;
FilePath = path;
AccessTime = DateTime.Now;
}
}
}
}

View File

@@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using ReFuel.FreeType;
using Dashboard.Media.Defaults.Fallback;
using Dashboard.Media.Defaults.Linux;
using Dashboard.Media.Font;
using Dashboard.PAL;
namespace Dashboard.Media.Defaults
{
public static class FontDataBaseProvider
{
public static IFontDataBase Instance { get; }
static FontDataBaseProvider()
{
try
{
// TODO: add as other operating systems are supported.
if (OperatingSystem.IsLinux())
{
Instance = new FontConfigFontDatabase();
}
else
{
Instance = new FallbackFontDatabase();
}
}
catch (Exception ex)
{
throw new NotSupportedException("Could not load a suitable font database implementation.", ex);
}
}
public static (FontFace, FileInfo) ResolveSystemFont(string envVar, string defaults, IFontDataBase db)
{
StringBuilder builder = new StringBuilder();
string user = Environment.GetEnvironmentVariable(envVar);
if (user != null)
{
builder.Append(user);
builder.Append(':');
}
builder.Append(defaults);
string[] list = builder.ToString().Split(':');
foreach (string item in list)
{
if (File.Exists(item))
{
// Process file.
if (FT.NewFace(FTProvider.Ft, item, 0, out FTFace ftface) != FTError.None)
{
continue;
}
FontFace face = FontFace.Parse(ftface.FamilyName, ftface.StyleName);
FT.DoneFace(ftface);
return (face, new FileInfo(item));
}
else
{
IEnumerable<FontFace> faces = db.Search(
new FontFace(item, FontSlant.Normal, FontWeight.Normal, FontStretch.Normal),
FontMatchCriteria.Family);
if (faces.Any())
{
FontFace face = faces.First();
return (face, db.FontFileInfo(face));
}
}
}
try
{
FontFace face = db.GetSystemFontFace(SystemFontFamily.Sans);
return (face, db.FontFileInfo(face));
}
catch (Exception ex)
{
throw new NotImplementedException("No fallback font yet.", ex);
}
}
}
}

View File

@@ -0,0 +1,24 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Dashboard.PAL;
namespace Dashboard.Media.Defaults
{
public class FreeTypeFontFactory : IFontFactory
{
public bool TryOpen(Stream stream, [NotNullWhen(true)] out QFont font)
{
try
{
font = new QFontFreeType(stream);
return true;
}
catch
{
font = null;
return false;
}
}
}
}

View File

@@ -0,0 +1,230 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Text;
using Dashboard;
namespace Dashboard.Media.Defaults
{
public static unsafe class FontConfig
{
private const string fontconfig = "fontconfig";
public static bool Exists { get; }
public static IntPtr FAMILY { get; } = Marshal.StringToHGlobalAnsi("family");
public static IntPtr STYLE { get; } = Marshal.StringToHGlobalAnsi("style");
public static IntPtr FILE { get; } = Marshal.StringToHGlobalAnsi("file");
public static IntPtr WEIGHT { get; } = Marshal.StringToHGlobalAnsi("weight");
public static IntPtr SLANT { get; } = Marshal.StringToHGlobalAnsi("slant");
static FontConfig()
{
try
{
if (FcInitLoadConfigAndFonts() == null)
{
Exists = false;
}
Exists = true;
}
catch
{
Exists = false;
}
}
[DllImport(fontconfig, EntryPoint = "FcInitLoadConfigAndFonts")]
public static extern FcConfig* FcInitLoadConfigAndFonts();
[DllImport(fontconfig, EntryPoint = "FcConfigGetCurrent")]
public static extern FcConfig ConfigGetCurrent();
[DllImport(fontconfig, EntryPoint = "FcPatternCreate")]
public static extern FcPattern PatternCreate();
[DllImport(fontconfig, EntryPoint = "FcPatternCreate")]
public static extern bool FcPatternAdd(FcPattern pattern, IntPtr what, FcValue value, bool append);
[DllImport(fontconfig, EntryPoint = "FcObjectSetBuild", CallingConvention = CallingConvention.Cdecl)]
public static extern FcObjectSet ObjectSetBuild(IntPtr i1, IntPtr i2, IntPtr i3, IntPtr i4, IntPtr i5, IntPtr i6);
public static FcObjectSet ObjectSetBuild(IntPtr i1)
{
return ObjectSetBuild(i1, IntPtr.Zero);
}
public static FcObjectSet ObjectSetBuild(IntPtr i1, IntPtr i2)
{
return ObjectSetBuild(i1, i2, IntPtr.Zero);
}
public static FcObjectSet ObjectSetBuild(IntPtr i1, IntPtr i2, IntPtr i3)
{
return ObjectSetBuild(i1, i2, i3, IntPtr.Zero);
}
public static FcObjectSet ObjectSetBuild(IntPtr i1, IntPtr i2, IntPtr i3, IntPtr i4)
{
return ObjectSetBuild(i1, i2, i3, i4, IntPtr.Zero);
}
public static FcObjectSet ObjectSetBuild(IntPtr i1, IntPtr i2, IntPtr i3, IntPtr i4, IntPtr i5)
{
return ObjectSetBuild(i1, i2, i3, i4, i5, IntPtr.Zero);
}
[DllImport(fontconfig, EntryPoint = "FcFontList")]
public static extern FcFontSet FontList(FcConfig config, FcPattern pattern, FcObjectSet objectSet);
[DllImport(fontconfig, EntryPoint = "FcNameUnparse")]
public static extern IntPtr NameUnparse(FcPattern pat);
public static string NameUnparseStr(FcPattern pat) => Marshal.PtrToStringAnsi(NameUnparse(pat));
[DllImport(fontconfig, EntryPoint = "FcPatternGetString")]
public static extern FcResult PatternGetString(FcPattern p, IntPtr what, int n, out IntPtr val);
public static FcResult PatternGetString(FcPattern p, IntPtr what, out string str)
{
FcResult i = PatternGetString(p, what, 0, out IntPtr ptr);
if (i == FcResult.Match)
{
str = Marshal.PtrToStringAnsi(ptr);
}
else
{
str = null;
}
Marshal.FreeHGlobal(ptr);
return i;
}
[DllImport(fontconfig, EntryPoint = "FcPatternGet")]
public static extern FcResult PatternGet(FcPattern p, IntPtr what, int id, out FcValue value);
[DllImport(fontconfig, EntryPoint = "FcFontSetDestroy")]
public static extern void FontSetDestroy(FcFontSet fs);
[DllImport(fontconfig, EntryPoint = "FcObjectSetDestroy")]
public static extern void ObjectSetDestroy (FcObjectSet os);
[DllImport(fontconfig, EntryPoint = "FcConfigDestroy")]
public static extern void ConfigDestroy (FcConfig cfg);
[DllImport(fontconfig, EntryPoint = "FcPatternDestroy")]
public static extern void PatternDestroy (FcPattern os);
#region Range
[DllImport(fontconfig, EntryPoint = "FcRangeCreateDouble")]
public static extern IntPtr RangeCreateDouble(double begin, double end);
[DllImport(fontconfig, EntryPoint = "FcRangeCreateInteger")]
public static extern IntPtr RangeCreateInteger (int begin, int end);
[DllImport(fontconfig, EntryPoint = "FcRangeDestroy")]
public static extern void RangeDestroy(IntPtr range);
[DllImport(fontconfig, EntryPoint = "FcRangeCopy")]
public static extern IntPtr RangeCopy (IntPtr range);
[DllImport(fontconfig, EntryPoint = "FcRangeGetDouble")]
public static extern bool RangeGetDouble(IntPtr range, out double start, out double end);
#endregion
}
public enum FcResult
{
Match,
NoMatch,
TypeMismatch,
NoId,
OutOfMemory
}
public struct FcConfig
{
public readonly IntPtr Handle;
}
public struct FcPattern
{
public readonly IntPtr Handle;
}
public unsafe struct FcObjectSet
{
public readonly IntPtr Handle;
private Accessor* AsPtr => (Accessor*)Handle;
public int NObject => AsPtr->nobject;
public int SObject => AsPtr->sobject;
#pragma warning disable CS0649 // Will always have default value.
private struct Accessor
{
public int nobject;
public int sobject;
public byte** objects;
}
#pragma warning restore CS0649
}
public unsafe struct FcFontSet
{
public readonly IntPtr Handle;
private Accessor* AsPtr => (Accessor*)Handle;
public int NFont => AsPtr->nfont;
public int SFont => AsPtr->sfont;
public FcPattern this[int i]
{
get
{
if (i < 0 || i >= NFont)
throw new IndexOutOfRangeException();
return AsPtr->fonts[i];
}
}
#pragma warning disable CS0649 // Will always have default value.
private struct Accessor
{
public int nfont;
public int sfont;
public FcPattern* fonts;
}
#pragma warning restore CS0649
}
public enum FcType
{
Unknown = -1,
Void,
Integer,
Double,
String,
Bool,
Matrix,
CharSet,
FTFace,
LangSet,
Range
}
[StructLayout(LayoutKind.Explicit)]
public readonly struct FcValue
{
[FieldOffset(0)] public readonly FcType Type;
[FieldOffset(sizeof(FcType))] public readonly IntPtr Pointer;
[FieldOffset(sizeof(FcType))] public readonly int Int;
[FieldOffset(sizeof(FcType))] public readonly double Double;
}
}

View File

@@ -0,0 +1,168 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using ReFuel.FreeType;
using Dashboard.Media.Font;
using Dashboard.PAL;
namespace Dashboard.Media.Defaults.Linux
{
/// <summary>
/// Font database for Linux libfontconfig systems.
/// </summary>
public class FontConfigFontDatabase : IFontDataBase
{
private Dictionary<FontFace, FileInfo> FilesMap { get; } = new Dictionary<FontFace, FileInfo>();
private Dictionary<string, List<FontFace>> ByFamily { get; } = new Dictionary<string, List<FontFace>>();
private Dictionary<SystemFontFamily, FontFace> SystemFonts { get; } = new Dictionary<SystemFontFamily, FontFace>();
private List<FontFace> All { get; } = new List<FontFace>();
IEnumerable<FontFace> IFontDataBase.All => this.All;
public FontConfigFontDatabase()
{
if (!FontConfig.Exists)
{
throw new NotSupportedException("This host doesn't have fontconfig installed.");
}
FcConfig config = FontConfig.ConfigGetCurrent();
FcPattern pattern = FontConfig.PatternCreate();
FcObjectSet os = FontConfig.ObjectSetBuild(FontConfig.FAMILY, FontConfig.STYLE, FontConfig.FILE);
FcFontSet fs = FontConfig.FontList(config, pattern, os);
for (int i = 0; i < fs.NFont; i++)
{
FcPattern current = fs[i];
if (
FontConfig.PatternGetString(current, FontConfig.FAMILY, 0, out IntPtr pFamily) != FcResult.Match ||
FontConfig.PatternGetString(current, FontConfig.STYLE, 0, out IntPtr pStyle) != FcResult.Match)
{
continue;
}
string family = Marshal.PtrToStringUTF8(pFamily);
string style = Marshal.PtrToStringUTF8(pStyle);
FontFace face = FontFace.Parse(family, style);
FontConfig.PatternGetString(current, FontConfig.FILE, 0, out IntPtr pFile);
string file = Marshal.PtrToStringAnsi(pFile);
AddFont(face, new FileInfo(file));
}
FontConfig.FontSetDestroy(fs);
FontConfig.ObjectSetDestroy(os);
FontConfig.PatternDestroy(pattern);
(FontFace, FileInfo) serif = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.SerifFont, LinuxFonts.DefaultSerifFamilies, this);
SystemFonts[SystemFontFamily.Serif] = serif.Item1;
(FontFace, FileInfo) sans = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.SansFont, LinuxFonts.DefaultSansFamilies, this);
SystemFonts[SystemFontFamily.Sans] = sans.Item1;
(FontFace, FileInfo) mono = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.MonospaceFont, LinuxFonts.DefaultMonospaceFamilies, this);
SystemFonts[SystemFontFamily.Monospace] = mono.Item1;
(FontFace, FileInfo) cursive = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.CursiveFont, LinuxFonts.DefaultCursiveFamilies, this);
SystemFonts[SystemFontFamily.Cursive] = cursive.Item1;
(FontFace, FileInfo) fantasy = FontDataBaseProvider.ResolveSystemFont(EnvironmentVariables.FantasyFont, LinuxFonts.DefaultFantasyFamilies, this);
SystemFonts[SystemFontFamily.Fantasy] = fantasy.Item1;
AddFont(serif.Item1, serif.Item2);
AddFont(sans.Item1, sans.Item2);
AddFont(mono.Item1, mono.Item2);
AddFont(cursive.Item1, cursive.Item2);
AddFont(fantasy.Item1, fantasy.Item2);
}
private void AddFont(FontFace face, FileInfo file)
{
if (!All.Contains(face))
All.Add(face);
FilesMap.TryAdd(face, file);
if (!ByFamily.TryGetValue(face.Family, out List<FontFace> siblings))
{
siblings = new List<FontFace>();
ByFamily.Add(face.Family, siblings);
}
if (!siblings.Contains(face))
siblings.Add(face);
}
public IEnumerable<FontFace> Search(FontFace prototype, FontMatchCriteria criteria = FontMatchCriteria.All)
{
// A bit scuffed and LINQ heavy but it should work.
IEnumerable<FontFace> candidates;
if (criteria.HasFlag(FontMatchCriteria.Family))
{
List<FontFace> siblings;
if (!ByFamily.TryGetValue(prototype.Family, out siblings))
{
return Enumerable.Empty<FontFace>();
}
candidates = siblings;
}
else
{
candidates = All;
}
return
candidates
.Where(x =>
implies(criteria.HasFlag(FontMatchCriteria.Slant), prototype.Slant == x.Slant) ||
implies(criteria.HasFlag(FontMatchCriteria.Weight), prototype.Weight == x.Weight) ||
implies(criteria.HasFlag(FontMatchCriteria.Stretch), prototype.Stretch == x.Stretch)
)
.OrderByDescending(x =>
(prototype.Slant == x.Slant ? 1 : 0) +
(prototype.Weight == x.Weight ? 1 : 0) +
(prototype.Stretch == x.Stretch ? 1 : 0) +
confidence(prototype.Family, x.Family) * 3
);
// a => b = a'+b
static bool implies(bool a, bool b)
{
return !a || b;
}
static int confidence(string target, string testee)
{
int i;
for (i = 0; i < target.Length && i < testee.Length && target[i] == testee[i]; i++);
return i;
}
}
public FileInfo FontFileInfo(FontFace face)
{
if (FilesMap.TryGetValue(face, out FileInfo info))
return info;
else
return null;
}
public Stream Open(FontFace face)
{
return FontFileInfo(face)?.OpenRead() ?? throw new FileNotFoundException();
}
public FontFace GetSystemFontFace(SystemFontFamily family)
{
return SystemFonts[family];
}
}
}

View File

@@ -0,0 +1,11 @@
namespace Dashboard.Media.Defaults.Linux
{
internal static class LinuxFonts
{
public const string DefaultSerifFamilies = "Noto Serif:Nimbus Roman:Liberation Serif:FreeSerif:Times:Times New Roman";
public const string DefaultSansFamilies = "Noto Sans:Nimbus Sans:Droid Sans:Liberation Sans:FreeSans:Helvetica Neue:Helvetica:Arial";
public const string DefaultMonospaceFamilies = "Noto Mono:Nimbus Mono PS:Liberation Mono:DejaVu Mono:FreeMono:Lucida Console:Consolas:Courier:Courier New";
public const string DefaultCursiveFamilies = "";
public const string DefaultFantasyFamilies = "";
}
}

View File

@@ -0,0 +1,83 @@
using System;
using System.Buffers;
using System.IO;
using ReFuel.FreeType;
using Dashboard.Media.Color;
using Dashboard.Media.Font;
namespace Dashboard.Media.Defaults
{
public class QFontFreeType : QFont
{
private MemoryStream ms;
private FTFace face;
public override FontFace Face => throw new NotImplementedException();
public QFontFreeType(Stream stream)
{
ms = new MemoryStream();
stream.CopyTo(ms);
FTError e = FT.NewMemoryFace(Ft, ms.GetBuffer(), ms.Length, 0, out face);
if (e != FTError.None)
{
throw new Exception("Could not load font face from stream.");
}
}
public override bool HasRune(int rune)
{
return FT.GetCharIndex(face, (ulong)rune) != 0;
}
protected override QImage Render(out QGlyphMetrics metrics, int codepoint, float size, in FontRasterizerOptions options)
{
FT.SetCharSize(face, 0, (long)Math.Round(64*size), 0, (uint)Math.Round(options.Resolution));
uint index = FT.GetCharIndex(face, (ulong)codepoint);
FT.LoadGlyph(face, index, FTLoadFlags.Default);
ref readonly FTGlyphMetrics ftmetrics = ref face.Glyph.Metrics;
metrics = new QGlyphMetrics(codepoint,
new QVec2(ftmetrics.Width/64f, ftmetrics.Height/64f),
new QVec2(ftmetrics.HorizontalBearingX/64f, ftmetrics.HorizontalBearingY/64f),
new QVec2(ftmetrics.VerticalBearingX/64f, ftmetrics.VerticalBearingY/64f),
new QVec2(ftmetrics.HorizontalAdvance/64f, ftmetrics.VerticalAdvance/64f)
);
FT.RenderGlyph(face.Glyph, options.Sdf ? FTRenderMode.Sdf : FTRenderMode.Normal);
ref readonly FTBitmap bitmap = ref face.Glyph.Bitmap;
if (bitmap.Width == 0 || bitmap.Pitch == 0 || bitmap.Buffer == IntPtr.Zero)
{
return null;
}
QImageBuffer image = new QImageBuffer(QImageFormat.AlphaU8, (int)bitmap.Width, (int)bitmap.Rows);
image.LockBits2d(out QImageLock lk, QImageLockOptions.Default);
unsafe
{
Buffer.MemoryCopy((void*)bitmap.Buffer, (void*)lk.ImagePtr, lk.Width * lk.Height, bitmap.Width * bitmap.Rows);
}
image.UnlockBits();
return image;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
ms.Dispose();
}
FT.DoneFace(face);
}
private static FTLibrary Ft => FTProvider.Ft;
}
}

View File

@@ -1,22 +1,22 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using Quik.Media;
using Quik.Media.Color;
using Quik.Stb;
using Dashboard.Media.Color;
using ReFuel.Stb;
namespace Quik.Media.Stb
namespace Dashboard.Media.Defaults
{
public unsafe class QImageStbi : QImage
{
private readonly StbImage image;
private ImageBuffer buffer;
private QImageBuffer buffer;
private bool isSdf = false;
public override int Width => image.Width;
public override int Height => image.Height;
public override int Depth => 1;
public override bool IsSdf => isSdf;
public override QImageFormat InternalFormat => Stb2QImageFormat(image.Format);
public QImageStbi(Stream source)
@@ -47,8 +47,8 @@ namespace Quik.Media.Stb
if (options.MipLevel > 0) throw new Exception("This image has no mip levels.");
buffer?.Dispose();
buffer = new ImageBuffer(options.Format, Width, Height);
QImageLock dst = buffer.Lock();
buffer = new QImageBuffer(options.Format, Width, Height);
buffer.LockBits2d(out QImageLock dst, QImageLockOptions.Default);
byte *srcPtr = (byte*)image.ImagePointer;
QImageLock src = new QImageLock(InternalFormat, Width, Height, 1, (IntPtr)srcPtr);
@@ -76,7 +76,12 @@ namespace Quik.Media.Stb
public override void UnlockBits()
{
buffer.Unlock();
buffer.UnlockBits();
}
public void SdfHint(bool value = true)
{
isSdf = value;
}
protected override void Dispose(bool disposing)

View File

@@ -4,14 +4,14 @@ using System.Collections;
using System.IO;
using System.Linq;
using System.Net;
using Quik.Media;
using Dashboard.Media.Font;
// WebRequest is obsolete but runs on .NET framework.
#pragma warning disable SYSLIB0014
namespace Quik.Media.Stb
namespace Dashboard.Media.Defaults
{
public class StbMediaLoader : MediaLoader<string>, MediaLoader<Uri>, MediaLoader<FileInfo>, MediaLoader<FontInfo>
public class StbMediaLoader : MediaLoader<string>, MediaLoader<Uri>, MediaLoader<FileInfo>, MediaLoader<FontFace>
{
public bool AllowRemoteTransfers { get; set; } = false;
private readonly ArrayPool<byte> ByteArrays = ArrayPool<byte>.Create();
@@ -31,9 +31,9 @@ namespace Quik.Media.Stb
{
return GetMedia((FileInfo)key, hint);
}
else if (t == typeof(FontInfo))
else if (t == typeof(FontFace))
{
return GetMedia((FontInfo)key, hint);
return GetMedia((FontFace)key, hint);
}
else
{
@@ -56,7 +56,7 @@ namespace Quik.Media.Stb
throw new NotImplementedException();
}
public IDisposable GetMedia(FontInfo key, MediaHint hint)
public IDisposable GetMedia(FontFace key, MediaHint hint)
{
throw new NotImplementedException();
}

View File

@@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
#if false
namespace Quik.Media.Defaults.Win32
{
public class EnumerateFonts
{
private const byte DEFAULT_CHARSET = 1;
public static void Enumerate(FontFace font)
{
/* It's windows, just borrow the desktop window. */
IntPtr hdc = GetDC(GetDesktopWindow());
List<(LogFontA, TextMetricA)> list = new List<(LogFontA, TextMetricA)>();
LogFontA font2 = new LogFontA()
{
//FaceName = font.Family,
Weight = ((font.Style & FontStyle.Bold) != 0) ? FontWeight.Bold : FontWeight.Regular,
Italic = (font.Style & FontStyle.Italic) != 0,
CharSet = DEFAULT_CHARSET
};
Console.WriteLine(font2.FaceName);
EnumFontFamiliesExProc proc = (in LogFontA font, in TextMetricA metric, int type, IntPtr lparam) =>
{
list.Add((font, metric));
return 0;
};
EnumFontFamiliesExA(hdc, font2, proc, IntPtr.Zero, 0);
}
private const string gdi32 = "Gdi32.dll";
private const string user32 = "User32.dll";
[DllImport(gdi32)]
private static extern int EnumFontFamiliesExA(
IntPtr hdc,
in LogFontA font,
[MarshalAs(UnmanagedType.FunctionPtr)] EnumFontFamiliesExProc proc,
IntPtr lparam,
int flags /* Should be zero. */);
[DllImport(user32)]
private static extern IntPtr /* HWND */ GetDesktopWindow();
[DllImport(user32)]
private static extern IntPtr /* HDC */ GetDC(IntPtr hwnd);
private delegate int EnumFontFamiliesExProc(in LogFontA font, in TextMetricA metric, int fontType, IntPtr lParam);
private struct LogFontA
{
public long Height;
public long Width;
public long Escapement;
public long Orientation;
public FontWeight Weight;
[MarshalAs(UnmanagedType.U1)]
public bool Italic;
[MarshalAs(UnmanagedType.U1)]
public bool Underline;
[MarshalAs(UnmanagedType.U1)]
public bool StrikeOut;
public byte CharSet;
public byte OutPrecision;
public byte ClipPrecision;
public byte PitchAndFamily;
private unsafe fixed byte aFaceName[32];
public unsafe string FaceName
{
get
{
fixed (byte* str = aFaceName)
{
int len = 0;
for (; str[len] != 0 && len < 32; len++) ;
return Encoding.UTF8.GetString(str, len);
}
}
set
{
fixed (byte *str = aFaceName)
{
Span<byte> span = new Span<byte>(str, 32);
Encoding.UTF8.GetBytes(value, span);
span[31] = 0;
}
}
}
}
private struct TextMetricA
{
public long Height;
public long Ascent;
public long Descent;
public long InternalLeading;
public long ExternalLeading;
public long AveCharWidth;
public long MaxCharWidth;
public long Weight;
public long Overhang;
public long DigitizedAspectX;
public long DigitizedAspectY;
public byte FirstChar;
public byte LastChar;
public byte DefaultChar;
public byte BreakChar;
public byte Italic;
public byte Underlined;
public byte StruckOut;
public byte PitchAndFamily;
public byte CharSet;
}
private enum FontWeight : long
{
DontCare = 0,
Thin = 100,
ExtraLight = 200,
UltraLight = 200,
Light = 300,
Normal = 400,
Regular = 400,
Medium = 500,
Semibold = 600,
Demibold = 600,
Bold = 700,
Extrabold = 800,
Ultrabold = 800,
Heavy = 900,
Black = 900
}
}
}
#endif

View File

@@ -2,16 +2,15 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>disable</Nullable>
<LangVersion>7.3</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTK" Version="4.7.4" />
<PackageReference Include="OpenTK" Version="4.8.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Quik\Quik.csproj" />
<ProjectReference Include="..\Dashboard\Dashboard.csproj" />
<EmbeddedResource Include="glsl\**"/>
</ItemGroup>

View File

@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;
using Dashboard.CommandMachine;
using Dashboard.Media;
using Dashboard.OpenGL;
using Dashboard.PAL;
namespace Dashboard.OpenTK
{
public class OpenTKPlatform : IDashboardPlatform
{
private readonly List<OpenTKPort> _ports = new List<OpenTKPort>();
// These shall remain a sad nop for now.
public string? Title { get; set; }
public QImage? Icon { get; set; } = null;
public event EventHandler? EventRaised;
public NativeWindowSettings DefaultSettings { get; set; } = NativeWindowSettings.Default;
public IReadOnlyList<OpenTKPort> Ports => _ports;
private bool IsGLInitialized = false;
public IDashHandle CreatePort()
{
NativeWindow window = new NativeWindow(DefaultSettings);
OpenTKPort port = new OpenTKPort(window);
_ports.Add(port);
if (!IsGLInitialized)
{
window.Context.MakeCurrent();
GL.LoadBindings(GLFW.GetProcAddress);
IsGLInitialized = true;
}
window.Closing += (ea) =>
{
Environment.Exit(0);
};
return port;
}
public void Dispose()
{
// FIXME: dispose pattern here!
// Copy the array to prevent collection modification exceptions.
foreach (OpenTKPort port in _ports.ToArray())
{
port.Dispose();
}
}
public void ProcessEvents(bool block)
{
NativeWindow.ProcessWindowEvents(block);
}
public void DestroyPort(IDashHandle port) => ((OpenTKPort)port).Dispose();
public string PortGetTitle(IDashHandle port) => ((OpenTKPort)port).Title;
public void PortSetTitle(IDashHandle port, string title) => ((OpenTKPort)port).Title = title;
public QVec2 PortGetSize(IDashHandle port) => ((OpenTKPort)port).Size;
public void PortSetSize(IDashHandle port, QVec2 size) => ((OpenTKPort)port).Size = size;
public QVec2 PortGetPosition(IDashHandle port) => ((OpenTKPort)port).Position;
public void PortSetPosition(IDashHandle port, QVec2 position) => ((OpenTKPort)port).Position = position;
public bool PortIsValid(IDashHandle port) => ((OpenTKPort)port).IsValid;
public void PortSubscribeEvent(IDashHandle port, EventHandler handler) => ((OpenTKPort)port).EventRaised += handler;
public void PortUnsubscribeEvent(IDashHandle port, EventHandler handler) => ((OpenTKPort)port).EventRaised -= handler;
public void PortFocus(IDashHandle port) => ((OpenTKPort)port).Focus();
public void PortShow(IDashHandle port, bool shown = true) => ((OpenTKPort)port).Show(shown);
public void PortPaint(IDashHandle port, CommandList commands) => ((OpenTKPort)port).Paint(commands);
public void GetMaximumImage(out int width, out int height)
{
GL.Get(GLEnum.GL_MAX_TEXTURE_SIZE, out int value);
width = height = value;
}
public void GetMaximumImage(out int width, out int height, out int depth)
{
GetMaximumImage(out width, out height);
GL.Get(GLEnum.GL_MAX_ARRAY_TEXTURE_LAYERS, out int value);
depth = value;
}
}
}

View File

@@ -1,14 +1,14 @@
using System;
using OpenTK.Mathematics;
using OpenTK.Windowing.Desktop;
using Quik.OpenGL;
using Quik.CommandMachine;
using Quik.PAL;
using Quik.VertexGenerator;
using Dashboard.OpenGL;
using Dashboard.CommandMachine;
using Dashboard.PAL;
using Dashboard.VertexGenerator;
namespace Quik.OpenTK
namespace Dashboard.OpenTK
{
public class OpenTKPort : IQuikPort
public class OpenTKPort : IDashHandle
{
private readonly NativeWindow _window;
private readonly GL21Driver _glDriver;
@@ -51,7 +51,7 @@ namespace Quik.OpenTK
public bool IsValid => !isDisposed;
public event EventHandler EventRaised;
public event EventHandler? EventRaised;
public OpenTKPort(NativeWindow window)
{
@@ -65,18 +65,20 @@ namespace Quik.OpenTK
_window.Focus();
}
public void Paint(CommandQueue queue)
public void Paint(CommandList queue)
{
QRectangle view = new QRectangle(Size, new QVec2(0, 0));
_vertexEngine.Reset();
_vertexEngine.ProcessCommands(new QRectangle(), queue);
_vertexEngine.ProcessCommands(view, queue);
if (!_window.Context.IsCurrent)
_window.Context.MakeCurrent();
if (!_glDriver.IsInit)
_glDriver.Init();
GL.Clear(GLEnum.GL_COLOR_BUFFER_BIT | GLEnum.GL_DEPTH_BUFFER_BIT);
_glDriver.Draw(_vertexEngine.DrawQueue, view);
_window.Context.SwapBuffers();

51
Dashboard.sln Normal file
View File

@@ -0,0 +1,51 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard", "Dashboard\Dashboard.csproj", "{4FE772DD-F424-4EAC-BF88-CB8F751B4926}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Media.Defaults", "Dashboard.Media.Defaults\Dashboard.Media.Defaults.csproj", "{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.OpenTK", "Dashboard.OpenTK\Dashboard.OpenTK.csproj", "{2013470A-915C-46F2-BDD3-FCAA39C845EE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{40F3B724-88A1-4D4F-93AB-FE0DC07A347E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Demo", "tests\Dashboard.Demo\Dashboard.Demo.csproj", "{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.BlurgText", "Dashboard.BlurgText\Dashboard.BlurgText.csproj", "{D05A9DEA-A5D1-43DC-AB41-36B07598B749}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4FE772DD-F424-4EAC-BF88-CB8F751B4926}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4FE772DD-F424-4EAC-BF88-CB8F751B4926}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FE772DD-F424-4EAC-BF88-CB8F751B4926}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FE772DD-F424-4EAC-BF88-CB8F751B4926}.Release|Any CPU.Build.0 = Release|Any CPU
{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3798F6DD-8F84-4B7D-A810-B0D4B5ACB672}.Release|Any CPU.Build.0 = Release|Any CPU
{2013470A-915C-46F2-BDD3-FCAA39C845EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2013470A-915C-46F2-BDD3-FCAA39C845EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2013470A-915C-46F2-BDD3-FCAA39C845EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2013470A-915C-46F2-BDD3-FCAA39C845EE}.Release|Any CPU.Build.0 = Release|Any CPU
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC}.Release|Any CPU.Build.0 = Release|Any CPU
{D05A9DEA-A5D1-43DC-AB41-36B07598B749}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D05A9DEA-A5D1-43DC-AB41-36B07598B749}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D05A9DEA-A5D1-43DC-AB41-36B07598B749}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D05A9DEA-A5D1-43DC-AB41-36B07598B749}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{EAA5488E-ADF0-4D68-91F4-FAE98C8691FC} = {40F3B724-88A1-4D4F-93AB-FE0DC07A347E}
EndGlobalSection
EndGlobal

View File

@@ -1,4 +1,4 @@
namespace Quik.CommandMachine
namespace Dashboard.CommandMachine
{
/// <summary>
/// Enumeration of built-in Quik commands.

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
namespace Quik.CommandMachine
namespace Dashboard.CommandMachine
{
public class CommandEngine
{
@@ -20,7 +20,7 @@ namespace Quik.CommandMachine
public QMat4 ActiveTransforms { get; }
public StyleStack Style { get; } = new StyleStack(new Quik.Style());
public StyleStack Style { get; } = new StyleStack(new Style());
protected CommandEngine()
{
@@ -34,10 +34,12 @@ namespace Quik.CommandMachine
return id;
}
public void ProcessCommands(QRectangle bounds, CommandQueue queue)
public void ProcessCommands(QRectangle bounds, CommandList queue)
{
if (!queue.Peek().IsCommand)
throw new ArgumentException("The first element in the queue must be a command frame.");
CommandQueue iterator = queue.GetEnumerator();
if (!iterator.Peek().IsCommand)
throw new ArgumentException("The first element in the iterator must be a command frame.");
Reset();
@@ -45,7 +47,7 @@ namespace Quik.CommandMachine
_viewportStack.Push(_viewport);
Frame frame;
while (queue.TryDequeue(out frame))
while (iterator.TryDequeue(out frame))
{
Command cmd = (Command)frame;
switch (cmd)
@@ -53,28 +55,28 @@ namespace Quik.CommandMachine
default:
if (cmd > Command.CustomCommandBase)
{
_customCommands[cmd - Command.CustomCommandBase].Invoke(this, queue);
_customCommands[cmd - Command.CustomCommandBase].Invoke(this, iterator);
}
else
{
ChildProcessCommand(cmd, queue);
ChildProcessCommand(cmd, iterator);
}
break;
case Command.ConditionalBegin: ConditionalHandler(queue); break;
case Command.ConditionalBegin: ConditionalHandler(iterator); break;
case Command.ConditionalEnd: /* nop */ break;
case Command.Invoke:
queue.Dequeue().As<QuikCommandHandler>().Invoke(this, queue);
iterator.Dequeue().As<QuikCommandHandler>().Invoke(this, iterator);
break;
case Command.PushViewport:
_viewportStack.Push(_viewport);
break;
case Command.IntersectViewport:
_viewport = QRectangle.Intersect((QRectangle)queue.Dequeue(), _viewport);
_viewport = QRectangle.Intersect((QRectangle)iterator.Dequeue(), _viewport);
break;
case Command.StoreViewport:
_viewport = (QRectangle)queue.Dequeue();
_viewport = (QRectangle)iterator.Dequeue();
break;
case Command.PopViewport:
_viewport = _viewportStack.TryPop(out QRectangle viewport) ? viewport : bounds;
@@ -86,10 +88,10 @@ namespace Quik.CommandMachine
_zIndex++;
break;
case Command.AddZ:
_zIndex += (int)queue.Dequeue();
_zIndex += (int)iterator.Dequeue();
break;
case Command.StoreZ:
_zIndex = (int)queue.Dequeue();
_zIndex = (int)iterator.Dequeue();
break;
case Command.DecrementZ:
_zIndex--;
@@ -97,6 +99,16 @@ namespace Quik.CommandMachine
case Command.PopZ:
_zIndex = _zStack.TryPop(out int zindex) ? zindex : 0;
break;
case Command.PushStyle:
Style.Push(iterator.Dequeue().As<Style>());
break;
case Command.StoreStyle:
Style.Pop();
Style.Push(iterator.Dequeue().As<Style>());
break;
case Command.PopStyle:
Style.Pop();
break;
}
}
}
@@ -110,16 +122,16 @@ namespace Quik.CommandMachine
_zIndex = 0;
_zStack.Clear();
_viewport = new QRectangle(float.MaxValue, float.MaxValue, float.MinValue, float.MinValue);
_viewport = new QRectangle(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue);
_viewportStack.Clear();
_matrixStack.Clear();
_matrixStack.Push(QMat4.Identity);
}
private void ConditionalHandler(CommandQueue queue)
private void ConditionalHandler(CommandQueue iterator)
{
Frame frame = queue.Dequeue();
Frame frame = iterator.Dequeue();
if (
frame.IsInteger && (int)frame != 0 ||
@@ -131,11 +143,11 @@ namespace Quik.CommandMachine
// Skip this branch.
int depth = 1;
while (queue.TryPeek(out frame))
while (iterator.TryPeek(out frame))
{
if (!frame.IsCommand)
{
queue.Dequeue();
iterator.Dequeue();
continue;
}
@@ -149,14 +161,72 @@ namespace Quik.CommandMachine
// Decrement condional depth, exit if zero.
if (--depth == 0)
{
queue.Dequeue();
iterator.Dequeue();
return;
}
break;
}
queue.Dequeue();
}
iterator.Dequeue();
}
}
private static readonly Dictionary<Type, ICommandListSerializer> s_serializers = new Dictionary<Type, ICommandListSerializer>();
/// <summary>
/// Add a custom serializer to the command engine.
/// </summary>
/// <typeparam name="T">Type object type.</typeparam>
/// <param name="serializer">The serializer.</param>
/// <param name="overwrite">True to allow overwriting.</param>
public static void AddSerializer<T>(ICommandListSerializer serializer, bool overwrite = false)
{
if (overwrite)
{
s_serializers[typeof(T)] = serializer;
}
else
{
s_serializers.Add(typeof(T), serializer);
}
}
/// <summary>
/// Add a custom serializer to the command engine.
/// </summary>
/// <typeparam name="T">Type object type.</typeparam>
/// <param name="serializer">The serializer.</param>
/// <param name="overwrite">True to allow overwriting.</param>
public static void AddSerializer<T>(ICommandListSerializer<T> serializer, bool overwrite = false)
=> AddSerializer<T>((ICommandListSerializer)serializer, overwrite);
/// <summary>
/// Get a serializer for the given object.
/// </summary>
/// <typeparam name="T">The object type.</typeparam>
/// <param name="value">Required parameter for the C# type inference to work.</param>
/// <returns>The serializer.</returns>
public static ICommandListSerializer<T> GetSerializer<T>(ICommandListSerializable<T>? value)
where T : ICommandListSerializable<T>, new()
{
if (!s_serializers.TryGetValue(typeof(T), out var serializer))
{
serializer = new CommandListSerializableSerializer<T>();
AddSerializer<T>(serializer);
}
return (ICommandListSerializer<T>)serializer;
}
/// <summary>
/// Get a serializer for the given object.
/// </summary>
/// <typeparam name="T">The object type.</typeparam>
/// <param name="value">Required parameter for the C# type inference to work.</param>
/// <returns>The serializer.</returns>
public static ICommandListSerializer<T> GetSerializer<T>(T? value)
{
return (ICommandListSerializer<T>)s_serializers[typeof(T)];
}
}
}

View File

@@ -1,4 +1,4 @@
namespace Quik.CommandMachine
namespace Dashboard.CommandMachine
{
/// <summary>
/// A delegate for a QUIK command.

View File

@@ -0,0 +1,433 @@
using Dashboard.Media;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Dashboard.CommandMachine
{
public class CommandList : IEnumerable<Frame>
{
private readonly List<Frame> _frames = new List<Frame>();
public void Clear()
{
_frames.Clear();
}
protected void Enqueue(in Frame frame)
{
_frames.Add(frame);
}
public void Invoke(QuikCommandHandler handler)
{
Enqueue(Command.Invoke);
Enqueue(new Frame(handler));
}
public void ConditionalBegin(bool value)
{
Enqueue(Command.ConditionalBegin);
Enqueue((Frame)(value ? 1 : 0));
}
public void ConditionalBegin(Func<bool> condition)
{
Enqueue(Command.ConditionalBegin);
Enqueue(new Frame(condition));
}
public void ConditionalEnd()
{
Enqueue(Command.ConditionalEnd);
}
public void PushViewport()
{
Enqueue(Command.PushViewport);
}
public void IntersectViewport(in QRectangle viewport)
{
Enqueue(Command.IntersectViewport);
Enqueue(viewport);
}
public void StoreViewport(in QRectangle viewport)
{
Enqueue(Command.StoreViewport);
Enqueue(viewport);
}
public void PopViewport()
{
Enqueue(Command.PopViewport);
}
public void PushZ()
{
Enqueue(Command.PushZ);
}
public void IncrementZ()
{
Enqueue(Command.IncrementZ);
}
public void AddZ(int value)
{
if (value == 1)
{
IncrementZ();
}
else if (value == -1)
{
DecrementZ();
}
else
{
Enqueue(Command.AddZ);
Enqueue((Frame)value);
}
}
public void StoreZ(int value)
{
Enqueue(Command.StoreZ);
Enqueue((Frame)value);
}
public void DecrementZ()
{
Enqueue(Command.DecrementZ);
}
public void PopZ()
{
Enqueue(Command.PopZ);
}
public void PushStyle(Style style)
{
Enqueue(Command.PushStyle);
Enqueue(new Frame(style));
}
public void StoreStyle(Style style)
{
Enqueue(Command.StoreStyle);
Enqueue(new Frame(style));
}
public void PopStyle()
{
Enqueue(Command.PopStyle);
}
public void Line(in QLine line)
{
Enqueue(Command.Line);
Enqueue(line);
}
public void Line(params QLine[] lines)
{
Enqueue(Command.Line);
Enqueue((Frame)lines.Length);
foreach (QLine line in lines)
Enqueue(line);
}
public void Bezier(in QBezier bezier)
{
Frame a, b;
Frame.Create(bezier, out a, out b);
Enqueue(Command.Bezier);
Enqueue(a);
Enqueue(b);
}
public void Bezier(params QBezier[] beziers)
{
Frame a, b;
Enqueue(Command.Bezier);
Enqueue((Frame)beziers.Length);
foreach (QBezier bezier in beziers)
{
Frame.Create(bezier, out a, out b);
Enqueue(a);
Enqueue(b);
}
}
public void Rectangle(in QRectangle rectangle)
{
Enqueue(Command.Rectangle);
Enqueue(rectangle);
}
public void Rectangle(QRectangle[] rectangles)
{
Enqueue(Command.Rectangle);
Enqueue((Frame)rectangles.Length);
foreach (QRectangle rectangle in rectangles)
Enqueue(rectangle);
}
public void Ellipse(in QEllipse ellipse)
{
Frame a, b;
Frame.Create(ellipse, out a, out b);
Enqueue(Command.Ellipse);
Enqueue(a);
Enqueue(b);
}
public void Ellipse(params QEllipse[] ellipses)
{
Frame a, b;
Enqueue(Command.Ellipse);
Enqueue((Frame)ellipses.Length);
foreach (QEllipse ellipse in ellipses)
{
Frame.Create(ellipse, out a, out b);
Enqueue(a);
Enqueue(b);
}
}
public void Triangle(in QTriangle triangle)
{
Enqueue(Command.Triangle);
Enqueue(triangle.A);
Enqueue(triangle.B);
Enqueue(triangle.C);
}
public void Triangle(params QTriangle[] triangles)
{
Enqueue(Command.Triangle);
Enqueue((Frame)triangles.Length);
foreach (QTriangle triangle in triangles)
{
Enqueue(triangle.A);
Enqueue(triangle.B);
Enqueue(triangle.C);
}
}
public void Polygon(params QVec2[] polygon)
{
Enqueue(Command.Polygon);
Enqueue((Frame)polygon.Length);
foreach (QVec2 vertex in polygon)
{
Enqueue(vertex);
}
}
public void Image(QImage texture, in QRectangle rectangle)
{
Enqueue(Command.Image);
Enqueue((Frame)(int)ImageCommandFlags.Single);
Enqueue(new Frame(texture));
Enqueue(rectangle);
}
public void Image(QImage texture, in QRectangle rectangle, in QRectangle uv)
{
Enqueue(Command.Image);
Enqueue((Frame)(int)(ImageCommandFlags.Single | ImageCommandFlags.UVs));
Enqueue(new Frame(texture));
Enqueue(rectangle);
Enqueue(uv);
}
public void Image(QImage texture, ReadOnlySpan<QRectangle> rectangles, bool interleavedUV = false)
{
int count = rectangles.Length;
ImageCommandFlags flags = ImageCommandFlags.None;
if (interleavedUV)
{
count /= 2;
flags |= ImageCommandFlags.UVs;
}
Enqueue(Command.Image);
Enqueue(new Frame((int)flags, count));
Enqueue(new Frame(texture));
foreach (QRectangle rectangle in rectangles)
{
Enqueue(rectangle);
}
}
public void Image(QImage texture, ReadOnlySpan<QRectangle> rectangles, ReadOnlySpan<QRectangle> uvs)
{
int count = Math.Min(rectangles.Length, uvs.Length);
Enqueue(Command.Image);
Enqueue(new Frame((int)ImageCommandFlags.UVs, count));
Enqueue(new Frame(texture));
for (int i = 0; i < count; i++)
{
Enqueue(rectangles[i]);
Enqueue(uvs[i]);
}
}
public void Image3D(QImage texture, in Image3DCall call)
{
Enqueue(Command.Image);
Enqueue(new Frame(ImageCommandFlags.Image3d | ImageCommandFlags.Single));
Enqueue(new Frame(texture));
Enqueue(call.Rectangle);
Enqueue(call.UVs);
Enqueue(new Frame(call.Layer));
}
public void Image3D(QImage texture, ReadOnlySpan<Image3DCall> calls)
{
Enqueue(Command.Image);
Enqueue(new Frame((int)ImageCommandFlags.Image3d, calls.Length));
Enqueue(new Frame(texture));
foreach (Image3DCall call in calls)
{
Enqueue(call.Rectangle);
Enqueue(call.UVs);
Enqueue(new Frame(call.Layer));
}
}
public void Splice(CommandList list)
{
foreach (Frame frame in list)
{
Enqueue(frame);
}
}
/// <summary>
/// Serialize an object into the command list.
/// </summary>
/// <typeparam name="T">The type of the value to serialize.</typeparam>
/// <param name="value">What to write into the command list.</param>
public void Write<T>(T value)
{
CommandEngine.GetSerializer(value).Serialize(value, this);
}
/// <summary>
/// Serialize an object into the command list.
/// </summary>
/// <typeparam name="T">The type of the value to serialize.</typeparam>
/// <param name="value">What to write into the command list.</param>
public void Write<T>(ICommandListSerializable<T> value)
where T : ICommandListSerializable<T>, new()
{
CommandEngine.GetSerializer(value).Serialize((T)value, this);
}
public CommandQueue GetEnumerator() => new CommandQueue(_frames);
IEnumerator<Frame> IEnumerable<Frame>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public class CommandQueue : IEnumerator<Frame>
{
private readonly IReadOnlyList<Frame> _frames;
private int _current;
public Frame Current => _frames[_current];
object IEnumerator.Current => Current;
public CommandQueue(IReadOnlyList<Frame> frames)
{
_current = -1;
_frames = frames;
}
public void Dispose()
{
}
public bool TryDequeue([NotNullWhen(true)] out Frame frame)
{
if (MoveNext())
{
frame = Current;
return true;
}
else
{
frame = default;
return false;
}
}
public Frame Dequeue() => TryDequeue(out Frame frame) ? frame : throw new Exception("No more frames left.");
public bool TryPeek([NotNullWhen(true)] out Frame frame)
{
if (_current + 1 < _frames.Count)
{
frame = _frames[_current + 1];
return true;
}
else
{
frame = default;
return false;
}
}
public Frame Peek() => TryPeek(out Frame frame) ? frame : throw new Exception("No more frames left.");
/// <summary>
/// Deserialize an object from the command queue.
/// </summary>
/// <typeparam name="T">Type of the object to deserialize.</typeparam>
/// <param name="value">The deserialized value.</param>
public void Read<T>([NotNull] out T? value)
{
value = CommandEngine.GetSerializer(default(T)).Deserialize(this);
}
/// <summary>
/// Deserialize an object from the command queue.
/// </summary>
/// <typeparam name="T">Type of the object to deserialize.</typeparam>
/// <param name="value">The deserialized value.</param>
public void Read<T>([NotNull] out ICommandListSerializable<T>? value)
where T : ICommandListSerializable<T>, new()
{
value = CommandEngine.GetSerializer(value = null).Deserialize(this);
}
/// <inheritdoc/>
public bool MoveNext()
{
if (_current + 1 < _frames.Count)
{
_current++;
return true;
}
return false;
}
/// <inheritdoc/>
public void Reset()
{
_current = -1;
}
}
}

View File

@@ -1,7 +1,7 @@
using System;
using System.Runtime.InteropServices;
namespace Quik.CommandMachine
namespace Dashboard.CommandMachine
{
[StructLayout(LayoutKind.Explicit)]
public struct Frame
@@ -28,7 +28,7 @@ namespace Quik.CommandMachine
private float _f4;
[FieldOffset(24)]
private object _object;
private object? _object = null;
public bool IsCommand => _type == FrameType.Command;
public bool IsInteger =>
@@ -78,6 +78,7 @@ namespace Quik.CommandMachine
_type = FrameType.None
};
#region Constructors
public Frame(Command command) : this()
{
_type = FrameType.Command;
@@ -195,9 +196,11 @@ namespace Quik.CommandMachine
_f4 = f4;
}
#endregion
public T As<T>()
{
return (T)_object;
return (T)_object!;
}
public float GetF(int i)
@@ -226,21 +229,109 @@ namespace Quik.CommandMachine
}
}
public static explicit operator int(in Frame frame) => frame._i1;
public static explicit operator float(in Frame frame) => frame._f1;
public static explicit operator Command(in Frame frame) => (Command)frame._i1;
public static explicit operator QVec2(in Frame frame) =>
frame.IsFloat ? new QVec2(frame._f1, frame._f2) : new QVec2(frame._i1, frame._i2);
public static explicit operator QColor(in Frame frame) =>
new QColor((byte)frame._i1, (byte)frame._i2, (byte)frame._i3, (byte)frame._i4);
public static explicit operator QRectangle(in Frame frame) =>
frame.IsFloat ?
new QRectangle(frame._f1, frame._f2, frame._f3, frame._f4) :
new QRectangle(frame._i1, frame._i2, frame._i3, frame._i4);
public static explicit operator QLine(in Frame frame) =>
frame.IsFloat ?
new QLine(frame._f1, frame._f2, frame._f3, frame._f4) :
new QLine(frame._i1, frame._i2, frame._i3, frame._i4);
#region Frame->T Conversion
public static explicit operator int(in Frame frame)
{
switch (frame.Type)
{
default:
throw new InvalidCastException();
case FrameType.Command:
case FrameType.IVec1:
case FrameType.IVec2:
case FrameType.IVec3:
case FrameType.IVec4:
return frame._i1;
case FrameType.Vec1:
case FrameType.Vec2:
case FrameType.Vec3:
case FrameType.Vec4:
return (int)frame._f1;
}
}
public static explicit operator float(in Frame frame)
{
switch (frame.Type)
{
default:
throw new InvalidCastException();
case FrameType.IVec1:
case FrameType.IVec2:
case FrameType.IVec3:
case FrameType.IVec4:
return frame._i1;
case FrameType.Vec1:
case FrameType.Vec2:
case FrameType.Vec3:
case FrameType.Vec4:
return frame._f1;
}
}
public static explicit operator Command(in Frame frame)
{
if (frame.Type != FrameType.Command)
{
throw new InvalidCastException("Not a command frame.");
}
return (Command)frame._i1;
}
public static explicit operator QVec2(in Frame frame)
{
switch (frame.Type)
{
default:
throw new InvalidCastException();
case FrameType.IVec2:
case FrameType.IVec3:
case FrameType.IVec4:
return new QVec2(frame._i1, frame._i2);
case FrameType.Vec2:
case FrameType.Vec3:
case FrameType.Vec4:
return new QVec2(frame._f1, frame._f2);
}
}
public static explicit operator QColor(in Frame frame)
{
if (frame.Type != FrameType.IVec4)
throw new InvalidCastException();
return new QColor((byte)frame._i1, (byte)frame._i2, (byte)frame._i3, (byte)frame._i4);
}
public static explicit operator QRectangle(in Frame frame)
{
switch (frame.Type)
{
default:
throw new InvalidCastException();
case FrameType.IVec4:
return new QRectangle(frame._i1, frame._i2, frame._i3, frame._i4);
case FrameType.Vec4:
return new QRectangle(frame._f1, frame._f2, frame._f3, frame._f4);
}
}
public static explicit operator QLine(in Frame frame)
{
switch (frame.Type)
{
default:
throw new InvalidCastException();
case FrameType.IVec4:
return new QLine(frame._i1, frame._i2, frame._i3, frame._i4);
case FrameType.Vec4:
return new QLine(frame._f1, frame._f2, frame._f3, frame._f4);
}
}
#endregion
public static explicit operator Frame(int i) => new Frame(i);
public static explicit operator Frame(float f) => new Frame(f);

View File

@@ -0,0 +1,68 @@
namespace Dashboard.CommandMachine
{
/// <summary>
/// Enumeration of command types in the Dashboard command lists.
/// </summary>
public enum FrameType
{
/// <summary>
/// A null value.
/// </summary>
None,
/// <summary>
/// A command frame.
/// </summary>
Command,
/// <summary>
/// An integer frame.
/// </summary>
IVec1,
/// <summary>
/// A two dimensional integer vector frame.
/// </summary>
IVec2,
/// <summary>
/// A three dimensional integer vector frame.
/// </summary>
IVec3,
/// <summary>
/// A four dimensional integer vector frame.
/// </summary>
IVec4,
/// <summary>
/// A floating point frame.
/// </summary>
Vec1,
/// <summary>
/// A two dimensional floating point vector frame.
/// </summary>
Vec2,
/// <summary>
/// A three dimensional floating point vector frame.
/// </summary>
Vec3,
/// <summary>
/// A four dimensional floating point vector frame.
/// </summary>
Vec4,
/// <summary>
/// A serialized object frame.
/// </summary>
Serialized,
/// <summary>
/// A .Net object frame.
/// </summary>
Object,
}
}

View File

@@ -0,0 +1,18 @@
namespace Dashboard.CommandMachine
{
public enum ImageCommandFlags
{
None = 0,
Single = 1 << 0,
UVs = 1 << 1,
Image3d = 1 << 2,
}
public struct Image3DCall
{
public QRectangle Rectangle;
public QRectangle UVs;
public int Layer;
}
}

View File

@@ -0,0 +1,69 @@
using System.Diagnostics.CodeAnalysis;
using System.Threading;
namespace Dashboard.CommandMachine
{
public interface ICommandListSerializable { }
/// <summary>
/// Interface for objects that can be serialized into the Dashboard command stream.
/// </summary>
public interface ICommandListSerializable<T> : ICommandListSerializable
{
/// <summary>
/// Seralize object.
/// </summary>
/// <param name="list">The object to serialize into.</param>
void Serialize(CommandList list);
/// <summary>
/// Deserialize object.
/// </summary>
/// <param name="queue">The command queue to deserialize from.</param>
void Deserialize(CommandQueue queue);
}
/// <summary>
/// Base interface for all Command List serializers.
/// </summary>
public interface ICommandListSerializer { }
public interface ICommandListSerializer<T> : ICommandListSerializer
{
/// <summary>
/// Serialize an object into the command list.
/// </summary>
/// <param name="value">The object to serialize.</param>
/// <param name="list">The command list to serialize into.</param>
void Serialize(T value, CommandList list);
/// <summary>
/// Deserialize an object from the command queue.
/// </summary>
/// <param name="queue">The command queue.</param>
/// <returns>The object deserialized from the command queue.</returns>
[return: NotNull]
T Deserialize(CommandQueue queue);
}
/// <summary>
/// Class for automatic serialization of <see cref="ICommandListSerializable"/> objects.
/// </summary>
/// <typeparam name="T">The object type to convert.</typeparam>
internal class CommandListSerializableSerializer<T> : ICommandListSerializer<T>
where T : ICommandListSerializable<T>, new()
{
public T Deserialize(CommandQueue queue)
{
T value = new T();
value.Deserialize(queue);
return value;
}
public void Serialize(T value, CommandList list)
{
value.Serialize(list);
}
}
}

View File

@@ -0,0 +1,56 @@
using System;
namespace Dashboard.Controls
{
public enum Dock
{
None,
Top,
Left,
Bottom,
Right,
Center
}
[Flags]
public enum Anchor
{
None = 0,
Top = 1 << 0,
Left = 1 << 1,
Bottom = 1 << 2,
Right = 1 << 3,
All = Top | Left | Bottom | Right
}
public enum Direction
{
Vertical,
Horizontal
}
public enum TextAlignment
{
Left,
Center,
Right,
Justify,
}
public enum VerticalAlignment
{
Top,
Center,
Bottom,
Justify,
}
public enum HorizontalAlignment
{
Left,
Center,
Right,
Justify
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Dashboard.Controls
{
public abstract class ContainerControl : Control, ICollection<Control>
{
private readonly List<Control> children = new List<Control>();
public int Count => children.Count;
public bool IsReadOnly => false;
public void Add(Control item)
{
children.Add(item);
}
public void Clear()
{
children.Clear();
}
public bool Contains(Control item)
{
return children.Contains(item);
}
public void CopyTo(Control[] array, int arrayIndex)
{
children.CopyTo(array, arrayIndex);
}
public IEnumerator<Control> GetEnumerator()
{
return children.GetEnumerator();
}
public bool Remove(Control item)
{
return children.Remove(item);
}
IEnumerator IEnumerable.GetEnumerator()
{
return children.GetEnumerator();
}
}
}

View File

@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using Dashboard.CommandMachine;
namespace Dashboard.Controls
{
public abstract class Control : UIBase
{
private readonly CommandList drawCommands = new CommandList();
public Style Style { get; set; } = new Style();
public float Padding
{
get => (float)(Style["padding"] ?? 0.0f);
set => Style["padding"] = value;
}
public bool IsVisualsValid { get; private set; } = false;
public bool IsLayoutValid { get; private set; } = false;
protected bool IsLayoutSuspended { get; private set; } = false;
public void InvalidateVisual()
{
IsVisualsValid = false;
OnVisualsInvalidated(this, EventArgs.Empty);
}
public void InvalidateLayout()
{
IsLayoutValid = false;
OnLayoutInvalidated(this, EventArgs.Empty);
}
public void SuspendLayout()
{
IsLayoutSuspended = true;
}
public void ResumeLayout()
{
IsLayoutSuspended = false;
InvalidateLayout();
}
protected abstract void ValidateVisual(CommandList cmd);
protected abstract void ValidateLayout();
protected override void PaintBegin(CommandList cmd)
{
base.PaintBegin(cmd);
if (!IsLayoutValid && !IsLayoutSuspended)
{
ValidateLayout();
OnLayoutValidated(this, EventArgs.Empty);
IsLayoutValid = true;
InvalidateVisual();
}
if (!IsVisualsValid)
{
ValidateVisual(drawCommands);
OnVisualsValidated(this, EventArgs.Empty);
IsVisualsValid = true;
}
cmd.PushStyle(Style);
cmd.PushViewport();
cmd.StoreViewport(AbsoluteBounds);
cmd.Splice(drawCommands);
cmd.PopViewport();
cmd.PopStyle();
}
public event EventHandler? StyleChanged;
public event EventHandler? VisualsInvalidated;
public event EventHandler? VisualsValidated;
public event EventHandler? LayoutInvalidated;
public event EventHandler? LayoutValidated;
protected virtual void OnStyleChanged(object sender, EventArgs ea)
{
StyleChanged?.Invoke(sender, ea);
InvalidateLayout();
}
protected virtual void OnVisualsInvalidated(object sender, EventArgs ea)
{
VisualsInvalidated?.Invoke(sender, ea);
}
protected virtual void OnVisualsValidated(object sender, EventArgs ea)
{
VisualsValidated?.Invoke(sender, ea);
}
protected virtual void OnLayoutInvalidated(object sender, EventArgs ea)
{
LayoutInvalidated?.Invoke(sender, ea);
}
protected virtual void OnLayoutValidated(object sender, EventArgs ea)
{
LayoutValidated?.Invoke(sender, ea);
}
protected void ValidateChildrenLayout()
{
if (this is IEnumerable<Control> enumerable)
{
foreach (Control child in enumerable)
{
if (child.IsLayoutValid)
continue;
child.ValidateLayout();
}
}
}
}
}

View File

@@ -0,0 +1,100 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Dashboard.CommandMachine;
namespace Dashboard.Controls
{
public class FlowBox : ContainerControl
{
public Direction FlowDirection { get; set; }
public bool AllowWrap { get; set; }
public VerticalAlignment VerticalAlignment { get; set; }
public HorizontalAlignment HorizontalAlignment { get; set; }
public float ItemPadding { get; set; } = 4f;
protected override void ValidateLayout()
{
ValidateChildrenLayout();
}
protected override void ValidateVisual(CommandList cmd)
{
throw new NotImplementedException();
}
private void FlowHorizontal()
{
IEnumerator<Control> controls = this.GetEnumerator();
List<Control> row;
}
// Enumerate a row.
private bool EnumerateRows(IEnumerator<Control> iterator, List<Control> row)
{
float width = 0;
do
{
if (width + iterator.Current.Size.X < Size.X)
{
row.Add(iterator.Current);
width += iterator.Current.Size.X + ItemPadding;
}
else
{
return true;
}
} while (iterator.MoveNext());
return false;
}
// Flows a row of children.
private void FlowRow(List<Control> line, QVec2 offset, QVec2 size, float packedWidth)
{
QVec2 pointer = offset;
pointer.X += hstart();
foreach (Control child in line)
{
child.Position = pointer;
pointer += new QVec2(child.Size.X + hoffset(child), voffset(child));
}
float hstart()
{
return HorizontalAlignment switch {
HorizontalAlignment.Center => (size.Y - packedWidth) / 2,
HorizontalAlignment.Right => size.Y - packedWidth,
_ => 0f
};
}
float hoffset(Control child)
{
if (line.Count == 1)
return 0;
else if (HorizontalAlignment == HorizontalAlignment.Justify)
{
return ItemPadding + ((size.Y - packedWidth) / (line.Count - 1));
}
else
{
return ItemPadding;
}
}
float voffset(Control child)
{
return VerticalAlignment switch {
VerticalAlignment.Top => 0f,
VerticalAlignment.Bottom => size.Y - child.Size.Y,
_ => (size.Y - child.Size.Y) / 2,
};
}
}
}
}

View File

@@ -0,0 +1,31 @@
using Dashboard.CommandMachine;
using Dashboard.Media;
using Dashboard.Typography;
namespace Dashboard.Controls
{
public class Label : Control
{
public string Text { get; set; } = string.Empty;
public QFont? Font { get; set; }
public float TextSize { get; set; }
public bool AutoSize { get; set; } = true;
protected override void ValidateLayout()
{
if (AutoSize)
{
QVec2 size = Typesetter.MeasureHorizontal(Text, TextSize, Font!);
Size = size;
}
}
protected override void ValidateVisual(CommandList cmd)
{
float padding = Padding;
QVec2 origin = new QVec2(padding, padding);
cmd.TypesetHorizontalDirect(Text, origin, TextSize, Font!);
}
}
}

View File

@@ -0,0 +1,102 @@
using System;
using Dashboard.CommandMachine;
namespace Dashboard.Controls
{
/// <summary>
/// Bases for all UI elements.
/// </summary>
public abstract class UIBase
{
private QVec2 size;
public UIBase? Parent { get; protected set; }
public string? Id { get; set; }
public QRectangle Bounds
{
get => new QRectangle(Position + Size, Position);
set
{
Size = value.Size;
Position = value.Min;
}
}
public QVec2 Position { get; set; }
public QVec2 Size
{
get => size;
set
{
QVec2 oldSize = size;
size = value;
OnResized(this, new ResizedEventArgs(size, oldSize));
}
}
public QRectangle AbsoluteBounds
{
get
{
if (Parent == null)
{
return Bounds;
}
else
{
return new QRectangle(Bounds.Max + Parent.Position, Bounds.Min + Parent.Position);
}
}
}
public QVec2 MaximumSize { get; set; } = new QVec2(-1, -1);
public QVec2 MinimumSize { get; set; } = new QVec2(-1, -1);
public bool IsMaximumSizeSet => MaximumSize != new QVec2(-1, -1);
public bool IsMinimumSizeSet => MinimumSize != new QVec2(-1, -1);
public virtual void NotifyEvent(object? sender, EventArgs args)
{
}
protected virtual void PaintBegin(CommandList cmd)
{
cmd.PushViewport();
cmd.StoreViewport(AbsoluteBounds);
cmd.PushZ();
}
protected virtual void PaintEnd(CommandList cmd)
{
cmd.PopViewport();
}
public void Paint(CommandList cmd)
{
PaintBegin(cmd);
PaintEnd(cmd);
}
public event EventHandler<ResizedEventArgs>? Resized;
public virtual void OnResized(object sender, ResizedEventArgs ea)
{
Resized?.Invoke(sender, ea);
}
}
public class ResizedEventArgs : EventArgs
{
public QVec2 NewSize { get; }
public QVec2 OldSize { get; }
public ResizedEventArgs(QVec2 newSize, QVec2 oldSize)
{
NewSize = newSize;
OldSize = oldSize;
}
}
}

View File

@@ -1,6 +1,6 @@
using System;
namespace Quik.Controls
namespace Dashboard.Controls
{
public class View : UIBase
{

View File

@@ -2,8 +2,8 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>disable</Nullable>
<LangVersion>7.3</LangVersion>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>

View File

@@ -1,6 +1,6 @@
using System;
namespace Quik.Media.Color
namespace Dashboard.Media.Color
{
public static class FormatConvert
{

View File

@@ -0,0 +1,69 @@
using System;
using System.Runtime.InteropServices;
namespace Dashboard.Media.Color
{
public class QImageBuffer : QImage
{
private byte[] buffer;
GCHandle handle;
private bool isSdf = false;
public override QImageFormat InternalFormat { get; }
public override int Width { get; }
public override int Height { get; }
public override int Depth { get; }
public override bool IsSdf => isSdf;
public QImageBuffer(QImageFormat format, int width, int height, int depth = 1)
{
InternalFormat = format;
Width = width;
Height = height;
Depth = depth;
buffer = new byte[(long)width * height * depth * format.BytesPerPixel()];
}
~QImageBuffer()
{
Dispose(false);
}
private QImageLock Lock()
{
if (handle.IsAllocated) handle.Free();
handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
return new QImageLock(InternalFormat, Width, Height, Depth, ptr);
}
protected override void Dispose(bool disposing)
{
if (handle.IsAllocated) handle.Free();
GC.SuppressFinalize(this);
}
public override void LockBits2d(out QImageLock imageLock, QImageLockOptions options)
{
imageLock = Lock();
}
public override void LockBits3d(out QImageLock imageLock, QImageLockOptions options)
{
imageLock = Lock();
}
public override void LockBits3d(out QImageLock imageLock, QImageLockOptions options, int depth)
{
imageLock = Lock();
}
public override void UnlockBits()
{
handle.Free();
}
public void SetSdf(bool value = true) => isSdf = value;
}
}

View File

@@ -1,6 +1,6 @@
using System;
namespace Quik.Media.Color
namespace Dashboard.Media.Color
{
public unsafe struct LockIO
{

View File

@@ -1,4 +1,4 @@
namespace Quik.Media
namespace Dashboard.Media
{
public static class Extensions
{
@@ -32,7 +32,7 @@ namespace Quik.Media
}
}
public static int BitsPerPixel(this QImageFormat format)
public static int BytesPerPixel(this QImageFormat format)
{
switch (format)
{

View File

@@ -0,0 +1,184 @@
using System;
using System.Collections.Generic;
using Dashboard.Media.Color;
namespace Dashboard.Media.Font
{
public struct FontAtlasGlyphInfo
{
public int Codepoint;
public QImage Image;
public QRectangle UVs;
}
public class FontAtlas
{
private readonly int width, height;
private readonly List<AtlasPage> atlases = new List<AtlasPage>();
private readonly Dictionary<int, FontAtlasGlyphInfo> glyphs = new Dictionary<int, FontAtlasGlyphInfo>();
private int index = 0;
private AtlasPage? last = null;
private bool isSdf = false;
private int expansion;
public bool IsSdf
{
get => isSdf;
set
{
foreach (AtlasPage page in atlases)
{
((QImageBuffer)page.Image).SetSdf(value);
}
isSdf = value;
}
}
public FontAtlas(int width, int height, bool isSdf, int expansion = 4)
{
this.width = width;
this.height = height;
IsSdf = isSdf;
this.expansion = expansion;
}
public bool GetGlyph(int codepoint, out FontAtlasGlyphInfo info)
{
return glyphs.TryGetValue(codepoint, out info);
}
public void PutGlyph(int codepoint, QImageLock source, out FontAtlasGlyphInfo info)
{
info = new FontAtlasGlyphInfo() { Codepoint = codepoint };
if (last == null || !last.WouldFit(source))
{
AddPage();
}
last!.PutGlyph(source, ref info);
}
private void AddPage()
{
index++;
if (index < atlases.Count)
{
last = atlases[index];
}
else
{
last = new AtlasPage(width, height, expansion);
((QImageBuffer)last.Image).SetSdf(IsSdf);
atlases.Add(last);
}
}
public void Clear()
{
// Trim any pages that were not used yet.
for (int i = atlases.Count -1; i >= 0; i--)
{
if (atlases[i].PointerX != 0 && atlases[i].PointerY != 0)
{
for (int j = i + 1; j < atlases.Count; j++)
{
atlases[j].Dispose();
}
if (i != atlases.Count - 1)
atlases.RemoveRange(i+1, atlases.Count - i - 1);
break;
}
}
if (atlases.Count > 0)
{
last = atlases[0];
}
else
{
last = null;
}
index = -1;
glyphs.Clear();
}
private class AtlasPage : IDisposable
{
public QImage Image;
public int PointerX, PointerY;
public int RowHeight;
public int Expansion;
public bool IsFull => PointerX > Image.Width || PointerY > Image.Height;
public AtlasPage(int width, int height, int expansion)
{
Image = new QImageBuffer(QImageFormat.AlphaU8, width, height);
Expansion = expansion;
Reset();
}
public void PutGlyph(QImageLock src, ref FontAtlasGlyphInfo prototype)
{
if (IsFull)
throw new Exception("Page is full!");
Image.LockBits2d(out QImageLock dst, QImageLockOptions.Default);
src.CopyTo(dst, PointerX, PointerY);
Image.UnlockBits();
QVec2 min = new QVec2((float)PointerX/Image.Width, (float)PointerY/Image.Height);
QVec2 size = new QVec2((float)src.Width/Image.Width, (float)src.Height/Image.Height);
prototype.Image = Image;
prototype.UVs = new QRectangle(min + size, min);
AdvanceColumn(src.Width, src.Height);
}
public void Reset()
{
RowHeight = PointerX = PointerY = 0;
}
public void AdvanceRow()
{
PointerX = 0;
PointerY += RowHeight + Expansion;
RowHeight = 0;
}
public void AdvanceColumn(int width, int height)
{
RowHeight = Math.Max(RowHeight, height);
PointerX += width + Expansion;
if (PointerX > Image.Width)
{
AdvanceRow();
}
}
private bool isDisposed = false;
public void Dispose()
{
if (isDisposed)
return;
Image?.Dispose();
isDisposed = true;
}
internal bool WouldFit(QImageLock source)
{
return !IsFull || PointerX + source.Width > Image.Width || PointerY + source.Height > Image.Height;
}
}
}
}

View File

@@ -0,0 +1,238 @@
using System;
using System.Text;
namespace Dashboard.Media.Font
{
public readonly struct FontFace : IEquatable<FontFace>
{
public string Family { get; }
public FontSlant Slant { get; }
public FontWeight Weight { get; }
public FontStretch Stretch { get; }
public FontFace(string family, FontSlant slant, FontWeight weight, FontStretch stretch)
{
Family = family;
Slant = slant;
Weight = weight;
Stretch = stretch;
}
public override string ToString()
{
StringBuilder builder = new StringBuilder(Family);
if (Slant != FontSlant.Normal)
{
builder.Append(' ');
builder.Append(Slant);
}
if (Stretch != FontStretch.Normal)
{
builder.Append(' ');
builder.Append(Stretch);
}
if (Weight != FontWeight.Normal)
{
builder.Append(' ');
builder.Append(Weight);
}
if (Slant == FontSlant.Normal &&
Stretch == FontStretch.Normal &&
Weight == FontWeight.Normal)
{
builder.Append(" Regular");
}
return builder.ToString();
}
public override int GetHashCode()
{
return HashCode.Combine(Family, Slant, Weight, Stretch);
}
public static bool operator==(FontFace a, FontFace b)
{
return (a.Slant == b.Slant) &&
(a.Weight == b.Weight) &&
(a.Stretch == b.Stretch) &&
(a.Family == a.Family);
}
public static bool operator!=(FontFace a, FontFace b)
{
return (a.Slant != b.Slant) ||
(a.Weight != b.Weight) ||
(a.Stretch != b.Stretch) ||
(a.Family != b.Family);
}
public bool Equals(FontFace other)
{
return this == other;
}
public override bool Equals(object? obj)
{
return (obj?.GetType() == typeof(FontFace)) &&
this == (FontFace)obj;
}
public static FontFace Parse(string family, string style)
{
FontSlant slant = FontSlant.Normal;
FontWeight weight = FontWeight.Normal;
FontStretch stretch = FontStretch.Normal;
string[] tokens = style.Split(' ');
foreach (string token in tokens)
{
/**/ if (TryParseSlant(token, out FontSlant xslant)) slant = xslant;
else if (TryParseWeight(token, out FontWeight xweight)) weight = xweight;
else if (TryParseStretch(token, out FontStretch xstretch)) stretch = xstretch;
}
return new FontFace(family, slant, weight, stretch);
}
public static FontFace Parse(string face)
{
StringBuilder family = new StringBuilder();
FontSlant slant = FontSlant.Normal;
FontWeight weight = FontWeight.Normal;
FontStretch stretch = FontStretch.Normal;
string[] tokens = face.Split(' ');
foreach (string token in tokens)
{
string xtoken = token.ToLower();
if (xtoken == "regular" || xtoken == "normal")
{
continue;
}
else if (TryParseSlant(xtoken, out FontSlant xslant)) slant = xslant;
else if (TryParseWeight(xtoken, out FontWeight xweight)) weight = xweight;
else if (TryParseStretch(xtoken, out FontStretch xstretch)) stretch = xstretch;
else
{
family.Append(token);
}
}
return new FontFace(family.ToString(), slant, weight, stretch);
}
/// <summary>
/// Try to convert a token that represents a font slant into its enum.
/// </summary>
/// <param name="token">The token to interpret.</param>
/// <param name="slant">The resulting slant.</param>
/// <returns>True if it matched any.</returns>
public static bool TryParseSlant(string token, out FontSlant slant)
{
switch (token.ToLower())
{
case "italic":
slant = FontSlant.Italic;
return true;
case "oblique":
slant = FontSlant.Oblique;
return true;
default:
slant = FontSlant.Normal;
return false;
}
}
/// <summary>
/// Try to convert a token that represents a font weight into its enum.
/// </summary>
/// <param name="token">The token to interpret.</param>
/// <param name="weight">The resulting weight.</param>
/// <returns>True if it matched any.</returns>
public static bool TryParseWeight(string token, out FontWeight weight)
{
switch (token.ToLower())
{
case "thin":
weight = FontWeight.Thin;
return true;
case "extralight":
case "ultralight":
weight = FontWeight._200;
return true;
case "light":
case "demilight":
case "semilight":
weight = FontWeight._300;
return true;
case "demibold":
case "semibold":
weight = FontWeight._600;
return true;
case "bold":
weight = FontWeight._700;
return true;
case "extrabold":
case "ultrabold":
weight = FontWeight._800;
return true;
case "heavy":
case "extrablack":
case "black":
case "ultrablack":
weight = FontWeight._900;
return true;
default:
weight = FontWeight.Normal;
return false;
}
}
/// <summary>
/// Try to convert a token that represents a font stretch into its enum.
/// </summary>
/// <param name="token">The token to interpret.</param>
/// <param name="stretch">The resulting stretch.</param>
/// <returns>True if it matched any.</returns>
public static bool TryParseStretch(string token, out FontStretch stretch)
{
switch (token.ToLower())
{
case "ultracondensed":
stretch = FontStretch.UltraCondensed;
return true;
case "extracondensed":
stretch = FontStretch.ExtraCondensed;
return true;
case "condensed":
stretch = FontStretch.Condensed;
return true;
case "semicondensed":
case "demicondensed":
stretch = FontStretch.SemiCondensed;
return true;
case "semiexpanded":
case "demiexpanded":
stretch = FontStretch.SemiExpanded;
return true;
case "expanded":
stretch = FontStretch.Expanded;
return true;
case "extraexpanded":
stretch = FontStretch.ExtraExpanded;
return true;
case "ultraexpanded":
stretch = FontStretch.UltraExpanded;
return true;
default:
stretch = FontStretch.Normal;
return false;
}
}
}
}

View File

@@ -0,0 +1,9 @@
namespace Dashboard.Media.Font
{
public enum FontSlant
{
Normal = 0,
Italic = 1,
Oblique = 2,
}
}

View File

@@ -0,0 +1,18 @@
namespace Dashboard.Media.Font
{
/// <summary>
/// Enumeration of font stretch values.
/// </summary>
public enum FontStretch
{
UltraCondensed = 500,
ExtraCondensed = 625,
Condensed = 750,
SemiCondensed = 875,
Normal = 1000,
SemiExpanded = 1125,
Expanded = 1250,
ExtraExpanded = 1500,
UltraExpanded = 2000,
}
}

View File

@@ -0,0 +1,22 @@
using System;
namespace Dashboard.Media.Font
{
public enum FontWeight
{
_100 = 100,
_200 = 200,
_300 = 300,
_400 = 400,
_500 = 500,
_600 = 600,
_700 = 700,
_800 = 800,
_900 = 900,
Thin = _100,
Normal = _400,
Bold = _700,
Heavy = _900,
}
}

View File

@@ -0,0 +1,26 @@
namespace Dashboard.Media.Font
{
public enum SystemFontFamily
{
/// <summary>
/// A font with serifs, like Times New Roman.
/// </summary>
Serif,
/// <summary>
/// A font without serifs, like Helvetica or Arial.
/// </summary>
Sans,
/// <summary>
/// A monospace font like Courier New.
/// </summary>
Monospace,
/// <summary>
/// A cursive font like Lucida Handwriting.
/// </summary>
Cursive,
/// <summary>
/// An immature font like Comic Sans or Papyrus, nghehehehe.
/// </summary>
Fantasy
}
}

View File

@@ -1,6 +1,6 @@
using System;
namespace Quik.Media
namespace Dashboard.Media
{
public enum QImageFormat
{

View File

@@ -1,7 +1,7 @@
using System;
using System.IO;
namespace Quik.Media
namespace Dashboard.Media
{
public enum MediaHint
{

129
Dashboard/Media/QFont.cs Normal file
View File

@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Dashboard.Media;
using Dashboard.Media.Font;
namespace Dashboard.Media
{
/// <summary>
/// Abstract class that represents a font.
/// </summary>
public abstract class QFont : IDisposable
{
public abstract FontFace Face { get; }
public string Family => Face.Family;
public FontSlant Slant => Face.Slant;
public FontWeight Weight => Face.Weight;
public FontStretch Stretch => Face.Stretch;
public abstract bool HasRune(int rune);
protected abstract QImage Render(out QGlyphMetrics metrics, int codepoint, float size, in FontRasterizerOptions options);
private readonly Dictionary<float, SizedFontCollection> _atlasses = new Dictionary<float, SizedFontCollection>();
public void Get(int codepoint, float size, out FontGlyph glyph)
{
SizedFontCollection? collection;
if (!_atlasses.TryGetValue(size, out collection))
{
collection = new SizedFontCollection(size);
_atlasses.Add(size, collection);
}
collection.Get(codepoint, out glyph, this);
}
// IDisposable
private bool isDisposed = false;
private void DisposePrivate(bool disposing)
{
if (isDisposed) return;
Dispose(disposing);
isDisposed = true;
}
protected virtual void Dispose(bool disposing) { }
public void Dispose() => DisposePrivate(true);
private class SizedFontCollection
{
public float Size { get; }
private readonly Dictionary<int, FontGlyph> glyphs = new Dictionary<int, FontGlyph>();
private readonly FontAtlas atlas;
public SizedFontCollection(float size)
{
Size = size;
DashboardApplication.Current.Platform.GetMaximumImage(out int height, out int width);
// Do no allow to create a texture that is greater than 16 square characters at 200 DPI.
width = Math.Min(width, (int)(size * 200 * 16));
height = Math.Min(height, (int)(size * 200 * 16));
// width = height = 256;
atlas = new FontAtlas(width, height, DashboardApplication.Current.FontProvider.RasterizerOptions.Sdf);
}
public void Get(int codepoint, out FontGlyph glyph, QFont font)
{
if (glyphs.TryGetValue(codepoint, out glyph))
return;
QImage image = font.Render(
out QGlyphMetrics metrics,
codepoint,
Size,
DashboardApplication.Current.FontProvider.RasterizerOptions);
if (image != null)
{
image.LockBits2d(out QImageLock l, QImageLockOptions.Default);
atlas.PutGlyph(codepoint, l, out FontAtlasGlyphInfo glyphInfo);
image.UnlockBits();
image.Dispose();
glyph = new FontGlyph(codepoint, glyphInfo.Image, metrics, glyphInfo.UVs);
}
else
{
glyph = new FontGlyph(codepoint, null, metrics, default);
}
glyphs[codepoint] = glyph;
}
}
}
public readonly struct FontGlyph
{
public readonly int CodePoint;
public readonly QImage? Image;
public readonly QGlyphMetrics Metrics;
public readonly QRectangle UVs;
public FontGlyph(int codepoint, QImage? image, in QGlyphMetrics metrics, in QRectangle uvs)
{
CodePoint = codepoint;
Image = image;
Metrics = metrics;
UVs = uvs;
}
}
public struct FontRasterizerOptions
{
public float Resolution { get; set; }
public bool Sdf { get; set; }
public static readonly FontRasterizerOptions Default = new FontRasterizerOptions()
{
Resolution = 96.0f,
Sdf = false
};
}
}

View File

@@ -1,4 +1,4 @@
namespace Quik.Media
namespace Dashboard.Media
{
/// <summary>
/// Glyph properties with metrics based on FreeType glyph metrics.
@@ -10,11 +10,6 @@ namespace Quik.Media
/// </summary>
public int Rune { get; }
/// <summary>
/// Location of the glyph on the atlas.
/// </summary>
public QRectangle Location { get; }
/// <summary>
/// Size of the glyph in units.
/// </summary>
@@ -37,14 +32,12 @@ namespace Quik.Media
public QGlyphMetrics(
int character,
QRectangle location,
QVec2 size,
QVec2 horizontalBearing,
QVec2 verticalBearing,
QVec2 advance)
{
Rune = character;
Location = location;
Size = size;
HorizontalBearing = horizontalBearing;
VerticalBearing = verticalBearing;

120
Dashboard/Media/QImage.cs Normal file
View File

@@ -0,0 +1,120 @@
using System;
namespace Dashboard.Media
{
public abstract class QImage : IDisposable
{
public abstract int Width { get; }
public abstract int Height { get; }
public abstract int Depth { get; }
public abstract QImageFormat InternalFormat { get; }
public virtual int MipMapLevels => 0;
public virtual bool Premultiplied => false;
public virtual bool IsSdf => false;
public abstract void LockBits2d(out QImageLock imageLock, QImageLockOptions options);
public abstract void LockBits3d(out QImageLock imageLock, QImageLockOptions options);
public abstract void LockBits3d(out QImageLock imageLock, QImageLockOptions options, int depth);
public abstract void UnlockBits();
// IDisposable
private bool isDisposed = false;
private void DisposePrivate(bool disposing)
{
if (isDisposed) return;
Dispose(disposing);
isDisposed = true;
}
protected virtual void Dispose(bool disposing) { }
public void Dispose() => DisposePrivate(true);
}
public struct QImageLockOptions
{
public QImageFormat Format { get; }
public bool Premultiply { get; }
public int MipLevel { get; }
public static QImageLockOptions Default { get; } = new QImageLockOptions(QImageFormat.RgbaU8, true, 0);
public QImageLockOptions(QImageFormat format, bool premultiply, int level)
{
Format = format;
Premultiply = premultiply;
MipLevel = level;
}
}
public struct QImageLock
{
public QImageFormat Format { get; }
public int Width { get; }
public int Height { get; }
public int Depth { get; }
public IntPtr ImagePtr { get; }
public QImageLock(QImageFormat format, int width, int height, int depth, IntPtr ptr)
{
Format = format;
Width = width;
Height = height;
Depth = depth;
ImagePtr = ptr;
}
public unsafe void CopyTo(QImageLock destination, int x, int y)
{
if (
Width + x >= destination.Width ||
Height + y >= destination.Height)
{
throw new Exception("Image falls outside the bounds of the destination.");
}
else if (Format != destination.Format)
{
throw new Exception("Image formats must be the same.");
}
int bpp = Format.BytesPerPixel();
for (int i = 0; i < Height; i++)
{
IntPtr srcPtr = (IntPtr)((long)ImagePtr + i * Width * bpp);
long dstPos = x + i * destination.Width;
IntPtr dstPtr = (IntPtr)((long)destination.ImagePtr + dstPos * bpp);
Buffer.MemoryCopy((void*)srcPtr, (void*)dstPtr, Width * bpp, Width * bpp);
}
}
public unsafe void ExtractFrom(QImageLock destination, int x, int y, int width, int height)
{
if (
width != destination.Width ||
height != destination.Height)
{
throw new Exception("Destination is not the same size as the subregion.");
}
else if (x + width > Width || y + height > Height)
{
throw new Exception("The subregion is larger than this image.");
}
else if (Format != destination.Format)
{
throw new Exception("Image formats must be the same.");
}
int bpp = Format.BytesPerPixel();
for (int i = 0; i < height; i++)
{
long srcPos = x + y * i;
IntPtr srcPtr = (IntPtr)((long)ImagePtr + srcPos * bpp);
long dstPos = i * destination.Width;
IntPtr dstPtr = (IntPtr)((long)destination.ImagePtr + dstPos * bpp);
Buffer.MemoryCopy((void*)srcPtr, (void*)dstPtr, width * bpp, width * bpp);
}
}
}
}

View File

@@ -1,8 +1,8 @@
using System;
namespace Quik
namespace Dashboard
{
public enum MouseButton
public enum MouseButton : byte
{
Primary = 1 << 0,
Secondary = 1 << 1,
@@ -11,7 +11,7 @@ namespace Quik
Auxilliary2 = 1 << 4,
Auxilliary3 = 1 << 5,
Auxilliary4 = 1 << 6,
Auxilliary5 = 1 << 8,
Auxilliary5 = 1 << 7,
}
public struct MouseState

View File

@@ -1,16 +1,16 @@
using System;
using System.Runtime.CompilerServices;
namespace Quik.OpenGL
namespace Dashboard.OpenGL
{
public unsafe static partial class GL
{
private delegate void BufferDataProc(GLEnum target, int size, void* data, GLEnum usageHint);
private static GenObjectsProc _genBuffers;
private static GenObjectsProc _deleteBuffers;
private static BindSlottedProc _bindBuffer;
private static BufferDataProc _bufferData;
private static GenObjectsProc? _genBuffers;
private static GenObjectsProc? _deleteBuffers;
private static BindSlottedProc? _bindBuffer;
private static BufferDataProc? _bufferData;
private static void LoadBuffer()
{
@@ -24,7 +24,7 @@ namespace Quik.OpenGL
public static void GenBuffers(int count, out int buffers)
{
fixed (int *ptr = &buffers)
_genBuffers(count, ptr);
_genBuffers!(count, ptr);
}
[MethodImpl(AggressiveInlining)]
@@ -41,7 +41,7 @@ namespace Quik.OpenGL
public static void DeleteBuffers(int count, ref int buffers)
{
fixed (int *ptr = &buffers)
_deleteBuffers(count, ptr);
_deleteBuffers!(count, ptr);
}
[MethodImpl(AggressiveInlining)]
@@ -53,19 +53,19 @@ namespace Quik.OpenGL
[MethodImpl(AggressiveInlining)]
public static void BindBuffer(GLEnum target, int buffer)
{
_bindBuffer(target, buffer);
_bindBuffer!(target, buffer);
}
[MethodImpl(AggressiveInlining)]
public static void BufferData(GLEnum target, int size, IntPtr data, GLEnum usageHint) =>
_bufferData(target, size, (void*)data, usageHint);
_bufferData!(target, size, (void*)data, usageHint);
[MethodImpl(AggressiveInlining)]
public static void BufferData<T>(GLEnum target, int size, ref T data, GLEnum usageHint)
where T : unmanaged
{
fixed (T* ptr = &data)
_bufferData(target, size, ptr, usageHint);
_bufferData!(target, size, ptr, usageHint);
}
[MethodImpl(AggressiveInlining)]

View File

@@ -1,8 +1,8 @@
using System.Runtime.CompilerServices;
using System.Text;
using static Quik.OpenGL.GLEnum;
using static Dashboard.OpenGL.GLEnum;
namespace Quik.OpenGL
namespace Dashboard.OpenGL
{
public unsafe static partial class GL
{
@@ -16,16 +16,16 @@ namespace Quik.OpenGL
private delegate void DeleteProgramProc(int program);
private delegate int GetShaderLocationProc(int program, byte *name);
private static CreateProgramProc _createProgram;
private static UseProgramProc _useProgram;
private static AttachShaderProc _attachShader;
private static DetachShaderProc _detachShader;
private static LinkProgramProc _linkProgram;
private static GetProgramProc _getProgram;
private static GetProgramInfoLogProc _getProgramInfoLog;
private static DeleteProgramProc _deleteProgram;
private static GetShaderLocationProc _getUniformLocation;
private static GetShaderLocationProc _getAttribLocation;
private static CreateProgramProc? _createProgram;
private static UseProgramProc? _useProgram;
private static AttachShaderProc? _attachShader;
private static DetachShaderProc? _detachShader;
private static LinkProgramProc? _linkProgram;
private static GetProgramProc? _getProgram;
private static GetProgramInfoLogProc? _getProgramInfoLog;
private static DeleteProgramProc? _deleteProgram;
private static GetShaderLocationProc? _getUniformLocation;
private static GetShaderLocationProc? _getAttribLocation;
private static void LoadProgram()
{
@@ -42,26 +42,26 @@ namespace Quik.OpenGL
}
[MethodImpl(AggressiveInlining)]
public static int CreateProgram() => _createProgram();
public static int CreateProgram() => _createProgram!();
[MethodImpl(AggressiveInlining)]
public static void UseProgram(int program) => _useProgram(program);
public static void UseProgram(int program) => _useProgram!(program);
[MethodImpl(AggressiveInlining)]
public static void AttachShader(int program, int shader) => _attachShader(program, shader);
public static void AttachShader(int program, int shader) => _attachShader!(program, shader);
[MethodImpl(AggressiveInlining)]
public static void DetachShader(int program, int shader) => _detachShader(program, shader);
public static void DetachShader(int program, int shader) => _detachShader!(program, shader);
[MethodImpl(AggressiveInlining)]
public static void LinkProgram(int program) => _linkProgram(program);
public static void LinkProgram(int program) => _linkProgram!(program);
[MethodImpl(AggressiveInlining)]
public static void GetProgram(int program, GLEnum pname, out int value)
{
value = default;
fixed (int* ptr = &value)
_getProgram(program, pname, ptr);
_getProgram!(program, pname, ptr);
}
[MethodImpl(AggressiveInlining)]
@@ -71,26 +71,26 @@ namespace Quik.OpenGL
byte[] infoLog = new byte[length];
fixed (byte *ptr = infoLog)
_getProgramInfoLog(program, length, &length, ptr);
_getProgramInfoLog!(program, length, &length, ptr);
return Encoding.UTF8.GetString(infoLog);
}
[MethodImpl(AggressiveInlining)]
public static void DeleteProgram(int program) => _deleteProgram(program);
public static void DeleteProgram(int program) => _deleteProgram!(program);
[MethodImpl(AggressiveInlining)]
public static int GetUniformLocation(int program, string name)
{
fixed(byte* ptr = Encoding.UTF8.GetBytes(name))
return _getUniformLocation(program, ptr);
return _getUniformLocation!(program, ptr);
}
[MethodImpl(AggressiveInlining)]
public static int GetAttribLocation(int program, string name)
{
fixed(byte* ptr = Encoding.UTF8.GetBytes(name))
return _getAttribLocation(program, ptr);
return _getAttribLocation!(program, ptr);
}
}
}

View File

@@ -2,9 +2,9 @@ using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using static Quik.OpenGL.GLEnum;
using static Dashboard.OpenGL.GLEnum;
namespace Quik.OpenGL
namespace Dashboard.OpenGL
{
public unsafe static partial class GL
{
@@ -15,12 +15,12 @@ namespace Quik.OpenGL
private delegate void GetShaderInfoLogProc(int shader, int maxLength, int* length, byte* infoLog);
private delegate void DeleteShaderProc(int id);
private static CreateShaderProc _createShader;
private static ShaderSourceProc _shaderSource;
private static CompileShaderProc _compileShader;
private static GetShaderProc _getShader;
private static GetShaderInfoLogProc _getShaderInfoLog;
private static DeleteShaderProc _deleteShader;
private static CreateShaderProc? _createShader;
private static ShaderSourceProc? _shaderSource;
private static CompileShaderProc? _compileShader;
private static GetShaderProc? _getShader;
private static GetShaderInfoLogProc? _getShaderInfoLog;
private static DeleteShaderProc? _deleteShader;
private static void LoadShader()
{
@@ -33,7 +33,7 @@ namespace Quik.OpenGL
}
[MethodImpl(AggressiveInlining)]
public static int CreateShader(GLEnum type) => _createShader(type);
public static int CreateShader(GLEnum type) => _createShader!(type);
[MethodImpl(AggressiveInlining)]
public static void ShaderSource(int shader, string source)
@@ -43,7 +43,7 @@ namespace Quik.OpenGL
fixed (byte* ptr = &sourceUTF8[0])
{
_shaderSource(shader, 1, &ptr, &length);
_shaderSource!(shader, 1, &ptr, &length);
}
}
@@ -69,7 +69,7 @@ namespace Quik.OpenGL
fixed (byte** ptr = &pointers[0])
fixed (int * len = &lengths[0])
{
_shaderSource(shader, count, ptr, len);
_shaderSource!(shader, count, ptr, len);
}
}
finally
@@ -82,14 +82,14 @@ namespace Quik.OpenGL
}
[MethodImpl(AggressiveInlining)]
public static void CompileShader(int shader) => _compileShader(shader);
public static void CompileShader(int shader) => _compileShader!(shader);
[MethodImpl(AggressiveInlining)]
public static void GetShader(int shader, GLEnum pname, out int value)
{
value = default;
fixed (int *ptr = &value)
_getShader(shader, pname, ptr);
_getShader!(shader, pname, ptr);
}
[MethodImpl(AggressiveInlining)]
@@ -99,12 +99,12 @@ namespace Quik.OpenGL
byte[] infoLog = new byte[length];
fixed (byte *ptr = infoLog)
_getShaderInfoLog(shader, length, &length, ptr);
_getShaderInfoLog!(shader, length, &length, ptr);
return Encoding.UTF8.GetString(infoLog);
}
[MethodImpl(AggressiveInlining)]
public static void DeleteShader(int shader) => _deleteShader(shader);
public static void DeleteShader(int shader) => _deleteShader!(shader);
}
}

View File

@@ -1,7 +1,7 @@
using System;
using System.Runtime.CompilerServices;
namespace Quik.OpenGL
namespace Dashboard.OpenGL
{
public unsafe static partial class GL
{
@@ -9,26 +9,30 @@ namespace Quik.OpenGL
private delegate void PixelStoreiProc(GLEnum pname, int param);
private delegate void PixelStorefProc(GLEnum pname, float param);
private delegate void TexImage2DProc(GLEnum target, int level, GLEnum internalFormat, int width, int height, int border, GLEnum format, GLEnum pixelType, void *data);
private delegate void TexImage3DProc(GLEnum target, int level, GLEnum internalFormat, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, void* data);
private delegate void TexSubImage2DProc(GLEnum target, int level, int x, int y, int width, int height, GLEnum format, GLEnum pixelType, void *data);
private delegate void TexSubImage3DProc(GLEnum target, int level, int x, int y, int z, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, void* data);
private delegate void TexParameteriProc(GLEnum target, GLEnum pname, int value);
private delegate void TexParameterfProc(GLEnum target, GLEnum pname, float value);
private delegate void TexParameterivProc(GLEnum target, GLEnum pname, int* value);
private delegate void TexParameterfvProc(GLEnum target, GLEnum pname, float* value);
private delegate void GenerateMipmapProc(GLEnum target);
private static GenObjectsProc _genTextures;
private static GenObjectsProc _deleteTextures;
private static BindSlottedProc _bindTexture;
private static ActiveTextureProc _activeTexture;
private static PixelStoreiProc _pixelStorei;
private static PixelStorefProc _pixelStoref;
private static TexImage2DProc _texImage2D;
private static TexSubImage2DProc _texSubImage2D;
private static TexParameteriProc _texParameteri;
private static TexParameterfProc _texParameterf;
private static TexParameterivProc _texParameteriv;
private static TexParameterfvProc _texParameterfv;
private static GenerateMipmapProc _generateMipmap;
private static GenObjectsProc? _genTextures;
private static GenObjectsProc? _deleteTextures;
private static BindSlottedProc? _bindTexture;
private static ActiveTextureProc? _activeTexture;
private static PixelStoreiProc? _pixelStorei;
private static PixelStorefProc? _pixelStoref;
private static TexImage2DProc? _texImage2D;
private static TexImage3DProc? _texImage3D;
private static TexSubImage2DProc? _texSubImage2D;
private static TexSubImage3DProc? _texSubImage3D;
private static TexParameteriProc? _texParameteri;
private static TexParameterfProc? _texParameterf;
private static TexParameterivProc? _texParameteriv;
private static TexParameterfvProc? _texParameterfv;
private static GenerateMipmapProc? _generateMipmap;
private static void LoadTexture()
{
@@ -39,7 +43,9 @@ namespace Quik.OpenGL
_pixelStorei = GetProcAddress<PixelStoreiProc>("glPixelStorei");
_pixelStoref = GetProcAddress<PixelStorefProc>("glPixelStoref");
_texImage2D = GetProcAddress<TexImage2DProc>("glTexImage2D");
_texImage3D = GetProcAddress<TexImage3DProc>("glTexImage3D");
_texSubImage2D = GetProcAddress<TexSubImage2DProc>("glTexSubImage2D");
_texSubImage3D = GetProcAddress<TexSubImage3DProc>("glTexSubImage3D");
_texParameteri = GetProcAddress<TexParameteriProc>("glTexParameteri");
_texParameterf = GetProcAddress<TexParameterfProc>("glTexParameterf");
_texParameteriv = GetProcAddress<TexParameterivProc>("glTexParameteriv");
@@ -51,7 +57,7 @@ namespace Quik.OpenGL
public static void GenTextures(int count, out int textures)
{
fixed (int *ptr = &textures)
_genTextures(count, ptr);
_genTextures!(count, ptr);
}
[MethodImpl(AggressiveInlining)]
public static int GenTexture()
@@ -66,7 +72,7 @@ namespace Quik.OpenGL
public static void DeleteTextures(int count, in int textures)
{
fixed (int* ptr = &textures)
_deleteTextures(count, ptr);
_deleteTextures!(count, ptr);
}
[MethodImpl(AggressiveInlining)]
@@ -76,41 +82,58 @@ namespace Quik.OpenGL
public static void DeleteTextures(int[] textures) => DeleteTextures(textures.Length, in textures[0]);
[MethodImpl(AggressiveInlining)]
public static void BindTexture(GLEnum target, int texture) => _bindTexture(target, texture);
public static void BindTexture(GLEnum target, int texture) => _bindTexture!(target, texture);
[MethodImpl(AggressiveInlining)]
public static void ActiveTexture(GLEnum unit) => _activeTexture(unit);
public static void ActiveTexture(GLEnum unit) => _activeTexture!(unit);
[MethodImpl(AggressiveInlining)]
public static void PixelStore(GLEnum pname, int value) => _pixelStorei(pname, value);
public static void PixelStore(GLEnum pname, int value) => _pixelStorei!(pname, value);
[MethodImpl(AggressiveInlining)]
public static void PixelStore(GLEnum pname, float value) => _pixelStoref(pname, value);
public static void PixelStore(GLEnum pname, float value) => _pixelStoref!(pname, value);
[MethodImpl(AggressiveInlining)]
public static void TexImage2D(GLEnum target, int level, GLEnum internalFormat, int width, int height, int border, GLEnum format, GLEnum pixelType, IntPtr data) =>
_texImage2D(target, level, internalFormat, width, height, border, format, pixelType, (void*)data);
_texImage2D!(target, level, internalFormat, width, height, border, format, pixelType, (void*)data);
[MethodImpl(AggressiveInlining)]
public static void TexImage2d<T>(GLEnum target, int level, GLEnum internalFormat, int width, int height, int border, GLEnum format, GLEnum pixelType, in T data) where T : unmanaged
public static void TexImage2D<T>(GLEnum target, int level, GLEnum internalFormat, int width, int height, int border, GLEnum format, GLEnum pixelType, in T data) where T : unmanaged
{
fixed(T *ptr = &data)
_texImage2D(target, level, internalFormat, width, height, border, format, pixelType, ptr);
_texImage2D!(target, level, internalFormat, width, height, border, format, pixelType, ptr);
}
[MethodImpl(AggressiveInlining)]
public static void TexImage2D<T>(GLEnum target, int level, GLEnum internalFormat, int width, int height, int border, GLEnum format, GLEnum pixelType, T[] data) where T : unmanaged =>
TexImage2d<T>(target, level, internalFormat, width, height, border, format, pixelType, in data[0]);
TexImage2D(target, level, internalFormat, width, height, border, format, pixelType, in data[0]);
[MethodImpl(AggressiveInlining)]
public static void TexImage3D(GLEnum target, int level, GLEnum internalFormat, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, void* data) =>
_texImage3D!(target, level, internalFormat, width, height, depth, border, format, pixelType, data);
[MethodImpl(AggressiveInlining)]
public static void TexImage3D<T>(GLEnum target, int level, GLEnum internalFormat, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, in T data)
where T : unmanaged
{
fixed (T* ptr = &data)
_texImage3D!(target, level, internalFormat, width, height, depth, border, format, pixelType, ptr);
}
[MethodImpl(AggressiveInlining)]
public static void TexImage3D<T>(GLEnum target, int level, GLEnum internalFormat, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, T[] data)
where T : unmanaged =>
TexImage3D(target, level, internalFormat, width, height, depth, border, format, pixelType, in data[0]);
[MethodImpl(AggressiveInlining)]
public static void TexSubImage2D(GLEnum target, int level, int x, int y, int width, int height, GLEnum format, GLEnum pixelType, IntPtr data) =>
_texSubImage2D(target, level, x, y, width, height,format, pixelType, (void*)data);
_texSubImage2D!(target, level, x, y, width, height,format, pixelType, (void*)data);
[MethodImpl(AggressiveInlining)]
public static void TexSubImage2d<T>(GLEnum target, int level, int x, int y, int width, int height, GLEnum format, GLEnum pixelType, in T data) where T : unmanaged
{
fixed(T *ptr = &data)
_texSubImage2D(target, level, x, y, width, height, format, pixelType, ptr);
_texSubImage2D!(target, level, x, y, width, height, format, pixelType, ptr);
}
[MethodImpl(AggressiveInlining)]
@@ -118,16 +141,33 @@ namespace Quik.OpenGL
TexSubImage2d<T>(target, level, x, y, width, height, format, pixelType, in data[0]);
[MethodImpl(AggressiveInlining)]
public static void TexParameter(GLEnum target, GLEnum pname, int value) => _texParameteri(target, pname, value);
public static void TexSubImage3D(GLEnum target, int level, int x, int y, int z, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, void* data) =>
_texSubImage3D!(target, level, x, y, z, width, height, depth, border, format, pixelType, data);
[MethodImpl(AggressiveInlining)]
public static void TexParameter(GLEnum target, GLEnum pname, GLEnum value) => _texParameteri(target, pname, (int)value);
public static void TexSubImage3D<T>(GLEnum target, int level, int x, int y, int z, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, in T data)
where T : unmanaged
{
fixed (T* ptr = &data)
_texSubImage3D!(target, level, x, y, z, width, height, depth, border, format, pixelType, ptr);
}
[MethodImpl(AggressiveInlining)]
public static void TexParameter(GLEnum target, GLEnum pname, float value) => _texParameterf(target, pname, value);
public static void TexSubImage3D<T>(GLEnum target, int level, int x, int y, int z, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, T[] data)
where T : unmanaged =>
TexSubImage3D(target, level, x, y, z, width, height, depth, border, format, pixelType, in data[0]);
[MethodImpl(AggressiveInlining)]
public static void TexParameter(GLEnum target, GLEnum pname, int value) => _texParameteri!(target, pname, value);
[MethodImpl(AggressiveInlining)]
public static void TexParameter(GLEnum target, GLEnum pname, GLEnum value) => _texParameteri!(target, pname, (int)value);
[MethodImpl(AggressiveInlining)]
public static void TexParameter(GLEnum target, GLEnum pname, float value) => _texParameterf!(target, pname, value);
[MethodImpl(AggressiveInlining)]
public static void TexParameter(GLEnum target, GLEnum pname, ref int values)
{
fixed (int *ptr = &values)
_texParameteriv(target, pname, ptr);
_texParameteriv!(target, pname, ptr);
}
[MethodImpl(AggressiveInlining)]
public static void TexParameter(GLEnum target, GLEnum pname, int[] values) => TexParameter(target, pname, ref values[0]);
@@ -136,12 +176,12 @@ namespace Quik.OpenGL
public static void TexParameter(GLEnum target, GLEnum pname, ref float values)
{
fixed (float *ptr = &values)
_texParameterfv(target, pname, ptr);
_texParameterfv!(target, pname, ptr);
}
[MethodImpl(AggressiveInlining)]
public static void TexParameter(GLEnum target, GLEnum pname, float[] values) => TexParameter(target, pname, ref values[0]);
[MethodImpl(AggressiveInlining)]
public static void GenerateMipmap(GLEnum target) => _generateMipmap(target);
public static void GenerateMipmap(GLEnum target) => _generateMipmap!(target);
}
}

View File

@@ -1,6 +1,6 @@
using System.Runtime.CompilerServices;
namespace Quik.OpenGL
namespace Dashboard.OpenGL
{
public unsafe static partial class GL
{
@@ -16,25 +16,25 @@ namespace Quik.OpenGL
private delegate void UniformNivProc(int location, int count, int* value);
private delegate void UniformMatrixNxNfvProc(int location, int count, bool transpose, float *value);
private static Uniform1fProc _uniform1f;
private static Uniform2fProc _uniform2f;
private static Uniform3fProc _uniform3f;
private static Uniform4fProc _uniform4f;
private static Uniform1iProc _uniform1i;
private static Uniform2iProc _uniform2i;
private static Uniform3iProc _uniform3i;
private static Uniform4iProc _uniform4i;
private static UniformNfvProc _uniform1fv;
private static UniformNfvProc _uniform2fv;
private static UniformNfvProc _uniform3fv;
private static UniformNfvProc _uniform4fv;
private static UniformNivProc _uniform1iv;
private static UniformNivProc _uniform2iv;
private static UniformNivProc _uniform3iv;
private static UniformNivProc _uniform4iv;
private static UniformMatrixNxNfvProc _uniformMatrix2fv;
private static UniformMatrixNxNfvProc _uniformMatrix3fv;
private static UniformMatrixNxNfvProc _uniformMatrix4fv;
private static Uniform1fProc? _uniform1f;
private static Uniform2fProc? _uniform2f;
private static Uniform3fProc? _uniform3f;
private static Uniform4fProc? _uniform4f;
private static Uniform1iProc? _uniform1i;
private static Uniform2iProc? _uniform2i;
private static Uniform3iProc? _uniform3i;
private static Uniform4iProc? _uniform4i;
private static UniformNfvProc? _uniform1fv;
private static UniformNfvProc? _uniform2fv;
private static UniformNfvProc? _uniform3fv;
private static UniformNfvProc? _uniform4fv;
private static UniformNivProc? _uniform1iv;
private static UniformNivProc? _uniform2iv;
private static UniformNivProc? _uniform3iv;
private static UniformNivProc? _uniform4iv;
private static UniformMatrixNxNfvProc? _uniformMatrix2fv;
private static UniformMatrixNxNfvProc? _uniformMatrix3fv;
private static UniformMatrixNxNfvProc? _uniformMatrix4fv;
public static void LoadUniform()
{
@@ -62,161 +62,161 @@ namespace Quik.OpenGL
[MethodImpl(AggressiveInlining)]
public static void Uniform1(int location, float x)
{
_uniform1f(location, x);
_uniform1f!(location, x);
}
[MethodImpl(AggressiveInlining)]
public static void Uniform2(int location, float x, float y)
{
_uniform2f(location, x, y);
_uniform2f!(location, x, y);
}
[MethodImpl(AggressiveInlining)]
public static void Uniform3(int location, float x, float y, float z)
{
_uniform3f(location, x, y, z);
_uniform3f!(location, x, y, z);
}
[MethodImpl(AggressiveInlining)]
public static void Uniform4(int location, float x, float y, float z, float w)
{
_uniform4f(location, x, y, z, w);
_uniform4f!(location, x, y, z, w);
}
[MethodImpl(AggressiveInlining)]
public static void Uniform1(int location, int x)
{
_uniform1i(location, x);
_uniform1i!(location, x);
}
[MethodImpl(AggressiveInlining)]
public static void Uniform2(int location, int x, int y)
{
_uniform2i(location, x, y);
_uniform2i!(location, x, y);
}
[MethodImpl(AggressiveInlining)]
public static void Uniform3(int location, int x, int y, int z)
{
_uniform3i(location, x, y, z);
_uniform3i!(location, x, y, z);
}
[MethodImpl(AggressiveInlining)]
public static void Uniform4(int location, int x, int y, int z, int w)
{
_uniform4i(location, x, y, z, w);
_uniform4i!(location, x, y, z, w);
}
[MethodImpl(AggressiveInlining)]
public static void Uniform1(int location, int count, ref float first)
{
fixed(float *ptr = &first)
_uniform1fv(location, count, ptr);
_uniform1fv!(location, count, ptr);
}
[MethodImpl(AggressiveInlining)]
public static void Uniform2(int location, int count, ref float first)
{
fixed(float *ptr = &first)
_uniform2fv(location, count, ptr);
_uniform2fv!(location, count, ptr);
}
[MethodImpl(AggressiveInlining)]
public static void Uniform3(int location, int count, ref float first)
{
fixed(float *ptr = &first)
_uniform3fv(location, count, ptr);
_uniform3fv!(location, count, ptr);
}
[MethodImpl(AggressiveInlining)]
public static void Uniform4(int location, int count, ref float first)
{
fixed(float *ptr = &first)
_uniform4fv(location, count, ptr);
_uniform4fv!(location, count, ptr);
}
[MethodImpl(AggressiveInlining)]
public static void Uniform1(int location, int count, ref int first)
{
fixed(int *ptr = &first)
_uniform1iv(location, count, ptr);
_uniform1iv!(location, count, ptr);
}
[MethodImpl(AggressiveInlining)]
public static void Uniform2(int location, int count, ref int first)
{
fixed(int *ptr = &first)
_uniform2iv(location, count, ptr);
_uniform2iv!(location, count, ptr);
}
[MethodImpl(AggressiveInlining)]
public static void Uniform3(int location, int count, ref int first)
{
fixed(int *ptr = &first)
_uniform3iv(location, count, ptr);
_uniform3iv!(location, count, ptr);
}
[MethodImpl(AggressiveInlining)]
public static void Uniform4(int location, int count, ref int first)
{
fixed(int *ptr = &first)
_uniform4iv(location, count, ptr);
_uniform4iv!(location, count, ptr);
}
[MethodImpl(AggressiveInlining)]
public static void UniformMatrix2(int location, bool transpose, ref float m11)
{
fixed (float* ptr = &m11)
_uniformMatrix2fv(location, 1, transpose, ptr);
_uniformMatrix2fv!(location, 1, transpose, ptr);
}
[MethodImpl(AggressiveInlining)]
public static void UniformMatrix3(int location, bool transpose, ref float m11)
{
fixed (float* ptr = &m11)
_uniformMatrix3fv(location, 1, transpose, ptr);
_uniformMatrix3fv!(location, 1, transpose, ptr);
}
[MethodImpl(AggressiveInlining)]
public static void UniformMatrix4(int location, bool transpose, ref float m11)
{
fixed (float* ptr = &m11)
_uniformMatrix4fv(location, 1, transpose, ptr);
_uniformMatrix4fv!(location, 1, transpose, ptr);
}
[MethodImpl(AggressiveInlining)]
public static void UniformMatrix2(int location, int count, bool transpose, ref float m11)
{
fixed (float* ptr = &m11)
_uniformMatrix2fv(location, count, transpose, ptr);
_uniformMatrix2fv!(location, count, transpose, ptr);
}
[MethodImpl(AggressiveInlining)]
public static void UniformMatrix3(int location, int count, bool transpose, ref float m11)
{
fixed (float* ptr = &m11)
_uniformMatrix3fv(location, count, transpose, ptr);
_uniformMatrix3fv!(location, count, transpose, ptr);
}
[MethodImpl(AggressiveInlining)]
public static void UniformMatrix4(int location, int count, bool transpose, ref float m11)
{
fixed (float* ptr = &m11)
_uniformMatrix4fv(location, count, transpose, ptr);
_uniformMatrix4fv!(location, count, transpose, ptr);
}
[MethodImpl(AggressiveInlining)]
public static void UniformMatrix4(int location, bool transpose, in QMat4 m4)
{
fixed (float* ptr = &m4.M11)
_uniformMatrix4fv(location, 1, transpose, ptr);
_uniformMatrix4fv!(location, 1, transpose, ptr);
}
[MethodImpl(AggressiveInlining)]
public static void UniformMatrix4(int location, int count, bool transpose, ref QMat4 m4)
{
fixed (float* ptr = &m4.M11)
_uniformMatrix4fv(location, count, transpose, ptr);
_uniformMatrix4fv!(location, count, transpose, ptr);
}
}
}

View File

@@ -1,7 +1,7 @@
using System;
using System.Runtime.CompilerServices;
namespace Quik.OpenGL
namespace Dashboard.OpenGL
{
public unsafe static partial class GL
{
@@ -9,13 +9,13 @@ namespace Quik.OpenGL
private delegate void VertexAttribPointerProc(int location, int size, GLEnum type, bool normalized, int stride, IntPtr offset);
private delegate void VertexAttribIPointerProc(int location, int size, GLEnum type, int stride, IntPtr offset);
private static GenObjectsProc _genVertexArrays;
private static GenObjectsProc _deleteVertexArrays;
private static BindObjectProc _bindVertexArray;
private static EnableVertexAttribArrayProc _enableVertexAttribArray;
private static EnableVertexAttribArrayProc _disableVertexAttribArray;
private static VertexAttribPointerProc _vertexAttribPointer;
private static VertexAttribIPointerProc _vertexAttribIPointer;
private static GenObjectsProc? _genVertexArrays;
private static GenObjectsProc? _deleteVertexArrays;
private static BindObjectProc? _bindVertexArray;
private static EnableVertexAttribArrayProc? _enableVertexAttribArray;
private static EnableVertexAttribArrayProc? _disableVertexAttribArray;
private static VertexAttribPointerProc? _vertexAttribPointer;
private static VertexAttribIPointerProc? _vertexAttribIPointer;
private static void LoadVertexArrays()
{
@@ -32,7 +32,7 @@ namespace Quik.OpenGL
public static void GenVertexArrays(int count, out int vertexArrays)
{
fixed (int *ptr = &vertexArrays)
_genVertexArrays(count, ptr);
_genVertexArrays!(count, ptr);
}
[MethodImpl(AggressiveInlining)]
@@ -49,7 +49,7 @@ namespace Quik.OpenGL
public static void DeleteVertexArrays(int count, ref int vertexArrays)
{
fixed (int *ptr = &vertexArrays)
_deleteVertexArrays(count, ptr);
_deleteVertexArrays!(count, ptr);
}
[MethodImpl(AggressiveInlining)]
@@ -59,20 +59,20 @@ namespace Quik.OpenGL
public static void DeleteVertexArray(int vertexArray) => DeleteVertexArrays(1, ref vertexArray);
[MethodImpl(AggressiveInlining)]
public static void BindVertexArray(int vertexArray) => _bindVertexArray(vertexArray);
public static void BindVertexArray(int vertexArray) => _bindVertexArray!(vertexArray);
[MethodImpl(AggressiveInlining)]
public static void EnableVertexAttribArray(int location) => _enableVertexAttribArray(location);
public static void EnableVertexAttribArray(int location) => _enableVertexAttribArray!(location);
[MethodImpl(AggressiveInlining)]
public static void DisableVertexAttribArray(int location) => _disableVertexAttribArray(location);
public static void DisableVertexAttribArray(int location) => _disableVertexAttribArray!(location);
[MethodImpl(AggressiveInlining)]
public static void VertexAttribPointer(int location, int size, GLEnum type, bool normalized, int stride, int offset) =>
_vertexAttribPointer(location, size, type, normalized, stride, (IntPtr)offset);
_vertexAttribPointer!(location, size, type, normalized, stride, (IntPtr)offset);
[MethodImpl(AggressiveInlining)]
public static void VertexAttribIPointer(int location, int size, GLEnum type, int stride, int offset) =>
_vertexAttribIPointer(location, size, type, stride, (IntPtr)offset);
_vertexAttribIPointer!(location, size, type, stride, (IntPtr)offset);
}
}

View File

@@ -2,7 +2,7 @@ using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Quik.OpenGL
namespace Dashboard.OpenGL
{
public delegate IntPtr GetProcAddressProc(string procName);
@@ -16,31 +16,32 @@ namespace Quik.OpenGL
private delegate void GLI4Proc(int x, int y, int z, int w);
private delegate void GLF4Proc(float x, float y, float z, float w);
private delegate void DrawElementsProc(GLEnum primitive, int size, GLEnum type, void *offset);
private delegate void DrawArraysProc(GLEnum primitive, int size, void *offset);
private delegate void DrawArraysProc(GLEnum primitive, int first, int offset);
private delegate void GetIntegervProc(GLEnum pname, int *data);
private delegate void GetFloatvProc(GLEnum pname, float *data);
private delegate byte* GetStringProc(GLEnum pname);
private const short AggressiveInlining = (short)MethodImplOptions.AggressiveInlining;
private static GetProcAddressProc _getProcAddress;
private static GLEnum1Proc _enable;
private static GLEnum1Proc _disable;
private static GLEnum2Proc _blendFunc;
private static GLEnum1Proc _depthFunc;
private static GLEnum1Proc _clear;
private static GLI4Proc _viewport;
private static GLF4Proc _clearColor;
private static DrawElementsProc _drawElements;
private static DrawArraysProc _drawArrays;
private static GetIntegervProc _getIntegerv;
private static GetFloatvProc _getFloatv;
private static GetStringProc _getString;
private static GetProcAddressProc? _getProcAddress;
private static GLEnum1Proc? _enable;
private static GLEnum1Proc? _disable;
private static GLEnum2Proc? _blendFunc;
private static GLEnum1Proc? _depthFunc;
private static GLEnum1Proc? _clear;
private static GLI4Proc? _viewport;
private static GLI4Proc? _scissor;
private static GLF4Proc? _clearColor;
private static DrawElementsProc? _drawElements;
private static DrawArraysProc? _drawArrays;
private static GetIntegervProc? _getIntegerv;
private static GetFloatvProc? _getFloatv;
private static GetStringProc? _getString;
private static T GetProcAddress<T>(string procName)
where T : Delegate
{
IntPtr funcptr = _getProcAddress(procName);
IntPtr funcptr = _getProcAddress!(procName);
return Marshal.GetDelegateForFunctionPointer<T>(funcptr);
}
@@ -54,6 +55,7 @@ namespace Quik.OpenGL
_depthFunc = GetProcAddress<GLEnum1Proc>("glDepthFunc");
_clear = GetProcAddress<GLEnum1Proc>("glClear");
_viewport = GetProcAddress<GLI4Proc>("glViewport");
_scissor = GetProcAddress<GLI4Proc>("glScissor");
_clearColor = GetProcAddress<GLF4Proc>("glClearColor");
_drawElements = GetProcAddress<DrawElementsProc>("glDrawElements");
_drawArrays = GetProcAddress<DrawArraysProc>("glDrawArrays");
@@ -70,25 +72,27 @@ namespace Quik.OpenGL
}
[MethodImpl(AggressiveInlining)]
public static void Enable(GLEnum cap) => _enable(cap);
public static void Enable(GLEnum cap) => _enable!(cap);
[MethodImpl(AggressiveInlining)]
public static void Disable(GLEnum cap) => _disable(cap);
public static void Disable(GLEnum cap) => _disable!(cap);
[MethodImpl(AggressiveInlining)]
public static void BlendFunc(GLEnum src, GLEnum dst) => _blendFunc(src, dst);
public static void BlendFunc(GLEnum src, GLEnum dst) => _blendFunc!(src, dst);
[MethodImpl(AggressiveInlining)]
public static void DepthFunc(GLEnum func) => _depthFunc(func);
public static void DepthFunc(GLEnum func) => _depthFunc!(func);
[MethodImpl(AggressiveInlining)]
public static void Clear(GLEnum buffer_bits) => _clear(buffer_bits);
public static void Clear(GLEnum buffer_bits) => _clear!(buffer_bits);
[MethodImpl(AggressiveInlining)]
public static void Viewport(int x, int y, int w, int h) => _viewport(x, y, w, h);
public static void Viewport(int x, int y, int w, int h) => _viewport!(x, y, w, h);
[MethodImpl(AggressiveInlining)]
public static void ClearColor(float r, float g, float b, float a) => _clearColor(r, g, b, a);
public static void Scissor(int x, int y, int w, int h) => _scissor!(x, y, w, h);
[MethodImpl(AggressiveInlining)]
public static void ClearColor(float r, float g, float b, float a) => _clearColor!(r, g, b, a);
[MethodImpl(AggressiveInlining)]
public static void DrawElements(GLEnum primitive, int count, GLEnum type, int offset) => _drawElements(primitive, count, type, (void*)offset);
public static void DrawElements(GLEnum primitive, int count, GLEnum type, int offset) => _drawElements!(primitive, count, type, (void*)offset);
[MethodImpl(AggressiveInlining)]
public static void DrawArrays(GLEnum primitive, int count, int offset) => _drawArrays(primitive, count, (void*)offset);
public static void DrawArrays(GLEnum primitive, int offset, int count) => _drawArrays!(primitive, offset, count);
[MethodImpl(AggressiveInlining)]
public static void Get(GLEnum pname, out int value)
@@ -96,7 +100,7 @@ namespace Quik.OpenGL
value = default;
fixed(int* ptr = &value)
{
_getIntegerv(pname, ptr);
_getIntegerv!(pname, ptr);
}
}
@@ -106,7 +110,7 @@ namespace Quik.OpenGL
value = default;
fixed (float* ptr = &value)
{
_getFloatv(pname, ptr);
_getFloatv!(pname, ptr);
}
}
@@ -114,7 +118,7 @@ namespace Quik.OpenGL
public static string GetString(GLEnum pname)
{
int length;
byte* str = _getString(pname);
byte* str = _getString!(pname);
for (length = 0; str[length] == 0 || length < 256; length++);

View File

@@ -0,0 +1,460 @@
using System;
using System.IO;
using System.Collections.Generic;
using Dashboard.VertexGenerator;
using static Dashboard.OpenGL.GLEnum;
using Dashboard.Media;
using System.Linq;
using System.Diagnostics;
namespace Dashboard.OpenGL
{
public class GL21Driver : IDisposable
{
private int program;
private int v2Position;
private int fZIndex;
private int v2TexPos;
private int fTexLayer;
private int v4Color;
private int m4Transforms;
private int fMaxZ;
private int iEnableSdf;
private int iEnableTexture;
private int iAlphaDiscard;
private int fSdfThreshold;
private int tx2d;
private int tx2darray;
private bool isDiposed;
private readonly Dictionary<DrawQueue, DrawData> data = new Dictionary<DrawQueue, DrawData>();
private readonly TextureManager textures = new TextureManager();
public bool IsInit { get; private set; } = false;
public event Action<GL21Driver>? OnGCDispose;
public GL21Driver()
{
}
~GL21Driver()
{
Dispose(false);
}
public void Init()
{
if (IsInit) return;
int vs = CreateShader(GL_VERTEX_SHADER, "Dashboard.res.gl21.vert");
int fs = CreateShader(GL_FRAGMENT_SHADER, "Dashboard.res.gl21.frag");
program = GL.CreateProgram();
GL.AttachShader(program, vs);
GL.AttachShader(program, fs);
GL.LinkProgram(program);
if (CheckProgram(program, out string msg) == false)
{
GraphicsException ex = new GraphicsException("Could not link shader program.");
ex.Data.Add("Program Info Log", msg);
}
GL.DeleteShader(vs);
GL.DeleteShader(fs);
v2Position = GL.GetAttribLocation(program, nameof(v2Position));
fZIndex = GL.GetAttribLocation(program, nameof(fZIndex));
v2TexPos = GL.GetAttribLocation(program, nameof(v2TexPos));
fTexLayer = GL.GetAttribLocation(program, nameof(fTexLayer));
v4Color = GL.GetAttribLocation(program, nameof(v4Color));
m4Transforms = GL.GetUniformLocation(program, nameof(m4Transforms));
fMaxZ = GL.GetUniformLocation(program, nameof(fMaxZ));
fSdfThreshold = GL.GetUniformLocation(program, nameof(fSdfThreshold));
iEnableSdf = GL.GetUniformLocation(program, nameof(iEnableSdf));
iEnableTexture = GL.GetUniformLocation(program, nameof(iEnableTexture));
iAlphaDiscard = GL.GetUniformLocation(program, nameof(iAlphaDiscard));
tx2d = GL.GetUniformLocation(program, nameof(tx2d));
tx2darray = GL.GetUniformLocation(program, nameof(tx2darray));
IsInit = true;
}
private void AssertInit()
{
if (!IsInit) throw new InvalidOperationException("Initialize the driver first.");
}
public void Draw(DrawQueue queue, in QRectangle view)
{
AssertInit();
if (!data.TryGetValue(queue, out DrawData? draw))
{
draw = new DrawData(this, queue);
data.Add(queue, draw);
}
// This already binds the vertex array for me.
draw.PrepareFrame();
QVec2 size = view.Size;
QMat4.Orthographic(out QMat4 viewMatrix, view);
GL.Viewport(0, 0, (int)view.Size.X, (int)view.Size.Y);
GL.UseProgram(program);
GL.Uniform1(fMaxZ, (float)(queue.ZDepth+1));
GL.Uniform1(fSdfThreshold, 0.5f);
GL.Uniform1(tx2d, 0);
GL.Enable(GL_BLEND);
GL.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
GL.Enable(GL_SCISSOR_TEST);
GL.Enable(GL_DEPTH_TEST);
GL.DepthFunc(GL_LESS);
foreach (DrawCall call in queue)
{
GL.Scissor(
(int)MathF.Round(call.Bounds.Min.X),
(int)MathF.Round(size.Y - call.Bounds.Max.Y),
(int)MathF.Round(call.Bounds.Size.X),
(int)MathF.Round(call.Bounds.Size.Y));
QMat4.Translation(out QMat4 modelMatrix, call.Bounds.Min.X, call.Bounds.Min.Y, 0);
QMat4 modelView = viewMatrix * modelMatrix;
GL.UniformMatrix4(m4Transforms, false, in modelView);
GL.ActiveTexture(GL_TEXTURE0);
GL.BindTexture(GL_TEXTURE_2D, 0);
if (call.Texture != null)
{
GL.Uniform1(iEnableSdf, call.Texture.IsSdf ? 1 : 0);
GL.Uniform1(iAlphaDiscard, 1);
if (call.Texture.Depth > 1)
{
GL.Uniform1(iEnableTexture, 3);
GL.ActiveTexture(GL_TEXTURE1);
GL.BindTexture(GL_TEXTURE_2D_ARRAY, textures.GetTexture(call.Texture));
}
else
{
GL.Uniform1(iEnableTexture, 2);
GL.ActiveTexture(GL_TEXTURE0);
GL.BindTexture(GL_TEXTURE_2D, textures.GetTexture(call.Texture));
}
}
else
{
GL.Uniform1(iEnableTexture, 0);
}
GL.DrawElements(GL_TRIANGLES, call.Count, GL_UNSIGNED_INT, sizeof(int)*call.Start);
}
GL.Disable(GL_SCISSOR_TEST);
GL.Disable(GL_DEPTH_TEST);
GL.Disable(GL_BLEND);
}
public void ClearDrawQueue(DrawQueue queue)
{
AssertInit();
if (!data.TryGetValue(queue, out DrawData? draw))
return;
draw.Dispose();
data.Remove(queue);
}
private static int CreateShader(GLEnum type, string name)
{
StreamReader source = new StreamReader(typeof(GL21Driver).Assembly.GetManifestResourceStream(name) ?? throw new Exception("Resource not found."));
string text = source.ReadToEnd();
source.Dispose();
int shader = GL.CreateShader(type);
GL.ShaderSource(shader, text);
GL.CompileShader(shader);
if (CheckShader(shader, out string msg) == false)
{
GraphicsException ex = new GraphicsException($"Failed to compile {type} shader stage.");
ex.Data.Add("Shader Stage", type);
ex.Data.Add("Shader Info Log", msg);
ex.Data.Add("Shader Source", text);
throw ex;
}
return shader;
}
private static bool CheckShader(int shader, out string message)
{
message = string.Empty;
GL.GetShader(shader, GL_COMPILE_STATUS, out int i);
if (i != (int)GL_TRUE)
{
message = GL.GetShaderInfoLog(shader);
return false;
}
return true;
}
private static bool CheckProgram(int program, out string message)
{
message = string.Empty;
GL.GetProgram(program, GL_LINK_STATUS, out int i);
if (i != (int)GL_OK)
{
message = GL.GetProgramInfoLog(program);
return false;
}
return true;
}
private void Dispose(bool disposing)
{
if (isDiposed) return;
if (!IsInit)
{
isDiposed = true;
return;
}
if (!disposing)
{
if (OnGCDispose == null)
{
throw new Exception("This object must strictly be disposed from the owning thread, not GC");
}
else
{
OnGCDispose(this);
return;
}
}
GL.DeleteProgram(program);
foreach (DrawData datum in data.Values)
{
datum.Dispose();
}
isDiposed = true;
GC.SuppressFinalize(this);
}
public void Dispose() => Dispose(true);
private class DrawData : IDisposable
{
public DrawQueue Queue { get; }
public int VertexArray { get; }
private readonly GL21Driver driver;
private int vbo1, vbo2;
private int ebo1, ebo2;
public DrawData(GL21Driver driver, DrawQueue queue)
{
Queue = queue;
this.driver = driver;
VertexArray = GL.GenVertexArray();
GL.GenBuffers(1, out vbo1);
GL.GenBuffers(1, out vbo2);
GL.GenBuffers(1, out ebo1);
GL.GenBuffers(1, out ebo2);
isDisposed = false;
}
public void PrepareFrame()
{
int vbo, ebo;
vbo = Swap(ref vbo1, ref vbo2);
ebo = Swap(ref ebo1, ref ebo2);
if (Queue.VertexCount == 0 || Queue.ElementCount == 0)
return;
GL.BindVertexArray(VertexArray);
GL.BindBuffer(GL_ARRAY_BUFFER, vbo);
GL.BufferData(GL_ARRAY_BUFFER, QuikVertex.Stride * Queue.VertexCount, Queue.VertexArray, GL_STREAM_DRAW);
GL.VertexAttribPointer(driver.v2Position, 2, GL_FLOAT, false, QuikVertex.Stride, QuikVertex.PositionOffset);
GL.VertexAttribPointer(driver.fZIndex, 1, GL_UNSIGNED_INT, false, QuikVertex.Stride, QuikVertex.ZIndexOffset);
GL.VertexAttribPointer(driver.v2TexPos, 2, GL_FLOAT, false, QuikVertex.Stride, QuikVertex.TextureCoordinatesOffset);
GL.VertexAttribPointer(driver.fTexLayer, 1, GL_FLOAT, false, QuikVertex.Stride, QuikVertex.TextureLayerOffset);
GL.VertexAttribPointer(driver.v4Color, 4, GL_UNSIGNED_BYTE, true, QuikVertex.Stride, QuikVertex.ColorOffset);
GL.EnableVertexAttribArray(driver.v2Position);
GL.EnableVertexAttribArray(driver.fZIndex);
GL.EnableVertexAttribArray(driver.v2TexPos);
GL.EnableVertexAttribArray(driver.v4Color);
GL.EnableVertexAttribArray(driver.fTexLayer);
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, Queue.ElementCount * sizeof(int), Queue.ElementArray, GL_STREAM_DRAW);
int Swap(ref int a, ref int b)
{
a ^= b;
b ^= a;
a ^= b;
return a;
}
}
private bool isDisposed;
public void Dispose()
{
if (isDisposed) return;
GL.DeleteVertexArray(VertexArray);
GL.DeleteBuffer(vbo1);
GL.DeleteBuffer(vbo2);
GL.DeleteBuffer(ebo1);
GL.DeleteBuffer(ebo2);
}
}
}
internal class TextureManager : IDisposable
{
private readonly Dictionary<QImage, int> textures = new Dictionary<QImage, int>();
private readonly HashSet<QImage> imagesNotUsed = new HashSet<QImage>();
private bool isDisposed = false;
public void BeginFrame()
{
if (imagesNotUsed.Count > 0)
{
foreach (QImage image in imagesNotUsed)
{
GL.DeleteTexture(textures[image]);
}
imagesNotUsed.Clear();
}
foreach (QImage image in textures.Keys)
{
imagesNotUsed.Add(image);
}
}
public int GetTexture(QImage image)
{
if (textures.TryGetValue(image, out int texture))
{
return texture;
}
if (image.Depth > 1)
{
texture = UploadTexture3d(image);
}
else
{
texture = UploadTexture2d(image);
}
return textures[image] = texture;
}
public int UploadTexture3d(QImage image3d)
{
int texture = GL.GenTexture();
GL.BindTexture(GL_TEXTURE_2D_ARRAY, texture);
image3d.LockBits3d(out QImageLock lck, QImageLockOptions.Default);
GL.TexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, lck.Width, lck.Height, lck.Depth, 0, s_InternalFormat[lck.Format], s_PixelType[lck.Format], lck.ImagePtr);
image3d.UnlockBits();
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
return texture;
}
public int UploadTexture2d(QImage image2d)
{
int texture = GL.GenTexture();
GL.BindTexture(GL_TEXTURE_2D, texture);
image2d.LockBits2d(out QImageLock lck, QImageLockOptions.Default);
GL.TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, lck.Width, lck.Height, 0, s_InternalFormat[lck.Format], s_PixelType[lck.Format], lck.ImagePtr);
image2d.UnlockBits();
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
switch (image2d.InternalFormat)
{
case QImageFormat.RedU8:
case QImageFormat.RedF:
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED);
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_RED);
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ONE);
break;
case QImageFormat.AlphaU8:
case QImageFormat.AlphaF:
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_ONE);
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_ONE);
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_ONE);
GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ALPHA);
break;
}
return texture;
}
public void Dispose()
{
if (isDisposed)
return;
isDisposed = true;
int[] ids = textures.Values.ToArray();
GL.DeleteTextures(ids);
}
private static readonly Dictionary<QImageFormat, GLEnum> s_InternalFormat = new Dictionary<QImageFormat, GLEnum>()
{
[QImageFormat.AlphaF] = GL_ALPHA,
[QImageFormat.AlphaU8] = GL_ALPHA,
[QImageFormat.RedF] = GL_RED,
[QImageFormat.RedU8] = GL_RED,
[QImageFormat.RgbF] = GL_RGB,
[QImageFormat.RgbU8] = GL_RGB,
[QImageFormat.RgbaU8] = GL_RGBA,
[QImageFormat.RgbaF] = GL_RGBA,
};
private static readonly Dictionary<QImageFormat, GLEnum> s_PixelType = new Dictionary<QImageFormat, GLEnum>()
{
[QImageFormat.AlphaF] = GL_FLOAT,
[QImageFormat.RedF] = GL_FLOAT,
[QImageFormat.RgbF] = GL_FLOAT,
[QImageFormat.RgbaF] = GL_FLOAT,
[QImageFormat.AlphaU8] = GL_UNSIGNED_BYTE,
[QImageFormat.RedU8] = GL_UNSIGNED_BYTE,
[QImageFormat.RgbU8] = GL_UNSIGNED_BYTE,
[QImageFormat.RgbaU8] = GL_UNSIGNED_BYTE,
};
}
}

View File

@@ -1,19 +1,27 @@
namespace Quik.OpenGL
namespace Dashboard.OpenGL
{
public enum GLEnum : int
{
GL_OK = 0,
GL_TRUE = 1,
GL_FALSE = 0,
GL_ONE = 1,
GL_ZERO = 0,
GL_MAJOR_VERSION = 0x821B,
GL_MINOR_VERSION = 0x821C,
GL_VENDOR = 0x1F00,
GL_RENDERER = 0x1F01,
GL_VERSION = 0x1F02,
GL_EXTENSIONS = 0x1F03,
GL_MAX_TEXTURE_SIZE = 0x0D33,
GL_MAX_3D_TEXTURE_SIZE = 0x8073,
GL_MAX_ARRAY_TEXTURE_LAYERS = 0x88FF,
GL_MULTISAMPLE = 0x809D,
GL_BLEND = 0x0BE2,
GL_COLOR_BUFFER_BIT = 0x00004000,
GL_DEPTH_BUFFER_BIT = 0x00000100,
GL_SRC_ALPHA = 0x0302,
GL_ONE_MINUS_SRC_ALPHA = 0x0303,
@@ -30,6 +38,8 @@ namespace Quik.OpenGL
GL_FLOAT = 0x1406,
GL_RED = 0x1903,
GL_GREEN = 0x1904,
GL_BLUE = 0x1905,
GL_ALPHA = 0x1906,
GL_RGB = 0x1907,
GL_RGBA = 0x1908,
@@ -48,6 +58,7 @@ namespace Quik.OpenGL
GL_TEXTURE6 = GL_TEXTURE0 + 6,
GL_TEXTURE_2D = 0x0DE1,
GL_TEXTURE_2D_ARRAY = 0x8C1A,
GL_UNPACK_ALIGNMENT = 0x0CF5,
GL_TEXTURE_MAG_FILTER = 0x2800,
@@ -69,5 +80,15 @@ namespace Quik.OpenGL
GL_REPEAT = 0x2901,
GL_TRIANGLES = 0x0004,
GL_SCISSOR_TEST = 0x0C11,
GL_DEPTH_TEST = 0x0B71,
GL_TEXTURE_SWIZZLE_R = 0x8E42,
GL_TEXTURE_SWIZZLE_G = 0x8E43,
GL_TEXTURE_SWIZZLE_B = 0x8E44,
GL_TEXTURE_SWIZZLE_A = 0x8E45,
GL_LESS = 0x0201,
}
}

View File

@@ -1,7 +1,7 @@
using System;
using static Quik.OpenGL.GLEnum;
using static Dashboard.OpenGL.GLEnum;
namespace Quik.OpenGL
namespace Dashboard.OpenGL
{
[System.Serializable]
public class GraphicsException : System.Exception

91
Dashboard/PAL/Dash.cs Normal file
View File

@@ -0,0 +1,91 @@
using Dashboard.CommandMachine;
using Dashboard.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Dashboard.PAL
{
/// <summary>
/// An abstraction layer over the UI input and output.
/// </summary>
public class Dash
{
private readonly IDashHandle handle;
private readonly IDashboardPlatform platform;
public string Title
{
get => platform.PortGetTitle(handle);
set => platform.PortSetTitle(handle, value);
}
public QVec2 Size
{
get => platform.PortGetSize(handle);
set => platform.PortSetSize(handle, value);
}
public QVec2 Position
{
get => platform.PortGetPosition(handle);
set => platform.PortSetPosition(handle, value);
}
public UIBase? UIElement { get; set; }
public bool IsValid => platform.PortIsValid(handle);
public event EventHandler EventRaised
{
add
{
platform.PortSubscribeEvent(handle, value);
}
remove
{
platform.PortUnsubscribeEvent(handle, value);
}
}
public Dash(IDashboardPlatform platform)
{
this.platform = platform;
handle = platform.CreatePort();
}
bool isDisposed = false;
public void Dispose()
{
if (isDisposed) return;
platform.DestroyPort(handle);
isDisposed = true;
}
public void Focus()
{
platform.PortFocus(handle);
}
public void Paint(CommandList? list = null)
{
if (UIElement == null)
return;
list ??= new CommandList();
list.Clear();
UIElement.Bounds = new QRectangle(Size, new QVec2(0,0));
UIElement.Paint(list);
platform.PortPaint(handle, list);
}
public void Show(bool shown = true)
{
platform.PortShow(handle, shown);
}
}
}

View File

@@ -0,0 +1,61 @@
using System;
using Dashboard.CommandMachine;
using Dashboard.Media;
namespace Dashboard.PAL
{
/// <summary>
/// An empty interface to statically type Quik port handles.
/// </summary>
public interface IDashHandle
{
}
/// <summary>
/// The primary primary platform abstraction interface for dashboard hosts.
/// </summary>
public interface IDashboardPlatform : IDisposable
{
/// <summary>
/// The title of the application.
/// </summary>
string? Title { get; set; }
/// <summary>
/// The default icon for the application.
/// </summary>
QImage? Icon { get; set; }
/// <summary>
/// The event raised when an event is received.
/// </summary>
event EventHandler? EventRaised;
/// <summary>
/// Raise the events that have been enqueued.
/// </summary>
/// <param name="block">True to block until a new event arrives.</param>
void ProcessEvents(bool block);
/// <summary>
/// Create a window.
/// </summary>
/// <returns>The window instance.</returns>
IDashHandle CreatePort();
void DestroyPort(IDashHandle port);
string PortGetTitle(IDashHandle port);
void PortSetTitle(IDashHandle port, string title);
QVec2 PortGetSize(IDashHandle port);
void PortSetSize(IDashHandle port, QVec2 size);
QVec2 PortGetPosition(IDashHandle port);
void PortSetPosition(IDashHandle port, QVec2 position);
bool PortIsValid(IDashHandle port);
void PortSubscribeEvent(IDashHandle port, EventHandler handler);
void PortUnsubscribeEvent(IDashHandle port, EventHandler handler);
void PortFocus(IDashHandle port);
void PortShow(IDashHandle port, bool shown = true);
void PortPaint(IDashHandle port, CommandList commands);
void GetMaximumImage(out int width, out int height);
void GetMaximumImage(out int width, out int height, out int depth);
}
}

View File

@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.IO;
using Dashboard.Media.Font;
namespace Dashboard.PAL
{
/// <summary>
/// Flags that effect font search criterea.
/// </summary>
[Flags]
public enum FontMatchCriteria
{
None = 0,
Family = 1 << 0,
Slant = 1 << 1,
Weight = 1 << 2,
Stretch = 1 << 3,
All = Family | Slant | Weight | Stretch,
}
/// <summary>
/// An abstraction over the system font database.
/// </summary>
public interface IFontDataBase
{
/// <summary>
/// All the fonts installed in the system.
/// </summary>
IEnumerable<FontFace> All { get; }
public FontFace Serif => GetSystemFontFace(SystemFontFamily.Serif);
public FontFace Sans => GetSystemFontFace(SystemFontFamily.Sans);
public FontFace Monospace => GetSystemFontFace(SystemFontFamily.Monospace);
public FontFace Cursive => GetSystemFontFace(SystemFontFamily.Cursive);
public FontFace Fantasy => GetSystemFontFace(SystemFontFamily.Fantasy);
/// <summary>
/// Search for the given font face.
/// </summary>
/// <param name="prototype">The font face prototype.</param>
/// <param name="criteria">The match criteria</param>
/// <returns>A list of fonts sorted by the closest match first.</returns>
IEnumerable<FontFace> Search(FontFace prototype, FontMatchCriteria criteria = FontMatchCriteria.All);
/// <summary>
/// Get the font face file info if it exists.
/// </summary>
/// <param name="face">The face to look for.</param>
/// <returns>The file info if it exists.</returns>
FileInfo FontFileInfo(FontFace face);
/// <summary>
/// Open a font face.
/// </summary>
/// <param name="face">The font face to open.</param>
/// <returns>The stream to the font face.</returns>
Stream Open(FontFace face);
/// <summary>
/// Get a system font family.
/// </summary>
/// <param name="family">The family type to look up.</param>
/// <returns>The name of a font in this family.</returns>
FontFace GetSystemFontFace(SystemFontFamily family);
}
}

View File

@@ -0,0 +1,12 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Dashboard.Media;
namespace Dashboard.PAL
{
public interface IFontFactory
{
bool TryOpen(Stream stream, [NotNullWhen(true)] out QFont font);
}
}

View File

@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Dashboard.CommandMachine;
using Dashboard.Controls;
using Dashboard.Media;
using Dashboard.PAL;
using Dashboard.Typography;
namespace Dashboard
{
/// <summary>
/// Main class for Quik applications.
/// </summary>
public class DashboardApplication
{
/// <summary>
/// The application platform driver.
/// </summary>
public IDashboardPlatform Platform { get; }
/// <summary>
/// Title of the application.
/// </summary>
public string? Title
{
get => Platform.Title;
set => Platform.Title = value;
}
/// <summary>
/// Application icon.
/// </summary>
public QImage? Icon
{
get => Platform.Icon;
set => Platform.Icon = value;
}
public PAL.Dash? MainPort { get; private set; } = null;
public FontProvider FontProvider { get; }
/// <summary>
/// List of media loaders, drivers that load media such as images and fonts.
/// </summary>
public List<MediaLoader> MediaLoaders { get; } = new List<MediaLoader>();
public DashboardApplication(IDashboardPlatform platform)
{
Platform = platform;
FontProvider = new FontProvider(this);
Current = this;
}
public IDisposable? GetMedia(object key, MediaHint hint)
{
IDisposable? disposable = null;
foreach (MediaLoader loader in MediaLoaders)
{
disposable = loader.GetMedia(key, hint);
if (disposable != null)
break;
}
return disposable;
}
public IDisposable? GetMedia<T>(T key, MediaHint hint)
{
IDisposable? disposable = null;
foreach (MediaLoader loader in MediaLoaders)
{
if (loader is MediaLoader<T> typedLoader)
{
disposable = typedLoader.GetMedia(key, hint);
if (disposable != null)
break;
}
}
return disposable;
}
private CommandList cmd = new CommandList();
public void Run(View mainView, bool yield = true)
{
Init(mainView);
while (RunSync())
{
if (yield)
{
Thread.Yield();
}
}
}
public void Init(View mainView)
{
MainPort = new PAL.Dash(Platform) { UIElement = mainView };
MainPort.EventRaised += (sender, ea) => mainView.NotifyEvent(sender, ea);
}
public bool RunSync()
{
if (!MainPort!.IsValid)
return false;
Platform.ProcessEvents(false);
if (MainPort.IsValid)
{
cmd.Clear();
MainPort.Paint(cmd);
}
return true;
}
public static DashboardApplication Current { get; private set; } = null!;
public static void SetCurrentApplication(DashboardApplication application)
{
Current = application;
}
}
}

View File

@@ -2,7 +2,7 @@ using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
namespace Quik
namespace Dashboard
{
/// <summary>
/// A 2 dimensional Vector.
@@ -66,7 +66,7 @@ namespace Quik
public static bool operator !=(QVec2 a, QVec2 b) => a.X != b.X || a.Y != b.Y;
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
if (obj is QVec2)
{
@@ -87,6 +87,15 @@ namespace Quik
{
return a.X * b.X + a.Y * b.Y;
}
public override string ToString()
{
return $"({X}; {Y})";
}
public static readonly QVec2 Zero = new QVec2(0, 0);
public static readonly QVec2 UnitX = new QVec2(1, 0);
public static readonly QVec2 UnitY = new QVec2(0, 1);
}
/// <summary>
@@ -335,16 +344,16 @@ namespace Quik
/// <summary>
/// A rectangle.
/// </summary>
[DebuggerDisplay("({Right}, {Top}, {Left}, {Bottom})")]
[DebuggerDisplay("({Left}, {Top}, {Right}, {Bottom})")]
public struct QRectangle
{
/// <summary>
/// Rectangle maximum point.
/// Position maximum point.
/// </summary>
public QVec2 Max;
/// <summary>
/// Rectangle minimum point.
/// Position minimum point.
/// </summary>
public QVec2 Min;
@@ -362,14 +371,14 @@ namespace Quik
public float Top
{
get => Max.Y;
set => Max.Y = value;
get => Min.Y;
set => Min.Y = value;
}
public float Bottom
{
get => Min.Y;
set => Min.Y = value;
get => Max.Y;
set => Max.Y = value;
}
public QVec2 Size
@@ -384,10 +393,10 @@ namespace Quik
Min = min;
}
public QRectangle(float r, float t, float l, float b)
public QRectangle(float r, float b, float l, float t)
{
Max = new QVec2() {X = r, Y = t};
Min = new QVec2() {X = l, Y = b};
Max = new QVec2() {X = r, Y = b};
Min = new QVec2() {X = l, Y = t};
}
public bool Contains(QVec2 point)
@@ -405,11 +414,11 @@ namespace Quik
public static QRectangle Intersect(in QRectangle a, in QRectangle b) =>
new QRectangle(
Math.Min(a.Right, b.Right),
Math.Min(a.Top, b.Top),
Math.Max(a.Left, b.Left),
Math.Max(a.Right, b.Right),
Math.Max(a.Bottom, b.Bottom)
);
,
Math.Min(a.Left, b.Left),
Math.Min(a.Top, b.Top));
}
/// <summary>
@@ -476,9 +485,9 @@ namespace Quik
public static void Translation(out QMat4 mat, float x, float y, float z)
{
mat = Identity;
mat.M41 = x;
mat.M42 = y;
mat.M43 = z;
mat.M14 = x;
mat.M24 = y;
mat.M34 = z;
}
public static void Scale(out QMat4 mat, float x, float y, float z)
@@ -493,20 +502,47 @@ namespace Quik
public static void Orthographic(out QMat4 mat, QRectangle bounds, float near = 1, float far = -1)
{
float a, b, c;
mat = default;
mat = Identity;
a = 1.0f/(bounds.Right - bounds.Left);
b = 1.0f/(bounds.Top - bounds.Bottom);
c = 1.0f/(near - far);
c = 1.0f/(far - near);
mat.M11 = 2 * a;
mat.M22 = 2 * b;
mat.M33 = 2 * c;
mat.M33 = -2 * c;
mat.M41 = -a * (bounds.Left + bounds.Right);
mat.M42 = -b * (bounds.Top + bounds.Bottom);
mat.M43 = -c * (near + far);
mat.M14 = -a * (bounds.Left + bounds.Right);
mat.M24 = -b * (bounds.Top + bounds.Bottom);
mat.M34 = -c * (far + near);
mat.M44 = 1.0f;
}
public static QMat4 operator *(in QMat4 a, in QMat4 b)
{
QMat4 mat4 = default;
mat4.M11 = a.M11 * b.M11 + a.M12 * b.M21 + a.M13 * b.M31 + a.M14 * b.M41;
mat4.M12 = a.M11 * b.M12 + a.M12 * b.M22 + a.M13 * b.M32 + a.M14 * b.M42;
mat4.M13 = a.M11 * b.M13 + a.M12 * b.M23 + a.M13 * b.M33 + a.M14 * b.M43;
mat4.M14 = a.M11 * b.M14 + a.M12 * b.M24 + a.M13 * b.M34 + a.M14 * b.M44;
mat4.M21 = a.M21 * b.M11 + a.M22 * b.M21 + a.M23 * b.M31 + a.M24 * b.M41;
mat4.M22 = a.M21 * b.M12 + a.M22 * b.M22 + a.M23 * b.M32 + a.M24 * b.M42;
mat4.M23 = a.M21 * b.M13 + a.M22 * b.M23 + a.M23 * b.M33 + a.M24 * b.M43;
mat4.M24 = a.M21 * b.M14 + a.M22 * b.M24 + a.M23 * b.M34 + a.M24 * b.M44;
mat4.M31 = a.M31 * b.M11 + a.M32 * b.M21 + a.M33 * b.M31 + a.M34 * b.M41;
mat4.M32 = a.M31 * b.M12 + a.M32 * b.M22 + a.M33 * b.M32 + a.M34 * b.M42;
mat4.M33 = a.M31 * b.M13 + a.M32 * b.M23 + a.M33 * b.M33 + a.M34 * b.M43;
mat4.M34 = a.M31 * b.M14 + a.M32 * b.M24 + a.M33 * b.M34 + a.M34 * b.M44;
mat4.M41 = a.M41 * b.M11 + a.M42 * b.M21 + a.M43 * b.M31 + a.M44 * b.M41;
mat4.M42 = a.M41 * b.M12 + a.M42 * b.M22 + a.M43 * b.M32 + a.M44 * b.M42;
mat4.M43 = a.M41 * b.M13 + a.M42 * b.M23 + a.M43 * b.M33 + a.M44 * b.M43;
mat4.M44 = a.M41 * b.M14 + a.M42 * b.M24 + a.M43 * b.M34 + a.M44 * b.M44;
return mat4;
}
}
}

View File

@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using Quik.Media;
using Quik.Typography;
using Dashboard.Media;
using Dashboard.Media.Font;
namespace Quik
namespace Dashboard
{
public enum TextAlignment
{
@@ -53,7 +53,7 @@ namespace Quik
public abstract class StyleBase
{
public abstract object this[string key] { get; set; }
public abstract object? this[string key] { get; set; }
public QColor? Color
{
@@ -109,9 +109,9 @@ namespace Quik
set => this["list-marker-position"] = value;
}
public QuikTexture ListMarkerImage
public QImage? ListMarkerImage
{
get => (QuikTexture)this["list-marker-image"];
get => (QImage?)this["list-marker-image"];
set => this["list-marker-image"] = value;
}
@@ -127,9 +127,9 @@ namespace Quik
set => this["stroke-color"] = value;
}
public FontInfo Font
public FontFace? Font
{
get => (FontInfo)this["font"];
get => (FontFace?)this["font"];
set => this["font"] = value;
}
@@ -144,9 +144,9 @@ namespace Quik
{
private readonly Dictionary<string, object> _keys = new Dictionary<string, object>();
public override object this[string styleKey]
public override object? this[string styleKey]
{
get => _keys.TryGetValue(styleKey, out object value) ? value : null;
get => _keys.TryGetValue(styleKey, out object? value) ? value : null;
set
{
if (value == null)
@@ -163,11 +163,11 @@ namespace Quik
public Style BaseStyle { get; }
public override object this[string key]
public override object? this[string key]
{
get
{
object value = null;
object? value = null;
for (int i = _styles.Count; i != 0 && value == null; i--)
{

View File

@@ -0,0 +1,108 @@
using Dashboard.Media;
using Dashboard.Media.Font;
using Dashboard.PAL;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
namespace Dashboard.Typography
{
/// <summary>
/// The font provider is a caching object that provides fonts for typesetting classes.
/// </summary>
public class FontProvider : IDisposable
{
private Dictionary<FontFace, QFont> Fonts { get; } = new Dictionary<FontFace, QFont>();
private HashSet<QFont> UsedFonts { get; } = new HashSet<QFont>();
public readonly FontRasterizerOptions RasterizerOptions;
public IFontDataBase? Database { get; set; }
public IFontFactory? FontFactory { get; set; }
private readonly DashboardApplication App;
public QFont this[FontFace info]
{
get
{
if (!Fonts.TryGetValue(info, out QFont? font))
{
using Stream str = Database?.Open(info) ?? throw new Exception("Font could not be found.");
if (FontFactory?.TryOpen(str, out font) ?? false)
{
Fonts.Add(info, font);
}
else
{
throw new Exception("Font not found.");
}
}
UsedFonts.Add(font);
return font;
}
}
public QFont this[SystemFontFamily family]
{
get
{
return this[Database?.GetSystemFontFace(family) ?? throw new Exception("No font database.")];
}
}
public FontProvider(DashboardApplication app, in FontRasterizerOptions options)
{
RasterizerOptions = options;
App = app;
Type? fdb = Type.GetType("Quik.Media.Defaults.FontDataBaseProvider, Quik.Media.Defaults");
if (fdb != null)
{
PropertyInfo? instanceProperty = fdb.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.GetProperty);
if (instanceProperty != null)
{
Database = (IFontDataBase)instanceProperty.GetValue(null)!;
}
}
Type? ffact = Type.GetType("Quik.Media.Defaults.FreeTypeFontFactory, Quik.Media.Defaults");
if (ffact != null)
{
ConstructorInfo? ctor = ffact.GetConstructor(Array.Empty<Type>());
FontFactory = (IFontFactory?)ctor?.Invoke(null);
}
}
public FontProvider(DashboardApplication app)
: this(app, FontRasterizerOptions.Default)
{
}
/// <summary>
/// Tracks the use of fonts used by this typesetter and removes any that haven't been referenced since the last cycle.
/// </summary>
public void Collect()
{
// foreach (FontJar jar in Fonts.Values.ToArray())
// {
// if (!UsedFonts.Contains(jar))
// {
// Fonts.Remove(jar.Info);
// }
// }
// UsedFonts.Clear();
}
private bool isDisposed = false;
public void Dispose()
{
if (isDisposed) return;
isDisposed = true;
foreach (QFont font in Fonts.Values)
{
font.Dispose();
}
}
}
}

View File

@@ -3,9 +3,9 @@ using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Quik.Media;
using Dashboard.Media;
namespace Quik.Typography
namespace Dashboard.Typography
{
/// <summary>
/// An atomic horizontal block of text which cannot be further divided.
@@ -226,7 +226,7 @@ namespace Quik.Typography
pen.Y -= PostSpace;
group.BoundingBox = new QRectangle(width, 0, 0, pen.Y);
group.BoundingBox = new QRectangle(width, pen.Y, 0, 0);
group.Translate(-pen);
}
@@ -353,13 +353,13 @@ namespace Quik.Typography
public struct TypesetCharacter
{
public int Character;
public QuikTexture Texture;
public QImage Texture;
public QRectangle Position;
public QRectangle UV;
public TypesetCharacter(
int chr,
QuikTexture texture,
QImage texture,
in QRectangle position,
in QRectangle uv)
{

View File

@@ -0,0 +1,181 @@
using Dashboard.CommandMachine;
using Dashboard.Media;
using System;
using System.Collections.Generic;
using System.Text;
namespace Dashboard.Typography
{
public static class Typesetter
{
private ref struct LineEnumerator
{
private ReadOnlySpan<char> Entire, Segment;
private bool Final;
public ReadOnlySpan<char> Current => Segment;
public LineEnumerator(ReadOnlySpan<char> value)
{
Entire = value;
Segment = ReadOnlySpan<char>.Empty;
Final = false;
}
public void Reset()
{
Segment = ReadOnlySpan<char>.Empty;
Final = false;
}
public bool MoveNext()
{
if (Final)
{
return false;
}
else if (Segment == ReadOnlySpan<char>.Empty)
{
int index = Entire.IndexOf('\n');
if (index == -1)
{
Segment = Entire;
}
else
{
Segment = Entire.Slice(0, index);
}
return true;
}
else
{
Entire.Overlaps(Segment, out int offset);
if (offset + Segment.Length >= Entire.Length)
{
return false;
}
ReadOnlySpan<char> rest = Entire.Slice(offset + Segment.Length + 1);
int index = rest.IndexOf('\n');
if (index == -1)
{
Segment = rest;
Final = true;
}
else
{
Segment = rest.Slice(0, index);
}
return true;
}
}
}
public static QVec2 MeasureHorizontal(ReadOnlySpan<char> str, float size, QFont font)
{
var enumerator = new LineEnumerator(str);
float width = 0.0f;
float height = 0.0f;
while (enumerator.MoveNext())
{
ReadOnlySpan<char> line = enumerator.Current;
float lineHeight = 0.0f;
foreach (Rune r in line.EnumerateRunes())
{
int codepoint = r.Value;
font.Get(codepoint, size, out FontGlyph glyph);
width += glyph.Metrics.Advance.X;
lineHeight = Math.Max(lineHeight, glyph.Metrics.Size.Y);
}
height += lineHeight;
}
return new QVec2(width, height);
}
public static void TypesetHorizontalDirect(this CommandList list, ReadOnlySpan<char> str, QVec2 origin, float size, QFont font)
{
Dictionary<QImage, FontDrawInfo> drawInfo = new Dictionary<QImage, FontDrawInfo>();
var enumerator = new LineEnumerator(str);
QVec2 pen = origin;
while (enumerator.MoveNext())
{
ReadOnlySpan<char> line = enumerator.Current;
float rise = 0.0f;
float fall = 0.0f;
// Find out all the code pages required, and the line height.
foreach (Rune r in line.EnumerateRunes())
{
int codepoint = r.Value;
font.Get(codepoint, size, out FontGlyph glyph);
float crise = glyph.Metrics.HorizontalBearing.Y;
float cfall = glyph.Metrics.Size.Y - crise;
rise = Math.Max(crise, rise);
fall = Math.Max(cfall, fall);
}
pen += new QVec2(0, rise);
foreach (Rune r in line.EnumerateRunes())
{
FontDrawInfo info;
int codepoint = r.Value;
font.Get(codepoint, size, out FontGlyph glyph);
ref readonly QGlyphMetrics metrics = ref glyph.Metrics;
QImage? image = glyph.Image;
if (image == null)
{
pen += new QVec2(metrics.Advance.X, 0);
continue;
}
if (!drawInfo.TryGetValue(image, out info))
{
info = new FontDrawInfo();
info.Image = image;
info.rectangles = new List<QRectangle>();
drawInfo[image] = info;
}
QRectangle dest = new QRectangle(
pen + new QVec2(metrics.HorizontalBearing.X + metrics.Size.X, metrics.Size.Y - metrics.HorizontalBearing.Y),
pen + new QVec2(metrics.HorizontalBearing.X, -metrics.HorizontalBearing.Y));
info.rectangles.Add(dest);
info.rectangles.Add(glyph.UVs);
pen.X += metrics.Advance.X;
}
pen.X = origin.X;
pen.Y += fall;
}
// Now for each rectangle we can dispatch draw calls.
foreach (FontDrawInfo info in drawInfo.Values)
{
list.Image(info.Image, info.rectangles.ToArray(), true);
}
}
private struct FontDrawInfo
{
public QImage Image;
public List<QRectangle> rectangles;
}
}
}

View File

@@ -1,7 +1,7 @@
using System;
using System.Text;
namespace Quik.Typography
namespace Dashboard.Typography
{
public static class UnicodeUtil
{

View File

@@ -1,9 +1,10 @@
using Dashboard.Media;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Quik.VertexGenerator
namespace Dashboard.VertexGenerator
{
public class DrawQueue : IEnumerable<DrawCall>
{
@@ -13,7 +14,7 @@ namespace Quik.VertexGenerator
private int _start;
private int _baseOffset;
private QRectangle _bounds;
private QuikTexture _texture;
private QImage? _texture;
public int ZDepth { get; private set; }
public QuikVertex[] VertexArray => _vertices.InternalArray;
@@ -32,7 +33,7 @@ namespace Quik.VertexGenerator
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void StartDrawCall(in QRectangle bounds, QuikTexture texture, int baseOffset)
public void StartDrawCall(in QRectangle bounds, QImage? texture, int baseOffset)
{
_start = ElementCount;
_texture = texture;
@@ -47,7 +48,7 @@ namespace Quik.VertexGenerator
public void StartDrawCall(in QRectangle bounds, int baseOffset) => StartDrawCall(bounds, null, baseOffset);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void StartDrawCall(in QRectangle bounds, QuikTexture texture) => StartDrawCall(bounds, texture, _vertices.Count);
public void StartDrawCall(in QRectangle bounds, QImage texture) => StartDrawCall(bounds, texture, _vertices.Count);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddVertex(in QuikVertex vertex)
@@ -99,9 +100,9 @@ namespace Quik.VertexGenerator
public int Start { get; }
public int Count { get; }
public QRectangle Bounds { get; }
public QuikTexture Texture { get; }
public QImage? Texture { get; }
public DrawCall(int start, int count, in QRectangle bounds, QuikTexture texture)
public DrawCall(int start, int count, in QRectangle bounds, QImage? texture)
{
Start = start;
Count = count;

View File

@@ -1,6 +1,6 @@
using System.Diagnostics;
namespace Quik.VertexGenerator
namespace Dashboard.VertexGenerator
{
/// <summary>
/// Represents a GPU vertex.
@@ -28,10 +28,16 @@ namespace Quik.VertexGenerator
/// </summary>
public int ZIndex;
/// <summary>
/// The texture layer to draw for 3d images.
/// </summary>
public float TextureLayer;
public static int PositionOffset => 0;
public static unsafe int TextureCoordinatesOffset => sizeof(QVec2);
public static unsafe int ColorOffset => 2 * sizeof(QVec2);
public static unsafe int ZIndexOffset => ColorOffset + sizeof(QColor);
public static unsafe int TextureLayerOffset => ZIndexOffset + sizeof(int);
public static unsafe int Stride => sizeof(QuikVertex);
}
}

View File

@@ -1,6 +1,6 @@
using System;
namespace Quik.VertexGenerator
namespace Dashboard.VertexGenerator
{
/// <summary>
/// A small list which whose items can be used by reference.

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using Quik.CommandMachine;
using Dashboard.CommandMachine;
using Dashboard.Media;
using Dashboard.Typography;
namespace Quik.VertexGenerator
namespace Dashboard.VertexGenerator
{
public class VertexGeneratorEngine : CommandEngine
{
@@ -13,6 +15,9 @@ namespace Quik.VertexGenerator
/// </summary>
protected float CurveGranularity =>
(Style["-vertex-curve-granularity"] is float value) ? value : 1.0f;
protected bool BlendTextures =>
(Style["-vertex-blend-textures"] is bool value) ? value : false;
protected QuikVertex StrokeVertex => new QuikVertex()
{
ZIndex = Style.ZIndex ?? this.ZIndex,
@@ -24,6 +29,12 @@ namespace Quik.VertexGenerator
Color = Style.Color ?? QColor.White,
};
protected QuikVertex ImageVertex => new QuikVertex()
{
ZIndex = Style.ZIndex ?? this.ZIndex,
Color = BlendTextures ? (Style.Color ?? QColor.White) : QColor.White,
};
public override void Reset()
{
base.Reset();
@@ -39,6 +50,7 @@ namespace Quik.VertexGenerator
case Command.Line: LineProc(queue); break;
case Command.Bezier: BezierProc(queue); break;
case Command.Rectangle: RectangleProc(queue); break;
case Command.Image: ImageProc(queue); break;
default: break;
}
}
@@ -422,8 +434,8 @@ namespace Quik.VertexGenerator
{
QRectangle outer = RectangleList[i];
QRectangle inner = new QRectangle(
outer.Right - stroke, outer.Top - stroke,
outer.Left + stroke, outer.Bottom + stroke);
outer.Right - stroke, outer.Bottom - stroke,
outer.Left + stroke, outer.Top + stroke);
GenerateRectangleBase(inner, Math.Max(radius - stroke, 0.0f));
@@ -469,13 +481,13 @@ namespace Quik.VertexGenerator
QVec2 aPos, bPos, cPos, dPos;
QuikVertex v = FillVertex;
aPos = v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom + radius);
aPos = v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom - radius);
DrawQueue.AddVertex(v);
bPos = v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom + radius);
bPos = v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom - radius);
DrawQueue.AddVertex(v);
cPos = v.Position = new QVec2(rectangle.Right - radius, rectangle.Top - radius);
cPos = v.Position = new QVec2(rectangle.Right - radius, rectangle.Top + radius);
DrawQueue.AddVertex(v);
dPos = v.Position = new QVec2(rectangle.Left + radius, rectangle.Top - radius);
dPos = v.Position = new QVec2(rectangle.Left + radius, rectangle.Top + radius);
DrawQueue.AddVertex(v);
DrawQueue.AddElement(0); DrawQueue.AddElement(1); DrawQueue.AddElement(2);
@@ -496,9 +508,9 @@ namespace Quik.VertexGenerator
// Draw east rectangle.
v.Position = new QVec2(rectangle.Right, rectangle.Bottom + radius);
v.Position = new QVec2(rectangle.Right, rectangle.Bottom - radius);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right, rectangle.Top - radius);
v.Position = new QVec2(rectangle.Right, rectangle.Top + radius);
DrawQueue.AddVertex(v);
DrawQueue.AddElement(1); DrawQueue.AddElement(6); DrawQueue.AddElement(7);
@@ -516,9 +528,9 @@ namespace Quik.VertexGenerator
// Draw west rectangle.
v.Position = new QVec2(rectangle.Left, rectangle.Top - radius);
v.Position = new QVec2(rectangle.Left, rectangle.Top + radius);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left, rectangle.Bottom + radius);
v.Position = new QVec2(rectangle.Left, rectangle.Bottom - radius);
DrawQueue.AddVertex(v);
DrawQueue.AddElement(11); DrawQueue.AddElement(0); DrawQueue.AddElement(3);
@@ -609,13 +621,13 @@ namespace Quik.VertexGenerator
DrawQueue.RestoreOffset();
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Bottom + stroke);
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Bottom - stroke);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Bottom + stroke);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Bottom - stroke);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Top - stroke);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Top + stroke);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Top - stroke);
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Top + stroke);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left, rectangle.Bottom);
@@ -669,13 +681,13 @@ namespace Quik.VertexGenerator
// a-b-c-d
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Bottom + stroke);
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Bottom - stroke);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Bottom + stroke);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Bottom - stroke);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Top - stroke);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Top + stroke);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Top - stroke);
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Top + stroke);
DrawQueue.AddVertex(v);
// ef-gh-ij-kl
@@ -684,24 +696,24 @@ namespace Quik.VertexGenerator
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Bottom);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right, rectangle.Bottom + stroke);
v.Position = new QVec2(rectangle.Right, rectangle.Bottom - stroke);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right, rectangle.Top - stroke);
v.Position = new QVec2(rectangle.Right, rectangle.Top + stroke);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Top);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Top);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left, rectangle.Top - stroke);
v.Position = new QVec2(rectangle.Left, rectangle.Top + stroke);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left, rectangle.Bottom + stroke);
v.Position = new QVec2(rectangle.Left, rectangle.Bottom - stroke);
DrawQueue.AddVertex(v);
// mno
v.Position = new QVec2(rectangle.Left, rectangle.Bottom + radius);
v.Position = new QVec2(rectangle.Left, rectangle.Bottom - radius);
DrawQueue.AddVertex(v);
nPos = v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom + radius);
nPos = v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom - radius);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom);
DrawQueue.AddVertex(v);
@@ -709,15 +721,15 @@ namespace Quik.VertexGenerator
// pqr
v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom);
DrawQueue.AddVertex(v);
qPos = v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom + radius);
qPos = v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom - radius);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right, rectangle.Bottom + radius);
v.Position = new QVec2(rectangle.Right, rectangle.Bottom - radius);
DrawQueue.AddVertex(v);
// stu
v.Position = new QVec2(rectangle.Right, rectangle.Top - radius);
v.Position = new QVec2(rectangle.Right, rectangle.Top + radius);
DrawQueue.AddVertex(v);
tPos = v.Position = new QVec2(rectangle.Right - radius, rectangle.Top - radius);
tPos = v.Position = new QVec2(rectangle.Right - radius, rectangle.Top + radius);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - radius, rectangle.Top);
DrawQueue.AddVertex(v);
@@ -726,9 +738,9 @@ namespace Quik.VertexGenerator
v.Position = new QVec2(rectangle.Left + radius, rectangle.Top);
DrawQueue.AddVertex(v);
wPos = v.Position = new QVec2(rectangle.Left + radius, rectangle.Top - radius);
wPos = v.Position = new QVec2(rectangle.Left + radius, rectangle.Top + radius);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left, rectangle.Top - radius);
v.Position = new QVec2(rectangle.Left, rectangle.Top + radius);
DrawQueue.AddVertex(v);
// E
@@ -859,23 +871,23 @@ namespace Quik.VertexGenerator
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom + stroke);
v.Position = new QVec2(rectangle.Right - radius, rectangle.Bottom - stroke);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom + stroke);
v.Position = new QVec2(rectangle.Left + radius, rectangle.Bottom - stroke);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Bottom + radius);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Bottom - radius);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right, rectangle.Top - radius);
v.Position = new QVec2(rectangle.Right, rectangle.Top + radius);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right, rectangle.Top - radius);
v.Position = new QVec2(rectangle.Right, rectangle.Top + radius);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Bottom + radius);
v.Position = new QVec2(rectangle.Right - stroke, rectangle.Bottom - radius);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left + radius, rectangle.Top - stroke);
v.Position = new QVec2(rectangle.Left + radius, rectangle.Top + stroke);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - radius, rectangle.Top - stroke);
v.Position = new QVec2(rectangle.Right - radius, rectangle.Top + stroke);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Right - radius, rectangle.Top);
DrawQueue.AddVertex(v);
@@ -884,9 +896,9 @@ namespace Quik.VertexGenerator
v.Position = new QVec2(rectangle.Left, rectangle.Bottom + radius);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Top - radius);
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Top + radius);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Top - radius);
v.Position = new QVec2(rectangle.Left + stroke, rectangle.Top + radius);
DrawQueue.AddVertex(v);
v.Position = new QVec2(rectangle.Left, rectangle.Bottom + radius);
DrawQueue.AddVertex(v);
@@ -1000,6 +1012,104 @@ namespace Quik.VertexGenerator
DrawQueue.AddElement(s1); DrawQueue.AddElement(s2); DrawQueue.AddElement(4);
}
private void ImageProc(CommandQueue queue)
{
Frame frame = queue.Dequeue();
ImageCommandFlags flags = (ImageCommandFlags)frame.I1;
QImage image = queue.Dequeue().As<QImage>();
int count = flags.HasFlag(ImageCommandFlags.Single) ? 1 : frame.I2;
if (flags.HasFlag(ImageCommandFlags.Image3d))
{
Image3d(queue, image, count);
}
else
{
Image2d(queue, image, count, flags.HasFlag(ImageCommandFlags.UVs));
}
}
private void Image2d(CommandQueue queue, QImage image, int count, bool uv)
{
DrawQueue.StartDrawCall(Viewport, image);
for (int i = 0; i < count; i++)
{
QRectangle rect = (QRectangle)queue.Dequeue();
QRectangle uvs;
if (uv)
{
uvs = (QRectangle)queue.Dequeue();
}
else
{
uvs = new QRectangle(1, 1, 0, 0);
}
DrawQueue.RestoreOffset();
QuikVertex vertex = ImageVertex;
vertex.Position = new QVec2(rect.Left, rect.Top);
vertex.TextureCoordinates = new QVec2(uvs.Left, uvs.Top);
DrawQueue.AddVertex(vertex);
vertex.Position = new QVec2(rect.Left, rect.Bottom);
vertex.TextureCoordinates = new QVec2(uvs.Left, uvs.Bottom);
DrawQueue.AddVertex(vertex);
vertex.Position = new QVec2(rect.Right, rect.Bottom);
vertex.TextureCoordinates = new QVec2(uvs.Right, uvs.Bottom);
DrawQueue.AddVertex(vertex);
vertex.Position = new QVec2(rect.Right, rect.Top);
vertex.TextureCoordinates = new QVec2(uvs.Right, uvs.Top);
DrawQueue.AddVertex(vertex);
DrawQueue.AddElement(0); DrawQueue.AddElement(2); DrawQueue.AddElement(3);
DrawQueue.AddElement(0); DrawQueue.AddElement(1); DrawQueue.AddElement(2);
}
DrawQueue.EndDrawCall();
}
private void Image3d(CommandQueue queue, QImage image, int count)
{
DrawQueue.StartDrawCall(Viewport, image);
for (int i = 0; i < count; i++)
{
QRectangle rect = (QRectangle)queue.Dequeue();
QRectangle uvs = (QRectangle)queue.Dequeue();
int layer = (int)queue.Dequeue();
DrawQueue.RestoreOffset();
QuikVertex vertex = ImageVertex;
vertex.TextureLayer = layer;
vertex.Position = new QVec2(rect.Top, rect.Left);
vertex.TextureCoordinates = new QVec2(uvs.Top, uvs.Left);
DrawQueue.AddVertex(vertex);
vertex.Position = new QVec2(rect.Bottom, rect.Left);
vertex.TextureCoordinates = new QVec2(uvs.Bottom, uvs.Left);
DrawQueue.AddVertex(vertex);
vertex.Position = new QVec2(rect.Bottom, rect.Right);
vertex.TextureCoordinates = new QVec2(uvs.Bottom, uvs.Right);
DrawQueue.AddVertex(vertex);
vertex.Position = new QVec2(rect.Top, rect.Right);
vertex.TextureCoordinates = new QVec2(uvs.Top, uvs.Right);
DrawQueue.AddVertex(vertex);
DrawQueue.AddElement(0); DrawQueue.AddElement(2); DrawQueue.AddElement(3);
DrawQueue.AddElement(0); DrawQueue.AddElement(1); DrawQueue.AddElement(2);
}
DrawQueue.EndDrawCall();
}
private struct LineInfo
{
public int BaseOffset { get; }

61
Dashboard/res/gl21.frag Normal file
View File

@@ -0,0 +1,61 @@
/**
* QUIK: User Interface Kit
* Copyright (C) 2023 Halit Utku Maden, et al.
*/
#version 130
in vec2 fv2TexPos;
in vec4 fv4Color;
in float ffTexLayer;
out vec4 fragColor;
uniform int iEnableSdf;
uniform int iEnableTexture;
uniform int iAlphaDiscard;
uniform float fSdfThreshold = 0.5;
uniform sampler2D tx2d;
const float fAlphaThreshold = 0.01;
vec4 getTexture()
{
if (iEnableTexture == 3)
{
// return texture(tx2dArray, vec3(fv2TexPos, ffTexLayer));
}
else if (iEnableSdf == 1)
{
vec2 texelSz = 1.0/vec2(textureSize(tx2d, 0));
vec2 txCoord2 = fv2TexPos + texelSz * (1 - mod(fv2TexPos, texelSz));
return texture(tx2d, txCoord2);
}
else
{
return texture(tx2d, fv2TexPos);
}
}
void main(void)
{
vec4 albedo = fv4Color;
if (iEnableTexture != 0)
{
vec4 value = getTexture();
if (iEnableSdf != 0)
{
value = vec4(vec3(1.0), smoothstep(fSdfThreshold-0.1, fSdfThreshold+0.1, value.r));
}
if (iAlphaDiscard != 0 && value.a <= fAlphaThreshold)
{
discard;
}
albedo = albedo * value;
}
fragColor = albedo;
}

35
Dashboard/res/gl21.vert Normal file
View File

@@ -0,0 +1,35 @@
/**
* QUIK: User Interface Kit
* Copyright (C) 2023 Halit Utku Maden, et al.
*/
#version 130
in vec2 v2Position; /**< The vertex position.*/
in float fZIndex; /**< The z index. */
in vec2 v2TexPos; /**< The texture coorindates. */
in float fTexLayer; /**< The texture layer for 3D textures. */
in vec4 v4Color; /**< The vertex color. */
out vec2 fv2TexPos;
out float ffTexLayer;
out vec4 fv4Color;
uniform mat4 m4Transforms; /**< The view matrix. */
uniform float fMaxZ; /**< Highest Z coordinate. */
const mat4 m4BaseTransforms = mat4(
vec4( 2.0, 0.0, 0.0, 0.0),
vec4( 0.0, -2.0, 0.0, 0.0),
vec4( 0.0, 0.0, 1.0, 0.0),
vec4(-1.0, 1.0, 0.0, 1.0)
);
void main(void)
{
vec4 v = vec4(v2Position, fZIndex/fMaxZ, 1);
gl_Position = m4Transforms * v;
fv2TexPos = v2TexPos;
fv4Color = v4Color;
ffTexLayer = fTexLayer;
}

View File

@@ -1,60 +0,0 @@
# This is going to create an environment for you to cross compile all the
# packages needed to build this project.
#
# As always, debian > ubuntu <3
FROM debian:stable-slim
WORKDIR /root
# Download and Install dependencies.
# Install WGET
RUN apt-get update
RUN apt-get install -y sudo wget
# Add the .NET package repository to the repository listing.
RUN wget https://packages.microsoft.com/config/debian/11/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
RUN dpkg -i packages-microsoft-prod.deb
RUN rm packages-microsoft-prod.deb
# APT dependencies.
RUN apt-get update
RUN apt-get install -y \
build-essential \
bzip2 \
cmake \
clang \
cpio \
dotnet-sdk-6.0 \
gcc-arm-linux-gnueabihf \
gcc-aarch64-linux-gnu \
gcc-i686-linux-gnu \
git \
libssl-dev \
libxml2-dev \
lzma-dev \
mingw-w64 \
nuget \
ninja-build \
patch \
python3 \
xz-utils \
zlib1g-dev
# Clone osxcross
# Let's do this later.
# RUN git clone https://github.com/tpoechtrager/osxcross.git osxcross
# Setup interactive shell.
# Setup sudo. Remove password prompt for group "wheel".
RUN echo "%wheel ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/quik_sudo_conf
# Create a default user and switch.
RUN adduser --comment "" --disabled-password quik
USER quik
WORKDIR /home/quik
# Copy bashrc
RUN cp /etc/bash.bashrc ~/.bashrc
RUN echo source $HOME/src/sh/bashrc.sh >> ~/.bashrc
# Execute an interactive shell.
CMD bash

View File

@@ -1,10 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>disable</Nullable>
<LangVersion>7.3</LangVersion>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
</Project>

View File

@@ -1,3 +0,0 @@
#!/bin/bash
cd $(dirname "$0")
../sh/quik_build_native.sh .

View File

@@ -1,36 +0,0 @@
using System;
using System.IO;
using Quik.Media.Color;
namespace Quik.Media.Stb
{
public class QFontStbtt : QFont
{
public override FontInfo Info => throw new NotImplementedException();
public QFontStbtt(Stream source)
{
}
public override QGlyphMetrics[] GetMetricsForPage(int codepage)
{
throw new NotImplementedException();
}
public override QGlyphMetrics GetMetricsForRune(int rune)
{
throw new NotImplementedException();
}
public override bool HasRune(int rune)
{
throw new NotImplementedException();
}
public override QImage RenderPage(int codepage, float resolution, bool sdf)
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,16 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LanguageVersion>7.3</LanguageVersion>
<Nullable>disable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Quik\Quik.csproj" />
<ProjectReference Include="..\Quik.StbImage\Quik.StbImage.csproj" />
<ProjectReference Include="..\Quik.StbTrueType\Quik.StbTrueType.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,59 +0,0 @@
using System;
using System.Collections.Generic;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;
using Quik.Media;
using Quik.OpenGL;
using Quik.PAL;
namespace Quik.OpenTK
{
public class OpenTKPlatform : IQuikPlatform
{
private readonly List<OpenTKPort> _ports = new List<OpenTKPort>();
// These shall remain a sad nop for now.
public string Title { get; set; }
public QImage Icon { get; set; }
public event EventHandler EventRaised;
public NativeWindowSettings DefaultSettings { get; set; } = NativeWindowSettings.Default;
public IReadOnlyList<OpenTKPort> Ports => _ports;
private bool IsGLInitialized = false;
public IQuikPort CreatePort()
{
NativeWindow window = new NativeWindow(DefaultSettings);
OpenTKPort port = new OpenTKPort(window);
_ports.Add(port);
if (!IsGLInitialized)
{
window.Context.MakeCurrent();
GL.LoadBindings((string proc) => GLFW.GetProcAddress(proc));
IsGLInitialized = true;
}
return port;
}
public void Dispose()
{
// FIXME: dispose pattern here!
// Copy the array to prevent collection modification exceptions.
foreach (OpenTKPort port in _ports.ToArray())
{
port.Dispose();
}
}
public void ProcessEvents(bool block)
{
NativeWindow.ProcessWindowEvents(block);
}
}
}

View File

@@ -1,12 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(quik_stbi LANGUAGES C VERSION 1.0)
add_compile_options(-static-libgcc)
add_library(stbi SHARED "quik_stbi.c")
target_include_directories(stbi PRIVATE "../lib")
install(
TARGETS stbi
RUNTIME DESTINATION .
LIBRARY DESTINATION .)

View File

@@ -1,12 +0,0 @@
using System;
namespace Quik.Stb.Image
{
[AttributeUsage(System.AttributeTargets.All, Inherited = false, AllowMultiple = true)]
internal sealed class NativeTypeNameAttribute : System.Attribute
{
public NativeTypeNameAttribute(string typename)
{
}
}
}

View File

@@ -1,30 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>disable</Nullable>
<LangVersion>7.3</LangVersion>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<RuntimeIdentifiers>linux-arm;linux-arm64;linux-x64;win-x86;win-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup>
<!-- Nuget Properties. -->
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageId>Quik.StbImage</PackageId>
<Version>1.0.0</Version>
<Authors>STBI Authors, H. Utku Maden</Authors>
<Description>
A C# wrapper for the ubiquitous Stb Image library.
</Description>
</PropertyGroup>
<ItemGroup>
<Content Include="runtimes/**">
<PackagePath>runtimes</PackagePath>
<Pack>true</Pack>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -1,144 +0,0 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Quik.Stb
{
/// <summary>
/// A class that encompasses all features of stb_image.h in a safe way.
/// </summary>
public unsafe class StbImage : IDisposable
{
private bool isDisposed = false;
/// <summary>
/// Pointer to the image.
/// </summary>
public IntPtr ImagePointer { get; }
/// <summary>
/// Width of the image.
/// </summary>
public int Width { get; }
/// <summary>
/// Height of the image.
/// </summary>
/// <value></value>
public int Height { get; }
/// <summary>
/// Internal image format.
/// </summary>
public StbiImageFormat Format { get; }
public bool IsFloat { get; }
private StbImage(IntPtr image, int x, int y, StbiImageFormat format, bool isFloat)
{
ImagePointer = image;
Width = x;
Height = y;
Format = format;
IsFloat = isFloat;
}
~StbImage()
{
Dispose(false);
}
public void Dispose() => Dispose(true);
private void Dispose(bool disposing)
{
if (isDisposed) return;
if (disposing)
{
GC.SuppressFinalize(this);
}
Stbi.image_free(ImagePointer.ToPointer());
isDisposed = true;
}
/// <summary>
/// Set to flip the y-axis of loaded images on load.
/// </summary>
public static bool FlipVerticallyOnLoad { set => Stbi.set_flip_vertically_on_load(1); }
/// <summary>
/// Set to unpremultiply images on load.
/// </summary>
/// <remarks>
/// According to the stb_image documentation, only iPhone PNG images
/// can come with premultiplied alpha.
/// </remarks>
public static bool UnpremultiplyOnLoad { set => Stbi.set_unpremultiply_on_load(1); }
/// <summary>
/// Try loading an image, without raising exceptions.
/// </summary>
/// <param name="image">The resulting image.</param>
/// <param name="stream">Source stream.</param>
/// <param name="format">The desired image format.</param>
/// <returns>True on success.</returns>
public static bool TryLoad(out StbImage image, Stream stream, StbiImageFormat format = StbiImageFormat.Default, bool isFloat = false)
{
int x, y, iFormat;
StbiStreamWrapper wrapper = new StbiStreamWrapper(stream, true);
wrapper.CreateCallbacks(out stbi_io_callbacks cb);
stream.Position = 0;
IntPtr imagePtr;
if (isFloat)
{
imagePtr = (IntPtr)Stbi.loadf_from_callbacks(&cb, null, &x, &y, &iFormat, (int)format);
}
else
{
imagePtr = (IntPtr)Stbi.load_from_callbacks(&cb, null, &x, &y, &iFormat, (int)format);
}
if (imagePtr != IntPtr.Zero)
{
image = new StbImage(imagePtr, x, y, (StbiImageFormat)iFormat, isFloat);
return true;
}
else
{
image = null;
return false;
}
}
/// <summary>
/// Load an image.
/// </summary>
/// <param name="stream">The stream to load from.</param>
/// <param name="format">The desired image format.</param>
/// <returns>The image object.</returns>
public static StbImage Load(Stream stream, StbiImageFormat format = StbiImageFormat.Default, bool isFloat = false)
{
if (TryLoad(out StbImage image, stream, format, isFloat))
{
return image;
}
string reason = Marshal.PtrToStringUTF8((IntPtr)Stbi.failure_reason());
throw new Exception($"Failed to load image: {reason}");
}
public bool IsLoadable(Stream stream)
{
int x, y, iFormat;
StbiStreamWrapper wrapper = new StbiStreamWrapper(stream, true);
wrapper.CreateCallbacks(out stbi_io_callbacks cb);
stream.Position = 0;
int result = Stbi.info_from_callbacks(&cb, null, &x, &y, &iFormat);
return result != 0;
}
}
}

View File

@@ -1,65 +0,0 @@
using System;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Reflection;
namespace Quik.Stb
{
public unsafe static partial class Stbi
{
private delegate void FailedAssertProc(byte *expression, byte *file, int line, byte *function);
private static readonly string[] LibraryNames = new string[]
{
//FIXME: This is wrong on so many levels, but, i need to do this
// in order to get a change of this running.
"runtimes/win-x64/native/libstbi.dll",
"runtimes/win-x86/native/libstbi.dll",
"runtimes/linux-arm/native/libstbi.so",
"runtimes/linux-arm64/native/libstbi.so",
"runtimes/linux-x64/native/libstbi.so",
"runtimes/native/libstbi.dylib",
"libstbi.dll",
"libstbi.so",
"libstbi.dylib",
};
static Stbi()
{
NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), Resolver);
quik_stbi_failed_assert_store(Marshal.GetFunctionPointerForDelegate<FailedAssertProc>(FailedAssert));
}
private static IntPtr Resolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
if (libraryName != "stbi")
return IntPtr.Zero;
foreach (string name in LibraryNames)
{
if (NativeLibrary.TryLoad(name, assembly, searchPath, out IntPtr handle))
{
return handle;
}
}
return NativeLibrary.Load(libraryName);
}
private static void FailedAssert(byte *expression, byte *file, int line, byte *function)
{
string expr = expression == null ? string.Empty : Marshal.PtrToStringUTF8((IntPtr)expression);
string f = file == null ? string.Empty : Marshal.PtrToStringUTF8((IntPtr)file);
string func = function == null ? string.Empty : Marshal.PtrToStringUTF8((IntPtr)function);
Exception ex = new Exception("Assert failed in native stbi code.");
ex.Data.Add("Expression", expr);
ex.Data.Add("File", f);
ex.Data.Add("Line", line);
ex.Data.Add("Function", func);
throw ex;
}
}
}

View File

@@ -1,177 +0,0 @@
using System;
using System.Runtime.InteropServices;
using Quik.Stb.Image;
namespace Quik.Stb
{
[NativeTypeName("unsigned int")]
public enum StbiEnum : uint
{
STBI_default = 0,
STBI_grey = 1,
STBI_grey_alpha = 2,
STBI_rgb = 3,
STBI_rgb_alpha = 4,
}
public partial struct stbi_io_callbacks
{
[NativeTypeName("int (*)(void *, char *, int)")]
public IntPtr read;
[NativeTypeName("void (*)(void *, int)")]
public IntPtr skip;
[NativeTypeName("int (*)(void *)")]
public IntPtr eof;
}
public static unsafe partial class Stbi
{
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void quik_stbi_failed_assert_store([NativeTypeName("quik_failed_assert_cb_t")] IntPtr cb);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_load_from_memory", ExactSpelling = true)]
[return: NativeTypeName("stbi_uc *")]
public static extern byte* load_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_load_from_callbacks", ExactSpelling = true)]
[return: NativeTypeName("stbi_uc *")]
public static extern byte* load_from_callbacks([NativeTypeName("const stbi_io_callbacks *")] stbi_io_callbacks* clbk, void* user, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_load", ExactSpelling = true)]
[return: NativeTypeName("stbi_uc *")]
public static extern byte* load([NativeTypeName("const char *")] sbyte* filename, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_load_from_file", ExactSpelling = true)]
[return: NativeTypeName("stbi_uc *")]
public static extern byte* load_from_file([NativeTypeName("FILE *")] void* f, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_load_gif_from_memory", ExactSpelling = true)]
[return: NativeTypeName("stbi_uc *")]
public static extern byte* load_gif_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len, int** delays, int* x, int* y, int* z, int* comp, int req_comp);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_load_16_from_memory", ExactSpelling = true)]
[return: NativeTypeName("stbi_us *")]
public static extern ushort* load_16_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_load_16_from_callbacks", ExactSpelling = true)]
[return: NativeTypeName("stbi_us *")]
public static extern ushort* load_16_from_callbacks([NativeTypeName("const stbi_io_callbacks *")] stbi_io_callbacks* clbk, void* user, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_load_16", ExactSpelling = true)]
[return: NativeTypeName("stbi_us *")]
public static extern ushort* load_16([NativeTypeName("const char *")] sbyte* filename, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_load_from_file_16", ExactSpelling = true)]
[return: NativeTypeName("stbi_us *")]
public static extern ushort* load_from_file_16([NativeTypeName("FILE *")] void* f, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_loadf_from_memory", ExactSpelling = true)]
public static extern float* loadf_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_loadf_from_callbacks", ExactSpelling = true)]
public static extern float* loadf_from_callbacks([NativeTypeName("const stbi_io_callbacks *")] stbi_io_callbacks* clbk, void* user, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_loadf", ExactSpelling = true)]
public static extern float* loadf([NativeTypeName("const char *")] sbyte* filename, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_loadf_from_file", ExactSpelling = true)]
public static extern float* loadf_from_file([NativeTypeName("FILE *")] void* f, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_hdr_to_ldr_gamma", ExactSpelling = true)]
public static extern void hdr_to_ldr_gamma(float gamma);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_hdr_to_ldr_scale", ExactSpelling = true)]
public static extern void hdr_to_ldr_scale(float scale);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_ldr_to_hdr_gamma", ExactSpelling = true)]
public static extern void ldr_to_hdr_gamma(float gamma);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_ldr_to_hdr_scale", ExactSpelling = true)]
public static extern void ldr_to_hdr_scale(float scale);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_is_hdr_from_callbacks", ExactSpelling = true)]
public static extern int is_hdr_from_callbacks([NativeTypeName("const stbi_io_callbacks *")] stbi_io_callbacks* clbk, void* user);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_is_hdr_from_memory", ExactSpelling = true)]
public static extern int is_hdr_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_is_hdr", ExactSpelling = true)]
public static extern int is_hdr([NativeTypeName("const char *")] sbyte* filename);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_is_hdr_from_file", ExactSpelling = true)]
public static extern int is_hdr_from_file([NativeTypeName("FILE *")] void* f);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_failure_reason", ExactSpelling = true)]
[return: NativeTypeName("const char *")]
public static extern sbyte* failure_reason();
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_image_free", ExactSpelling = true)]
public static extern void image_free(void* retval_from_stbi_load);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_info_from_memory", ExactSpelling = true)]
public static extern int info_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len, int* x, int* y, int* comp);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_info_from_callbacks", ExactSpelling = true)]
public static extern int info_from_callbacks([NativeTypeName("const stbi_io_callbacks *")] stbi_io_callbacks* clbk, void* user, int* x, int* y, int* comp);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_is_16_bit_from_memory", ExactSpelling = true)]
public static extern int is_16_bit_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_is_16_bit_from_callbacks", ExactSpelling = true)]
public static extern int is_16_bit_from_callbacks([NativeTypeName("const stbi_io_callbacks *")] stbi_io_callbacks* clbk, void* user);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_info", ExactSpelling = true)]
public static extern int info([NativeTypeName("const char *")] sbyte* filename, int* x, int* y, int* comp);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_info_from_file", ExactSpelling = true)]
public static extern int info_from_file([NativeTypeName("FILE *")] void* f, int* x, int* y, int* comp);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_is_16_bit", ExactSpelling = true)]
public static extern int is_16_bit([NativeTypeName("const char *")] sbyte* filename);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_is_16_bit_from_file", ExactSpelling = true)]
public static extern int is_16_bit_from_file([NativeTypeName("FILE *")] void* f);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_set_unpremultiply_on_load", ExactSpelling = true)]
public static extern void set_unpremultiply_on_load(int flag_true_if_should_unpremultiply);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_convert_iphone_png_to_rgb", ExactSpelling = true)]
public static extern void convert_iphone_png_to_rgb(int flag_true_if_should_convert);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_set_flip_vertically_on_load", ExactSpelling = true)]
public static extern void set_flip_vertically_on_load(int flag_true_if_should_flip);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_set_unpremultiply_on_load_thread", ExactSpelling = true)]
public static extern void set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_convert_iphone_png_to_rgb_thread", ExactSpelling = true)]
public static extern void convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_set_flip_vertically_on_load_thread", ExactSpelling = true)]
public static extern void set_flip_vertically_on_load_thread(int flag_true_if_should_flip);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_zlib_decode_malloc_guesssize", ExactSpelling = true)]
[return: NativeTypeName("char *")]
public static extern sbyte* zlib_decode_malloc_guesssize([NativeTypeName("const char *")] sbyte* buffer, int len, int initial_size, int* outlen);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_zlib_decode_malloc_guesssize_headerflag", ExactSpelling = true)]
[return: NativeTypeName("char *")]
public static extern sbyte* zlib_decode_malloc_guesssize_headerflag([NativeTypeName("const char *")] sbyte* buffer, int len, int initial_size, int* outlen, int parse_header);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_zlib_decode_malloc", ExactSpelling = true)]
[return: NativeTypeName("char *")]
public static extern sbyte* zlib_decode_malloc([NativeTypeName("const char *")] sbyte* buffer, int len, int* outlen);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_zlib_decode_buffer", ExactSpelling = true)]
public static extern int zlib_decode_buffer([NativeTypeName("char *")] sbyte* obuffer, int olen, [NativeTypeName("const char *")] sbyte* ibuffer, int ilen);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_zlib_decode_noheader_malloc", ExactSpelling = true)]
[return: NativeTypeName("char *")]
public static extern sbyte* zlib_decode_noheader_malloc([NativeTypeName("const char *")] sbyte* buffer, int len, int* outlen);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, EntryPoint = "stbi_zlib_decode_noheader_buffer", ExactSpelling = true)]
public static extern int zlib_decode_noheader_buffer([NativeTypeName("char *")] sbyte* obuffer, int olen, [NativeTypeName("const char *")] sbyte* ibuffer, int ilen);
}
}

View File

@@ -1,11 +0,0 @@
namespace Quik.Stb
{
public enum StbiImageFormat
{
Default = (int)StbiEnum.STBI_default,
Grey = (int)StbiEnum.STBI_grey,
GreyAlpha = (int)StbiEnum.STBI_grey_alpha,
Rgb = (int)StbiEnum.STBI_rgb,
Rgba = (int)StbiEnum.STBI_rgb_alpha
}
}

View File

@@ -1,60 +0,0 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Quik.Stb
{
public unsafe class StbiStreamWrapper : IDisposable
{
private Stream _stream;
private bool _keepOpen;
private bool _isDisposed;
private delegate int ReadProc(void *userdata, byte* buffer, int count);
private delegate void SkipProc(void *userdata, int count);
private delegate int Eof(void *userdata);
public StbiStreamWrapper(Stream stream, bool keepOpen = false)
{
if (stream == null) throw new ArgumentNullException(nameof(stream));
_stream = stream;
_keepOpen = keepOpen;
}
public void CreateCallbacks(out stbi_io_callbacks cb)
{
cb = default;
cb.read = Marshal.GetFunctionPointerForDelegate<ReadProc>(ReadCb);
cb.skip = Marshal.GetFunctionPointerForDelegate<SkipProc>(SkipCb);
cb.eof = Marshal.GetFunctionPointerForDelegate<Eof>(EofCb);
}
private int ReadCb(void *userdata, byte* buffer, int count)
{
Span<byte> bytes = new Span<byte>(buffer, count);
return _stream.Read(bytes);
}
private void SkipCb(void *userdata, int count)
{
_stream.Seek(count, SeekOrigin.Current);
}
private int EofCb(void *userdata)
{
if (!_stream.CanRead || _stream.Position == _stream.Length)
return 1;
return 0;
}
public void Dispose()
{
if (_isDisposed) return;
if (!_keepOpen) _stream.Dispose();
_isDisposed = true;
}
}
}

View File

@@ -1,3 +0,0 @@
#!/bin/bash
cd $(dirname "$0")
../sh/quik_build_native.sh .

View File

@@ -1,30 +0,0 @@
-x
c
-l
stbi
--config
compatible-codegen
single-file
exclude-fnptr-codegen
generate-aggressive-inlining
generate-setslastsystemerror-attribute
unix-types
--include-directory
../lib
--include-directory
../Quik.StbImage
--include-directory
/usr/lib/llvm-14/lib/clang/14.0.6/include
--file
../Quik.StbImage.redist/quik_stbi.h
../lib/stb/stb_image.h
--methodClassName
Stbi
--namespace
Quik.Stb
--output
Stbi.cs
--prefixStrip
stbi_
--with-type
FILE=void

View File

@@ -1,6 +0,0 @@
#include "quik_stbi.h"
QUIK_DEFINE_LIB(quik_stbi);
#define STB_IMAGE_IMPLEMENTATION 1
#include "stb/stb_image.h"

View File

@@ -1,18 +0,0 @@
#ifndef _QUIK_STBI_H_
#define _QUIK_STBI_H_
#include "quik/quik_common.h"
QUIK_DECLARE_LIB(quik_stbi)
/* TODO: Change this declaration so we can export a DLL properly in windows. */
#define STBIDEF QEXTERN
#define STBI_ASSERT(EXPR) do { \
if (!(EXPR)) \
quik_stbi_failed_assert(#EXPR, __FILE__, __LINE__ - 2, __QUIK_FUNCTION__); \
} while(0)
#include "stb/stb_image.h"
#endif

View File

@@ -1,12 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(quik_stbtt LANGUAGES C VERSION 1.0)
add_compile_options(-static-libgcc)
add_library(stbtt SHARED "quik_stbtt.c")
target_include_directories(stbtt PRIVATE "../lib")
install(
TARGETS stbtt
RUNTIME DESTINATION .
LIBRARY DESTINATION .)

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