代码基本归档,待接入
This commit is contained in:
parent
2213c26533
commit
eba5794fd1
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
/*.vsconfig
|
||||
/Library/
|
||||
/Temp/
|
||||
/UserSettings/
|
||||
/.vs/
|
||||
/*.csproj
|
||||
/*.sln
|
||||
/obj/
|
||||
/Logs/
|
||||
/log/
|
||||
/ProjectSettings/Packages/
|
||||
1057
Assets/InputSystem_Actions.inputactions
Normal file
1057
Assets/InputSystem_Actions.inputactions
Normal file
File diff suppressed because it is too large
Load Diff
14
Assets/InputSystem_Actions.inputactions.meta
Normal file
14
Assets/InputSystem_Actions.inputactions.meta
Normal 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
8
Assets/Plugins.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c0135c9b9e95e1a41b89d4e3bed05fb6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Plugins/Newtonsoft.Json.dll
Normal file
BIN
Assets/Plugins/Newtonsoft.Json.dll
Normal file
Binary file not shown.
2
Assets/Plugins/Newtonsoft.Json.dll.meta
Normal file
2
Assets/Plugins/Newtonsoft.Json.dll.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86371939112254644a31e78df202d79c
|
||||
8
Assets/Plugins/StoicGooseUnity.meta
Normal file
8
Assets/Plugins/StoicGooseUnity.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9604fb0f1487b95488164f5dc29a00ba
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins/StoicGooseUnity/StoicGoose.Common.meta
Normal file
8
Assets/Plugins/StoicGooseUnity/StoicGoose.Common.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e1e5391f68e92f4db7f61cf8ed9557e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70afb0cb69f156a4b877a6dd0462fa04
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d881068effe996b459fdd198b8e7b046
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e358d7d3a0a0dd4a835853c38de5b88
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e622fd59969209c48842cc5f6951d34f
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 62a562265df1f9b41949fb0a0d5d4491
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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)); }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 319acc894b323fd4f90b8e025383be58
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8d552996c36ed1478421faa10628ce6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e4fde992b04dbe42be66c0ea83bb7c1
|
||||
@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07c7fb2a5f53f2f4aaa60f1673087d9c
|
||||
@ -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]", "");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da7cb8ba3dd19cb4e96fedb8dd687ab0
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2df240a96bd839c46a5d441273339c11
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e82b9629c32ff9f46bfd29ee8db43083
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 574b4bdd297790146bf92464ada13031
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 758af90e896ed334daa4b2c0b1ab907e
|
||||
@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75f627f66f93bc941b7f2317eb530132
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d26ef25f71ffd7a44aee2c51ca6f86e2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3875a4162fb861b4eaa9afae91a79970
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d09910e9ec835b4499af6123b16a840a
|
||||
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 178551683b089fd41bbc20f82964d581
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d34d8d41a7c062747ba60af3cca96bdd
|
||||
109
Assets/Plugins/StoicGooseUnity/StoicGoose.Common/OpenGL/State.cs
Normal file
109
Assets/Plugins/StoicGooseUnity/StoicGoose.Common/OpenGL/State.cs
Normal 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}") { }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 792e396ff7046af4eb6149dbaad83660
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e8e182c0d8fab6345820f6479ae9d179
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a45b95df3f0085f46bbfafbc4238c75e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c1a6e5abf4fb164680ad3838edd5d0a
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: def2e09bbdbff364a98af688ebd96f48
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6af09d6382461c4ab65e947b2f2fb2b
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fef7b73ffc9a5004390704d47e96f3d4
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d4f7ad547458c8d43ab14ab22118f93d
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 92c5ec8dd24a17346ae45748b1a0178f
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35f3ae40f4eab43439ede677f7faa3ec
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f8ad0c178d3da54590b050a192b3278
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20ad22abc90ef954b8cce3348210bbb4
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fbb5a6d3f29b9a44da578e67ac4e4100
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf80a2f2d6662894793579d648d7bdce
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65357137316d75b4f871a534339a4892
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,4 @@
|
||||
namespace StoicGoose.Common.OpenGL.Vertices
|
||||
{
|
||||
public interface IVertexStruct { }
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7f1ee3812c5583429ba5077b21aa114
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b58b069763f795d42a28445d38bf14ca
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c89413b44ed365f4f8abc8587da9ba94
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d8f3e1419988e144b05f78cdc6c73b5
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d169728b1f850a4db4171c5dab2507c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f30cf7d655b21e49a24ff276bbc9861
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e71734276750fc04990a5d08f116ee5d
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d00bd13e7a297b34683f7c739fc50d46
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76e83b1a596480f48a7afe097d979b83
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: afa4b03878ac3704bb52cae6e463f1f0
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 916e8e9742f5dfc41823c7eb3a544606
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20f7f2e2f16528949b80073b5e8778f9
|
||||
8
Assets/Plugins/StoicGooseUnity/StoicGoose.Core.meta
Normal file
8
Assets/Plugins/StoicGooseUnity/StoicGoose.Core.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b4feaaee1d7195a46a3aa956e4128489
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
41
Assets/Plugins/StoicGooseUnity/StoicGoose.Core/Bootstrap.cs
Normal file
41
Assets/Plugins/StoicGooseUnity/StoicGoose.Core/Bootstrap.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d194949ecf04a5341b082bac647c847d
|
||||
8
Assets/Plugins/StoicGooseUnity/StoicGoose.Core/CPU.meta
Normal file
8
Assets/Plugins/StoicGooseUnity/StoicGoose.Core/CPU.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1c58bce2e7f29f419b70dc124e97204
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1f6daccd87f15e48a0e766b94f6d27f
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d3774b3b6efb724ca93707535be9a35
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user