基本定义各种Handler
This commit is contained in:
parent
eba5794fd1
commit
75b3f84ba1
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 574b4bdd297790146bf92464ada13031
|
||||
guid: f08bda821691d2a41a751fae22728b71
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
@ -1,89 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 758af90e896ed334daa4b2c0b1ab907e
|
||||
@ -1,30 +0,0 @@
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75f627f66f93bc941b7f2317eb530132
|
||||
@ -1,18 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d09910e9ec835b4499af6123b16a840a
|
||||
@ -1,80 +0,0 @@
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 178551683b089fd41bbc20f82964d581
|
||||
@ -1,34 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d34d8d41a7c062747ba60af3cca96bdd
|
||||
@ -1,109 +0,0 @@
|
||||
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}") { }
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 792e396ff7046af4eb6149dbaad83660
|
||||
@ -1,169 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e8e182c0d8fab6345820f6479ae9d179
|
||||
@ -1,16 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c1a6e5abf4fb164680ad3838edd5d0a
|
||||
@ -1,15 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: def2e09bbdbff364a98af688ebd96f48
|
||||
@ -1,33 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6af09d6382461c4ab65e947b2f2fb2b
|
||||
@ -1,15 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fef7b73ffc9a5004390704d47e96f3d4
|
||||
@ -1,16 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d4f7ad547458c8d43ab14ab22118f93d
|
||||
@ -1,16 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 92c5ec8dd24a17346ae45748b1a0178f
|
||||
@ -1,16 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35f3ae40f4eab43439ede677f7faa3ec
|
||||
@ -1,15 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f8ad0c178d3da54590b050a192b3278
|
||||
@ -1,16 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20ad22abc90ef954b8cce3348210bbb4
|
||||
@ -1,16 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fbb5a6d3f29b9a44da578e67ac4e4100
|
||||
@ -1,16 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf80a2f2d6662894793579d648d7bdce
|
||||
@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65357137316d75b4f871a534339a4892
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,4 +0,0 @@
|
||||
namespace StoicGoose.Common.OpenGL.Vertices
|
||||
{
|
||||
public interface IVertexStruct { }
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7f1ee3812c5583429ba5077b21aa114
|
||||
@ -1,13 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b58b069763f795d42a28445d38bf14ca
|
||||
@ -1,213 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c89413b44ed365f4f8abc8587da9ba94
|
||||
@ -1,12 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d8f3e1419988e144b05f78cdc6c73b5
|
||||
@ -1,31 +1,33 @@
|
||||
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 enum LogType { Debug, Warning, Error }
|
||||
|
||||
public static class Log
|
||||
public interface IStoicGooseLogger
|
||||
{
|
||||
public void Log(LogType logtype, string message);
|
||||
public void Debug(string message);
|
||||
public void Warning(string message);
|
||||
public void Err(string message);
|
||||
}
|
||||
|
||||
public static class Log
|
||||
{
|
||||
const string defaultTemplate = "{Message}{NewLine}{Exception}";
|
||||
|
||||
readonly static Dictionary<LogSeverity, LogEventLevel> severityToEventLevelMapping = new()
|
||||
readonly static Dictionary<LogSeverity, LogType> 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 }
|
||||
{ LogSeverity.Verbose, LogType.Debug },
|
||||
{ LogSeverity.Debug, LogType.Debug },
|
||||
{ LogSeverity.Information, LogType.Debug },
|
||||
{ LogSeverity.Warning, LogType.Warning },
|
||||
{ LogSeverity.Error, LogType.Error },
|
||||
{ LogSeverity.Fatal, LogType.Error }
|
||||
};
|
||||
|
||||
readonly static Dictionary<LogSeverity, string> logSeverityAnsiColors = new()
|
||||
@ -38,75 +40,51 @@ namespace StoicGoose.Common.Utilities
|
||||
{ LogSeverity.Fatal, Ansi.Red }
|
||||
};
|
||||
|
||||
static Logger mainLogger = default;
|
||||
static Logger fileLogger = default;
|
||||
static IStoicGooseLogger mainLogger;
|
||||
|
||||
public static string LogPath { get; private set; } = string.Empty;
|
||||
|
||||
public static void Initialize(string logPath)
|
||||
public static void Initialize(IStoicGooseLogger logger)
|
||||
{
|
||||
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();
|
||||
mainLogger = logger;
|
||||
}
|
||||
|
||||
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);
|
||||
public static void WriteLine(string message) => mainLogger.Debug(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());
|
||||
}
|
||||
//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 eventLevel = severityToEventLevelMapping.ContainsKey(severity) ? severityToEventLevelMapping[severity] : LogType.Debug;
|
||||
var logMessage = $"{logSeverityAnsiColors[severity]}[{source?.GetType().Name ?? string.Empty}]{Ansi.Reset}: {message}";
|
||||
mainLogger?.Write(eventLevel, logMessage);
|
||||
fileLogger?.Write(eventLevel, logMessage.RemoveAnsi());
|
||||
mainLogger.Log(eventLevel, logMessage);
|
||||
}
|
||||
}
|
||||
|
||||
class TextWriterSink : ILogEventSink
|
||||
{
|
||||
readonly TextWriter textWriter = default;
|
||||
readonly ITextFormatter textFormatter = default;
|
||||
//class TextWriterSink : ILogEventSink
|
||||
//{
|
||||
// readonly TextWriter textWriter = default;
|
||||
// readonly ITextFormatter textFormatter = default;
|
||||
|
||||
readonly object syncRoot = new();
|
||||
// readonly object syncRoot = new();
|
||||
|
||||
public TextWriterSink(TextWriter writer, ITextFormatter formatter)
|
||||
{
|
||||
textWriter = writer;
|
||||
textFormatter = formatter ?? throw new ArgumentNullException(nameof(formatter));
|
||||
}
|
||||
// //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();
|
||||
}
|
||||
}
|
||||
}
|
||||
// public void Emit(LogEvent logEvent)
|
||||
// {
|
||||
// lock (syncRoot)
|
||||
// {
|
||||
// textFormatter.Format(logEvent ?? throw new ArgumentNullException(nameof(logEvent)), textWriter);
|
||||
// textWriter.Flush();
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
@ -11,11 +11,20 @@ using StoicGoose.Core.Sound;
|
||||
|
||||
namespace StoicGoose.Core.Interfaces
|
||||
{
|
||||
|
||||
public interface IMachine
|
||||
{
|
||||
static IEnumerable<Type> GetMachineTypes() => Assembly.GetAssembly(typeof(IMachine)).GetTypes().Where(x => !x.IsInterface && !x.IsAbstract && x.IsAssignableTo(typeof(IMachine)));
|
||||
//static IEnumerable<Type> GetMachineTypes() => Assembly.GetAssembly(typeof(IMachine)).GetTypes().Where(x => !x.IsInterface && !x.IsAbstract && x.IsAssignableTo(typeof(IMachine)));
|
||||
static IEnumerable<Type> GetMachineTypes()
|
||||
{
|
||||
return Assembly.GetAssembly(typeof(IMachine))
|
||||
.GetTypes()
|
||||
.Where(x => !x.IsInterface &&
|
||||
!x.IsAbstract &&
|
||||
typeof(IMachine).IsAssignableFrom(x));
|
||||
}
|
||||
|
||||
string Manufacturer { get; }
|
||||
string Manufacturer { get; }
|
||||
string Model { get; }
|
||||
|
||||
int ScreenWidth { get; }
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d26ef25f71ffd7a44aee2c51ca6f86e2
|
||||
guid: 95c03a05d1f914a4e8d52e1d4e852d27
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3875a4162fb861b4eaa9afae91a79970
|
||||
guid: c710e077fe5cc1544ba2dd05c4972558
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a45b95df3f0085f46bbfafbc4238c75e
|
||||
guid: 3e0f65346834a04458adcab39a439a13
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
72
Assets/Script/AppMain/Emulator/RingBuffer.cs
Normal file
72
Assets/Script/AppMain/Emulator/RingBuffer.cs
Normal file
@ -0,0 +1,72 @@
|
||||
using System.Threading;
|
||||
|
||||
public class RingBuffer<T>
|
||||
{
|
||||
private readonly T[] buffer;
|
||||
private readonly int capacity;
|
||||
private int writePos;
|
||||
private int readPos;
|
||||
private int count;
|
||||
|
||||
public RingBuffer(int capacity)
|
||||
{
|
||||
this.capacity = capacity;
|
||||
this.buffer = new T[capacity];
|
||||
this.writePos = 0;
|
||||
this.readPos = 0;
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
public void Write(T item)
|
||||
{
|
||||
int localWritePos;
|
||||
int localReadPos;
|
||||
|
||||
do
|
||||
{
|
||||
localWritePos = Volatile.Read(ref writePos);
|
||||
localReadPos = Volatile.Read(ref readPos);
|
||||
|
||||
int nextWritePos = (localWritePos + 1) % capacity;
|
||||
|
||||
if (nextWritePos == localReadPos)
|
||||
{
|
||||
// 缓冲区已满,覆盖最旧的未读数据
|
||||
Interlocked.CompareExchange(ref readPos, (localReadPos + 1) % capacity, localReadPos);
|
||||
}
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref writePos, (localWritePos + 1) % capacity, localWritePos) != localWritePos);
|
||||
|
||||
buffer[localWritePos] = item;
|
||||
Interlocked.Increment(ref count);
|
||||
}
|
||||
|
||||
public bool TryRead(out T item)
|
||||
{
|
||||
item = default(T);
|
||||
|
||||
int localReadPos;
|
||||
int localWritePos;
|
||||
|
||||
do
|
||||
{
|
||||
localReadPos = Volatile.Read(ref readPos);
|
||||
localWritePos = Volatile.Read(ref writePos);
|
||||
|
||||
if (localReadPos == localWritePos)
|
||||
{
|
||||
return false; // 缓冲区为空
|
||||
}
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref readPos, (localReadPos + 1) % capacity, localReadPos) != localReadPos);
|
||||
|
||||
item = buffer[localReadPos];
|
||||
Interlocked.Decrement(ref count);
|
||||
return true;
|
||||
}
|
||||
|
||||
public int Available()
|
||||
{
|
||||
return Volatile.Read(ref count);
|
||||
}
|
||||
}
|
||||
2
Assets/Script/AppMain/Emulator/RingBuffer.cs.meta
Normal file
2
Assets/Script/AppMain/Emulator/RingBuffer.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5cf9b14d4d67664b9b13beeefc68423
|
||||
8
Assets/Script/AppMain/Emulator/StoicGooseEmulator.meta
Normal file
8
Assets/Script/AppMain/Emulator/StoicGooseEmulator.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 218c1e917c9305445b77d6ee8c43c78f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,90 @@
|
||||
using StoicGoose.Common.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
|
||||
public sealed class Configuration : ConfigurationBase<Configuration>
|
||||
{
|
||||
[DisplayName("General")]
|
||||
[Description("General settings.")]
|
||||
public GeneralConfiguration General { get; set; } = new GeneralConfiguration();
|
||||
[DisplayName("Video")]
|
||||
[Description("Settings related to video output.")]
|
||||
public VideoConfiguration Video { get; set; } = new VideoConfiguration();
|
||||
[DisplayName("Sound")]
|
||||
[Description("Settings related to sound output.")]
|
||||
public SoundConfiguration Sound { get; set; } = new SoundConfiguration();
|
||||
[DisplayName("Input")]
|
||||
[Description("Settings related to emulation input.")]
|
||||
public InputConfiguration Input { get; set; } = new InputConfiguration();
|
||||
}
|
||||
|
||||
public sealed class GeneralConfiguration : ConfigurationBase<GeneralConfiguration>
|
||||
{
|
||||
[DisplayName("Prefer Original WS")]
|
||||
[Description("Prefer emulation of the original non-Color system.")]
|
||||
public bool PreferOriginalWS { get; set; } = false;
|
||||
[DisplayName("Use Bootstrap ROM")]
|
||||
[Description("Toggle using WonderSwan bootstrap ROM images.")]
|
||||
public bool UseBootstrap { get; set; } = false;
|
||||
[DisplayName("WS Bootstrap ROM Path")]
|
||||
[Description("Path to the WonderSwan bootstrap ROM image to use.")]
|
||||
public string BootstrapFile { get; set; } = string.Empty;
|
||||
[DisplayName("WSC Bootstrap ROM Path")]
|
||||
[Description("Path to the WonderSwan Color bootstrap ROM image to use.")]
|
||||
public string BootstrapFileWSC { get; set; } = string.Empty;
|
||||
[DisplayName("Limit FPS")]
|
||||
[Description("Toggle limiting the framerate to the system's native ~75.47 Hz.")]
|
||||
public bool LimitFps { get; set; } = true;
|
||||
[DisplayName("Enable Cheats")]
|
||||
[Description("Toggle using the cheat system.")]
|
||||
public bool EnableCheats { get; set; } = true;
|
||||
[DisplayName("Recent Files")]
|
||||
[Description("List of recently loaded files.")]
|
||||
public List<string> RecentFiles { get; set; } = new List<string>(15);
|
||||
}
|
||||
|
||||
public sealed class VideoConfiguration : ConfigurationBase<VideoConfiguration>
|
||||
{
|
||||
[DisplayName("Screen Size")]
|
||||
[Description("Size of the emulated screen, in times original display resolution.")]
|
||||
public int ScreenSize { get; set; } = 3;
|
||||
[DisplayName("Shader")]
|
||||
[Description("Currently selected shader.")]
|
||||
public string Shader { get; set; } = string.Empty;
|
||||
[DisplayName("Brightness")]
|
||||
[Description("Adjust the brightness of the emulated screen, in percent.")]
|
||||
[Range(-100, 100)]
|
||||
public int Brightness { get; set; } = 0;
|
||||
[DisplayName("Contrast")]
|
||||
[Description("Adjust the contrast of the emulated screen, in percent.")]
|
||||
[Range(0, 200)]
|
||||
public int Contrast { get; set; } = 100;
|
||||
[DisplayName("Saturation")]
|
||||
[Description("Adjust the saturation of the emulated screen, in percent.")]
|
||||
[Range(0, 200)]
|
||||
public int Saturation { get; set; } = 100;
|
||||
}
|
||||
|
||||
public sealed class SoundConfiguration : ConfigurationBase<SoundConfiguration>
|
||||
{
|
||||
[DisplayName("Mute")]
|
||||
[Description("Toggles muting all sound output.")]
|
||||
public bool Mute { get; set; } = false;
|
||||
[DisplayName("Low-Pass Filter")]
|
||||
[Description("Toggles low-pass filter for all sound output.")]
|
||||
public bool LowPassFilter { get; set; } = true;
|
||||
}
|
||||
|
||||
public sealed class InputConfiguration : ConfigurationBase<InputConfiguration>
|
||||
{
|
||||
[DisplayName("Automatic Remapping")]
|
||||
[Description("Automatically remap X-/Y-pads with game orientation.")]
|
||||
public bool AutoRemap { get; set; } = true;
|
||||
[DisplayName("Game Controls")]
|
||||
[Description("Controls related to game input, i.e. X-/Y-pads, etc.")]
|
||||
public Dictionary<string, List<string>> GameControls { get; set; } = new Dictionary<string, List<string>>();
|
||||
[DisplayName("System Controls")]
|
||||
[Description("Controls related to hardware functions, i.e. volume button.")]
|
||||
public Dictionary<string, List<string>> SystemControls { get; set; } = new Dictionary<string, List<string>>();
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee0a5e22651983e479915e5429079d99
|
||||
@ -0,0 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
|
||||
public static class GlobalVariables
|
||||
{
|
||||
public static readonly bool IsAuthorsMachine = System.Environment.MachineName == "KAMIKO";
|
||||
#if DEBUG
|
||||
public static readonly bool IsDebugBuild = true;
|
||||
#else
|
||||
public static readonly bool IsDebugBuild = false;
|
||||
#endif
|
||||
public static readonly bool EnableLocalDebugIO = IsAuthorsMachine;
|
||||
|
||||
public static readonly bool EnableSuperVerbosity = false;
|
||||
public static readonly bool EnableOpenGLDebug = false;
|
||||
|
||||
public static readonly bool EnableSkipBootstrapIfFound = false;
|
||||
|
||||
public static string[] Dump()
|
||||
{
|
||||
var vars = new List<string>();
|
||||
foreach (var fieldInfo in typeof(GlobalVariables).GetFields(BindingFlags.Static | BindingFlags.Public))
|
||||
vars.Add($"{fieldInfo.Name} == {fieldInfo.GetValue(null)}");
|
||||
return vars.ToArray();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 043ec2227e850204faefe0ce10f2ae04
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb360a28c0621034ea0d310e18e5a747
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,187 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
using StoicGoose.Common.Utilities;
|
||||
|
||||
|
||||
public sealed class DatabaseHandler
|
||||
{
|
||||
readonly Dictionary<string, DatFile> datFiles = new();
|
||||
|
||||
public DatabaseHandler(string directory)
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(directory, "*.dat").OrderBy(x => x.Length))
|
||||
{
|
||||
var root = new XmlRootAttribute("datafile") { IsNullable = true };
|
||||
var serializer = new XmlSerializer(typeof(DatFile), root);
|
||||
using FileStream stream = new(Path.Combine(directory, file), FileMode.Open);
|
||||
var reader = XmlReader.Create(stream, new() { DtdProcessing = DtdProcessing.Ignore });
|
||||
datFiles.Add(Path.GetFileName(file), (DatFile)serializer.Deserialize(reader));
|
||||
}
|
||||
|
||||
Log.WriteEvent(LogSeverity.Information, this, $"Loaded {datFiles.Count} .dat file(s) with {datFiles.Sum(x => x.Value.Game.Length)} known game(s).");
|
||||
foreach (var datFile in datFiles.Select(x => x.Value))
|
||||
Log.WriteLine($" '{datFile.Header.Name} ({datFile.Header.Version})' from {datFile.Header.Homepage}");
|
||||
}
|
||||
|
||||
private DatGame GetGame(uint romCrc32, int romSize)
|
||||
{
|
||||
return datFiles.Select(x => x.Value.Game).Select(x => x.FirstOrDefault(x => x.Rom.Any(y => y.Crc.ToLowerInvariant() == $"{romCrc32:x8}" && y.Size.ToLowerInvariant() == $"{romSize:D}"))).FirstOrDefault(x => x != null);
|
||||
}
|
||||
|
||||
public string GetGameTitle(uint romCrc32, int romSize)
|
||||
{
|
||||
return GetGame(romCrc32, romSize)?.Name.Replace("&", "&&") ?? "unrecognized game";
|
||||
}
|
||||
|
||||
public class DatHeader
|
||||
{
|
||||
[XmlElement("name")]
|
||||
public string Name { get; set; }
|
||||
[XmlElement("description")]
|
||||
public string Description { get; set; }
|
||||
[XmlElement("category")]
|
||||
public string Category { get; set; }
|
||||
[XmlElement("version")]
|
||||
public string Version { get; set; }
|
||||
[XmlElement("date")]
|
||||
public string Date { get; set; }
|
||||
[XmlElement("author")]
|
||||
public string Author { get; set; }
|
||||
[XmlElement("email")]
|
||||
public string Email { get; set; }
|
||||
[XmlElement("homepage")]
|
||||
public string Homepage { get; set; }
|
||||
[XmlElement("url")]
|
||||
public string Url { get; set; }
|
||||
[XmlElement("comment")]
|
||||
public string Comment { get; set; }
|
||||
}
|
||||
|
||||
public class DatRelease
|
||||
{
|
||||
[XmlAttribute("name")]
|
||||
public string Name { get; set; }
|
||||
[XmlAttribute("region")]
|
||||
public string Region { get; set; }
|
||||
[XmlAttribute("language")]
|
||||
public string Language { get; set; }
|
||||
[XmlAttribute("date")]
|
||||
public string Date { get; set; }
|
||||
[XmlAttribute("default")]
|
||||
public string Default { get; set; }
|
||||
}
|
||||
|
||||
public class DatBiosSet
|
||||
{
|
||||
[XmlAttribute("name")]
|
||||
public string Name { get; set; }
|
||||
[XmlAttribute("description")]
|
||||
public string Description { get; set; }
|
||||
[XmlAttribute("default")]
|
||||
public string Default { get; set; }
|
||||
}
|
||||
|
||||
public class DatRom
|
||||
{
|
||||
[XmlAttribute("name")]
|
||||
public string Name { get; set; }
|
||||
[XmlAttribute("size")]
|
||||
public string Size { get; set; }
|
||||
[XmlAttribute("crc")]
|
||||
public string Crc { get; set; }
|
||||
[XmlAttribute("sha1")]
|
||||
public string Sha1 { get; set; }
|
||||
[XmlAttribute("md5")]
|
||||
public string Md5 { get; set; }
|
||||
[XmlAttribute("merge")]
|
||||
public string Merge { get; set; }
|
||||
[XmlAttribute("status")]
|
||||
public string Status { get; set; }
|
||||
[XmlAttribute("date")]
|
||||
public string Date { get; set; }
|
||||
}
|
||||
|
||||
public class DatDisk
|
||||
{
|
||||
[XmlAttribute("name")]
|
||||
public string Name { get; set; }
|
||||
[XmlAttribute("sha1")]
|
||||
public string Sha1 { get; set; }
|
||||
[XmlAttribute("md5")]
|
||||
public string Md5 { get; set; }
|
||||
[XmlAttribute("merge")]
|
||||
public string Merge { get; set; }
|
||||
[XmlAttribute("status")]
|
||||
public string Status { get; set; }
|
||||
}
|
||||
|
||||
public class DatSample
|
||||
{
|
||||
[XmlAttribute("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class DatArchive
|
||||
{
|
||||
[XmlAttribute("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class DatGame
|
||||
{
|
||||
[XmlAttribute("name")]
|
||||
public string Name { get; set; }
|
||||
[XmlAttribute("sourcefile")]
|
||||
public string SourceFile { get; set; }
|
||||
[XmlAttribute("isbios")]
|
||||
public string IsBios { get; set; }
|
||||
[XmlAttribute("cloneof")]
|
||||
public string CloneOf { get; set; }
|
||||
[XmlAttribute("romof")]
|
||||
public string RomOf { get; set; }
|
||||
[XmlAttribute("sampleof")]
|
||||
public string SampleOf { get; set; }
|
||||
[XmlAttribute("board")]
|
||||
public string Board { get; set; }
|
||||
[XmlAttribute("rebuildto")]
|
||||
public string RebuildTo { get; set; }
|
||||
|
||||
[XmlElement("year")]
|
||||
public string Year { get; set; }
|
||||
[XmlElement("manufacturer")]
|
||||
public string Manufacturer { get; set; }
|
||||
|
||||
[XmlElement("release")]
|
||||
public DatRelease[] Release { get; set; }
|
||||
|
||||
[XmlElement("biosset")]
|
||||
public DatBiosSet[] BiosSet { get; set; }
|
||||
|
||||
[XmlElement("rom")]
|
||||
public DatRom[] Rom { get; set; }
|
||||
|
||||
[XmlElement("disk")]
|
||||
public DatDisk[] Disk { get; set; }
|
||||
|
||||
[XmlElement("sample")]
|
||||
public DatSample[] Sample { get; set; }
|
||||
|
||||
[XmlElement("archive")]
|
||||
public DatArchive[] Archive { get; set; }
|
||||
}
|
||||
|
||||
[Serializable()]
|
||||
public class DatFile
|
||||
{
|
||||
[XmlElement("header")]
|
||||
public DatHeader Header { get; set; }
|
||||
|
||||
[XmlElement("game")]
|
||||
public DatGame[] Game { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 28105de94482c1643b7398d0bbb32af2
|
||||
@ -0,0 +1,121 @@
|
||||
using StoicGoose.Core.Interfaces;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
public class EmulatorHandler
|
||||
{
|
||||
readonly static string threadName = $"Unity_Emulation";
|
||||
|
||||
Thread thread = default;
|
||||
volatile bool threadRunning = false, threadPaused = false;
|
||||
|
||||
volatile bool isResetRequested = false;
|
||||
volatile bool isPauseRequested = false, newPauseState = false;
|
||||
volatile bool isFpsLimiterChangeRequested = false, limitFps = true, newLimitFps = false;
|
||||
|
||||
public bool IsRunning => threadRunning;
|
||||
public bool IsPaused => threadPaused;
|
||||
|
||||
public IMachine Machine { get; } = default;
|
||||
|
||||
public EmulatorHandler(Type machineType)
|
||||
{
|
||||
Machine = Activator.CreateInstance(machineType) as IMachine;
|
||||
Machine.Initialize();
|
||||
}
|
||||
|
||||
public void Startup()
|
||||
{
|
||||
Machine.Reset();
|
||||
|
||||
threadRunning = true;
|
||||
threadPaused = false;
|
||||
|
||||
thread = new Thread(ThreadMainLoop) { Name = threadName, Priority = ThreadPriority.AboveNormal, IsBackground = false };
|
||||
thread.Start();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
isResetRequested = true;
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
isPauseRequested = true;
|
||||
newPauseState = true;
|
||||
}
|
||||
|
||||
public void Unpause()
|
||||
{
|
||||
isPauseRequested = true;
|
||||
newPauseState = false;
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
threadRunning = false;
|
||||
threadPaused = false;
|
||||
|
||||
thread?.Join();
|
||||
|
||||
Machine.Shutdown();
|
||||
}
|
||||
|
||||
public void SetFpsLimiter(bool value)
|
||||
{
|
||||
isFpsLimiterChangeRequested = true;
|
||||
newLimitFps = value;
|
||||
}
|
||||
|
||||
private void ThreadMainLoop()
|
||||
{
|
||||
var stopWatch = Stopwatch.StartNew();
|
||||
var interval = 1000.0 / Machine.RefreshRate;
|
||||
var lastTime = 0.0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!threadRunning) break;
|
||||
|
||||
if (isResetRequested)
|
||||
{
|
||||
Machine.Reset();
|
||||
stopWatch.Restart();
|
||||
lastTime = 0.0;
|
||||
|
||||
isResetRequested = false;
|
||||
}
|
||||
|
||||
if (isPauseRequested)
|
||||
{
|
||||
threadPaused = newPauseState;
|
||||
isPauseRequested = false;
|
||||
}
|
||||
|
||||
if (isFpsLimiterChangeRequested)
|
||||
{
|
||||
limitFps = newLimitFps;
|
||||
isFpsLimiterChangeRequested = false;
|
||||
}
|
||||
|
||||
if (!threadPaused)
|
||||
{
|
||||
if (limitFps)
|
||||
{
|
||||
while ((stopWatch.Elapsed.TotalMilliseconds - lastTime) < interval)
|
||||
Thread.Sleep(0);
|
||||
|
||||
lastTime += interval;
|
||||
}
|
||||
else
|
||||
lastTime = stopWatch.Elapsed.TotalMilliseconds;
|
||||
|
||||
Machine.RunFrame();
|
||||
}
|
||||
else
|
||||
lastTime = stopWatch.Elapsed.TotalMilliseconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da891d0a5babf7649992f83861f6f33d
|
||||
8
Assets/Script/AppMain/Emulator/StoicGooseInterface.meta
Normal file
8
Assets/Script/AppMain/Emulator/StoicGooseInterface.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8193f4d4ec66bb441b59f6549e95e013
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class SGKeyboard : MonoBehaviour
|
||||
{
|
||||
internal void PollInput(ref List<string> buttonsPressed, ref List<string> buttonsHeld)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
internal void SetVerticalOrientation(bool isVerticalOrientation)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54c184e653057e64da4b9be96f4d876d
|
||||
@ -0,0 +1,328 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
public class SGSoundPlayer : MonoBehaviour//, ISoundPlayer
|
||||
{
|
||||
[SerializeField]
|
||||
private AudioSource m_as;
|
||||
private RingBuffer<float> _buffer = new RingBuffer<float>(44100 * 2);
|
||||
private TimeSpan lastElapsed;
|
||||
public double audioFPS { get; private set; }
|
||||
public bool IsRecording { get; private set; }
|
||||
|
||||
void Awake()
|
||||
{
|
||||
// 获取当前音频配置
|
||||
AudioConfiguration config = AudioSettings.GetConfiguration();
|
||||
|
||||
// 设置目标音频配置
|
||||
config.sampleRate = 44100; // 采样率为 44100Hz
|
||||
config.numRealVoices = 32; // 设置最大音频源数量(可选)
|
||||
config.numVirtualVoices = 512; // 设置虚拟音频源数量(可选)
|
||||
config.dspBufferSize = 1024; // 设置 DSP 缓冲区大小(可选)
|
||||
config.speakerMode = AudioSpeakerMode.Stereo; // 设置为立体声(2 声道)
|
||||
|
||||
// 应用新的音频配置
|
||||
if (AudioSettings.Reset(config))
|
||||
{
|
||||
Debug.Log("Audio settings updated successfully.");
|
||||
Debug.Log("Sample Rate: " + config.sampleRate + "Hz");
|
||||
Debug.Log("Speaker Mode: " + config.speakerMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Failed to update audio settings.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Queue<float> sampleQueue = new Queue<float>();
|
||||
|
||||
|
||||
// Unity 音频线程回调
|
||||
void OnAudioFilterRead(float[] data, int channels)
|
||||
{
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
if (_buffer.TryRead(out float rawData))
|
||||
data[i] = rawData;
|
||||
else
|
||||
data[i] = 0; // 无数据时静音
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
if (!m_as.isPlaying)
|
||||
{
|
||||
m_as.Play();
|
||||
}
|
||||
}
|
||||
|
||||
public void StopPlay()
|
||||
{
|
||||
if (m_as.isPlaying)
|
||||
{
|
||||
m_as.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void SubmitSamples(short[] buffer, short[][] ChannelSamples, int samples_a)
|
||||
{
|
||||
var current = UStoicGoose.sw.Elapsed;
|
||||
var delta = current - lastElapsed;
|
||||
lastElapsed = current;
|
||||
audioFPS = 1d / delta.TotalSeconds;
|
||||
|
||||
for (int i = 0; i < samples_a; i += 1)
|
||||
{
|
||||
_buffer.Write(buffer[i] / 32767.0f);
|
||||
|
||||
}
|
||||
if (IsRecording)
|
||||
{
|
||||
dataChunk.AddSampleData(buffer, samples_a);
|
||||
waveHeader.FileLength += (uint)samples_a;
|
||||
}
|
||||
}
|
||||
public void BufferWirte(int Off, byte[] Data)
|
||||
{
|
||||
}
|
||||
|
||||
public void GetCurrentPosition(out int play_position, out int write_position)
|
||||
{
|
||||
play_position = 0;
|
||||
write_position = 0;
|
||||
}
|
||||
|
||||
public void SetVolume(int Vol)
|
||||
{
|
||||
//TODO 音量
|
||||
if (m_as)
|
||||
return;
|
||||
m_as.volume = Vol;
|
||||
}
|
||||
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.F3))
|
||||
{
|
||||
BeginRecording();
|
||||
Debug.Log("录制");
|
||||
}
|
||||
if (Input.GetKeyDown(KeyCode.F4))
|
||||
{
|
||||
SaveRecording("D:/1.wav");
|
||||
Debug.Log("保存");
|
||||
}
|
||||
}
|
||||
WaveHeader waveHeader;
|
||||
FormatChunk formatChunk;
|
||||
DataChunk dataChunk;
|
||||
public void BeginRecording()
|
||||
{
|
||||
waveHeader = new WaveHeader();
|
||||
formatChunk = new FormatChunk(44100, 2);
|
||||
dataChunk = new DataChunk();
|
||||
waveHeader.FileLength += formatChunk.Length();
|
||||
|
||||
IsRecording = true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void SaveRecording(string filename)
|
||||
{
|
||||
using (FileStream file = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
|
||||
{
|
||||
file.Write(waveHeader.GetBytes(), 0, (int)waveHeader.Length());
|
||||
file.Write(formatChunk.GetBytes(), 0, (int)formatChunk.Length());
|
||||
file.Write(dataChunk.GetBytes(), 0, (int)dataChunk.Length());
|
||||
}
|
||||
|
||||
IsRecording = false;
|
||||
|
||||
}
|
||||
|
||||
internal void EnqueueSamples(short[] s)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
internal void Unpause()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
internal void Pause()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
class WaveHeader
|
||||
{
|
||||
const string fileTypeId = "RIFF";
|
||||
const string mediaTypeId = "WAVE";
|
||||
|
||||
public string FileTypeId { get; private set; }
|
||||
public uint FileLength { get; set; }
|
||||
public string MediaTypeId { get; private set; }
|
||||
|
||||
public WaveHeader()
|
||||
{
|
||||
FileTypeId = fileTypeId;
|
||||
MediaTypeId = mediaTypeId;
|
||||
FileLength = 4; /* Minimum size is always 4 bytes */
|
||||
}
|
||||
|
||||
public byte[] GetBytes()
|
||||
{
|
||||
List<byte> chunkData = new List<byte>();
|
||||
|
||||
chunkData.AddRange(Encoding.ASCII.GetBytes(FileTypeId));
|
||||
chunkData.AddRange(BitConverter.GetBytes(FileLength));
|
||||
chunkData.AddRange(Encoding.ASCII.GetBytes(MediaTypeId));
|
||||
|
||||
return chunkData.ToArray();
|
||||
}
|
||||
|
||||
public uint Length()
|
||||
{
|
||||
return (uint)GetBytes().Length;
|
||||
}
|
||||
}
|
||||
|
||||
class FormatChunk
|
||||
{
|
||||
const string chunkId = "fmt ";
|
||||
|
||||
ushort bitsPerSample, channels;
|
||||
uint frequency;
|
||||
|
||||
public string ChunkId { get; private set; }
|
||||
public uint ChunkSize { get; private set; }
|
||||
public ushort FormatTag { get; private set; }
|
||||
|
||||
public ushort Channels
|
||||
{
|
||||
get { return channels; }
|
||||
set { channels = value; RecalcBlockSizes(); }
|
||||
}
|
||||
|
||||
public uint Frequency
|
||||
{
|
||||
get { return frequency; }
|
||||
set { frequency = value; RecalcBlockSizes(); }
|
||||
}
|
||||
|
||||
public uint AverageBytesPerSec { get; private set; }
|
||||
public ushort BlockAlign { get; private set; }
|
||||
|
||||
public ushort BitsPerSample
|
||||
{
|
||||
get { return bitsPerSample; }
|
||||
set { bitsPerSample = value; RecalcBlockSizes(); }
|
||||
}
|
||||
|
||||
public FormatChunk()
|
||||
{
|
||||
ChunkId = chunkId;
|
||||
ChunkSize = 16;
|
||||
FormatTag = 1; /* MS PCM (Uncompressed wave file) */
|
||||
Channels = 2; /* Default to stereo */
|
||||
Frequency = 44100; /* Default to 44100hz */
|
||||
BitsPerSample = 16; /* Default to 16bits */
|
||||
RecalcBlockSizes();
|
||||
}
|
||||
|
||||
public FormatChunk(int frequency, int channels) : this()
|
||||
{
|
||||
Channels = (ushort)channels;
|
||||
Frequency = (ushort)frequency;
|
||||
RecalcBlockSizes();
|
||||
}
|
||||
|
||||
private void RecalcBlockSizes()
|
||||
{
|
||||
BlockAlign = (ushort)(channels * (bitsPerSample / 8));
|
||||
AverageBytesPerSec = frequency * BlockAlign;
|
||||
}
|
||||
|
||||
public byte[] GetBytes()
|
||||
{
|
||||
List<byte> chunkBytes = new List<byte>();
|
||||
|
||||
chunkBytes.AddRange(Encoding.ASCII.GetBytes(ChunkId));
|
||||
chunkBytes.AddRange(BitConverter.GetBytes(ChunkSize));
|
||||
chunkBytes.AddRange(BitConverter.GetBytes(FormatTag));
|
||||
chunkBytes.AddRange(BitConverter.GetBytes(Channels));
|
||||
chunkBytes.AddRange(BitConverter.GetBytes(Frequency));
|
||||
chunkBytes.AddRange(BitConverter.GetBytes(AverageBytesPerSec));
|
||||
chunkBytes.AddRange(BitConverter.GetBytes(BlockAlign));
|
||||
chunkBytes.AddRange(BitConverter.GetBytes(BitsPerSample));
|
||||
|
||||
return chunkBytes.ToArray();
|
||||
}
|
||||
|
||||
public uint Length()
|
||||
{
|
||||
return (uint)GetBytes().Length;
|
||||
}
|
||||
}
|
||||
|
||||
class DataChunk
|
||||
{
|
||||
const string chunkId = "data";
|
||||
|
||||
public string ChunkId { get; private set; }
|
||||
public uint ChunkSize { get; set; }
|
||||
public List<short> WaveData { get; private set; }
|
||||
|
||||
public DataChunk()
|
||||
{
|
||||
ChunkId = chunkId;
|
||||
ChunkSize = 0;
|
||||
WaveData = new List<short>();
|
||||
}
|
||||
|
||||
public byte[] GetBytes()
|
||||
{
|
||||
List<byte> chunkBytes = new List<byte>();
|
||||
|
||||
chunkBytes.AddRange(Encoding.ASCII.GetBytes(ChunkId));
|
||||
chunkBytes.AddRange(BitConverter.GetBytes(ChunkSize));
|
||||
byte[] bufferBytes = new byte[WaveData.Count * 2];
|
||||
Buffer.BlockCopy(WaveData.ToArray(), 0, bufferBytes, 0, bufferBytes.Length);
|
||||
chunkBytes.AddRange(bufferBytes.ToList());
|
||||
|
||||
return chunkBytes.ToArray();
|
||||
}
|
||||
|
||||
public uint Length()
|
||||
{
|
||||
return (uint)GetBytes().Length;
|
||||
}
|
||||
|
||||
public void AddSampleData(short[] stereoBuffer)
|
||||
{
|
||||
WaveData.AddRange(stereoBuffer);
|
||||
|
||||
ChunkSize += (uint)(stereoBuffer.Length * 2);
|
||||
}
|
||||
//public unsafe void AddSampleData(short* stereoBuffer, int lenght)
|
||||
//{
|
||||
// for (int i = 0; i < lenght; i++)
|
||||
// {
|
||||
// WaveData.Add(stereoBuffer[i]);
|
||||
// }
|
||||
|
||||
// ChunkSize += (uint)(lenght * 2);
|
||||
//}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 925771571ae9709429297d42587ce36d
|
||||
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
public class SGVideoPlayer : MonoBehaviour//, ISoundPlayer
|
||||
{
|
||||
public bool IsVerticalOrientation { get; internal set; }
|
||||
|
||||
internal void UpdateScreen(byte[] obj)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e8778828cf820b640b9d26ae977cdaba
|
||||
577
Assets/Script/AppMain/Emulator/UStoicGoose.cs
Normal file
577
Assets/Script/AppMain/Emulator/UStoicGoose.cs
Normal file
@ -0,0 +1,577 @@
|
||||
using StoicGoose.Common.Extensions;
|
||||
using StoicGoose.Common.Utilities;
|
||||
using StoicGoose.Core.Machines;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using CartridgeMetadata = StoicGoose.Core.Cartridges.Metadata;
|
||||
|
||||
public class UStoicGoose : MonoBehaviour
|
||||
{
|
||||
public static UStoicGoose instance;
|
||||
public static System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
void Awake()
|
||||
{
|
||||
instance = this;
|
||||
Program.InitPath(Application.persistentDataPath);
|
||||
Init();
|
||||
LoadAndRunCartridge("");
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
EmuClose();
|
||||
}
|
||||
|
||||
/* Constants */
|
||||
readonly static int maxScreenSizeFactor = 5;
|
||||
readonly static int maxRecentFiles = 15;
|
||||
readonly static int statusIconSize = 12;
|
||||
|
||||
readonly static List<(string description, string extension, Func<string, Stream> streamReadFunc)> supportedFileInformation = new()
|
||||
{
|
||||
("WonderSwan ROMs", ".ws", GetStreamFromFile),
|
||||
("WonderSwan Color ROMs", ".wsc", GetStreamFromFile),
|
||||
("Zip Archives", ".zip", GetStreamFromFirstZippedFile)
|
||||
};
|
||||
|
||||
/* Various handlers */
|
||||
DatabaseHandler databaseHandler = default;
|
||||
SGVideoPlayer graphicsHandler = default;
|
||||
SGSoundPlayer soundHandler = default;
|
||||
SGKeyboard inputHandler = default;
|
||||
EmulatorHandler emulatorHandler = default;
|
||||
|
||||
/* Misc. windows */
|
||||
//SoundRecorderForm soundRecorderForm = default;
|
||||
//CheatsForm cheatsForm = default;
|
||||
|
||||
/* Misc. runtime variables */
|
||||
Type machineType = default;
|
||||
bool isVerticalOrientation = false;
|
||||
string internalEepromPath = string.Empty;
|
||||
//Cheat[] cheats = default;
|
||||
|
||||
|
||||
private void Init()
|
||||
{
|
||||
Log.WriteEvent(LogSeverity.Information, this, "Initializing emulator and UI...");
|
||||
|
||||
machineType = Program.Configuration.General.PreferOriginalWS ? typeof(WonderSwan) : typeof(WonderSwanColor);
|
||||
|
||||
InitializeEmulatorHandler();
|
||||
VerifyConfiguration();
|
||||
InitializeOtherHandlers();
|
||||
//InitializeWindows();
|
||||
|
||||
SizeAndPositionWindow();
|
||||
SetWindowTitleAndStatus();
|
||||
Log.WriteEvent(LogSeverity.Information, this, "Initialization done!");
|
||||
}
|
||||
|
||||
|
||||
private void EmuClose()
|
||||
{
|
||||
SaveAllData();
|
||||
emulatorHandler.Shutdown();
|
||||
|
||||
Program.SaveConfiguration();
|
||||
}
|
||||
|
||||
|
||||
private void InitializeEmulatorHandler()
|
||||
{
|
||||
emulatorHandler = new EmulatorHandler(machineType);
|
||||
emulatorHandler.SetFpsLimiter(Program.Configuration.General.LimitFps);
|
||||
}
|
||||
|
||||
private void VerifyConfiguration()
|
||||
{
|
||||
foreach (var button in emulatorHandler.Machine.GameControls.Replace(" ", "").Split(','))
|
||||
{
|
||||
if (!Program.Configuration.Input.GameControls.ContainsKey(button))
|
||||
Program.Configuration.Input.GameControls[button] = new();
|
||||
}
|
||||
|
||||
foreach (var button in emulatorHandler.Machine.HardwareControls.Replace(" ", "").Split(','))
|
||||
{
|
||||
if (!Program.Configuration.Input.SystemControls.ContainsKey(button))
|
||||
Program.Configuration.Input.SystemControls[button] = new();
|
||||
}
|
||||
|
||||
if (Program.Configuration.Video.ScreenSize < 2 || Program.Configuration.Video.ScreenSize > maxScreenSizeFactor)
|
||||
Program.Configuration.Video.ResetToDefault(nameof(Program.Configuration.Video.ScreenSize));
|
||||
|
||||
//if (string.IsNullOrEmpty(Program.Configuration.Video.Shader) || (graphicsHandler != null && !graphicsHandler.AvailableShaders.Contains(Program.Configuration.Video.Shader)))
|
||||
// Program.Configuration.Video.Shader = GraphicsHandler.DefaultShaderName;
|
||||
}
|
||||
|
||||
private void InitializeOtherHandlers()
|
||||
{
|
||||
databaseHandler = new DatabaseHandler(Program.NoIntroDatPath);
|
||||
|
||||
//statusIconsLocation = machineType == typeof(WonderSwan) ? new(0, DisplayControllerCommon.ScreenHeight) : new(DisplayControllerCommon.ScreenWidth, 0);
|
||||
graphicsHandler = this.gameObject.GetComponent<SGVideoPlayer>();
|
||||
//TODO graphicsHandler基本参数,可能需要补上
|
||||
//graphicsHandler = new GraphicsHandler(machineType, new(emulatorHandler.Machine.ScreenWidth, emulatorHandler.Machine.ScreenHeight), statusIconsLocation, statusIconSize, machineType != typeof(WonderSwan), Program.Configuration.Video.Shader)
|
||||
//{
|
||||
// IsVerticalOrientation = isVerticalOrientation
|
||||
//};
|
||||
|
||||
soundHandler = this.gameObject.GetComponent<SGSoundPlayer>();
|
||||
//TODO 声音基本参数,可能需要补上
|
||||
//soundHandler = new SoundHandler(44100, 2);
|
||||
//soundHandler.SetVolume(1.0f);
|
||||
//soundHandler.SetMute(Program.Configuration.Sound.Mute);
|
||||
//soundHandler.SetLowPassFilter(Program.Configuration.Sound.LowPassFilter);
|
||||
|
||||
inputHandler = this.gameObject.GetComponent<SGKeyboard>();
|
||||
|
||||
//TODO Input基本参数,可能需要补上
|
||||
//inputHandler = new InputHandler(renderControl);
|
||||
//inputHandler.SetKeyMapping(Program.Configuration.Input.GameControls, Program.Configuration.Input.SystemControls);
|
||||
//inputHandler.SetVerticalOrientation(isVerticalOrientation);
|
||||
//inputHandler.SetEnableRemapping(Program.Configuration.Input.AutoRemap);
|
||||
//inputHandler.SetVerticalRemapping(emulatorHandler.Machine.VerticalControlRemap
|
||||
// .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
// .Select(x => x.Split('=', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
||||
// .ToDictionary(x => x[0], x => x[1]));
|
||||
|
||||
emulatorHandler.Machine.DisplayController.SendFramebuffer = graphicsHandler.UpdateScreen;
|
||||
emulatorHandler.Machine.SoundController.SendSamples = (s) =>
|
||||
{
|
||||
soundHandler.EnqueueSamples(s);
|
||||
//soundRecorderForm.EnqueueSamples(s);
|
||||
};
|
||||
|
||||
emulatorHandler.Machine.ReceiveInput += () =>
|
||||
{
|
||||
var buttonsPressed = new List<string>();
|
||||
var buttonsHeld = new List<string>();
|
||||
|
||||
inputHandler.PollInput(ref buttonsPressed, ref buttonsHeld);
|
||||
|
||||
if (buttonsPressed.Contains("Volume"))
|
||||
emulatorHandler.Machine.SoundController.ChangeMasterVolume();
|
||||
|
||||
return (buttonsPressed, buttonsHeld);
|
||||
};
|
||||
|
||||
//renderControl.Resize += (s, e) => { if (s is Control control) graphicsHandler.Resize(control.ClientRectangle); };
|
||||
//renderControl.Paint += (s, e) =>
|
||||
//{
|
||||
// graphicsHandler.SetClearColor(Color.Black);
|
||||
|
||||
// graphicsHandler.ClearFrame();
|
||||
|
||||
// if (emulatorHandler.Machine is MachineCommon machine)
|
||||
// {
|
||||
// var activeIcons = new List<string>() { "Power" };
|
||||
|
||||
// if (machine.BuiltInSelfTestOk) activeIcons.Add("Initialized");
|
||||
|
||||
// if (machine.DisplayController.IconSleep) activeIcons.Add("Sleep");
|
||||
// if (machine.DisplayController.IconVertical) activeIcons.Add("Vertical");
|
||||
// if (machine.DisplayController.IconHorizontal) activeIcons.Add("Horizontal");
|
||||
// if (machine.DisplayController.IconAux1) activeIcons.Add("Aux1");
|
||||
// if (machine.DisplayController.IconAux2) activeIcons.Add("Aux2");
|
||||
// if (machine.DisplayController.IconAux3) activeIcons.Add("Aux3");
|
||||
|
||||
// if (machine.SoundController.HeadphonesConnected) activeIcons.Add("Headphones");
|
||||
// if (machine.SoundController.MasterVolume == 0) activeIcons.Add("Volume0");
|
||||
// if (machine.SoundController.MasterVolume == 1) activeIcons.Add("Volume1");
|
||||
// if (machine.SoundController.MasterVolume == 2) activeIcons.Add("Volume2");
|
||||
// if (machine.SoundController.MasterVolume == 3 && machine is WonderSwanColor) activeIcons.Add("Volume3");
|
||||
|
||||
// graphicsHandler.UpdateStatusIcons(activeIcons);
|
||||
// }
|
||||
// graphicsHandler.DrawFrame();
|
||||
//};
|
||||
|
||||
internalEepromPath = Path.Combine(Program.InternalDataPath, $"{machineType.Name}.eep");
|
||||
}
|
||||
|
||||
//private void InitializeWindows()
|
||||
//{
|
||||
// soundRecorderForm = new(soundHandler.SampleRate, soundHandler.NumChannels);
|
||||
// cheatsForm = new()
|
||||
// {
|
||||
// Callback = (c) =>
|
||||
// {
|
||||
// if (emulatorHandler.IsRunning)
|
||||
// cheats = (Cheat[])c.Clone();
|
||||
// }
|
||||
// };
|
||||
//}
|
||||
|
||||
private void SizeAndPositionWindow()
|
||||
{
|
||||
//if (WindowState == FormWindowState.Maximized)
|
||||
// WindowState = FormWindowState.Normal;
|
||||
|
||||
//MinimumSize = SizeFromClientSize(CalculateRequiredClientSize(2));
|
||||
//Size = SizeFromClientSize(CalculateRequiredClientSize(Program.Configuration.Video.ScreenSize));
|
||||
|
||||
//var screen = Screen.FromControl(this);
|
||||
//var workingArea = screen.WorkingArea;
|
||||
//Location = new Point()
|
||||
//{
|
||||
// X = Math.Max(workingArea.X, workingArea.X + (workingArea.Width - Width) / 2),
|
||||
// Y = Math.Max(workingArea.Y, workingArea.Y + (workingArea.Height - Height) / 2)
|
||||
//};
|
||||
}
|
||||
|
||||
//TODO 设置屏幕宽高 看是否需要
|
||||
//private Size CalculateRequiredClientSize(int screenSize)
|
||||
//{
|
||||
// if (emulatorHandler == null || graphicsHandler == null)
|
||||
// return ClientSize;
|
||||
|
||||
// var statusIconsOnRight = statusIconsLocation.X > statusIconsLocation.Y;
|
||||
|
||||
// int screenWidth, screenHeight;
|
||||
|
||||
// if (!isVerticalOrientation)
|
||||
// {
|
||||
// screenWidth = emulatorHandler.Machine.ScreenWidth;
|
||||
// screenHeight = emulatorHandler.Machine.ScreenHeight;
|
||||
// if (statusIconsOnRight) screenWidth += statusIconSize;
|
||||
// if (!statusIconsOnRight) screenHeight += statusIconSize;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// screenWidth = emulatorHandler.Machine.ScreenHeight;
|
||||
// screenHeight = emulatorHandler.Machine.ScreenWidth;
|
||||
// if (!statusIconsOnRight) screenWidth += statusIconSize;
|
||||
// if (statusIconsOnRight) screenHeight += statusIconSize;
|
||||
// }
|
||||
|
||||
// return new(screenWidth * screenSize, (screenHeight * screenSize) + menuStrip.Height + statusStrip.Height);
|
||||
//}
|
||||
|
||||
private void SetWindowTitleAndStatus()
|
||||
{
|
||||
//TODO 修改为状态字符串,显示在某个地方
|
||||
|
||||
//var titleStringBuilder = new StringBuilder();
|
||||
|
||||
//titleStringBuilder.Append($"{Application.ProductName} {Program.GetVersionString(false)}");
|
||||
|
||||
//if (emulatorHandler.Machine.Cartridge.IsLoaded)
|
||||
//{
|
||||
// titleStringBuilder.Append($" - [{Path.GetFileName(Program.Configuration.General.RecentFiles.First())}]");
|
||||
|
||||
// var statusStringBuilder = new StringBuilder();
|
||||
// statusStringBuilder.Append($"Emulating {emulatorHandler.Machine.Manufacturer} {emulatorHandler.Machine.Model}, ");
|
||||
// statusStringBuilder.Append($"playing {databaseHandler.GetGameTitle(emulatorHandler.Machine.Cartridge.Crc32, emulatorHandler.Machine.Cartridge.SizeInBytes)} ({emulatorHandler.Machine.Cartridge.Metadata.GameIdString})");
|
||||
|
||||
// tsslStatus.Text = statusStringBuilder.ToString();
|
||||
// tsslEmulationStatus.Text = emulatorHandler.IsRunning ? (emulatorHandler.IsPaused ? "Paused" : "Running") : "Stopped";
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// tsslStatus.Text = "Ready";
|
||||
// tsslEmulationStatus.Text = "Stopped";
|
||||
//}
|
||||
|
||||
//Text = titleStringBuilder.ToString();
|
||||
}
|
||||
|
||||
private void LoadBootstrap(string filename)
|
||||
{
|
||||
if (GlobalVariables.EnableSkipBootstrapIfFound) return;
|
||||
|
||||
if (!emulatorHandler.IsRunning)
|
||||
{
|
||||
if (File.Exists(filename))
|
||||
{
|
||||
using var stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
var data = new byte[stream.Length];
|
||||
stream.Read(data, 0, data.Length);
|
||||
emulatorHandler.Machine.LoadBootstrap(data);
|
||||
}
|
||||
emulatorHandler.Machine.UseBootstrap = Program.Configuration.General.UseBootstrap;
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadInternalEeprom()
|
||||
{
|
||||
if (!emulatorHandler.IsRunning && File.Exists(internalEepromPath))
|
||||
{
|
||||
using var stream = new FileStream(internalEepromPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
var data = new byte[stream.Length];
|
||||
stream.Read(data, 0, data.Length);
|
||||
emulatorHandler.Machine.LoadInternalEeprom(data);
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream GetStreamFromFile(string filename)
|
||||
{
|
||||
return new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
}
|
||||
|
||||
private static Stream GetStreamFromFirstZippedFile(string filename)
|
||||
{
|
||||
return new ZipArchive(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)).Entries.FirstOrDefault()?.Open();
|
||||
}
|
||||
|
||||
private void LoadAndRunCartridge(string filename)
|
||||
{
|
||||
if (emulatorHandler.IsRunning)
|
||||
{
|
||||
SaveAllData();
|
||||
emulatorHandler.Shutdown();
|
||||
}
|
||||
|
||||
using var inputStream = supportedFileInformation.FirstOrDefault(x => x.extension == Path.GetExtension(filename)).streamReadFunc(filename) ?? GetStreamFromFile(filename);
|
||||
using var stream = new MemoryStream();
|
||||
inputStream.CopyTo(stream);
|
||||
stream.Position = 0;
|
||||
|
||||
var data = new byte[stream.Length];
|
||||
stream.Read(data, 0, data.Length);
|
||||
emulatorHandler.Machine.LoadRom(data);
|
||||
|
||||
graphicsHandler.IsVerticalOrientation = isVerticalOrientation = emulatorHandler.Machine.Cartridge.Metadata.Orientation == CartridgeMetadata.Orientations.Vertical;
|
||||
inputHandler.SetVerticalOrientation(isVerticalOrientation);
|
||||
|
||||
LoadRam();
|
||||
|
||||
LoadBootstrap(emulatorHandler.Machine is WonderSwan ? Program.Configuration.General.BootstrapFile : Program.Configuration.General.BootstrapFileWSC);
|
||||
LoadInternalEeprom();
|
||||
|
||||
emulatorHandler.Startup();
|
||||
|
||||
SizeAndPositionWindow();
|
||||
SetWindowTitleAndStatus();
|
||||
|
||||
Program.SaveConfiguration();
|
||||
}
|
||||
|
||||
private void LoadRam()
|
||||
{
|
||||
var path = Path.Combine(Program.SaveDataPath, $"{Path.GetFileNameWithoutExtension(Program.Configuration.General.RecentFiles.First())}.sav");
|
||||
if (!File.Exists(path)) return;
|
||||
|
||||
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
var data = new byte[stream.Length];
|
||||
stream.Read(data, 0, data.Length);
|
||||
if (data.Length != 0)
|
||||
emulatorHandler.Machine.LoadSaveData(data);
|
||||
}
|
||||
|
||||
private void SaveAllData()
|
||||
{
|
||||
SaveInternalEeprom();
|
||||
SaveRam();
|
||||
}
|
||||
|
||||
private void SaveInternalEeprom()
|
||||
{
|
||||
var data = emulatorHandler.Machine.GetInternalEeprom();
|
||||
if (data.Length == 0) return;
|
||||
|
||||
using var stream = new FileStream(internalEepromPath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
|
||||
stream.Write(data, 0, data.Length);
|
||||
}
|
||||
|
||||
private void SaveRam()
|
||||
{
|
||||
var data = emulatorHandler.Machine.GetSaveData();
|
||||
if (data.Length == 0) return;
|
||||
|
||||
var path = Path.Combine(Program.SaveDataPath, $"{Path.GetFileNameWithoutExtension(Program.Configuration.General.RecentFiles.First())}.sav");
|
||||
|
||||
using var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
|
||||
stream.Write(data, 0, data.Length);
|
||||
}
|
||||
|
||||
|
||||
private void PauseEmulation()
|
||||
{
|
||||
if (!emulatorHandler.IsRunning) return;
|
||||
|
||||
emulatorHandler.Pause();
|
||||
soundHandler.Pause();
|
||||
|
||||
SetWindowTitleAndStatus();
|
||||
}
|
||||
|
||||
private void UnpauseEmulation()
|
||||
{
|
||||
if (!emulatorHandler.IsRunning) return;
|
||||
|
||||
emulatorHandler.Unpause();
|
||||
soundHandler.Unpause();
|
||||
|
||||
SetWindowTitleAndStatus();
|
||||
}
|
||||
|
||||
private void ResetEmulation()
|
||||
{
|
||||
SaveAllData();
|
||||
emulatorHandler.Reset();
|
||||
|
||||
Program.SaveConfiguration();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class Program
|
||||
{
|
||||
static string jsonConfigFileName;//= "Config.json";
|
||||
static string logFileName;//= "Log.txt";
|
||||
static string internalDataDirectoryName;//= "Internal";
|
||||
static string saveDataDirectoryName;//= "Saves";
|
||||
static string cheatDataDirectoryName;//= "Cheats";
|
||||
static string debuggingDataDirectoryName;//= "Debugging";
|
||||
static string assetsDirectoryName;//= "Assets";
|
||||
static string shaderDirectoryName;//= "Shaders";
|
||||
static string noIntroDatDirectoryName;//= "No-Intro";
|
||||
static string mutexName;//= $"Unity_{GetVersionDetails()}";
|
||||
static string programDataDirectory;//= Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), Application.ProductName);
|
||||
static string programConfigPath;//= Path.Combine(programDataDirectory, jsonConfigFileName);
|
||||
|
||||
public static Configuration Configuration { get; private set; } = LoadConfiguration(programConfigPath);
|
||||
|
||||
public static string DataPath;//{ get; } = string.Empty;
|
||||
public static string InternalDataPath;//{ get; } = string.Empty;
|
||||
public static string SaveDataPath;//{ get; } = string.Empty;
|
||||
public static string CheatsDataPath;//{ get; } = string.Empty;
|
||||
public static string DebuggingDataPath;//{ get; } = string.Empty;
|
||||
|
||||
readonly static string programApplicationDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||||
readonly static string programAssetsDirectory = Path.Combine(programApplicationDirectory, assetsDirectoryName);
|
||||
|
||||
//public static string ShaderPath { get; } = string.Empty;
|
||||
public static string NoIntroDatPath;// { get; } = string.Empty;
|
||||
|
||||
//static MainForm mainForm = default;
|
||||
|
||||
public static void InitPath(string CustonDataDir)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
jsonConfigFileName = "Config.json";
|
||||
logFileName = "Log.txt";
|
||||
internalDataDirectoryName = "Internal";
|
||||
saveDataDirectoryName = "Saves";
|
||||
cheatDataDirectoryName = "Cheats";
|
||||
debuggingDataDirectoryName = "Debugging";
|
||||
assetsDirectoryName = "Assets";
|
||||
shaderDirectoryName = "Shaders";
|
||||
noIntroDatDirectoryName = "No-Intro";
|
||||
mutexName = $"Unity_{GetVersionDetails()}";
|
||||
programDataDirectory = Path.Combine(CustonDataDir, "AxibugEmu");
|
||||
programConfigPath = Path.Combine(programDataDirectory, jsonConfigFileName);
|
||||
|
||||
Log.WriteLine(Path.Combine(programDataDirectory, logFileName));
|
||||
|
||||
Directory.CreateDirectory(DataPath = programDataDirectory);
|
||||
Directory.CreateDirectory(InternalDataPath = Path.Combine(programDataDirectory, internalDataDirectoryName));
|
||||
Directory.CreateDirectory(SaveDataPath = Path.Combine(programDataDirectory, saveDataDirectoryName));
|
||||
Directory.CreateDirectory(CheatsDataPath = Path.Combine(programDataDirectory, cheatDataDirectoryName));
|
||||
Directory.CreateDirectory(DebuggingDataPath = Path.Combine(programDataDirectory, debuggingDataDirectoryName));
|
||||
|
||||
//if (!Directory.Exists(ShaderPath = Path.Combine(programAssetsDirectory, shaderDirectoryName)))
|
||||
// throw new DirectoryNotFoundException("Shader directory missing");
|
||||
|
||||
if (!Directory.Exists(NoIntroDatPath = Path.Combine(programAssetsDirectory, noIntroDatDirectoryName)))
|
||||
throw new DirectoryNotFoundException("No-Intro .dat directory missing");
|
||||
}
|
||||
catch (DirectoryNotFoundException e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
//[STAThread]
|
||||
//static void Main()
|
||||
//{
|
||||
// using var mutex = new Mutex(true, mutexName, out bool newInstance);
|
||||
// if (!newInstance)
|
||||
// {
|
||||
// MessageBox.Show($"Another instance of {Application.ProductName} is already running.\n\nThis instance will now shut down.",
|
||||
// $"{Application.ProductName} Startup Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
// Environment.Exit(-1);
|
||||
// }
|
||||
|
||||
// Application.SetHighDpiMode(HighDpiMode.SystemAware);
|
||||
// Application.EnableVisualStyles();
|
||||
// Application.SetCompatibleTextRenderingDefault(false);
|
||||
|
||||
// if (!Debugger.IsAttached)
|
||||
// {
|
||||
// Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
|
||||
// Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
|
||||
// AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
|
||||
// }
|
||||
|
||||
// Application.Run(mainForm = new MainForm());
|
||||
//}
|
||||
|
||||
//static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
|
||||
//{
|
||||
// if (e.Exception is GLFWException glEx)
|
||||
// {
|
||||
// var renderControl = mainForm.Controls["renderControl"] as OpenGL.RenderControl;
|
||||
// MessageBox.Show($"{glEx.Message.EnsureEndsWithPeriod()}\n\n{Application.ProductName} requires GPU and drivers supporting OpenGL {renderControl.APIVersion.Major}.{renderControl.APIVersion.Minor}.", $"{Application.ProductName} Startup Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// MessageBox.Show(e.Exception.Message.EnsureEndsWithPeriod(), $"{Application.ProductName} Startup Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
// }
|
||||
|
||||
// Environment.Exit(-1);
|
||||
//}
|
||||
|
||||
//static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
//{
|
||||
// MessageBox.Show((e.ExceptionObject as Exception).Message, $"{Application.ProductName} Startup Error");
|
||||
// Environment.Exit(-1);
|
||||
//}
|
||||
|
||||
private static Configuration LoadConfiguration(string filename)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(filename));
|
||||
|
||||
Configuration configuration;
|
||||
if (!File.Exists(filename) || (configuration = filename.DeserializeFromFile<Configuration>()) == null)
|
||||
{
|
||||
configuration = new Configuration();
|
||||
configuration.SerializeToFile(filename);
|
||||
}
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
public static void ReplaceConfiguration(Configuration newConfig)
|
||||
{
|
||||
ConfigurationBase.CopyConfiguration(newConfig, Configuration);
|
||||
SaveConfiguration();
|
||||
}
|
||||
|
||||
public static void SaveConfiguration()
|
||||
{
|
||||
Configuration?.SerializeToFile(programConfigPath);
|
||||
}
|
||||
|
||||
private static string GetVersionDetails()
|
||||
{
|
||||
//return $"{ThisAssembly.Git.Branch}-{ThisAssembly.Git.Commit}{(ThisAssembly.Git.IsDirty ? "-dirty" : string.Empty)}{(GlobalVariables.IsDebugBuild ? "+debug" : string.Empty)}";
|
||||
return $"{(GlobalVariables.IsDebugBuild ? "+debug" : string.Empty)}";
|
||||
}
|
||||
|
||||
//public static string GetVersionString(bool detailed)
|
||||
//{
|
||||
// var version = new Version(Application.ProductVersion);
|
||||
// var stringBuilder = new StringBuilder();
|
||||
// stringBuilder.Append($"v{version.Major:D3}{(version.Minor != 0 ? $".{version.Minor}" : string.Empty)}");
|
||||
// if (detailed) stringBuilder.Append($" ({GetVersionDetails()})");
|
||||
// return stringBuilder.ToString();
|
||||
//}
|
||||
}
|
||||
|
||||
2
Assets/Script/AppMain/Emulator/UStoicGoose.cs.meta
Normal file
2
Assets/Script/AppMain/Emulator/UStoicGoose.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b7e1a8282bc4d764c81f63e62fd7aec8
|
||||
Loading…
Reference in New Issue
Block a user