代码基本归档,待接入

This commit is contained in:
sin365 2025-02-21 17:34:45 +08:00
parent 2213c26533
commit eba5794fd1
241 changed files with 16673 additions and 0 deletions

11
.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
/*.vsconfig
/Library/
/Temp/
/UserSettings/
/.vs/
/*.csproj
/*.sln
/obj/
/Logs/
/log/
/ProjectSettings/Packages/

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 052faaac586de48259a63d0c4782560b
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3}
generateWrapperCode: 0
wrapperCodePath:
wrapperClassName:
wrapperCodeNamespace:

8
Assets/Plugins.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c0135c9b9e95e1a41b89d4e3bed05fb6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 86371939112254644a31e78df202d79c

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9604fb0f1487b95488164f5dc29a00ba
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7e1e5391f68e92f4db7f61cf8ed9557e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 70afb0cb69f156a4b877a6dd0462fa04
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,21 @@
using System;
namespace StoicGoose.Common.Attributes
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class BitDescriptionAttribute : Attribute
{
public string Description { get; set; } = string.Empty;
public int LowBit { get; set; } = -1;
public int HighBit { get; set; } = -1;
public string BitString => LowBit != -1 ? $"B{LowBit}{(HighBit > LowBit ? $"-{HighBit}" : string.Empty)}: " : string.Empty;
public BitDescriptionAttribute(string desc, int low = -1, int high = -1)
{
Description = desc;
LowBit = low;
HighBit = high;
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d881068effe996b459fdd198b8e7b046

View File

@ -0,0 +1,17 @@
using System;
namespace StoicGoose.Common.Attributes
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class FormatAttribute : Attribute
{
public string Format { get; set; } = string.Empty;
public int Shift { get; set; } = 0;
public FormatAttribute(string format, int shift = 0)
{
Format = format;
Shift = shift;
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 2e358d7d3a0a0dd4a835853c38de5b88

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
namespace StoicGoose.Common.Attributes
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class PortAttribute : Attribute
{
public string Name { get; set; } = string.Empty;
public List<ushort> Numbers { get; set; } = new();
public PortAttribute(string name, params ushort[] numbers)
{
Name = name;
Numbers.AddRange(numbers);
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e622fd59969209c48842cc5f6951d34f

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 62a562265df1f9b41949fb0a0d5d4491
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,55 @@
using System;
using System.IO;
using System.Linq;
namespace StoicGoose.Common.Drawing
{
/* RGBA bitmap file format -- https://github.com/bzotto/rgba_bitmap
* ".rgba is the dumbest possible image interchange format, now available for your programming pleasure."
*/
public class RgbaFile
{
const string expectedMagic = "RGBA";
public string MagicNumber { get; protected set; }
public uint Width { get; protected set; }
public uint Height { get; protected set; }
public byte[] PixelData { get; protected set; }
public RgbaFile(string filename) : this(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { }
public RgbaFile(Stream stream)
{
MagicNumber = ReadString(stream, 4);
Width = ReadUInt32(stream);
Height = ReadUInt32(stream);
PixelData = new byte[Width * Height * 4];
stream.Read(PixelData);
}
public RgbaFile(uint width, uint height, byte[] pixelData)
{
MagicNumber = expectedMagic;
Width = width;
Height = height;
PixelData = pixelData;
}
public void Save(string filename) => Save(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite));
public void Save(Stream stream)
{
WriteString(stream, MagicNumber);
WriteUInt32(stream, Width);
WriteUInt32(stream, Height);
stream.Write(PixelData);
}
private static string ReadString(Stream stream, int length) => new(Enumerable.Range(0, length).Select(_ => (char)stream.ReadByte()).ToArray());
private static uint ReadUInt32(Stream stream) => (uint)(((stream.ReadByte() & 0xFF) << 24) | ((stream.ReadByte() & 0xFF) << 16) | ((stream.ReadByte() & 0xFF) << 8) | ((stream.ReadByte() & 0xFF) << 0));
private static void WriteString(Stream stream, string str) => Array.ForEach(str.ToCharArray(), (x) => stream.WriteByte((byte)x));
private static void WriteUInt32(Stream stream, uint val) { stream.WriteByte((byte)((val >> 24) & 0xFF)); stream.WriteByte((byte)((val >> 16) & 0xFF)); stream.WriteByte((byte)((val >> 8) & 0xFF)); stream.WriteByte((byte)((val >> 0) & 0xFF)); }
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 319acc894b323fd4f90b8e025383be58

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d8d552996c36ed1478421faa10628ce6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,20 @@
using Newtonsoft.Json;
namespace StoicGoose.Common.Extensions
{
public static class ObjectExtensionMethods
{
/* https://dotnetcoretutorials.com/2020/09/09/cloning-objects-in-c-and-net-core/ */
public static T Clone<T>(this T source)
{
if (source is null) return default;
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source, new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
}), new JsonSerializerSettings()
{
ObjectCreationHandling = ObjectCreationHandling.Replace
});
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6e4fde992b04dbe42be66c0ea83bb7c1

View File

@ -0,0 +1,31 @@
using System.IO;
using Newtonsoft.Json;
namespace StoicGoose.Common.Extensions
{
public static class SerializationExtensionMethods
{
public static void SerializeToFile(this object obj, string jsonFileName)
{
SerializeToFile(obj, jsonFileName, new JsonSerializerSettings() { Formatting = Formatting.Indented });
}
public static void SerializeToFile(this object obj, string jsonFileName, JsonSerializerSettings serializerSettings)
{
using var writer = new StreamWriter(jsonFileName);
writer.Write(JsonConvert.SerializeObject(obj, serializerSettings));
}
public static T DeserializeFromFile<T>(this string jsonFileName)
{
using var reader = new StreamReader(jsonFileName);
return (T)JsonConvert.DeserializeObject(reader.ReadToEnd(), typeof(T), new JsonSerializerSettings() { Formatting = Formatting.Indented });
}
public static T DeserializeObject<T>(this string jsonString)
{
return (T)JsonConvert.DeserializeObject(jsonString, typeof(T), new JsonSerializerSettings() { Formatting = Formatting.Indented });
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 07c7fb2a5f53f2f4aaa60f1673087d9c

View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace StoicGoose.Common.Extensions
{
public static class StringExtensionMethods
{
/* Modified from https://stackoverflow.com/a/2641383 */
public static List<int> IndexOfAll(this string str, string value)
{
if (string.IsNullOrEmpty(value))
throw new ArgumentException("Search string is null or empty", nameof(value));
var idxs = new List<int>();
for (var i = 0; ; i += value.Length)
{
i = str.IndexOf(value, i);
if (i == -1) return idxs;
idxs.Add(i);
}
}
public static string EnsureEndsWithPeriod(this string str) => str + (!str.EndsWith('.') ? "." : string.Empty);
/* Regex via https://superuser.com/a/380778 */
public static string RemoveAnsi(this string str) => Regex.Replace(str, @"\x1b\[[0-9;]*[mGKHF]", "");
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: da7cb8ba3dd19cb4e96fedb8dd687ab0

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2df240a96bd839c46a5d441273339c11
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,37 @@
using System;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace StoicGoose.Common.Localization
{
public static class Localizer
{
public static string FallbackCulture { get; set; } = "en";
static JObject source = default;
public static void Initialize(string jsonData) => source = JsonConvert.DeserializeObject(jsonData) as JObject;
public static CultureInfo[] GetSupportedLanguages() => source?.Children().Select(x => new CultureInfo((x as JProperty).Name)).ToArray() ?? Array.Empty<CultureInfo>();
private static JToken GetToken(string path) => source?.SelectToken($"{CultureInfo.CurrentUICulture.TwoLetterISOLanguageName}.{path}") ?? source?.SelectToken($"{FallbackCulture}.{path}");
public static string GetString(string path) => GetToken(path)?.Value<string>() ?? path[(path.LastIndexOf('.') + 1)..];
public static string GetString(string path, object parameters)
{
var result = GetString(path);
var properties = parameters.GetType().GetProperties();
foreach (Match match in Regex.Matches(result, @"{(?<param>[^}:]*):*(?<format>[^}]*)}").Where(x => x.Success))
{
var property = properties.First(x => x.Name == match.Groups["param"].Value);
var format = match.Groups["format"].Value;
var formattedValue = string.IsNullOrEmpty(format) ? $"{property.GetValue(parameters)}" : string.Format($"{{0:{format}}}", property.GetValue(parameters));
result = result.Replace(match.Value, formattedValue);
}
return result;
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e82b9629c32ff9f46bfd29ee8db43083

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 574b4bdd297790146bf92464ada13031
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,89 @@
using System;
using System.Runtime.InteropServices;
using OpenTK.Graphics.OpenGL4;
using StoicGoose.Common.OpenGL.Vertices;
namespace StoicGoose.Common.OpenGL
{
public sealed class Buffer : IDisposable
{
internal readonly Type dataType = default;
internal readonly BufferTarget bufferTarget = 0;
internal readonly BufferUsageHint bufferUsageHint = 0;
internal readonly int handle = GL.GenBuffer();
internal readonly int sizeInBytes = 0;
internal int count = 0;
public Buffer(Type type, BufferTarget target, BufferUsageHint usage)
{
dataType = type;
bufferTarget = target;
bufferUsageHint = usage;
sizeInBytes = Marshal.SizeOf(dataType);
}
~Buffer()
{
Dispose();
}
public void Dispose()
{
if (GL.IsBuffer(handle))
GL.DeleteBuffer(handle);
GC.SuppressFinalize(this);
}
public static Buffer CreateBuffer<T>(BufferTarget target, BufferUsageHint usage) where T : struct => new(typeof(T), target, usage);
public static Buffer CreateVertexBuffer<T>(BufferUsageHint usage) where T : struct, IVertexStruct => CreateBuffer<T>(BufferTarget.ArrayBuffer, usage);
public static Buffer CreateIndexBuffer<T>(BufferUsageHint usage) where T : struct, IConvertible => CreateBuffer<T>(BufferTarget.ElementArrayBuffer, usage);
public void Bind()
{
GL.BindBuffer(bufferTarget, handle);
}
public void Update<T>(T[] data) where T : struct
{
if (dataType != typeof(T))
throw new Exception("Type mismatch on buffer update");
if (data != null)
{
Bind();
if (data.Length == count)
GL.BufferSubData(bufferTarget, IntPtr.Zero, new IntPtr(count * sizeInBytes), data);
else
{
count = data.Length;
GL.BufferData(bufferTarget, new IntPtr(count * sizeInBytes), data, bufferUsageHint);
}
}
}
public void Update<T>(IntPtr data, int size) where T : struct
{
if (dataType != typeof(T))
throw new Exception("Type mismatch on buffer update");
if (data != IntPtr.Zero)
{
Bind();
if (size == count)
GL.BufferSubData(bufferTarget, IntPtr.Zero, new IntPtr(count * sizeInBytes), data);
else
{
count = size;
GL.BufferData(bufferTarget, new IntPtr(count * sizeInBytes), data, bufferUsageHint);
}
}
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 758af90e896ed334daa4b2c0b1ab907e

View File

@ -0,0 +1,30 @@
using System.Linq;
using OpenTK.Graphics.OpenGL4;
using StoicGoose.Common.Utilities;
namespace StoicGoose.Common.OpenGL
{
public static class ContextInfo
{
public static string GLRenderer { get; } = GL.GetString(StringName.Renderer);
public static string GLShadingLanguageVersion { get; } = GL.GetString(StringName.ShadingLanguageVersion);
public static string GLVendor { get; } = GL.GetString(StringName.Vendor);
public static string GLVersion { get; } = GL.GetString(StringName.Version);
public static string[] GLExtensions { get; } = new string[GL.GetInteger(GetPName.NumExtensions)].Select((x, i) => x = GL.GetString(StringNameIndexed.Extensions, i)).ToArray();
public static void WriteToLog(object source, bool withExtensions = false)
{
Log.WriteEvent(LogSeverity.Debug, source, "OpenGL context:");
Log.WriteEvent(LogSeverity.Debug, source, $"- Renderer: {GLRenderer}");
Log.WriteEvent(LogSeverity.Debug, source, $"- Vendor: {GLVendor}");
Log.WriteEvent(LogSeverity.Debug, source, $"- Version: {GLVersion}");
Log.WriteEvent(LogSeverity.Debug, source, $"- GLSL version: {GLShadingLanguageVersion}");
Log.WriteEvent(LogSeverity.Debug, source, $"- {GLExtensions.Length} extension(s) supported.");
if (withExtensions)
foreach (var extension in GLExtensions)
Log.WriteEvent(LogSeverity.Debug, source, $" {extension}");
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 75f627f66f93bc941b7f2317eb530132

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d26ef25f71ffd7a44aee2c51ca6f86e2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3875a4162fb861b4eaa9afae91a79970
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace StoicGoose.Common.OpenGL.Shaders.Bundles
{
public enum FilterMode { Linear, Nearest }
public enum WrapMode { Repeat, Edge, Border, Mirror }
public class BundleManifest
{
[JsonConverter(typeof(StringEnumConverter))]
public FilterMode Filter { get; set; } = FilterMode.Linear;
[JsonConverter(typeof(StringEnumConverter))]
public WrapMode Wrap { get; set; } = WrapMode.Repeat;
public int Samplers { get; set; } = 3;
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d09910e9ec835b4499af6123b16a840a

View File

@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using OpenTK.Graphics.OpenGL4;
namespace StoicGoose.Common.OpenGL.Shaders
{
public sealed class Program : IDisposable
{
public int Handle { get; } = GL.CreateProgram();
readonly Dictionary<string, int> uniformLocations = new();
bool disposed = false;
public Program(params int[] shaders)
{
foreach (var shader in shaders) GL.AttachShader(Handle, shader);
GL.LinkProgram(Handle);
GL.GetProgram(Handle, GetProgramParameterName.LinkStatus, out int status);
if (status != 1)
{
GL.GetProgramInfoLog(Handle, out string info);
GL.DeleteProgram(Handle);
throw new Exception($"Program link failed:\n{info}");
}
foreach (var shader in shaders)
{
GL.DetachShader(Handle, shader);
GL.DeleteShader(shader);
}
GL.UseProgram(0);
}
~Program()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing)
{
if (GL.IsProgram(Handle))
GL.DeleteProgram(Handle);
}
disposed = true;
}
public void Bind()
{
GL.UseProgram(Handle);
}
public int GetUniformLocation(string name)
{
if (!uniformLocations.ContainsKey(name))
{
var location = GL.GetUniformLocation(Handle, name);
if (location != -1) uniformLocations[name] = location;
return location;
}
else
return uniformLocations[name];
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 178551683b089fd41bbc20f82964d581

View File

@ -0,0 +1,34 @@
using System;
using System.Linq;
using OpenTK.Graphics.OpenGL4;
namespace StoicGoose.Common.OpenGL.Shaders
{
public static class ShaderFactory
{
public static int FromSource(ShaderType shaderType, params string[] shaderSource)
{
shaderSource = Sanitize(shaderSource);
int handle = GL.CreateShader(shaderType);
GL.ShaderSource(handle, shaderSource.Length, shaderSource, (int[])null);
GL.CompileShader(handle);
GL.GetShader(handle, ShaderParameter.CompileStatus, out int status);
if (status != 1)
{
GL.GetShaderInfoLog(handle, out string info);
GL.DeleteShader(handle);
throw new Exception($"{shaderType} compile failed:\n{info}");
}
return handle;
}
private static string[] Sanitize(string[] shaderSource)
{
return shaderSource.Where(x => !string.IsNullOrEmpty(x)).Select(z => string.Concat(z, Environment.NewLine)).ToArray();
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d34d8d41a7c062747ba60af3cca96bdd

View File

@ -0,0 +1,109 @@
using System;
using System.Drawing;
using System.Runtime.CompilerServices;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
namespace StoicGoose.Common.OpenGL
{
public class State
{
static State lastState = default;
bool depthTestEnable = true, blendEnable = true, cullFaceEnable = true, scissorTestEnable = false;
BlendingFactor blendSource = BlendingFactor.SrcAlpha, blendDest = BlendingFactor.OneMinusSrcAlpha;
CullFaceMode cullFaceMode = CullFaceMode.Back;
Vector4i scissorBox = Vector4i.Zero;
Color clearColor = Color.Black;
Vector4i viewport = Vector4i.Zero;
public void Enable(EnableCap cap) => SetCap(cap, true);
public void Disable(EnableCap cap) => SetCap(cap, false);
public void SetBlending(BlendingFactor source, BlendingFactor dest) { blendSource = source; blendDest = dest; }
public void SetCullFace(CullFaceMode mode) => cullFaceMode = mode;
public void SetScissor(Vector4i box) => scissorBox = box;
public void SetScissor(int x, int y, int width, int height) => scissorBox = new(x, y, width, height);
public void SetClearColor(Color color) => clearColor = color;
public void SetViewport(Vector4i vp) => viewport = vp;
public void SetViewport(int x, int y, int width, int height) => viewport = new(x, y, width, height);
private void SetCap(EnableCap cap, bool value)
{
switch (cap)
{
case EnableCap.DepthTest: depthTestEnable = value; break;
case EnableCap.Blend: blendEnable = value; break;
case EnableCap.CullFace: cullFaceEnable = value; break;
case EnableCap.ScissorTest: scissorTestEnable = value; break;
default: throw new StateException($"{cap} not implemented");
}
}
private bool GetCap(EnableCap cap)
{
return cap switch
{
EnableCap.DepthTest => depthTestEnable,
EnableCap.Blend => blendEnable,
EnableCap.CullFace => cullFaceEnable,
EnableCap.ScissorTest => scissorTestEnable,
_ => throw new StateException($"{cap} not implemented"),
};
}
public void Submit()
{
if (lastState?.clearColor != clearColor)
GL.ClearColor(clearColor);
SubmitState(EnableCap.DepthTest, depthTestEnable);
SubmitState(EnableCap.Blend, blendEnable);
SubmitState(EnableCap.CullFace, cullFaceEnable);
SubmitState(EnableCap.ScissorTest, scissorTestEnable);
if (lastState?.viewport != viewport)
GL.Viewport(viewport.X, viewport.Y, viewport.Z, viewport.W);
lastState = (State)MemberwiseClone();
}
private void SubmitState(EnableCap cap, bool value)
{
var enableChanged = lastState?.GetCap(cap) != GetCap(cap);
if (value)
{
if (enableChanged) GL.Enable(cap);
switch (cap)
{
case EnableCap.Blend:
if (lastState?.blendSource != blendSource || lastState?.blendDest != blendDest)
GL.BlendFunc(blendSource, blendDest);
break;
case EnableCap.CullFace:
if (lastState?.cullFaceMode != cullFaceMode)
GL.CullFace(cullFaceMode);
break;
case EnableCap.ScissorTest:
if (lastState?.scissorBox != scissorBox)
GL.Scissor(scissorBox.X, scissorBox.Y, scissorBox.Z, scissorBox.W);
break;
}
}
else
{
if (enableChanged) GL.Disable(cap);
}
}
}
public class StateException : Exception
{
public StateException(string message, [CallerMemberName] string callerName = "") : base($"In {callerName}: {message}") { }
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 792e396ff7046af4eb6149dbaad83660

View File

@ -0,0 +1,169 @@
using System;
using System.Runtime.InteropServices;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using StoicGoose.Common.Drawing;
namespace StoicGoose.Common.OpenGL
{
public sealed class Texture : IDisposable
{
const TextureMinFilter defaultMinFilter = TextureMinFilter.Nearest;
const TextureMagFilter defaultMagFilter = TextureMagFilter.Nearest;
const TextureWrapMode defaultWrapModeS = TextureWrapMode.Repeat;
const TextureWrapMode defaultWrapModeT = TextureWrapMode.Repeat;
public int Handle { get; } = GL.GenTexture();
public Vector2i Size { get; private set; } = Vector2i.Zero;
byte[] pixelData = default;
bool isDirty = false;
bool disposed = false;
public Texture(int width, int height) : this(width, height, 255, 255, 255, 255) { }
public Texture(int width, int height, byte r, byte g, byte b, byte a)
{
Size = new Vector2i(width, height);
var data = new byte[width * height * 4];
for (var i = 0; i < data.Length; i += 4)
{
data[i + 0] = r;
data[i + 1] = g;
data[i + 2] = b;
data[i + 3] = a;
}
SetInitialTexImage(data);
}
public Texture(RgbaFile rgbaFile)
{
Size = new Vector2i((int)rgbaFile.Width, (int)rgbaFile.Height);
SetInitialTexImage(rgbaFile.PixelData);
}
public Texture(int width, int height, byte[] data)
{
Size = new Vector2i(width, height);
SetInitialTexImage(data);
}
public Texture(int width, int height, IntPtr data)
{
Size = new Vector2i(width, height);
SetInitialTexImage(data);
}
~Texture()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing)
{
if (GL.IsTexture(Handle))
GL.DeleteTexture(Handle);
}
disposed = true;
}
private void SetInitialTexImage(byte[] data)
{
var handle = GCHandle.Alloc(data, GCHandleType.Pinned);
var pointer = handle.AddrOfPinnedObject();
SetInitialTexImage(pointer);
handle.Free();
}
private void SetInitialTexImage(IntPtr pixels)
{
ChangeTextureParams(() =>
{
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, Size.X, Size.Y, 0, PixelFormat.Rgba, PixelType.UnsignedByte, pixels);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)defaultMinFilter);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)defaultMagFilter);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)defaultWrapModeS);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)defaultWrapModeT);
});
}
public void SetTextureFilter(TextureMinFilter textureMinFilter, TextureMagFilter textureMagFilter)
{
ChangeTextureParams(() =>
{
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)textureMinFilter);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)textureMagFilter);
});
}
public void SetTextureWrapMode(TextureWrapMode textureWrapModeS, TextureWrapMode textureWrapModeT)
{
ChangeTextureParams(() =>
{
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)textureWrapModeS);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)textureWrapModeT);
});
}
private void ChangeTextureParams(Action action)
{
var lastTextureSet = GL.GetInteger(GetPName.Texture2D);
if (Handle != lastTextureSet) GL.BindTexture(TextureTarget.Texture2D, Handle);
action?.Invoke();
GL.BindTexture(TextureTarget.Texture2D, lastTextureSet);
}
public void Bind()
{
Bind(0);
}
public void Bind(int textureUnit)
{
GL.ActiveTexture(TextureUnit.Texture0 + textureUnit);
GL.BindTexture(TextureTarget.Texture2D, Handle);
if (isDirty)
{
GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, Size.X, Size.Y, PixelFormat.Rgba, PixelType.UnsignedByte, pixelData);
isDirty = false;
}
}
public void Update(byte[] data)
{
isDirty = true;
pixelData = data;
}
public void Fill(byte r, byte g, byte b, byte a)
{
isDirty = true;
var data = new byte[Size.X * Size.Y * 4];
for (var i = 0; i < data.Length; i += 4)
{
data[i + 0] = r;
data[i + 1] = g;
data[i + 2] = b;
data[i + 3] = a;
}
pixelData = data;
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e8e182c0d8fab6345820f6479ae9d179

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a45b95df3f0085f46bbfafbc4238c75e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,16 @@
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
namespace StoicGoose.Common.OpenGL.Uniforms
{
public sealed class Color4Uniform : GenericUniform<Color4>
{
public Color4Uniform(string name) : this(name, Color4.White) { }
public Color4Uniform(string name, Color4 value) : base(name, value) { }
protected override void SubmitUniform(int location)
{
GL.Uniform4(location, value);
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9c1a6e5abf4fb164680ad3838edd5d0a

View File

@ -0,0 +1,15 @@
using OpenTK.Graphics.OpenGL4;
namespace StoicGoose.Common.OpenGL.Uniforms
{
public sealed class FloatUniform : GenericUniform<float>
{
public FloatUniform(string name) : this(name, 0.0f) { }
public FloatUniform(string name, float value) : base(name, value) { }
protected override void SubmitUniform(int location)
{
GL.Uniform1(location, value);
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: def2e09bbdbff364a98af688ebd96f48

View File

@ -0,0 +1,33 @@
using ShaderProgram = StoicGoose.Common.OpenGL.Shaders.Program;
namespace StoicGoose.Common.OpenGL.Uniforms
{
public abstract class GenericUniform<T>
{
protected readonly string name;
protected T value;
public string Name => name;
public T Value
{
get => value;
set => this.value = value;
}
public GenericUniform(string name) : this(name, default) { }
public GenericUniform(string name, T value)
{
this.name = name;
this.value = value;
}
public void SubmitToProgram(ShaderProgram shaderProgram)
{
var location = shaderProgram.GetUniformLocation(name);
if (location != -1) SubmitUniform(location);
}
protected abstract void SubmitUniform(int location);
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f6af09d6382461c4ab65e947b2f2fb2b

View File

@ -0,0 +1,15 @@
using OpenTK.Graphics.OpenGL4;
namespace StoicGoose.Common.OpenGL.Uniforms
{
public sealed class IntUniform : GenericUniform<int>
{
public IntUniform(string name) : this(name, 0) { }
public IntUniform(string name, int value) : base(name, value) { }
protected override void SubmitUniform(int location)
{
GL.Uniform1(location, value);
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fef7b73ffc9a5004390704d47e96f3d4

View File

@ -0,0 +1,16 @@
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
namespace StoicGoose.Common.OpenGL.Uniforms
{
public sealed class Matrix2Uniform : GenericUniform<Matrix2>
{
public Matrix2Uniform(string name) : this(name, Matrix2.Identity) { }
public Matrix2Uniform(string name, Matrix2 value) : base(name, value) { }
protected override void SubmitUniform(int location)
{
GL.UniformMatrix2(location, false, ref value);
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d4f7ad547458c8d43ab14ab22118f93d

View File

@ -0,0 +1,16 @@
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
namespace StoicGoose.Common.OpenGL.Uniforms
{
public sealed class Matrix3Uniform : GenericUniform<Matrix3>
{
public Matrix3Uniform(string name) : this(name, Matrix3.Identity) { }
public Matrix3Uniform(string name, Matrix3 value) : base(name, value) { }
protected override void SubmitUniform(int location)
{
GL.UniformMatrix3(location, false, ref value);
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 92c5ec8dd24a17346ae45748b1a0178f

View File

@ -0,0 +1,16 @@
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
namespace StoicGoose.Common.OpenGL.Uniforms
{
public sealed class Matrix4Uniform : GenericUniform<Matrix4>
{
public Matrix4Uniform(string name) : this(name, Matrix4.Identity) { }
public Matrix4Uniform(string name, Matrix4 value) : base(name, value) { }
protected override void SubmitUniform(int location)
{
GL.UniformMatrix4(location, false, ref value);
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 35f3ae40f4eab43439ede677f7faa3ec

View File

@ -0,0 +1,15 @@
using OpenTK.Graphics.OpenGL4;
namespace StoicGoose.Common.OpenGL.Uniforms
{
public sealed class UintUniform : GenericUniform<uint>
{
public UintUniform(string name) : this(name, 0) { }
public UintUniform(string name, uint value) : base(name, value) { }
protected override void SubmitUniform(int location)
{
GL.Uniform1(location, value);
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7f8ad0c178d3da54590b050a192b3278

View File

@ -0,0 +1,16 @@
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
namespace StoicGoose.Common.OpenGL.Uniforms
{
public sealed class Vector2Uniform : GenericUniform<Vector2>
{
public Vector2Uniform(string name) : this(name, Vector2.Zero) { }
public Vector2Uniform(string name, Vector2 value) : base(name, value) { }
protected override void SubmitUniform(int location)
{
GL.Uniform2(location, value);
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 20ad22abc90ef954b8cce3348210bbb4

View File

@ -0,0 +1,16 @@
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
namespace StoicGoose.Common.OpenGL.Uniforms
{
public sealed class Vector3Uniform : GenericUniform<Vector3>
{
public Vector3Uniform(string name) : this(name, Vector3.Zero) { }
public Vector3Uniform(string name, Vector3 value) : base(name, value) { }
protected override void SubmitUniform(int location)
{
GL.Uniform3(location, value);
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fbb5a6d3f29b9a44da578e67ac4e4100

View File

@ -0,0 +1,16 @@
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
namespace StoicGoose.Common.OpenGL.Uniforms
{
public sealed class Vector4Uniform : GenericUniform<Vector4>
{
public Vector4Uniform(string name) : this(name, Vector4.Zero) { }
public Vector4Uniform(string name, Vector4 value) : base(name, value) { }
protected override void SubmitUniform(int location)
{
GL.Uniform4(location, value);
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: bf80a2f2d6662894793579d648d7bdce

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 65357137316d75b4f871a534339a4892
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,4 @@
namespace StoicGoose.Common.OpenGL.Vertices
{
public interface IVertexStruct { }
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d7f1ee3812c5583429ba5077b21aa114

View File

@ -0,0 +1,13 @@
using System.Runtime.InteropServices;
using OpenTK.Mathematics;
namespace StoicGoose.Common.OpenGL.Vertices
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Vertex : IVertexStruct
{
public Vector2 Position;
public Vector2 TexCoord;
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b58b069763f795d42a28445d38bf14ca

View File

@ -0,0 +1,213 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
namespace StoicGoose.Common.OpenGL.Vertices
{
public sealed class VertexArray : IDisposable
{
static readonly Dictionary<Type, VertexAttribMethodType> methodTypeIdentifier = new()
{
{ typeof(sbyte), VertexAttribMethodType.Integer },
{ typeof(byte), VertexAttribMethodType.Integer },
{ typeof(short), VertexAttribMethodType.Integer },
{ typeof(ushort), VertexAttribMethodType.Integer },
{ typeof(int), VertexAttribMethodType.Integer },
{ typeof(uint), VertexAttribMethodType.Integer },
{ typeof(float), VertexAttribMethodType.Pointer },
{ typeof(double), VertexAttribMethodType.Pointer },
{ typeof(Color4), VertexAttribMethodType.Pointer },
{ typeof(Vector2), VertexAttribMethodType.Pointer },
{ typeof(Vector3), VertexAttribMethodType.Pointer },
{ typeof(Vector4), VertexAttribMethodType.Pointer },
{ typeof(Vector2d), VertexAttribMethodType.Pointer },
{ typeof(Vector3d), VertexAttribMethodType.Pointer },
{ typeof(Vector4d), VertexAttribMethodType.Pointer },
{ typeof(Vector2i), VertexAttribMethodType.Integer },
{ typeof(Vector3i), VertexAttribMethodType.Integer },
{ typeof(Vector4i), VertexAttribMethodType.Integer }
};
static readonly Dictionary<Type, VertexAttribPointerType> pointerTypeTranslator = new()
{
{ typeof(sbyte), VertexAttribPointerType.Byte },
{ typeof(byte), VertexAttribPointerType.UnsignedByte },
{ typeof(short), VertexAttribPointerType.Short },
{ typeof(ushort), VertexAttribPointerType.UnsignedShort },
{ typeof(int), VertexAttribPointerType.Int },
{ typeof(uint), VertexAttribPointerType.UnsignedInt },
{ typeof(float), VertexAttribPointerType.Float },
{ typeof(double), VertexAttribPointerType.Double },
{ typeof(Color4), VertexAttribPointerType.Float },
{ typeof(Vector2), VertexAttribPointerType.Float },
{ typeof(Vector3), VertexAttribPointerType.Float },
{ typeof(Vector4), VertexAttribPointerType.Float },
{ typeof(Vector2d), VertexAttribPointerType.Double },
{ typeof(Vector3d), VertexAttribPointerType.Double },
{ typeof(Vector4d), VertexAttribPointerType.Double },
{ typeof(Vector2i), VertexAttribPointerType.Int },
{ typeof(Vector3i), VertexAttribPointerType.Int },
{ typeof(Vector4i), VertexAttribPointerType.Int }
};
static readonly Dictionary<Type, VertexAttribIntegerType> integerTypeTranslator = new()
{
{ typeof(sbyte), VertexAttribIntegerType.Byte },
{ typeof(byte), VertexAttribIntegerType.UnsignedByte },
{ typeof(short), VertexAttribIntegerType.Short },
{ typeof(ushort), VertexAttribIntegerType.UnsignedShort },
{ typeof(int), VertexAttribIntegerType.Int },
{ typeof(uint), VertexAttribIntegerType.UnsignedInt },
{ typeof(Vector2i), VertexAttribIntegerType.Int },
{ typeof(Vector3i), VertexAttribIntegerType.Int },
{ typeof(Vector4i), VertexAttribIntegerType.Int }
};
static readonly Dictionary<Type, DrawElementsType> drawElementsTypeTranslator = new()
{
{ typeof(byte), DrawElementsType.UnsignedByte },
{ typeof(ushort), DrawElementsType.UnsignedShort },
{ typeof(uint), DrawElementsType.UnsignedInt }
};
enum VertexAttribMethodType { Pointer, Integer }
internal readonly Buffer vertexBuffer = default, indexBuffer = default;
internal readonly int handle = 0;
internal readonly VertexAttribute[] attributes = default;
public int NumVertices => vertexBuffer.count;
public int NumIndices => indexBuffer != null ? indexBuffer.count : 0;
public VertexArray(Buffer vtxBuffer) : this(vtxBuffer, null) { }
public VertexArray(Buffer vtxBuffer, Buffer idxBuffer)
{
vertexBuffer = vtxBuffer;
indexBuffer = idxBuffer;
handle = GL.GenVertexArray();
attributes = DeconstructVertexLayout(vtxBuffer.dataType);
GL.BindVertexArray(handle);
vertexBuffer.Bind();
for (var i = 0; i < attributes.Length; i++)
{
var attribute = attributes[i];
if (!methodTypeIdentifier.ContainsKey(attribute.Type)) continue;
GL.EnableVertexAttribArray(i);
switch (methodTypeIdentifier[attribute.Type])
{
case VertexAttribMethodType.Pointer:
GL.VertexAttribPointer(i, attribute.Size, GetVertexAttribPointerType(attribute.Type), false, vtxBuffer.sizeInBytes, new IntPtr(attribute.Offset));
break;
case VertexAttribMethodType.Integer:
GL.VertexAttribIPointer(i, attribute.Size, GetVertexAttribIntegerType(attribute.Type), vtxBuffer.sizeInBytes, new IntPtr(attribute.Offset));
break;
}
}
indexBuffer?.Bind();
GL.BindVertexArray(0);
}
~VertexArray()
{
Dispose();
}
public void Dispose()
{
if (GL.IsVertexArray(handle))
GL.DeleteVertexArray(handle);
vertexBuffer?.Dispose();
indexBuffer?.Dispose();
GC.SuppressFinalize(this);
}
private static VertexAttribute[] DeconstructVertexLayout(Type vertexType)
{
var attributes = new List<VertexAttribute>();
foreach (var field in vertexType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
if (!field.FieldType.IsArray)
{
var fieldSize = Marshal.SizeOf(field.FieldType);
if (field.FieldType.IsValueType && !field.FieldType.IsEnum)
{
var structFields = field.FieldType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (structFields == null || structFields.Length < 1 || structFields.Length > 4) throw new Exception("Invalid number of fields in struct");
fieldSize = structFields.Length;
}
attributes.Add(new VertexAttribute()
{
Type = field.FieldType,
Size = fieldSize,
Offset = Marshal.OffsetOf(vertexType, field.Name).ToInt32(),
Name = field.Name
});
}
else
throw new NotImplementedException("GLSL arrays not implemented");
}
return attributes.ToArray();
}
private static VertexAttribPointerType GetVertexAttribPointerType(Type type)
{
if (pointerTypeTranslator.ContainsKey(type))
return pointerTypeTranslator[type];
else
throw new ArgumentException("Unimplemented or unsupported vertex attribute pointer type");
}
private static VertexAttribIntegerType GetVertexAttribIntegerType(Type type)
{
if (integerTypeTranslator.ContainsKey(type))
return integerTypeTranslator[type];
else
throw new ArgumentException("Unimplemented or unsupported vertex attribute integer type");
}
private static DrawElementsType GetDrawElementsType(Type type)
{
if (drawElementsTypeTranslator.ContainsKey(type))
return drawElementsTypeTranslator[type];
else
throw new ArgumentException("Unsupported draw elements type");
}
public void Draw(PrimitiveType primitiveType)
{
GL.BindVertexArray(handle);
if (indexBuffer != null)
GL.DrawElements(primitiveType, indexBuffer.count, GetDrawElementsType(indexBuffer.dataType), 0);
else
GL.DrawArrays(primitiveType, 0, vertexBuffer.count);
}
public void DrawIndices(PrimitiveType primitiveType, int offset, int count)
{
if (indexBuffer == null) throw new NotImplementedException("Cannot use DrawIndices without an indexbuffer");
GL.BindVertexArray(handle);
GL.DrawElements(primitiveType, count, GetDrawElementsType(indexBuffer.dataType), offset * indexBuffer.sizeInBytes);
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c89413b44ed365f4f8abc8587da9ba94

View File

@ -0,0 +1,12 @@
using System;
namespace StoicGoose.Common.OpenGL.Vertices
{
public sealed class VertexAttribute
{
public Type Type { get; internal set; } = default;
public int Size { get; internal set; } = -1;
public int Offset { get; internal set; } = -1;
public string Name { get; internal set; } = string.Empty;
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8d8f3e1419988e144b05f78cdc6c73b5

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6d169728b1f850a4db4171c5dab2507c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace StoicGoose.Common.Utilities
{
public static class Ansi
{
public readonly static string Reset = "\x1B[0m";
public readonly static string Black = "\x1B[30m";
public readonly static string Red = "\x1B[31m";
public readonly static string Green = "\x1B[32m";
public readonly static string Yellow = "\x1B[33m";
public readonly static string Blue = "\x1B[34m";
public readonly static string Magenta = "\x1B[35m";
public readonly static string Cyan = "\x1B[36m";
public readonly static string White = "\x1B[37m";
public static string RGB(byte r, byte g, byte b) => $"\x1B[38;2;{r};{g};{b}m";
// Such a stupid gimmick... but hey, I like stupid gimmicks and I especially like making them, so whatever~
public static string Gradient(string text, bool useHsl, params (byte r, byte g, byte b)[] colors)
{
var stepsPerColor = (int)Math.Round(text.Length / (colors.Length - 1f), MidpointRounding.AwayFromZero);
var steps = Math.Max(stepsPerColor * (colors.Length - 1), text.Length);
List<(byte r, byte g, byte b)> gradient = new();
for (int i = 0, c = 0; i < steps; i += stepsPerColor, c++)
{
// TODO: this is a workaround for a out-of-range bug, but ugh, it's for a mere gimmick barely anyone will ever see, soooooo... whatever!
if (c + 1 >= colors.Length) c--;
if (useHsl)
{
var (h1, s1, l1) = RgbToHsl(colors[c + 0].r, colors[c + 0].g, colors[c + 0].b);
var (h2, s2, l2) = RgbToHsl(colors[c + 1].r, colors[c + 1].g, colors[c + 1].b);
for (var j = 0; j < stepsPerColor; j++)
{
var by = Math.Clamp(j / 1f / ((stepsPerColor - 1) / 1f), 0f, 1f);
var (h, s, l) = Lerp(h1, s1, l1, h2, s2, l2, by);
gradient.Add(HslToRgb(h, s, l));
}
}
else
{
var (r1, g1, b1) = (colors[c + 0].r / 255f, colors[c + 0].g / 255f, colors[c + 0].b / 255f);
var (r2, g2, b2) = (colors[c + 1].r / 255f, colors[c + 1].g / 255f, colors[c + 1].b / 255f);
for (var j = 0; j < stepsPerColor; j++)
{
var by = Math.Clamp(j / 1f / ((stepsPerColor - 1) / 1f), 0f, 1f);
gradient.Add(((byte)(Lerp(r1, r2, by) * 255), (byte)(Lerp(g1, g2, by) * 255), (byte)(Lerp(b1, b2, by) * 255)));
}
}
}
var builder = new StringBuilder();
for (var i = 0; i < Math.Min(gradient.Count, text.Length); i++)
builder.Append($"{RGB(gradient[i].r, gradient[i].g, gradient[i].b)}{text[i]}");
return builder.ToString();
}
private static float Lerp(float v1, float v2, float by) => v1 * (1f - by) + v2 * by;
private static (float h, float s, float l) Lerp(float h1, float s1, float l1, float h2, float s2, float l2, float by) => (Lerp(h1, h2, by) % 360f, Math.Clamp(Lerp(s1, s2, by), 0f, 1f), Math.Clamp(Lerp(l1, l2, by), 0f, 1f));
// http://www.easyrgb.com/en/math.php
private static (float h, float s, float l) RgbToHsl(byte red, byte green, byte blue)
{
float h = 0f, s, l;
var r = red / 255f;
var g = green / 255f;
var b = blue / 255f;
var min = Math.Min(Math.Min(r, g), b);
var max = Math.Max(Math.Max(r, g), b);
var deltaMax = max - min;
l = (max + min) / 2f;
if (deltaMax == 0)
{
h = 0;
s = 0;
}
else
{
if (l < 0.5f) s = deltaMax / (max + min);
else s = deltaMax / (2f - max - min);
var deltaR = ((max - r) / 6f + deltaMax / 2f) / deltaMax;
var deltaG = ((max - g) / 6f + deltaMax / 2f) / deltaMax;
var deltaB = ((max - b) / 6f + deltaMax / 2f) / deltaMax;
if (r == max) h = deltaB - deltaG;
else if (g == max) h = 1f / 3f + deltaR - deltaB;
else if (b == max) h = 2f / 3f + deltaG - deltaR;
if (h < 0f) h++;
if (h > 1f) h--;
}
return (h, s, l);
}
// http://www.easyrgb.com/en/math.php
private static (byte r, byte g, byte b) HslToRgb(float hue, float saturation, float lightness)
{
byte r, g, b;
if (saturation == 0f)
{
r = (byte)(lightness * 255);
g = (byte)(lightness * 255);
b = (byte)(lightness * 255);
}
else
{
float v1, v2;
if (lightness < 0.5f) v2 = lightness * (1f + saturation);
else v2 = lightness + saturation - saturation * lightness;
v1 = 2f * lightness - v2;
r = (byte)(255 * HueToRgb(v1, v2, hue + 1f / 3f));
g = (byte)(255 * HueToRgb(v1, v2, hue));
b = (byte)(255 * HueToRgb(v1, v2, hue - 1f / 3f));
}
return (r, g, b);
}
private static float HueToRgb(float v1, float v2, float vh)
{
if (vh < 0f) vh++;
if (vh > 1) vh--;
if (6f * vh < 1f) return v1 + (v2 - v1) * 6f * vh;
if (2f * vh < 1f) return v2;
if (3f * vh < 2f) return v1 + (v2 - v1) * (2f / 3f - vh) * 6f;
return v1;
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0f30cf7d655b21e49a24ff276bbc9861

View File

@ -0,0 +1,8 @@
namespace StoicGoose.Common.Utilities
{
public static class Bcd
{
public static int DecimalToBcd(int value) => ((value / 10) << 4) + (value % 10);
public static int BcdToDecimal(int value) => ((value >> 4) * 10) + value % 16;
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e71734276750fc04990a5d08f116ee5d

View File

@ -0,0 +1,15 @@
namespace StoicGoose.Common.Utilities
{
public static class BitHandling
{
public static void ChangeBit(ref byte value, int bit, bool state)
{
if (state)
value |= (byte)(1 << bit);
else
value &= (byte)~(1 << bit);
}
public static bool IsBitSet(byte value, int bit) => (value & (1 << bit)) != 0;
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d00bd13e7a297b34683f7c739fc50d46

View File

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace StoicGoose.Common.Utilities
{
public abstract class ConfigurationBase<T> where T : class, new()
{
public static readonly Dictionary<string, object> Defaults = default;
static ConfigurationBase()
{
Defaults = GetDefaultValues();
}
private static Dictionary<string, object> GetDefaultValues()
{
var dict = new Dictionary<string, object>();
var instance = new T();
foreach (var property in typeof(T).GetProperties().Where(x => x.CanWrite))
{
var value = property.GetValue(instance);
if (value == null || (property.PropertyType == typeof(string) && string.IsNullOrEmpty(value as string))) continue;
dict.Add(property.Name, value);
}
return dict;
}
public void ResetToDefault(string name)
{
var property = GetType().GetProperty(name);
if (property == null) throw new ArgumentException($"Setting '{name}' not found in {GetType().Name}", nameof(name));
property.SetValue(this, Defaults[name]);
}
}
public static class ConfigurationBase
{
public static void CopyConfiguration(object source, object destination)
{
if (source == null) throw new ArgumentNullException(nameof(source), "Source cannot be null");
if (destination == null) throw new ArgumentNullException(nameof(destination), "Destination cannot be null");
var sourceType = source.GetType();
var destType = destination.GetType();
foreach (var sourceProperty in sourceType.GetProperties().Where(x => x.CanRead))
{
var destProperty = destType.GetProperty(sourceProperty.Name);
if (destProperty == null || !destProperty.CanWrite || destProperty.GetSetMethod(true) == null || destProperty.GetSetMethod(true).IsPrivate ||
destProperty.GetSetMethod(true).Attributes.HasFlag(System.Reflection.MethodAttributes.Static) ||
!destProperty.PropertyType.IsAssignableFrom(sourceProperty.PropertyType))
continue;
var sourceValue = sourceProperty.GetValue(source, null);
var destValue = destProperty.GetValue(destination, null);
if ((sourceProperty.PropertyType.BaseType.IsGenericType ? sourceProperty.PropertyType.BaseType.GetGenericTypeDefinition() : sourceProperty.PropertyType.BaseType) == typeof(ConfigurationBase<>))
CopyConfiguration(sourceValue, destValue);
else
destProperty.SetValue(destination, sourceValue);
}
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 76e83b1a596480f48a7afe097d979b83

View File

@ -0,0 +1,82 @@
using System;
using System.IO;
namespace StoicGoose.Common.Utilities
{
public static class Crc32
{
static readonly uint[] crcTable;
static readonly uint crcPolynomial = 0xEDB88320;
static readonly uint crcSeed = 0xFFFFFFFF;
static Crc32()
{
crcTable = new uint[256];
for (var i = 0; i < 256; i++)
{
var entry = (uint)i;
for (int j = 0; j < 8; j++)
{
if ((entry & 0x00000001) == 0x00000001) entry = (entry >> 1) ^ crcPolynomial;
else entry >>= 1;
}
crcTable[i] = entry;
}
}
private static void VerifyStartAndLength(int dataLength, int segmentStart, int segmentLength)
{
if (segmentStart >= dataLength) throw new Exception("Segment start offset is greater than total length");
if (segmentLength > dataLength) throw new Exception("Segment length is greater than total length");
if ((segmentStart + segmentLength) > dataLength) throw new Exception("Segment end offset is greater than total length");
}
public static uint Calculate(FileInfo fileInfo)
{
return Calculate(fileInfo, 0, (int)fileInfo.Length);
}
public static uint Calculate(FileInfo fileInfo, int start, int length)
{
VerifyStartAndLength((int)fileInfo.Length, start, length);
using FileStream file = fileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
return Calculate(file, start, length);
}
public static uint Calculate(Stream stream)
{
return Calculate(stream, 0, (int)stream.Length);
}
public static uint Calculate(Stream stream, int start, int length)
{
VerifyStartAndLength((int)stream.Length, start, length);
var lastStreamPosition = stream.Position;
var data = new byte[length];
stream.Position = start;
stream.Read(data, 0, length);
var crc = Calculate(data, 0, data.Length);
stream.Position = lastStreamPosition;
return crc;
}
public static uint Calculate(byte[] data)
{
return Calculate(data, 0, data.Length);
}
public static uint Calculate(byte[] data, int start, int length)
{
VerifyStartAndLength(data.Length, start, length);
uint crc = crcSeed;
for (var i = start; i < (start + length); i++)
crc = ((crc >> 8) ^ crcTable[data[i] ^ (crc & 0x000000FF)]);
return ~crc;
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: afa4b03878ac3704bb52cae6e463f1f0

View File

@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.IO;
using Serilog;
using Serilog.Core;
using Serilog.Events;
using Serilog.Formatting;
using Serilog.Formatting.Display;
using StoicGoose.Common.Extensions;
namespace StoicGoose.Common.Utilities
{
public enum LogSeverity { Verbose, Debug, Information, Warning, Error, Fatal }
public static class Log
{
const string defaultTemplate = "{Message}{NewLine}{Exception}";
readonly static Dictionary<LogSeverity, LogEventLevel> severityToEventLevelMapping = new()
{
{ LogSeverity.Verbose, LogEventLevel.Verbose },
{ LogSeverity.Debug, LogEventLevel.Debug },
{ LogSeverity.Information, LogEventLevel.Information },
{ LogSeverity.Warning, LogEventLevel.Warning },
{ LogSeverity.Error, LogEventLevel.Error },
{ LogSeverity.Fatal, LogEventLevel.Fatal }
};
readonly static Dictionary<LogSeverity, string> logSeverityAnsiColors = new()
{
{ LogSeverity.Verbose, Ansi.White },
{ LogSeverity.Debug, Ansi.Cyan },
{ LogSeverity.Information, Ansi.Green },
{ LogSeverity.Warning, Ansi.Yellow },
{ LogSeverity.Error, Ansi.Magenta },
{ LogSeverity.Fatal, Ansi.Red }
};
static Logger mainLogger = default;
static Logger fileLogger = default;
public static string LogPath { get; private set; } = string.Empty;
public static void Initialize(string logPath)
{
if (File.Exists(logPath)) File.Delete(logPath);
mainLogger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.Console(outputTemplate: defaultTemplate)
.CreateLogger();
fileLogger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.File(LogPath = logPath, restrictedToMinimumLevel: LogEventLevel.Verbose)
.CreateLogger();
}
public static void AttachTextWriter(TextWriter writer)
{
mainLogger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.Sink(mainLogger)
.WriteTo.Sink(new TextWriterSink(writer, new MessageTemplateTextFormatter(defaultTemplate)))
.CreateLogger();
}
public static void WriteLine(string message) => Write(LogEventLevel.Information, message);
public static void WriteFatal(string message) => Write(LogEventLevel.Fatal, message);
private static void Write(LogEventLevel logEventLevel, string message)
{
mainLogger?.Write(logEventLevel, message);
fileLogger?.Write(logEventLevel, message.RemoveAnsi());
}
public static void WriteEvent(LogSeverity severity, object source, string message)
{
if (mainLogger == null && fileLogger == null) return;
var eventLevel = severityToEventLevelMapping.ContainsKey(severity) ? severityToEventLevelMapping[severity] : LogEventLevel.Verbose;
var logMessage = $"{logSeverityAnsiColors[severity]}[{source?.GetType().Name ?? string.Empty}]{Ansi.Reset}: {message}";
mainLogger?.Write(eventLevel, logMessage);
fileLogger?.Write(eventLevel, logMessage.RemoveAnsi());
}
}
class TextWriterSink : ILogEventSink
{
readonly TextWriter textWriter = default;
readonly ITextFormatter textFormatter = default;
readonly object syncRoot = new();
public TextWriterSink(TextWriter writer, ITextFormatter formatter)
{
textWriter = writer;
textFormatter = formatter ?? throw new ArgumentNullException(nameof(formatter));
}
public void Emit(LogEvent logEvent)
{
lock (syncRoot)
{
textFormatter.Format(logEvent ?? throw new ArgumentNullException(nameof(logEvent)), textWriter);
textWriter.Flush();
}
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 916e8e9742f5dfc41823c7eb3a544606

View File

@ -0,0 +1,41 @@
using System.IO;
using System.Reflection;
using StoicGoose.Common.Drawing;
namespace StoicGoose.Common.Utilities
{
public static class Resources
{
private static Stream GetEmbeddedResourceStream(string name)
{
var assembly = Assembly.GetEntryAssembly();
name = $"{assembly.GetName().Name}.{name}";
return assembly.GetManifestResourceStream(name);
}
public static RgbaFile GetEmbeddedRgbaFile(string name)
{
using var stream = GetEmbeddedResourceStream(name);
if (stream == null) return null;
return new RgbaFile(stream);
}
public static string GetEmbeddedText(string name)
{
using var stream = GetEmbeddedResourceStream(name);
if (stream == null) return string.Empty;
using var reader = new StreamReader(stream);
return reader.ReadToEnd();
}
public static byte[] GetEmbeddedRawData(string name)
{
using var stream = GetEmbeddedResourceStream(name);
if (stream == null) return null;
var buffer = new byte[stream.Length];
stream.Read(buffer, 0, buffer.Length);
return buffer;
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 20f7f2e2f16528949b80073b5e8778f9

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b4feaaee1d7195a46a3aa956e4128489
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,41 @@
using System;
using StoicGoose.Core.Interfaces;
namespace StoicGoose.Core
{
public class Bootstrap : IComponent
{
readonly byte[] rom = Array.Empty<byte>();
readonly uint romMask = 0;
public Bootstrap(int size)
{
rom = new byte[size];
romMask = (uint)(rom.Length - 1);
}
public void Reset()
{
//
}
public void Shutdown()
{
//
}
public void LoadRom(byte[] data)
{
if (data.Length != rom.Length)
throw new Exception("Data size mismatch error");
Buffer.BlockCopy(data, 0, rom, 0, data.Length);
}
public byte ReadMemory(uint address)
{
return rom[address & romMask];
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d194949ecf04a5341b082bac647c847d

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c1c58bce2e7f29f419b70dc124e97204
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,96 @@
namespace StoicGoose.Core.CPU
{
public sealed partial class V30MZ
{
private byte ReadOpcodeEb()
{
ReadModRM();
if (modRm.Mod == ModRM.Modes.Register)
return GetRegister8((RegisterNumber8)modRm.Mem);
else
return ReadMemory8(modRm.Segment, modRm.Offset);
}
private ushort ReadOpcodeEw()
{
ReadModRM();
if (modRm.Mod == ModRM.Modes.Register)
return GetRegister16((RegisterNumber16)modRm.Mem);
else
return ReadMemory16(modRm.Segment, modRm.Offset);
}
private void WriteOpcodeEb(byte value)
{
ReadModRM();
if (modRm.Mod == ModRM.Modes.Register)
SetRegister8((RegisterNumber8)modRm.Mem, value);
else
WriteMemory8(modRm.Segment, modRm.Offset, value);
}
private void WriteOpcodeEw(ushort value)
{
ReadModRM();
if (modRm.Mod == ModRM.Modes.Register)
SetRegister16((RegisterNumber16)modRm.Mem, value);
else
WriteMemory16(modRm.Segment, modRm.Offset, value);
}
private byte ReadOpcodeGb()
{
ReadModRM();
return GetRegister8((RegisterNumber8)modRm.Reg);
}
private ushort ReadOpcodeGw()
{
ReadModRM();
return GetRegister16((RegisterNumber16)modRm.Reg);
}
private void WriteOpcodeGb(byte value)
{
ReadModRM();
SetRegister8((RegisterNumber8)modRm.Reg, value);
}
private void WriteOpcodeGw(ushort value)
{
ReadModRM();
SetRegister16((RegisterNumber16)modRm.Reg, value);
}
private ushort ReadOpcodeSw()
{
ReadModRM();
return GetSegment((SegmentNumber)modRm.Reg);
}
private void WriteOpcodeSw(ushort value)
{
ReadModRM();
SetSegment((SegmentNumber)modRm.Reg, value);
}
private byte ReadOpcodeIb()
{
return ReadMemory8(cs, ip++);
}
private ushort ReadOpcodeIw()
{
var value = ReadMemory16(cs, ip);
ip += 2;
return value;
}
private ushort ReadOpcodeJb()
{
var tmp1 = (ushort)(ip + 1);
var tmp2 = (sbyte)ReadOpcodeIb();
return (ushort)(tmp1 + tmp2);
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e1f6daccd87f15e48a0e766b94f6d27f

View File

@ -0,0 +1,49 @@
using System;
namespace StoicGoose.Core.CPU
{
public sealed partial class V30MZ
{
[Flags]
public enum Flags : ushort
{
Carry = 1 << 0, /* CF */
ReservedB1 = 1 << 1, /* (reserved) */
Parity = 1 << 2, /* PF */
ReservedB3 = 1 << 3, /* (reserved) */
Auxiliary = 1 << 4, /* AF */
ReservedB5 = 1 << 5, /* (reserved) */
Zero = 1 << 6, /* ZF */
Sign = 1 << 7, /* SF */
Trap = 1 << 8, /* TF */
InterruptEnable = 1 << 9, /* IF */
Direction = 1 << 10, /* DF */
Overflow = 1 << 11, /* OF */
ReservedB12 = 1 << 12, /* (reserved) */
ReservedB13 = 1 << 13, /* (reserved) */
ReservedB14 = 1 << 14, /* (reserved) */
ReservedB15 = 1 << 15 /* (reserved) */
}
private void SetFlags(Flags flags)
{
this.flags |= flags;
}
private void ClearFlags(Flags flags)
{
this.flags &= ~flags;
}
public bool IsFlagSet(Flags flags)
{
return (this.flags & flags) == flags;
}
private void SetClearFlagConditional(Flags flags, bool condition)
{
if (condition) this.flags |= flags;
else this.flags &= ~flags;
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8d3774b3b6efb724ca93707535be9a35

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