Replace Magick.NET with stb_image; refactor asset pipeline

- Switched image loading/saving from Magick.NET to native stb_image (Ghost.StbI), removing Magick.NET dependency.
- Added Ghost.StbI project with native DLL, P/Invoke bindings, and wrapper.
- Refactored TextureAssetHandler and TextureProcessor for stb_image, memory-mapped IO, and HDR/16-bit support.
- Split IAssetHandler into IImportableAssetHandler and IPackableAssetHandler; updated interfaces to use FileStream.
- Added shader and mesh asset handlers (GraphicsShaderAssetHandler, ComputeShaderAssetHandler, FBXAssetHandler).
- Improved asset registry/catalog path handling and naming consistency.
- Updated asset import pipeline to use new interfaces and trigger engine reimport.
- Enhanced UI toolbar button styles and EditPage layout.
- Added StbIBindingTest, DisableRuntimeMarshalling, and native wrapper attributes.
- Updated wrapper generator for regex derivesFrom; added stbi.json config.
- Removed Magick.NET reference; added Ghost.StbI and Ghost.Ufbx references.
- Miscellaneous bugfixes and code cleanup.
This commit is contained in:
2026-04-24 00:40:27 +09:00
parent 3533d3367f
commit 4757c0c91a
52 changed files with 8343 additions and 270 deletions

View File

@@ -18,7 +18,7 @@ public struct DSLShaderError
}
}
internal static class DSLShaderCompiler
public static class DSLShaderCompiler
{
private static PipelineState MeragePipeline(PipelineSemantic? semantic, PipelineState parent)
{
@@ -141,21 +141,27 @@ internal static class DSLShaderCompiler
var descriptor = new GraphicsShaderDescriptor
{
name = semantics.name,
propertyBufferSize = propertyInfo.size,
Name = semantics.name,
PropertyBufferSize = propertyInfo.size,
shaderModel = semantics.shaderModel,
passes = passes
ShaderModel = semantics.shaderModel,
Passes = passes
};
for (var i = 0; i < descriptor.passes.Length; i++)
for (var i = 0; i < descriptor.Passes.Length; i++)
{
descriptor.passes[i].shader = descriptor;
descriptor.Passes[i].shader = descriptor;
}
return descriptor;
}
public static Result<GraphicsShaderDescriptor> CompileGraphicsShader(Stream stream)
{
using var reader = new StreamReader(stream);
return CompileGraphicsShader(reader.ReadToEnd());
}
public static Result<GraphicsShaderDescriptor> CompileGraphicsShader(string shaderPath)
{
try
@@ -163,7 +169,8 @@ internal static class DSLShaderCompiler
var source = File.ReadAllText(shaderPath);
// Use ANTLR4 parser
var shaderModels = AntlrShaderCompiler.ParseShaders(source, out var parseErrors);
var parseErrors = new List<DSLShaderError>();
var shaderModels = AntlrShaderCompiler.ParseShaders(source, parseErrors);
if (parseErrors.Count != 0)
{
@@ -209,13 +216,20 @@ internal static class DSLShaderCompiler
}
}
public static Result<ComputeShaderDescriptor> CompileComputeShader(Stream stream)
{
using var reader = new StreamReader(stream);
return CompileComputeShader(reader.ReadToEnd());
}
public static Result<ComputeShaderDescriptor> CompileComputeShader(string shaderPath)
{
try
{
var source = File.ReadAllText(shaderPath);
var shaderModels = AntlrShaderCompiler.ParseComputeShaders(source, out var parseErrors);
var parseErrors = new List<DSLShaderError>();
var shaderModels = AntlrShaderCompiler.ParseComputeShaders(source, parseErrors);
if (parseErrors.Count != 0)
{
@@ -281,12 +295,12 @@ internal static class DSLShaderCompiler
return new ComputeShaderDescriptor
{
name = semantics.name,
propertyBufferSize = propertyInfo.size,
shaderModel = semantics.shaderModel,
shaderCodes = shaderCodes,
defines = semantics.defines?.ToArray() ?? Array.Empty<string>(),
keywords = semantics.keywords?.ToArray() ?? Array.Empty<KeywordsGroup>()
Name = semantics.name,
PropertyBufferSize = propertyInfo.size,
ShaderModel = semantics.shaderModel,
ShaderCodes = shaderCodes,
Defines = semantics.defines?.ToArray() ?? Array.Empty<string>(),
Keywords = semantics.keywords?.ToArray() ?? Array.Empty<KeywordsGroup>()
};
}
}

View File

@@ -7,10 +7,8 @@ namespace Ghost.DSL.ShaderParser;
public class AntlrShaderCompiler
{
public static List<GraphicsShaderModel> ParseShaders(string source, out List<DSLShaderError> errors)
public static List<GraphicsShaderModel> ParseShaders(string source, List<DSLShaderError> errors)
{
errors = new List<DSLShaderError>();
try
{
var inputStream = new AntlrInputStream(source);
@@ -53,7 +51,7 @@ public class AntlrShaderCompiler
}
}
public static List<ComputeShaderModel> ParseComputeShaders(string source, out List<DSLShaderError> errors)
public static List<ComputeShaderModel> ParseComputeShaders(string source, List<DSLShaderError> errors)
{
errors = new List<DSLShaderError>();

View File

@@ -6,7 +6,7 @@ namespace Ghost.Editor.Core.AssetHandler;
[AttributeUsage(AttributeTargets.Class)]
public sealed class CustomAssetHandlerAttribute : Attribute
{
public CustomAssetHandlerAttribute(string TypeID, string[] supportedExtensions, int version = 1)
public CustomAssetHandlerAttribute(string assetTypeID, string[] supportedExtensions, int version = 1)
{
}
}
@@ -33,14 +33,23 @@ public interface IAssetExportOptions;
public interface IAssetHandler
{
bool CanExport => false;
AssetType RuntimeAssetType { get; }
Guid EditorAssetTypeID { get; }
IAssetSettings? CreateDefaultSettings();
ValueTask<Result<IAsset>> LoadAssetAsync(Stream assetStream, Guid id, IAssetSettings? settings, CancellationToken token = default);
ValueTask<Result> SaveAssetAsync(Stream targetStream, IAsset asset, CancellationToken token = default);
ValueTask<Result> ImportAsync(Stream sourceStream, Stream targetStream, Guid id, IAssetSettings? settings, CancellationToken token = default);
ValueTask<Result> ExportAsync(Stream assetStream, Stream targetStream, IAssetExportOptions? options, CancellationToken token = default);
ValueTask<Result<IAsset>> LoadAssetAsync(FileStream assetStream, Guid id, IAssetSettings? settings, CancellationToken token = default);
ValueTask<Result> SaveAssetAsync(FileStream targetStream, IAsset asset, CancellationToken token = default);
}
public interface IImportableAssetHandler : IAssetHandler
{
bool CanExport { get; }
ValueTask<Result> ImportAsync(FileStream sourceStream, FileStream targetStream, Guid id, IAssetSettings? settings, CancellationToken token = default);
ValueTask<Result> ExportAsync(FileStream assetStream, FileStream targetStream, IAssetExportOptions? options, CancellationToken token = default);
}
public interface IPackableAssetHandler : IAssetHandler
{
ValueTask<Result> PackAsync(FileStream assetStream, Stream targetStream, CancellationToken token = default);
}

View File

@@ -2,10 +2,6 @@ using Ghost.Engine;
namespace Ghost.Editor.Core.AssetHandler;
/// <summary>
/// One-time scan at editor startup → two dictionaries.
/// All lookups are O(1) after construction.
/// </summary>
public static class AssetHandlerRegistry
{
private static readonly Dictionary<string, IAssetHandler> s_byExtension;
@@ -21,10 +17,10 @@ public static class AssetHandlerRegistry
s_versionByTypeId = new Dictionary<Guid, int>();
}
public static void RegisterHandler(IAssetHandler handler, Guid typeId, ReadOnlySpan<string> extensions, int version)
public static void RegisterHandler(IAssetHandler handler, Guid assetTypeId, ReadOnlySpan<string> extensions, int version)
{
s_byTypeId[typeId] = handler;
s_versionByTypeId[typeId] = version;
s_byTypeId[assetTypeId] = handler;
s_versionByTypeId[assetTypeId] = version;
foreach (var ext in extensions)
{
@@ -46,13 +42,13 @@ public static class AssetHandlerRegistry
return handler;
}
public static IAssetHandler? GetByTypeId(Guid typeId)
public static IAssetHandler? GetByAssetTypeId(Guid typeId)
{
s_byTypeId.TryGetValue(typeId, out var handler);
return handler;
}
public static int GetVersionByTypeId(Guid typeId)
public static int GetVersionByAssetTypeId(Guid typeId)
{
s_versionByTypeId.TryGetValue(typeId, out var version);
return version;
@@ -63,7 +59,7 @@ public static class AssetHandlerRegistry
return s_byExtension.Keys;
}
public static AssetType GetAssetTypeByExtension(string extension)
public static AssetType GetRuntimeAssetTypeByExtension(string extension)
{
if (string.IsNullOrEmpty(extension))
{

View File

@@ -0,0 +1,380 @@
using Ghost.Core;
using Ghost.Engine;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Utilities;
using Ghost.MeshOptimizer;
using Ghost.Ufbx;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using Misaki.HighPerformance.Mathematics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Ghost.Editor.Core.AssetHandler;
public abstract class MeshAsset : IAsset
{
private UnsafeList<Vertex> _vertices;
private UnsafeList<uint> _indices;
public Guid ID
{
get;
}
public IAssetSettings Settings
{
get;
}
public Guid TypeID => typeof(MeshAsset).GUID;
public Span<Vertex> Vertices => _vertices.AsSpan();
public Span<uint> Indices => _indices.AsSpan();
internal MeshAsset(ref UnsafeList<Vertex> vertices, ref UnsafeList<uint> indices, Guid id, MeshAssetSettings settings)
{
_vertices = vertices;
_indices = indices;
ID = id;
Settings = settings;
}
public void Dispose()
{
_vertices.Dispose();
_indices.Dispose();
}
}
[Guid(GUID)]
public partial class FBXAsset : MeshAsset
{
public const string GUID = "B99CA68E-EE7A-4822-BF1C-AA0A5120C36A";
internal FBXAsset(ref UnsafeList<Vertex> vertices, ref UnsafeList<uint> indices, Guid id, FbxAssetSettings settings)
: base(ref vertices, ref indices, id, settings)
{
}
}
public enum CoordinateAxis
{
PositiveX,
PositiveY,
PositiveZ,
NegativeX,
NegativeY,
NegativeZ
}
public enum VertexDataSource
{
Imported,
Computed,
ComputedIfMissing
}
public class MeshAssetSettings : IAssetSettings
{
public VertexDataSource NormalDataSource
{
get; set;
} = VertexDataSource.ComputedIfMissing;
public VertexDataSource TangentDataSource
{
get; set;
} = VertexDataSource.ComputedIfMissing;
}
internal class ObjAssetSettings : MeshAssetSettings
{
public CoordinateAxis ObjectUpAxis
{
get; set;
} = CoordinateAxis.PositiveY;
public CoordinateAxis ObjectForwardAxis
{
get; set;
} = CoordinateAxis.NegativeZ;
public CoordinateAxis ObjectRightAxis
{
get; set;
} = CoordinateAxis.PositiveX;
public float UnitMeterScale
{
get; set;
} = 1.0f;
}
internal class FbxAssetSettings : MeshAssetSettings
{
}
internal class MeshParsingWorkItem : IThreadPoolWorkItem
{
private readonly string _filePath;
private readonly AllocationHandle _allocationHandle;
private readonly MeshAssetSettings _settings;
private readonly TaskCompletionSource<Result> _taskCompletionSource;
public UnsafeList<Vertex> vertices;
public UnsafeList<uint> indices;
public Task<Result> Task => _taskCompletionSource.Task;
public MeshParsingWorkItem(string filePath, AllocationHandle allocationHandle, MeshAssetSettings settings)
{
_filePath = filePath;
_allocationHandle = allocationHandle;
_settings = settings;
_taskCompletionSource = new TaskCompletionSource<Result>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float4 ComputeTangent(float3 t, float3 n, float3 b)
{
var proj = n * math.dot(n, t);
t = math.normalize(t - proj);
var w = math.dot(math.cross(n, t), b) < 0.0f ? -1.0f : 1.0f;
return new float4(t.xyz, w);
}
private static ufbx_coordinate_axis ToUfbxCoordinateAxis(CoordinateAxis axis)
{
return axis switch
{
CoordinateAxis.PositiveX => ufbx_coordinate_axis.UFBX_COORDINATE_AXIS_POSITIVE_X,
CoordinateAxis.PositiveY => ufbx_coordinate_axis.UFBX_COORDINATE_AXIS_POSITIVE_Y,
CoordinateAxis.PositiveZ => ufbx_coordinate_axis.UFBX_COORDINATE_AXIS_POSITIVE_Z,
CoordinateAxis.NegativeX => ufbx_coordinate_axis.UFBX_COORDINATE_AXIS_NEGATIVE_X,
CoordinateAxis.NegativeY => ufbx_coordinate_axis.UFBX_COORDINATE_AXIS_NEGATIVE_Y,
CoordinateAxis.NegativeZ => ufbx_coordinate_axis.UFBX_COORDINATE_AXIS_NEGATIVE_Z,
_ => throw new ArgumentOutOfRangeException(nameof(axis), axis, null)
};
}
public unsafe void Execute()
{
if (!File.Exists(_filePath))
{
_taskCompletionSource.SetResult(Result.Failure("Invalid file path."));
return;
}
if (!Path.GetExtension(_filePath).Equals(".obj", StringComparison.OrdinalIgnoreCase)
&& !Path.GetExtension(_filePath).Equals(".fbx", StringComparison.OrdinalIgnoreCase))
{
_taskCompletionSource.SetResult(Result.Failure("Unsupported file format. Only .obj and .fbx are supported."));
return;
}
var error = new ufbx_error();
var load_Opts = new ufbx_load_opts
{
target_unit_meters = 1.0f,
target_axes = ufbx_coordinate_axes.left_handed_y_up,
// Force z-axis mirroring to correctly convert handedness to Left-Handed,
// while preserving correct left/right orientation when viewed from the front.
handedness_conversion_axis = ufbx_mirror_axis.UFBX_MIRROR_AXIS_Z,
space_conversion = ufbx_space_conversion.UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY,
};
if (_settings is ObjAssetSettings objSettings)
{
load_Opts.obj_axes = new ufbx_coordinate_axes
{
right = ToUfbxCoordinateAxis(objSettings.ObjectRightAxis),
up = ToUfbxCoordinateAxis(objSettings.ObjectUpAxis),
front = ToUfbxCoordinateAxis(objSettings.ObjectForwardAxis)
};
load_Opts.obj_unit_meters = objSettings.UnitMeterScale;
load_Opts.obj_search_mtl_by_filename = true;
}
using var str = new UnsafeArray<byte>(Encoding.UTF8.GetByteCount(_filePath) + 1, AllocationHandle.FreeList);
var count = Encoding.UTF8.GetBytes(_filePath, str.AsSpan());
str[count] = 0;
using var scene = new DisposablePtr<ufbx_scene>(ufbx_scene.LoadFile((sbyte*)str.GetUnsafePtr(), &load_Opts, &error));
if (scene.Get() == null)
{
_taskCompletionSource.SetResult(Result.Failure(error.description.ToString()));
return;
}
using var flatVertices = new UnsafeList<Vertex>(1024, AllocationHandle.FreeList);
var missingNormals = false;
var missingTangents = false;
for (var i = 0u; i < scene.Get()->nodes.count; i++)
{
var data = scene.Get()->nodes.data;
var node = scene.Get()->nodes.data[i];
if (node->is_root)
{
continue;
}
if (node->mesh != null)
{
var pMesh = node->mesh;
if (pMesh->num_faces == 0)
{
continue;
}
var maxScratchIndices = (int)(pMesh->max_face_triangles * 3u);
using var triIndicesArray = new UnsafeArray<uint>(maxScratchIndices, AllocationHandle.FreeList);
for (var j = 0u; j < pMesh->num_faces; j++)
{
var face = pMesh->faces.data[j];
var numTris = UfbxApi.TriangulateFace(triIndicesArray.AsSpan(0, maxScratchIndices), pMesh, face);
var totalIndices = numTris * 3;
for (var k = 0; k < totalIndices; k++)
{
var ufbxTopologyIndex = triIndicesArray[k];
var posIdx = pMesh->vertex_position.indices.data[ufbxTopologyIndex];
var normIdx = pMesh->vertex_normal.exists ? pMesh->vertex_normal.indices.data[ufbxTopologyIndex] : uint.MaxValue;
var tanIdx = pMesh->vertex_tangent.exists ? pMesh->vertex_tangent.indices.data[ufbxTopologyIndex] : uint.MaxValue;
var uvIdx = pMesh->vertex_uv.exists ? pMesh->vertex_uv.indices.data[ufbxTopologyIndex] : uint.MaxValue;
var colIdx = pMesh->vertex_color.exists ? pMesh->vertex_color.indices.data[ufbxTopologyIndex] : uint.MaxValue;
var btanIdx = pMesh->vertex_bitangent.exists ? pMesh->vertex_bitangent.indices.data[ufbxTopologyIndex] : uint.MaxValue;
var position = pMesh->vertex_position.values.data[posIdx];
var normal = normIdx != uint.MaxValue ? pMesh->vertex_normal.values.data[normIdx] : default;
var uv = uvIdx != uint.MaxValue ? pMesh->vertex_uv.values.data[uvIdx] : default;
var color = colIdx != uint.MaxValue ? pMesh->vertex_color.values.data[colIdx] : default;
var vertex = new Vertex
{
position = new float3(position.x, position.y, position.z),
normal = new float3(normal.x, normal.y, normal.z),
uv = new float2(uv.x, uv.y),
color = new Color128(color.x, color.y, color.z, color.w)
};
if (tanIdx != uint.MaxValue)
{
var mt = pMesh->vertex_tangent.values.data[tanIdx];
var mb = btanIdx != uint.MaxValue ? pMesh->vertex_bitangent.values.data[btanIdx] : default;
var t = new float3(mt.x, mt.y, mt.z);
var n = vertex.normal;
var b = btanIdx != uint.MaxValue ? new float3(mb.x, mb.y, mb.z) : math.cross(n, t);
vertex.tangent = ComputeTangent(t, n, b);
}
var newIndex = (uint)flatVertices.Count;
flatVertices.Add(vertex);
if (!missingNormals)
{
missingNormals = normIdx == uint.MaxValue;
}
if (!missingTangents)
{
missingTangents = tanIdx == uint.MaxValue || btanIdx == uint.MaxValue;
}
}
}
}
}
var numIndices = (uint)flatVertices.Count;
using var weldedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
using var cachedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
var stream = new ufbx_vertex_stream
{
data = flatVertices.GetUnsafePtr(),
vertex_count = numIndices,
vertex_size = (nuint)sizeof(Vertex)
};
var numUniqueVertices = UfbxApi.GenerateIndices([stream], weldedIndices, null, &error);
if (numUniqueVertices == 0 && error.type != ufbx_error_type.UFBX_ERROR_NONE)
{
_taskCompletionSource.SetResult(Result.Failure($"Welding failed: {error.description}"));
return;
}
MeshOptApi.OptimizeVertexCache((uint*)cachedIndices.GetUnsafePtr(), (uint*)weldedIndices.GetUnsafePtr(), numIndices, numUniqueVertices);
vertices = new UnsafeList<Vertex>((int)numUniqueVertices, _allocationHandle);
indices = new UnsafeList<uint>((int)numIndices, _allocationHandle);
var finalVertexCount = MeshOptApi.OptimizeVertexFetch(vertices.GetUnsafePtr(), (uint*)cachedIndices.GetUnsafePtr(), numIndices, flatVertices.GetUnsafePtr(), numIndices, (nuint)sizeof(Vertex));
vertices.UnsafeSetCount((int)finalVertexCount);
MemoryUtility.MemCpy(indices.GetUnsafePtr(), cachedIndices.GetUnsafePtr(), numIndices * sizeof(uint));
indices.UnsafeSetCount((int)numIndices);
if (_settings.NormalDataSource == VertexDataSource.Computed || (_settings.NormalDataSource == VertexDataSource.ComputedIfMissing && missingNormals))
{
MeshBuilder.ComputeNormal(vertices, indices);
}
if (_settings.TangentDataSource == VertexDataSource.Computed || (_settings.TangentDataSource == VertexDataSource.ComputedIfMissing && missingTangents))
{
MeshBuilder.ComputeTangents(vertices, indices);
}
_taskCompletionSource.SetResult(Result.Success());
}
}
internal class FBXAssetHandler : IImportableAssetHandler
{
public AssetType RuntimeAssetType => AssetType.Mesh;
public Guid EditorAssetTypeID => typeof(FBXAsset).GUID;
public bool CanExport => false;
public IAssetSettings? CreateDefaultSettings()
{
throw new NotImplementedException();
}
public ValueTask<Result<IAsset>> LoadAssetAsync(FileStream assetStream, Guid id, IAssetSettings? settings, CancellationToken token = default)
{
throw new NotImplementedException();
}
public ValueTask<Result> SaveAssetAsync(FileStream targetStream, IAsset asset, CancellationToken token = default)
{
throw new NotImplementedException();
}
public ValueTask<Result> ImportAsync(FileStream sourceStream, FileStream targetStream, Guid id, IAssetSettings? settings, CancellationToken token = default)
{
throw new NotImplementedException();
}
public ValueTask<Result> ExportAsync(FileStream assetStream, FileStream targetStream, IAssetExportOptions? options, CancellationToken token = default)
{
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,158 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.DSL.ShaderCompiler;
using Ghost.Engine;
using System.Runtime.InteropServices;
namespace Ghost.Editor.Core.AssetHandler;
[Guid(GUID)]
public sealed partial class GraphicsShaderAsset : IAsset
{
public const string GUID = "7BD4591C-B017-4814-AA0B-3F30EB3E727E";
public Guid ID
{
get;
}
public IAssetSettings Settings
{
get;
}
public Guid TypeID => typeof(GraphicsShaderAsset).GUID;
public GraphicsShaderDescriptor Descriptor
{
get;
}
internal GraphicsShaderAsset(GraphicsShaderDescriptor descriptor, Guid id)
{
ID = id;
Descriptor = descriptor;
}
public void Dispose()
{
}
}
[Guid(GUID)]
public sealed partial class ComputeShaderAsset : IAsset
{
public const string GUID = "EA881979-CD8D-4088-B568-D42645F18C2A";
public Guid ID
{
get;
}
public IAssetSettings Settings
{
get;
}
public Guid TypeID => typeof(ComputeShaderAsset).GUID;
public ComputeShaderDescriptor Descriptor
{
get;
}
internal ComputeShaderAsset(ComputeShaderDescriptor descriptor, Guid id)
{
ID = id;
Descriptor = descriptor;
}
public void Dispose()
{
}
}
// Shader does not handle import/export via asset registry, it will handled by the hot reload system.
[CustomAssetHandler(GraphicsShaderAsset.GUID, [".gshdr"], 1)]
internal class GraphicsShaderAssetHandler : IPackableAssetHandler
{
public AssetType RuntimeAssetType => AssetType.Shader;
public Guid EditorAssetTypeID => typeof(GraphicsShaderAsset).GUID;
public IAssetSettings? CreateDefaultSettings()
{
return null;
}
public async ValueTask<Result<IAsset>> LoadAssetAsync(FileStream assetStream, Guid id, IAssetSettings? settings, CancellationToken token = default)
{
try
{
using var reader = new StreamReader(assetStream);
var shaderCode = await reader.ReadToEndAsync(token);
var result = DSLShaderCompiler.CompileGraphicsShader(shaderCode);
if (result.IsFailure)
{
return Result.Failure(result.Message);
}
return new GraphicsShaderAsset(result.Value, id);
}
catch (Exception ex)
{
return Result.Failure($"Failed to load shader asset: {ex.Message}");
}
}
public ValueTask<Result> SaveAssetAsync(FileStream targetStream, IAsset asset, CancellationToken token = default)
{
return new ValueTask<Result>(Result.Failure("Saving shader assets is not supported yet as it's read-only. Please edit the shader source file directly if you need to modify it."));
}
public ValueTask<Result> PackAsync(FileStream assetStream, Stream targetStream, CancellationToken token = default)
{
throw new NotImplementedException();
}
}
[CustomAssetHandler(ComputeShaderAsset.GUID, [".gcomp"], 1)]
internal class ComputeShaderAssetHandler : IPackableAssetHandler
{
public AssetType RuntimeAssetType => AssetType.Shader;
public Guid EditorAssetTypeID => typeof(ComputeShaderAsset).GUID;
public IAssetSettings? CreateDefaultSettings()
{
return null;
}
public async ValueTask<Result<IAsset>> LoadAssetAsync(FileStream assetStream, Guid id, IAssetSettings? settings, CancellationToken token = default)
{
try
{
using var reader = new StreamReader(assetStream);
var shaderCode = await reader.ReadToEndAsync(token);
var result = DSLShaderCompiler.CompileComputeShader(shaderCode);
if (result.IsFailure)
{
return Result.Failure(result.Message);
}
return new ComputeShaderAsset(result.Value, id);
}
catch (Exception ex)
{
return Result.Failure($"Failed to load shader asset: {ex.Message}");
}
}
public ValueTask<Result> SaveAssetAsync(FileStream targetStream, IAsset asset, CancellationToken token = default)
{
return new ValueTask<Result>(Result.Failure("Saving shader assets is not supported yet as it's read-only. Please edit the shader source file directly if you need to modify it."));
}
public ValueTask<Result> PackAsync(FileStream assetStream, Stream targetStream, CancellationToken token = default)
{
throw new NotImplementedException();
}
}

View File

@@ -1,9 +1,12 @@
using Ghost.Core;
using Ghost.Engine;
using Ghost.Graphics.RHI;
using ImageMagick;
using Ghost.StbI;
using Misaki.HighPerformance.LowLevel;
using System.IO.MemoryMappedFiles;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using TerraFX.Interop.Windows;
namespace Ghost.Editor.Core.AssetHandler;
@@ -48,7 +51,7 @@ public enum MipmapFilter : uint
}
[Guid(GUID)]
public class TextureAsset : IAsset
public unsafe class TextureAsset : IAsset
{
public const string GUID = "27965FFF-860C-40EF-9123-1874D7DE9CDC";
@@ -57,7 +60,7 @@ public class TextureAsset : IAsset
private readonly Guid _id;
private readonly IAssetSettings _settings;
private readonly MagickImage _textureData;
private readonly IntPtr _textureData;
private readonly uint _width;
private readonly uint _height;
private readonly uint _depth;
@@ -65,17 +68,17 @@ public class TextureAsset : IAsset
private readonly uint _dimension;
public Guid ID => _id;
public Guid TypeID => s_typeID;
public Guid TypeID => typeof(TextureAsset).GUID;
public IAssetSettings Settings => _settings;
public MagickImage TextureData => _textureData;
public IntPtr TextureData => _textureData;
public uint Width => _width;
public uint Height => _height;
public uint Depth => _depth;
public uint Dimension => _dimension;
public uint ColorComponents => _colorComponents;
internal TextureAsset([OwnershipTransfer] MagickImage data, TextureContentHeader header, Guid id, IAssetSettings settings)
internal TextureAsset([OwnershipTransfer] IntPtr data, TextureContentHeader header, Guid id, IAssetSettings settings)
{
_id = id;
_settings = settings;
@@ -95,7 +98,7 @@ public class TextureAsset : IAsset
public void Dispose()
{
_textureData.Dispose();
StbIApi.ImageFree((void*)_textureData);
GC.SuppressFinalize(this);
}
}
@@ -253,8 +256,19 @@ public class TextureAssetSettings : IAssetSettings
}
[CustomAssetHandler(TextureAsset.GUID, [".png", ".jpg", ".jpeg", ".tga", ".bmp", ".hdr"], 1)]
internal class TextureAssetHandler : IAssetHandler
internal class TextureAssetHandler : IImportableAssetHandler, IPackableAssetHandler
{
internal struct TextureInfo
{
public IntPtr pixelData;
public int width;
public int height;
public int depth;
public int colorComponents;
public bool isHDR;
}
public bool CanExport => false;
public AssetType RuntimeAssetType => AssetType.Texture;
public Guid EditorAssetTypeID => typeof(TextureAsset).GUID;
@@ -291,23 +305,86 @@ internal class TextureAssetHandler : IAssetHandler
return TextureDimension.Texture2D;
}
public ValueTask<Result<IAsset>> LoadAssetAsync(Stream assetStream, Guid id, IAssetSettings? settings, CancellationToken token = default)
private static unsafe Result<TextureInfo> GetImageInfo(FileStream sourceStream)
{
using var mmf = MemoryMappedFile.CreateFromFile(sourceStream, null, 0, MemoryMappedFileAccess.Read, HandleInheritability.None, true);
using var accessor = mmf.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read);
byte* ptr = null;
try
{
var ext = Path.GetExtension(sourceStream.Name);
var isHDR = ext.Equals(".hdr", StringComparison.OrdinalIgnoreCase);
accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
int imageWidth, imageHeight, bitsPerChannel, colorComponents;
var bufferSpan = new ReadOnlySpan<byte>(ptr, (int)sourceStream.Length);
var code = StbIApi.InfoFromMemory(bufferSpan, &imageWidth, &imageHeight, &colorComponents);
if (code == 0)
{
return Result.Failure("Failed to read image info from memory.");
}
bitsPerChannel = StbIApi.Is16BitFromMemory(bufferSpan) > 0 ? 16 : 8;
void* pPixels;
if (bitsPerChannel > 8)
{
pPixels = StbIApi.LoadfFromMemory(bufferSpan, &imageWidth, &imageHeight, &colorComponents, 4);
}
else
{
pPixels = StbIApi.LoadFromMemory(bufferSpan, &imageWidth, &imageHeight, &colorComponents, 4);
}
return new TextureInfo
{
pixelData = (IntPtr)pPixels,
width = imageWidth,
height = imageHeight,
depth = bitsPerChannel,
colorComponents = colorComponents,
isHDR = isHDR,
};
}
catch (Exception ex)
{
return Result<TextureInfo>.Failure($"Failed to get image info: {ex.Message}");
}
finally
{
if (ptr != null)
{
accessor.SafeMemoryMappedViewHandle.ReleasePointer();
}
}
}
public ValueTask<Result<IAsset>> LoadAssetAsync(FileStream assetStream, Guid id, IAssetSettings? settings, CancellationToken token = default)
{
try
{
var image = new MagickImage(assetStream);
var infoResult = GetImageInfo(assetStream);
if (infoResult.IsFailure)
{
return ValueTask.FromResult(Result<IAsset>.Failure(infoResult.Message));
}
var info = infoResult.Value;
var textureSettings = settings as TextureAssetSettings ?? new TextureAssetSettings();
var contentHeader = new TextureContentHeader
{
width = image.Width,
height = image.Height,
depth = image.Depth,
colorComponents = image.ChannelCount,
width = (uint)info.width,
height = (uint)info.height,
depth = (uint)info.depth,
colorComponents = (uint)info.colorComponents,
dimension = (uint)GetTextureDimension(textureSettings)
};
return ValueTask.FromResult(Result.Success<IAsset>(new TextureAsset(image, contentHeader, id, textureSettings)));
return ValueTask.FromResult(Result.Success<IAsset>(new TextureAsset(info.pixelData, contentHeader, id, textureSettings)));
}
catch (Exception ex)
{
@@ -315,54 +392,109 @@ internal class TextureAssetHandler : IAssetHandler
}
}
public async ValueTask<Result> SaveAssetAsync(Stream targetStream, IAsset asset, CancellationToken token = default)
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private unsafe static void WriteCallback(void* context, void* data, int size)
{
var stream = (Stream)GCHandle.FromIntPtr((IntPtr)context).Target!;
var buffer = new ReadOnlySpan<byte>(data, size);
stream.Write(buffer);
}
public async ValueTask<Result> SaveAssetAsync(FileStream targetStream, IAsset asset, CancellationToken token = default)
{
if (asset is not TextureAsset textureAsset)
{
return Result.Failure("Asset type is not TextureAsset");
}
return await Task.Run(() =>
{
var gcHandle = GCHandle.Alloc(targetStream, GCHandleType.Normal);
try
{
await textureAsset.TextureData.WriteAsync(targetStream, token);
var ext = Path.GetExtension(targetStream.Name);
unsafe
{
switch (ext)
{
case ".png":
StbIApi.WritePngToFunc(&WriteCallback, (void*)GCHandle.ToIntPtr(gcHandle), (int)textureAsset.Width, (int)textureAsset.Height, (int)textureAsset.ColorComponents, (void*)textureAsset.TextureData, 0);
break;
case ".jpg":
StbIApi.WriteJpgToFunc(&WriteCallback, (void*)GCHandle.ToIntPtr(gcHandle), (int)textureAsset.Width, (int)textureAsset.Height, (int)textureAsset.ColorComponents, (void*)textureAsset.TextureData, 90);
break;
// TODO: Add support for other image formats
default:
return Result.Failure($"Unsupported image format: {ext}");
}
}
return Result.Success();
}
catch (Exception ex)
{
return Result.Failure(ex.Message);
}
finally
{
gcHandle.Free();
}
}, token).ConfigureAwait(false);
}
public async ValueTask<Result> ImportAsync(Stream sourceStream, Stream targetStream, Guid id, IAssetSettings? settings, CancellationToken token = default)
public async ValueTask<Result> ImportAsync(FileStream sourceStream, FileStream targetStream, Guid id, IAssetSettings? settings, CancellationToken token = default)
{
if (sourceStream.Length > int.MaxValue)
{
return Result.Failure("Source stream is too large.");
}
try
{
using var image = new MagickImage(sourceStream);
var pixels = image.GetPixelsUnsafe().GetAreaPointer(0, 0, image.Width, image.Height);
if (pixels == 0)
var textureSettings = settings as TextureAssetSettings ?? new TextureAssetSettings();
using var mmf = MemoryMappedFile.CreateFromFile(sourceStream, null, 0, MemoryMappedFileAccess.Read, HandleInheritability.None, true);
using var accessor = mmf.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read);
var infoResult = GetImageInfo(sourceStream);
if (!infoResult.IsSuccess)
{
return Result.Failure("Failed to retrieve pixel data from the source image.");
return Result.Failure(infoResult.Message);
}
var textureSettings = settings as TextureAssetSettings ?? new TextureAssetSettings();
var (path, mip) = await TextureProcessor.CompressToCacheAsync(EditorApplication.CacheFolderPath, id, pixels, image.Width, image.Height, image.Depth, textureSettings, token)
var info = infoResult.Value;
var result = await TextureProcessor.CompressToCacheAsync(EditorApplication.CacheFolderPath, id,
info,
textureSettings, token)
.ConfigureAwait(false);
if (result.IsFailure)
{
return result;
}
var (cachePath, mip) = result.Value;
targetStream.Seek(0, SeekOrigin.Begin);
var contentHeader = new TextureContentHeader
var header = new TextureContentHeader
{
width = image.Width,
height = image.Height,
depth = image.Depth,
colorComponents = image.ChannelCount,
width = (uint)info.width,
height = (uint)info.height,
depth = (uint)info.depth,
colorComponents = (uint)info.colorComponents,
mipLevels = (uint)mip,
dimension = (uint)GetTextureDimension(textureSettings)
};
targetStream.Write(MemoryMarshal.AsBytes(new Span<TextureContentHeader>(ref contentHeader)));
targetStream.Write(MemoryMarshal.AsBytes(new Span<TextureContentHeader>(ref header)));
await using var ddsStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
await using var ddsStream = new FileStream(cachePath, FileMode.Open, FileAccess.Read, FileShare.Read);
await ddsStream.CopyToAsync(targetStream, token).ConfigureAwait(false);
await targetStream.FlushAsync(token).ConfigureAwait(false);
@@ -374,8 +506,13 @@ internal class TextureAssetHandler : IAssetHandler
}
}
public ValueTask<Result> ExportAsync(Stream assetStream, Stream targetStream, IAssetExportOptions? options, CancellationToken token = default)
public ValueTask<Result> ExportAsync(FileStream assetStream, FileStream targetStream, IAssetExportOptions? options, CancellationToken token = default)
{
return ValueTask.FromResult(Result.Failure("Exporting texture assets is not supported yet."));
}
public ValueTask<Result> PackAsync(FileStream assetStream, Stream targetStream, CancellationToken token = default)
{
throw new NotImplementedException();
}
}

View File

@@ -1,3 +1,4 @@
using Ghost.Core;
using Ghost.Nvtt;
using Misaki.HighPerformance.LowLevel;
using System.IO.Hashing;
@@ -24,27 +25,19 @@ internal static class TextureProcessor
{
private readonly string _outputPath;
private readonly nint _image;
private readonly uint _depth;
private readonly uint _width;
private readonly uint _height;
private readonly TextureAssetHandler.TextureInfo _textureInfo;
private readonly TextureAssetSettings _settings;
private readonly TaskCompletionSource _completionSource;
private readonly TaskCompletionSource<Result<int>> _completionSource;
public int mipmapCount;
public Task<Result<int>> Task => _completionSource.Task;
public Task Task => _completionSource.Task;
public NvttPipelineTask(string outputPath, nint image, uint width, uint height, uint depth, TextureAssetSettings settings)
public NvttPipelineTask(string outputPath, TextureAssetHandler.TextureInfo textureInfo, TextureAssetSettings settings)
{
_outputPath = outputPath;
_image = image;
_width = width;
_height = height;
_depth = depth;
_textureInfo = textureInfo;
_settings = settings;
_completionSource = new TaskCompletionSource();
_completionSource = new TaskCompletionSource<Result<int>>();
}
public unsafe void Execute()
@@ -54,15 +47,22 @@ internal static class TextureProcessor
using var pOutOpts = new DisposablePtr<NvttOutputOptions>(NvttOutputOptions.Create());
using var pCtx = new DisposablePtr<NvttContext>(NvttContext.Create());
var inputFormat = _depth > 8
var inputFormat = _textureInfo.colorComponents == 1
? NvttInputFormat.NVTT_InputFormat_R_32F
: _textureInfo.depth > 8
? NvttInputFormat.NVTT_InputFormat_RGBA_32F
: NvttInputFormat.NVTT_InputFormat_BGRA_8UB; // we'll swizzle RB below
pSurface.Get()->SetImageData(inputFormat, (int)_width, (int)_height, 1, (void*)_image, NvttBoolean.NVTT_True, null);
var needUnsigned = _settings.Basic.TextureType == TextureType.Normal ? NvttBoolean.NVTT_True : NvttBoolean.NVTT_False;
if (pSurface.Get()->SetImageData(inputFormat, _textureInfo.width, _textureInfo.height, _textureInfo.depth, (void*)_textureInfo.pixelData, needUnsigned, null))
{
_completionSource.SetResult(Result.Failure("Failed to set image data for NVTT compression."));
return;
}
// stb gives us RGBA byte order; NVTT BGRA_8UB reads it as BGRA,
// so channels R and B are swapped — fix with swizzle(2,1,0,3).
if (_depth <= 8)
if (_textureInfo.colorComponents > 1 && _textureInfo.depth <= 8)
{
pSurface.Get()->Swizzle(2, 1, 0, 3, null);
}
@@ -101,7 +101,7 @@ internal static class TextureProcessor
pSurface.Get()->PremultiplyAlpha(null);
}
pCompOpts.Get()->SetFormat(SelectFormat(_settings));
pCompOpts.Get()->SetFormat(SelectFormat(_settings, _textureInfo.isHDR));
pCompOpts.Get()->SetQuality(SelectQuality(_settings.Advanced.CompressionLevel));
if (_settings.Advanced.CutoutAlpha)
@@ -117,6 +117,7 @@ internal static class TextureProcessor
var nvttFilter = SelectMipmapFilter(_settings.Advanced.MipmapFilter);
int mipmapCount;
if (!_settings.Advanced.GenerateMipmaps)
{
mipmapCount = 1;
@@ -155,11 +156,13 @@ internal static class TextureProcessor
}
}
_completionSource.SetResult();
_completionSource.SetResult(Result.Success(mipmapCount));
}
}
public static async ValueTask<(string cachePath, int mipmapCount)> CompressToCacheAsync(string cachesFolderPath, Guid assetId, nint image, uint width, uint height, uint depth, TextureAssetSettings settings, CancellationToken cancellationToken)
public static async ValueTask<Result<(string cachePath, int mipmapCount)>> CompressToCacheAsync(string cachesFolderPath, Guid assetId,
TextureAssetHandler.TextureInfo textureInfo,
TextureAssetSettings settings, CancellationToken cancellationToken)
{
var settingsHash = ComputeSettingsHash(settings);
var cacheFileName = $"texturecache_{assetId:N}_{settingsHash:X16}.dds";
@@ -202,15 +205,21 @@ internal static class TextureProcessor
}
ScheduleWork:
var workItem = new NvttPipelineTask(cachePath, image, width, height, depth, settings);
var workItem = new NvttPipelineTask(cachePath, textureInfo, settings);
ThreadPool.UnsafeQueueUserWorkItem(workItem, true);
await workItem.Task.WaitAsync(cancellationToken).ConfigureAwait(false);
return (cachePath, workItem.mipmapCount);
var result = await workItem.Task.WaitAsync(cancellationToken).ConfigureAwait(false);
if (result.IsFailure)
{
return Result.Failure(result.Message);
}
private static NvttFormat SelectFormat(TextureAssetSettings settings)
=> settings.Basic.TextureType switch
return (cachePath, result.Value);
}
private static NvttFormat SelectFormat(TextureAssetSettings settings, bool isHDR)
=> isHDR
? NvttFormat.NVTT_Format_BC6U
: settings.Basic.TextureType switch
{
TextureType.Normal => NvttFormat.NVTT_Format_BC5, // RG normal map
TextureType.SingleChannel => NvttFormat.NVTT_Format_BC4, // single channel

View File

@@ -1,5 +1,6 @@
using Ghost.Core;
using Ghost.Editor.Core.AssetHandler;
using Ghost.Editor.Core.Services;
using Ghost.Engine.AssetLoader;
namespace Ghost.Editor.Core.Contracts;
@@ -40,12 +41,14 @@ public sealed class AssetChangedEventArgs : EventArgs
public interface IAssetRegistry : IDisposable
{
event EventHandler<AssetChangedEventArgs>? OnAssetChanged;
event EventHandler<Guid>? OnAssetImported;
AssetCatalog GetAssetCatalog();
string? GetAssetPath(Guid id);
Guid GetAssetGuid(string assetPath);
event EventHandler<AssetChangedEventArgs>? OnAssetChanged;
ValueTask<Result<Guid>> ImportAssetAsync(string sourceFilePath, string targetAssetPath, CancellationToken token = default);
ValueTask<Result> ReimportAssetAsync(Guid assetId, string sourceFilePath, CancellationToken token = default);
ValueTask<Result<IAsset>> LoadAssetAsync(Guid id, CancellationToken token = default);

View File

@@ -55,6 +55,8 @@ public static class EditorApplication
internal static void Initialize(IServiceProvider serviceProvider, string projectPath, string projectName)
{
Environment.CurrentDirectory = projectPath;
s_serviceProvider = serviceProvider;
s_currentProjectPath = projectPath;
s_currentProjectName = projectName;

View File

@@ -15,7 +15,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentIcons.WinUI" Version="2.1.324" />
<PackageReference Include="Magick.NET-Q16-HDRI-OpenMP-x64" Version="14.12.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.6" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.28000.1721" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.260317003" />
@@ -29,6 +28,8 @@
<ProjectReference Include="..\..\Runtime\Ghost.Generator\Ghost.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\..\ThridParty\Ghost.DXC\Ghost.DXC.csproj" />
<ProjectReference Include="..\..\ThridParty\Ghost.Nvtt\Ghost.Nvtt.csproj" />
<ProjectReference Include="..\..\ThridParty\Ghost.Ufbx\Ghost.Ufbx.csproj" />
<ProjectReference Include="..\..\ThridParty\Ghost.StbI\Ghost.StbI.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -7,7 +7,7 @@ namespace Ghost.Editor.Core.Services;
/// Thread-safe SQLite-backed asset catalog.
/// Replaces the in-memory dictionary approach with persistent storage.
/// </summary>
internal sealed class AssetCatalog : IDisposable
public sealed partial class AssetCatalog : IDisposable
{
private readonly SqliteConnection _connection;
private readonly Lock _writeLock = new();
@@ -101,10 +101,20 @@ internal sealed class AssetCatalog : IDisposable
cmd.ExecuteNonQuery();
}
private static string ToUniversalPath(string path)
{
if (OperatingSystem.IsWindows())
{
return Path.GetFullPath(path).Replace('\\', '/');
}
return path;
}
public Guid GetGuid(string sourcePath)
{
_cmdGetGuid.Parameters.Clear();
_cmdGetGuid.Parameters.AddWithValue("@path", sourcePath);
_cmdGetGuid.Parameters.AddWithValue("@path", ToUniversalPath(sourcePath));
var result = _cmdGetGuid.ExecuteScalar();
return result is byte[] bytes ? new Guid(bytes) : Guid.Empty;
}
@@ -122,7 +132,7 @@ internal sealed class AssetCatalog : IDisposable
{
_cmdUpsert.Parameters.Clear();
_cmdUpsert.Parameters.AddWithValue("@guid", meta.Guid.ToByteArray());
_cmdUpsert.Parameters.AddWithValue("@path", sourcePath);
_cmdUpsert.Parameters.AddWithValue("@path", ToUniversalPath(sourcePath));
_cmdUpsert.Parameters.AddWithValue("@handler_id", meta.HandlerTypeId?.ToByteArray() ?? (object)DBNull.Value);
_cmdUpsert.Parameters.AddWithValue("@version", meta.HandlerVersion);
_cmdUpsert.ExecuteNonQuery();

View File

@@ -3,6 +3,7 @@ using Ghost.Core.Utilities;
using Ghost.Editor.Core.AssetHandler;
using Ghost.Editor.Core.Contracts;
using System.Collections.Concurrent;
using System.IO.MemoryMappedFiles;
namespace Ghost.Editor.Core.Services;
@@ -22,6 +23,11 @@ internal sealed class AssetRegistry : IAssetRegistry, IDisposable
private readonly ConcurrentHashSet<Guid> _dirtyAssets;
public event EventHandler<AssetChangedEventArgs>? OnAssetChanged;
public event EventHandler<Guid>? OnAssetImported
{
add => _importCoordinator.OnImportCompleted += value;
remove => _importCoordinator.OnImportCompleted -= value;
}
public AssetRegistry()
{
@@ -67,7 +73,7 @@ internal sealed class AssetRegistry : IAssetRegistry, IDisposable
if (meta != null)
{
var sourceRelative = AssetMetaIO.GetSourcePath(Path.GetRelativePath(EditorApplication.AssetsFolderPath, metaPath));
_catalog.Upsert(meta, sourceRelative.Replace(Path.DirectorySeparatorChar, '/'));
_catalog.Upsert(meta, sourceRelative);
foundGuids.Add(meta.Guid);
}
}
@@ -84,7 +90,7 @@ internal sealed class AssetRegistry : IAssetRegistry, IDisposable
private async void OnFileSystemEvent(object sender, FileSystemEventArgs e)
{
var ext = Path.GetExtension(e.FullPath);
var relativePath = Path.GetRelativePath(EditorApplication.AssetsFolderPath, e.FullPath).Replace(Path.DirectorySeparatorChar, '/');
var relativePath = Path.GetRelativePath(EditorApplication.AssetsFolderPath, e.FullPath);
if (_ignoreMetaWrites.TryRemove(e.FullPath, out _))
{
@@ -114,7 +120,7 @@ internal sealed class AssetRegistry : IAssetRegistry, IDisposable
var changeType = AssetChangeType.None;
if (e.ChangeType == WatcherChangeTypes.Created)
{
await HandleNewSourceFileAsync(e.FullPath, relativePath);
await HandleNewSourceFileAsync(relativePath);
changeType = AssetChangeType.Created;
}
else if (e.ChangeType == WatcherChangeTypes.Changed)
@@ -144,14 +150,14 @@ internal sealed class AssetRegistry : IAssetRegistry, IDisposable
private void OnFileSystemRenameEvent(object sender, RenamedEventArgs e)
{
var oldRelative = Path.GetRelativePath(EditorApplication.AssetsFolderPath, e.OldFullPath).Replace(Path.DirectorySeparatorChar, '/');
var newRelative = Path.GetRelativePath(EditorApplication.AssetsFolderPath, e.FullPath).Replace(Path.DirectorySeparatorChar, '/');
var oldRelative = Path.GetRelativePath(EditorApplication.AssetsFolderPath, e.OldFullPath);
var newRelative = Path.GetRelativePath(EditorApplication.AssetsFolderPath, e.FullPath);
var guid = _catalog.GetGuid(oldRelative);
if (guid != Guid.Empty)
{
_catalog.Remove(guid);
var metaFile = AssetMetaIO.GetMetaPath(e.FullPath);
var metaFile = AssetMetaIO.GetMetaPath(newRelative);
if (File.Exists(metaFile))
{
var meta = AssetMetaIO.ReadAsync(metaFile).AsTask().Result;
@@ -165,12 +171,12 @@ internal sealed class AssetRegistry : IAssetRegistry, IDisposable
OnAssetChanged?.Invoke(this, new AssetChangedEventArgs(newRelative, oldRelative, AssetChangeType.Renamed));
}
private async Task HandleNewSourceFileAsync(string fullPath, string relativePath)
private async Task HandleNewSourceFileAsync(string relativePath)
{
var ext = Path.GetExtension(relativePath);
var handler = AssetHandlerRegistry.GetByExtension(ext);
var metaPath = AssetMetaIO.GetMetaPath(fullPath);
var metaPath = AssetMetaIO.GetMetaPath(relativePath);
if (File.Exists(metaPath))
{
return;
@@ -193,6 +199,11 @@ internal sealed class AssetRegistry : IAssetRegistry, IDisposable
await _importCoordinator.EnqueueAsync(new ImportJob(meta.Guid, relativePath, metaPath, ImportReason.NewAsset));
}
public AssetCatalog GetAssetCatalog()
{
return _catalog;
}
public string? GetAssetPath(Guid id)
{
return _catalog.GetSourcePath(id);
@@ -200,7 +211,7 @@ internal sealed class AssetRegistry : IAssetRegistry, IDisposable
public Guid GetAssetGuid(string path)
{
return _catalog.GetGuid(path.Replace(Path.DirectorySeparatorChar, '/'));
return _catalog.GetGuid(path);
}
public async ValueTask<Result<Guid>> ImportAssetAsync(string sourceFilePath, string targetAssetPath, CancellationToken token = default)
@@ -208,17 +219,13 @@ internal sealed class AssetRegistry : IAssetRegistry, IDisposable
// Simple copy + wait for FSW or manually trigger?
// Current requirement: "returns the new GUID immediately (import happens in background)"
var ext = Path.GetExtension(sourceFilePath);
var relativePath = targetAssetPath.Replace(Path.DirectorySeparatorChar, '/');
var fullPath = Path.Combine(EditorApplication.AssetsFolderPath, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!);
File.Copy(sourceFilePath, fullPath, true);
Directory.CreateDirectory(Path.GetDirectoryName(targetAssetPath)!);
File.Copy(sourceFilePath, targetAssetPath, true);
// FSW will trigger but we can speed it up
await HandleNewSourceFileAsync(fullPath, relativePath);
await HandleNewSourceFileAsync(targetAssetPath);
var guid = _catalog.GetGuid(relativePath);
var guid = _catalog.GetGuid(targetAssetPath);
return Result.Success(guid);
}
@@ -230,8 +237,7 @@ internal sealed class AssetRegistry : IAssetRegistry, IDisposable
return Result.Failure("Asset not found");
}
var fullPath = Path.Combine(EditorApplication.AssetsFolderPath, path);
var metaPath = AssetMetaIO.GetMetaPath(fullPath);
var metaPath = AssetMetaIO.GetMetaPath(path);
await _importCoordinator.EnqueueAsync(new ImportJob(assetId, path, metaPath, ImportReason.ManualReimport), token);
return Result.Success();
@@ -293,13 +299,14 @@ internal sealed class AssetRegistry : IAssetRegistry, IDisposable
return Result.Failure("Asset does not exist.");
}
var handler = AssetHandlerRegistry.GetByTypeId(asset.TypeID);
var handler = AssetHandlerRegistry.GetByAssetTypeId(asset.TypeID);
if (handler is null)
{
return Result.Failure("No Avaliable handler type.");
}
await using var stream = new FileStream(path, FileMode.Open, FileAccess.Write, FileShare.None);
// This will trigger the fsw and reimport automatically.
return await handler.SaveAssetAsync(stream, asset, token);
}
catch (Exception ex)

View File

@@ -1,5 +1,6 @@
using Ghost.Core;
using Ghost.Editor.Core.AssetHandler;
using Ghost.Editor.Core.Contracts;
using Ghost.Engine;
namespace Ghost.Editor.Core.Services;
@@ -8,9 +9,9 @@ internal class EditorContentProvider : IContentProvider
{
private readonly AssetCatalog _catalog;
public EditorContentProvider(AssetCatalog catalog)
public EditorContentProvider(IAssetRegistry assetRegistry)
{
_catalog = catalog;
_catalog = assetRegistry.GetAssetCatalog();
}
public bool HasAsset(Guid guid)
@@ -20,7 +21,7 @@ internal class EditorContentProvider : IContentProvider
public Result<Stream> OpenRead(Guid guid, CancellationToken token = default)
{
var importedPath = Path.Combine(EditorApplication.LibraryImportsFolderPath, $"{guid:N}{ImportCoordinator.IMPORTED_EXTENSION}");
var importedPath = ImportCoordinator.GetImportedAssetPath(guid);
if (!File.Exists(importedPath))
{
return Result.Failure($"Imported asset not found for GUID: {guid}");
@@ -37,7 +38,7 @@ internal class EditorContentProvider : IContentProvider
public AssetType GetAssetType(Guid guid)
{
var handlerID = _catalog.GetHandlerTypeId(guid);
var handler = AssetHandlerRegistry.GetByTypeId(handlerID);
var handler = AssetHandlerRegistry.GetByAssetTypeId(handlerID);
return handler?.RuntimeAssetType ?? AssetType.Unknown;
}
}

View File

@@ -24,7 +24,7 @@ internal readonly record struct ImportJob(
ImportReason Reason
);
internal sealed class ImportCoordinator : IDisposable
internal sealed partial class ImportCoordinator : IDisposable
{
public const string IMPORTED_EXTENSION_NAME = "Imported";
public const string IMPORTED_EXTENSION = ".imported";
@@ -34,9 +34,7 @@ internal sealed class ImportCoordinator : IDisposable
private readonly CancellationTokenSource _cts;
private readonly Task[] _workers;
// In a real implementation, this event would be used to notify the UI/Rest of engine
// For now we just focus on the core logic
// public event EventHandler<AssetChangedEventArgs>? OnAssetChanged;
public event EventHandler<Guid>? OnImportCompleted;
public ImportCoordinator(AssetCatalog catalog, int workerCount = 2)
{
@@ -76,9 +74,19 @@ internal sealed class ImportCoordinator : IDisposable
}
}
private async ValueTask ProcessImportAsync(ImportJob job, CancellationToken token)
public static string GetImportedAssetPath(Guid assetGuid)
{
var fileName = $"{assetGuid:N}{IMPORTED_EXTENSION}";
var folderName = fileName.Substring(0, 2);
var finalPath = Path.Combine(EditorApplication.LibraryImportsFolderPath, folderName, fileName);
Directory.CreateDirectory(finalPath);
return finalPath;
}
private static async ValueTask ProcessImportAsync(ImportJob job, CancellationToken token)
{
var fullSourcePath = Path.Combine(EditorApplication.AssetsFolderPath, job.SourcePath);
var meta = await AssetMetaIO.ReadAsync(job.MetaPath, token);
if (meta is null)
{
@@ -87,27 +95,27 @@ internal sealed class ImportCoordinator : IDisposable
}
var handler = meta.HandlerTypeId.HasValue
? AssetHandlerRegistry.GetByTypeId(meta.HandlerTypeId.Value)
? AssetHandlerRegistry.GetByAssetTypeId(meta.HandlerTypeId.Value)
: AssetHandlerRegistry.GetByExtension(Path.GetExtension(job.SourcePath));
var contentHash = await ComputeFileHashAsync(fullSourcePath, token);
var contentHash = await ComputeFileHashAsync(job.SourcePath, token);
var settingsHash = ComputeSettingsHash(meta.Settings);
// Check if we can skip (if not a manual reimport)
if (job.Reason != ImportReason.ManualReimport &&
meta.ContentHash == contentHash &&
meta.SettingsHash == settingsHash &&
meta.HandlerVersion == AssetHandlerRegistry.GetVersionByTypeId(meta.HandlerTypeId ?? Guid.Empty))
meta.HandlerVersion == AssetHandlerRegistry.GetVersionByAssetTypeId(meta.HandlerTypeId ?? Guid.Empty))
{
return;
}
var importResult = Result.Success();
if (handler is IAssetHandler importable)
if (handler is IImportableAssetHandler importable)
{
var targetPath = Path.Combine(EditorApplication.LibraryImportsFolderPath, $"{job.AssetGuid:N}{IMPORTED_EXTENSION}");
var targetPath = GetImportedAssetPath(job.AssetGuid);
await using var sourceStream = new FileStream(fullSourcePath, FileMode.Open, FileAccess.Read, FileShare.Read);
await using var sourceStream = new FileStream(job.SourcePath, FileMode.Open, FileAccess.Read, FileShare.Read);
await using var targetStream = new FileStream(targetPath, FileMode.Create, FileAccess.Write, FileShare.None);
importResult = await importable.ImportAsync(sourceStream, targetStream, job.AssetGuid, meta.Settings, token);
@@ -117,7 +125,7 @@ internal sealed class ImportCoordinator : IDisposable
{
meta.ContentHash = contentHash;
meta.SettingsHash = settingsHash;
meta.HandlerVersion = AssetHandlerRegistry.GetVersionByTypeId(meta.HandlerTypeId ?? Guid.Empty);
meta.HandlerVersion = AssetHandlerRegistry.GetVersionByAssetTypeId(meta.HandlerTypeId ?? Guid.Empty);
meta.LastImportedUtc = DateTime.UtcNow;
await AssetMetaIO.WriteAsync(job.MetaPath, meta, token);

View File

@@ -130,7 +130,7 @@ internal static class ShaderCompilerUtility
public static Result<UnsafeArray<UnsafeArray<byte>>> CompileComputeShader(this IShaderCompiler shaderCompiler, ComputeShaderDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, AllocationHandle allocationHandle)
{
var fullDefines = CombineDefines(descriptor.defines, additionalConfig.defines);
var fullDefines = CombineDefines(descriptor.Defines, additionalConfig.defines);
var config = new ShaderCompilationConfig
{
@@ -141,11 +141,11 @@ internal static class ShaderCompilerUtility
stage = ShaderStage.ComputeShader,
};
var compiled = new UnsafeArray<UnsafeArray<byte>>(descriptor.shaderCodes.Length, allocationHandle);
for (int i = 0; i < descriptor.shaderCodes.Length; i++)
var compiled = new UnsafeArray<UnsafeArray<byte>>(descriptor.ShaderCodes.Length, allocationHandle);
for (int i = 0; i < descriptor.ShaderCodes.Length; i++)
{
config.shaderCode = descriptor.shaderCodes[i].code;
config.entryPoint = descriptor.shaderCodes[i].entryPoint;
config.shaderCode = descriptor.ShaderCodes[i].code;
config.entryPoint = descriptor.ShaderCodes[i].entryPoint;
var result = shaderCompiler.Compile(ref config, allocationHandle);
if (result.IsFailure)

View File

@@ -1,3 +1,4 @@
using Ghost.Editor.Core.Contracts;
using Ghost.Editor.Core.Utilities;
using Ghost.Editor.Models;
using Ghost.Engine;
@@ -65,7 +66,13 @@ internal static class ActivationHandler
AllocationManager.Initialize(opts);
App.GetService<EngineCore>();
var assetRegistry = App.GetService<IAssetRegistry>();
var engineCore = App.GetService<EngineCore>();
assetRegistry.OnAssetImported += (sender, e) =>
{
engineCore.AssetManager.ReimportAsset(e);
};
return ValueTask.CompletedTask;
}

View File

@@ -50,10 +50,102 @@
</Style>
<!-- Named Style -->
<Style
x:Key="ToolbarButton"
BasedOn="{StaticResource SubtleButtonStyle}"
TargetType="Button" />
<Style x:Key="ToolbarButton" TargetType="Button">
<Setter Property="Padding" Value="2" />
<Setter Property="Background" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid
x:Name="RootGrid"
Padding="10,5"
Background="{TemplateBinding Background}"
CornerRadius="4">
<ContentPresenter
x:Name="ContentPresenter"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Content}" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Target="RootGrid.Background" Value="{ThemeResource SubtleFillColorSecondaryBrush}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Target="RootGrid.Background" Value="{ThemeResource SubtleFillColorTertiaryBrush}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="RootGrid.Background" Value="{ThemeResource SubtleFillColorDisabledBrush}" />
<Setter Target="ContentPresenter.Foreground" Value="{ThemeResource ControlStrongFillColorDisabledBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="AccentToolbarButton" TargetType="Button">
<Setter Property="Padding" Value="2" />
<Setter Property="Background" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
<Setter Property="Foreground" Value="{ThemeResource AccentFillColorDefaultBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid
x:Name="RootGrid"
Padding="10,5"
Background="{TemplateBinding Background}"
CornerRadius="4">
<ContentPresenter
x:Name="ContentPresenter"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Content}" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Target="RootGrid.Background" Value="{ThemeResource SubtleFillColorSecondaryBrush}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Target="RootGrid.Background" Value="{ThemeResource SubtleFillColorTertiaryBrush}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="RootGrid.Background" Value="{ThemeResource SubtleFillColorDisabledBrush}" />
<Setter Target="ContentPresenter.Foreground" Value="{ThemeResource AccentFillColorDisabledBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="VerticalDivider" TargetType="Border">
<Setter Property="BorderBrush" Value="{ThemeResource DividerStrokeColorDefaultBrush}" />

View File

@@ -90,7 +90,7 @@ internal partial class ContentBrowserViewModel : ObservableObject
if (!isDir)
{
var ext = Path.GetExtension(fullPath);
assetType = AssetHandlerRegistry.GetAssetTypeByExtension(ext);
assetType = AssetHandlerRegistry.GetRuntimeAssetTypeByExtension(ext);
}
Files.Add(new ExplorerItem(Path.GetFileName(fullPath), fullPath, isDir, assetType));
}
@@ -144,7 +144,7 @@ internal partial class ContentBrowserViewModel : ObservableObject
}
var ext = Path.GetExtension(file);
var assetType = AssetHandlerRegistry.GetAssetTypeByExtension(ext);
var assetType = AssetHandlerRegistry.GetRuntimeAssetTypeByExtension(ext);
var fileItem = new ExplorerItem(Path.GetFileName(file), file, false, assetType);
Files.Add(fileItem);

View File

@@ -33,8 +33,6 @@ internal sealed partial class ContentBrowser : UserControl
Loaded += ProjectBrowser_Loaded;
Unloaded += ProjectBrowser_Unloaded;
GettingFocus += ProjectBrowser_GettingFocus;
}
private void ProjectBrowser_GettingFocus(UIElement sender, GettingFocusEventArgs args)
@@ -50,11 +48,13 @@ internal sealed partial class ContentBrowser : UserControl
private void ProjectBrowser_Loaded(object sender, RoutedEventArgs e)
{
_inspectorService.OnSelectionChanged += _inspectorService_OnSelectionChanged;
GettingFocus += ProjectBrowser_GettingFocus;
}
private void ProjectBrowser_Unloaded(object sender, RoutedEventArgs e)
{
_inspectorService.OnSelectionChanged -= _inspectorService_OnSelectionChanged;
GettingFocus -= ProjectBrowser_GettingFocus;
if (LastFocused == this)
{

View File

@@ -6,6 +6,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource LayerFillColorDefaultBrush}"
NavigationCacheMode="Enabled"
mc:Ignorable="d">
<Grid>
@@ -15,24 +16,33 @@
</Grid.RowDefinitions>
<!-- Toolbar -->
<StackPanel
<Grid
Grid.Row="0"
Padding="8,0"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource ControlElevationBorderBrush}"
BorderThickness="0,0,0,1"
BorderThickness="0,0,0,1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel
Grid.Column="0"
VerticalAlignment="Center"
Orientation="Horizontal">
<ComboBox
Width="200"
VerticalAlignment="Center"
SelectedIndex="0">
<StackPanel Orientation="Horizontal" Spacing="4">
<FontIcon FontSize="{StaticResource ToolbarFontIconFontSize}" Glyph="&#xE8B0;" />
<TextBlock Text="Selection Mode" />
<FontIcon FontSize="{StaticResource ToolbarFontIconFontSize}" Glyph="&#xE71D;" />
<TextBlock Text="Hierarchy Mode" />
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="4">
<FontIcon FontSize="{StaticResource ToolbarFontIconFontSize}" Glyph="&#xEC26;" />
<TextBlock Text="Placement Mode" />
<TextBlock Text="Scatter Mode" />
</StackPanel>
</ComboBox>
@@ -59,6 +69,25 @@
</MenuBar>
</StackPanel>
<StackPanel
Grid.Column="0"
Grid.ColumnSpan="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal"
Spacing="4">
<Button Style="{ThemeResource AccentToolbarButton}">
<FontIcon FontSize="{StaticResource ToolbarFontIconFontSize}" Glyph="&#xF5B0;" />
</Button>
<Button Style="{ThemeResource AccentToolbarButton}">
<FontIcon FontSize="{StaticResource ToolbarFontIconFontSize}" Glyph="&#xF8AE;" />
</Button>
<Button IsEnabled="False" Style="{ThemeResource AccentToolbarButton}">
<FontIcon FontSize="{StaticResource ToolbarFontIconFontSize}" Glyph="&#xEE95;" />
</Button>
</StackPanel>
</Grid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.25*" MaxWidth="350" />

View File

@@ -5,17 +5,24 @@ namespace Ghost.Editor.Views.Pages;
public sealed partial class EditPage : Page
{
private readonly ContentBrowser _contentBrowser;
private readonly LogViewer _logViewer;
private ContentBrowser? _contentBrowser;
private LogViewer? _logViewer;
public EditPage()
{
InitializeComponent();
_contentBrowser = new ContentBrowser();
_logViewer = new LogViewer();
ContentBrowserPresenter.Content = GetContentBrowser();
}
ContentBrowserPresenter.Content = _contentBrowser;
private ContentBrowser GetContentBrowser()
{
return _contentBrowser ??= new ContentBrowser();
}
private LogViewer GetLogViewer()
{
return _logViewer ??= new LogViewer();
}
private void SelectorBar_SelectionChanged(SelectorBar sender, SelectorBarSelectionChangedEventArgs args)
@@ -25,10 +32,10 @@ public sealed partial class EditPage : Page
switch (currentSelectedIndex)
{
case 0:
ContentBrowserPresenter.Content = _contentBrowser;
ContentBrowserPresenter.Content = GetContentBrowser();
break;
case 2:
ContentBrowserPresenter.Content = _logViewer;
ContentBrowserPresenter.Content = GetLogViewer();
break;
default:
break;

View File

@@ -66,6 +66,7 @@
Text="Edit" />
<SelectorBarItem x:Name="AnalysisSelectorItem" Text="Analysis" />
<SelectorBarItem x:Name="BuildSelectorItem" Text="Build" />
<SelectorBarItem x:Name="SettingsSelectorItem" Text="Settings" />
</SelectorBar>
</StackPanel>

View File

@@ -21,6 +21,7 @@
<Project Path="ThridParty/Ghost.FMOD/Ghost.FMOD.csproj" />
<Project Path="ThridParty/Ghost.MeshOptimizer/Ghost.MeshOptimizer.csproj" />
<Project Path="ThridParty/Ghost.Nvtt/Ghost.Nvtt.csproj" />
<Project Path="ThridParty/Ghost.StbI/Ghost.StbI.csproj" Id="969a1d70-31ee-49e9-9449-ab96a47cb925" />
<Project Path="ThridParty/Ghost.Ufbx/Ghost.Ufbx.csproj" Id="c4bd647c-6d77-49d8-ba81-6ed4946474d1" />
</Folder>
<Folder Name="/Runtime/">

View File

@@ -60,18 +60,56 @@ public struct PassDescriptor
public class GraphicsShaderDescriptor
{
public required string name = string.Empty;
public required uint propertyBufferSize;
public required ShaderModel shaderModel;
public required PassDescriptor[] passes = Array.Empty<PassDescriptor>();
public required string Name
{
get; init;
}
public required uint PropertyBufferSize
{
get; init;
}
public required ShaderModel ShaderModel
{
get; init;
}
public required PassDescriptor[] Passes
{
get; init;
}
}
public class ComputeShaderDescriptor
{
public required string name = string.Empty;
public required uint propertyBufferSize;
public required ShaderModel shaderModel;
public required ShaderCode[] shaderCodes;
public required string[] defines;
public required KeywordsGroup[] keywords;
public required string Name
{
get; init;
}
public required uint PropertyBufferSize
{
get; init;
}
public required ShaderModel ShaderModel
{
get; init;
}
public required ShaderCode[] ShaderCodes
{
get; init;
}
public required string[] Defines
{
get; init;
}
public required KeywordsGroup[] Keywords
{
get; init;
}
}

View File

@@ -197,7 +197,7 @@ internal unsafe partial class AssetEntry
if (Interlocked.CompareExchange(ref _pendingReimport, false, true))
{
_assetManager.InvalidateAsset(_assetId); // re-queue
_assetManager.ReimportAsset(_assetId); // re-queue
}
}
@@ -415,7 +415,7 @@ internal partial class AssetManager : IDisposable
return entry;
}
public void InvalidateAsset(Guid guid)
public void ReimportAsset(Guid guid)
{
if (!_entries.TryGetValue(guid, out var entry))
{

View File

@@ -4,6 +4,12 @@ using Misaki.HighPerformance.Jobs;
namespace Ghost.Engine;
public interface IRuntimeInitializeCallback
{
void Initialize();
void Shutdown();
}
public sealed partial class EngineCore : IDisposable
{
private readonly IContentProvider _contentProvider;
@@ -13,6 +19,10 @@ public sealed partial class EngineCore : IDisposable
private readonly RenderSystem _renderSystem;
private readonly AssetManager _assetManager;
internal JobScheduler JobScheduler => _jobScheduler;
internal RenderSystem RenderSystem => _renderSystem;
internal AssetManager AssetManager => _assetManager;
public EngineCore(IContentProvider contentProvider)
{
_contentProvider = contentProvider;

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Ghost.Engine;
internal class TestSetup : IDisposable
{
public void Dispose()
{
throw new NotImplementedException();
}
}

View File

@@ -188,11 +188,16 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
if (isSubAllocation)
{
hr = CreateResource(&allocationDesc, &resourceDesc, D3D12_BARRIER_LAYOUT_COMMON, options, (uint)additionalDesc.CastableFormat.Length, pCastableFormats, __uuidof(pResource), (void**)&pResource);
hr = CreateResource(&allocationDesc, &resourceDesc, D3D12_BARRIER_LAYOUT_COMMON, options,
(uint)additionalDesc.CastableFormat.Length, pCastableFormats,
__uuidof(pResource), (void**)&pResource);
}
else
{
hr = CreateResource(&allocationDesc, &resourceDesc, D3D12_BARRIER_LAYOUT_COMMON, options, (uint)additionalDesc.CastableFormat.Length, pCastableFormats, __uuidof(pAllocation), (void**)&pAllocation);
hr = CreateResource(&allocationDesc, &resourceDesc, D3D12_BARRIER_LAYOUT_COMMON, options,
(uint)additionalDesc.CastableFormat.Length, pCastableFormats,
__uuidof(pAllocation), (void**)&pAllocation);
if (hr.SUCCEEDED)
{
pResource = pAllocation->GetResource();
@@ -249,11 +254,16 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
if (isSubAllocation)
{
hr = CreateResource(&allocationDesc, &resourceDesc, D3D12_BARRIER_LAYOUT_UNDEFINED, options, 0u, null, __uuidof(pResource), (void**)&pResource);
hr = CreateResource(&allocationDesc, &resourceDesc, D3D12_BARRIER_LAYOUT_UNDEFINED, options,
0u, null,
__uuidof(pResource), (void**)&pResource);
}
else
{
hr = CreateResource(&allocationDesc, &resourceDesc, D3D12_BARRIER_LAYOUT_UNDEFINED, options, 0u, null, __uuidof(pAllocation), (void**)&pAllocation);
hr = CreateResource(&allocationDesc, &resourceDesc, D3D12_BARRIER_LAYOUT_UNDEFINED, options,
0u, null,
__uuidof(pAllocation), (void**)&pAllocation);
if (hr.SUCCEEDED)
{
pResource = pAllocation->GetResource();

View File

@@ -75,15 +75,15 @@ public partial struct Shader : IResourceReleasable
internal Shader(GraphicsShaderDescriptor descriptor)
{
_nameHash = RHIUtility.GetShaderID(descriptor.name);
_propertyBufferSize = descriptor.propertyBufferSize;
_shaderPasses = new UnsafeArray<ShaderPass>(descriptor.passes.Length, AllocationHandle.Persistent);
_passIDToLocal = new UnsafeHashMap<int, int>(descriptor.passes.Length, AllocationHandle.Persistent);
_nameHash = RHIUtility.GetShaderID(descriptor.Name);
_propertyBufferSize = descriptor.PropertyBufferSize;
_shaderPasses = new UnsafeArray<ShaderPass>(descriptor.Passes.Length, AllocationHandle.Persistent);
_passIDToLocal = new UnsafeHashMap<int, int>(descriptor.Passes.Length, AllocationHandle.Persistent);
_keywordIDToLocal = new UnsafeHashMap<int, int>(32, AllocationHandle.Persistent);
for (var i = 0; i < descriptor.passes.Length; i++)
for (var i = 0; i < descriptor.Passes.Length; i++)
{
ref readonly var pass = ref descriptor.passes[i];
ref readonly var pass = ref descriptor.Passes[i];
var keywords = new LocalKeywordSet();
@@ -189,7 +189,7 @@ public partial struct Shader : IResourceReleasable
public unsafe partial struct ComputeShader : IResourceReleasable
{
private readonly ulong _nameHash;
private fixed ulong entryHashes[8]; // Support up to 8 entry points for now, can be extended if needed.
private fixed ulong _entryHashes[8]; // Support up to 8 entry points for now, can be extended if needed.
private readonly uint _propertyBufferSize;
private LocalKeywordSet _localKeywordSet;
@@ -200,20 +200,20 @@ public unsafe partial struct ComputeShader : IResourceReleasable
internal ComputeShader(ComputeShaderDescriptor descriptor)
{
_nameHash = RHIUtility.GetShaderID(descriptor.name);
_propertyBufferSize = descriptor.propertyBufferSize;
_nameHash = RHIUtility.GetShaderID(descriptor.Name);
_propertyBufferSize = descriptor.PropertyBufferSize;
_keywordIDToLocal = new UnsafeHashMap<int, int>(32, AllocationHandle.Persistent);
for (var i = 0; i < descriptor.shaderCodes.Length; i++)
for (var i = 0; i < descriptor.ShaderCodes.Length; i++)
{
entryHashes[i] = RHIUtility.GetPassID(_nameHash, i);
_entryHashes[i] = RHIUtility.GetPassID(_nameHash, i);
}
var localKeywordIndex = 0;
for (var i = 0; i < descriptor.keywords.Length; i++)
for (var i = 0; i < descriptor.Keywords.Length; i++)
{
var group = descriptor.keywords[i];
var group = descriptor.Keywords[i];
if (group.keywords == null)
{
continue;
@@ -236,7 +236,7 @@ public unsafe partial struct ComputeShader : IResourceReleasable
public ulong GetEntryID(int entryIndex)
{
Logger.DebugAssert(entryIndex >= 0 && entryIndex < 8, "Entry index out of bounds.");
return entryHashes[entryIndex];
return _entryHashes[entryIndex];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@@ -251,7 +251,6 @@ public class RenderSystem : IDisposable
try
{
// Wait for either CPU ready signal or shutdown signal
waitHandles[0] = frameResource.CpuReadyEvent;
var waitResult = WaitHandle.WaitAny(waitHandles);

View File

@@ -1,5 +1,6 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Utilities;
using Ghost.MeshOptimizer;
using Ghost.Ufbx;
using Misaki.HighPerformance.LowLevel;
@@ -170,11 +171,11 @@ internal static class MeshUtility
MemoryUtility.MemCpy(indices.GetUnsafePtr(), cachedIndices.GetUnsafePtr(), numIndices * sizeof(uint));
indices.UnsafeSetCount((int)numIndices);
//if (needComputeNormals)
//{
// MeshBuilder.ComputeNormal(vertices, indices);
// MeshBuilder.ComputeTangents(vertices, indices);
//}
if (needComputeNormals)
{
MeshBuilder.ComputeNormal(vertices, indices);
MeshBuilder.ComputeTangents(vertices, indices);
}
return Result.Success();
}

View File

@@ -14,9 +14,9 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Runtime\Ghost.Graphics\Ghost.Graphics.csproj" />
<ProjectReference Include="..\..\Test\Ghost.Test.Core\Ghost.Test.Core.csproj" />
<ProjectReference Include="..\..\ThridParty\Ghost.Nvtt\Ghost.Nvtt.csproj" />
<ProjectReference Include="..\..\ThridParty\Ghost.StbI\Ghost.StbI.csproj" />
<ProjectReference Include="..\..\ThridParty\Ghost.Ufbx\Ghost.Ufbx.csproj" />
</ItemGroup>

View File

@@ -1,5 +1,4 @@
using Ghost.MicroTest;
using Ghost.Test.Core;
//TestRunner.Run<MeshoptBenchmark>();
Console.WriteLine();
TestRunner.Run<StbIBindingTest>();

View File

@@ -0,0 +1,72 @@
using Ghost.StbI;
using Ghost.Test.Core;
namespace Ghost.MicroTest;
internal class StbIBindingTest : ITest
{
public void Setup()
{
}
public unsafe void Run()
{
using var stream = File.OpenRead("C:\\Users\\Misaki\\Downloads\\Screenshot 2024-07-20 035047.png");
var bytes = new byte[stream.Length];
stream.ReadExactly(bytes);
int width, height, channels;
var buff = StbIApi.LoadFromMemory(bytes, &width, &height, &channels, 4);
if (buff == null)
{
Console.WriteLine("Failed to load image");
return;
}
try
{
Console.WriteLine($"Image loaded: {width}x{height}, channels: {channels}");
var expectedColor = (Span<byte>)stackalloc byte[] { 122, 145, 224, 255 };
var firstPixel = new Span<byte>(buff, 4);
Console.WriteLine("First pixel RGBA: " + string.Join(", ", firstPixel.ToArray()));
Console.WriteLine("Expected RGBA: " + string.Join(", ", expectedColor.ToArray()));
if (!firstPixel.SequenceEqual(expectedColor))
{
Console.WriteLine("First pixel does not match expected color");
}
else
{
Console.WriteLine("First pixel matches expected color");
}
firstPixel.Fill(0xFF);
int result;
var newFilePath = "C:\\Users\\Misaki\\Downloads\\ModifiedImage.jpg"u8;
fixed (byte* pathPtr = newFilePath)
{
result = StbIApi.WriteJpg((sbyte*)pathPtr, width, height, 4, buff, 90);
}
if (result == 0)
{
Console.WriteLine("Failed to write image");
}
else
{
Console.WriteLine("Image written successfully");
}
}
finally
{
StbIApi.ImageFree(buff);
}
}
public void Cleanup()
{
}
}

View File

@@ -0,0 +1,25 @@
using System.Reflection;
using System.Runtime.InteropServices;
namespace Ghost.Nvtt;
public partial class Api
{
static Api()
{
NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), (libraryName, assembly, searchPath) =>
{
var platform = OperatingSystem.IsWindows() ? "win" :
OperatingSystem.IsLinux() ? "linux" :
OperatingSystem.IsMacOS() ? "osx" : "unknown";
var ext = OperatingSystem.IsWindows() ? ".dll" :
OperatingSystem.IsLinux() ? ".so" :
OperatingSystem.IsMacOS() ? ".dylib" : "";
var arch = Environment.Is64BitProcess ? "x64" : "x86";
var nativeDllDir = Path.Combine("./runtimes", platform + "-" + arch, "native");
return NativeLibrary.Load(Path.Combine(nativeDllDir, libraryName + ext));
});
}
}

View File

@@ -1,28 +1,9 @@
using System.Reflection;
using System.Runtime.InteropServices;
namespace Ghost.Nvtt
{
public static unsafe partial class Api
{
static Api()
{
NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), (libraryName, assembly, searchPath) =>
{
var platform = OperatingSystem.IsWindows() ? "win" :
OperatingSystem.IsLinux() ? "linux" :
OperatingSystem.IsMacOS() ? "osx" : "unknown";
var ext = OperatingSystem.IsWindows() ? ".dll" :
OperatingSystem.IsLinux() ? ".so" :
OperatingSystem.IsMacOS() ? ".dylib" : "";
var arch = Environment.Is64BitProcess ? "x64" : "x86";
var nativeDllDir = Path.Combine("./runtimes", platform + "-" + arch, "native");
return NativeLibrary.Load(Path.Combine(nativeDllDir, libraryName + ext));
});
}
[DllImport("nvtt", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern NvttBoolean nvttIsCudaSupported();

View File

@@ -15,6 +15,9 @@ namespace Ghost.Nvtt
private readonly int _value;
public readonly bool IsTrue => _value != 0;
public readonly bool IsFalse => _value == 0;
public NvttBoolean(int value)
{
_value = value;

View File

@@ -0,0 +1,25 @@
using System.Reflection;
using System.Runtime.InteropServices;
namespace Ghost.StbI;
public partial class Api
{
static Api()
{
NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), (libraryName, assembly, searchPath) =>
{
var platform = OperatingSystem.IsWindows() ? "win" :
OperatingSystem.IsLinux() ? "linux" :
OperatingSystem.IsMacOS() ? "osx" : "unknown";
var ext = OperatingSystem.IsWindows() ? ".dll" :
OperatingSystem.IsLinux() ? ".so" :
OperatingSystem.IsMacOS() ? ".dylib" : "";
var arch = Environment.Is64BitProcess ? "x64" : "x86";
var nativeDllDir = Path.Combine("./runtimes", platform + "-" + arch, "native");
return NativeLibrary.Load(Path.Combine(nativeDllDir, libraryName + ext));
});
}
}

View File

@@ -0,0 +1,171 @@
using System.Runtime.InteropServices;
namespace Ghost.StbI;
public static unsafe partial class Api
{
public const int STBI_default = 0;
public const int STBI_grey = 1;
public const int STBI_grey_alpha = 2;
public const int STBI_rgb = 3;
public const int STBI_rgb_alpha = 4;
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("stbi_uc *")]
public static extern byte* stbi_load_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("stbi_uc *")]
public static extern byte* stbi_load_from_callbacks([NativeTypeName("const stbi_io_callbacks *")] stbi_io_callbacks* clbk, void* user, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("stbi_uc *")]
public static extern byte* stbi_load([NativeTypeName("const char *")] sbyte* filename, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("stbi_uc *")]
public static extern byte* stbi_load_gif_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len, int** delays, int* x, int* y, int* z, int* comp, int req_comp);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("stbi_us *")]
public static extern ushort* stbi_load_16_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("stbi_us *")]
public static extern ushort* stbi_load_16_from_callbacks([NativeTypeName("const stbi_io_callbacks *")] stbi_io_callbacks* clbk, void* user, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("stbi_us *")]
public static extern ushort* stbi_load_16([NativeTypeName("const char *")] sbyte* filename, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern float* stbi_loadf_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern float* stbi_loadf_from_callbacks([NativeTypeName("const stbi_io_callbacks *")] stbi_io_callbacks* clbk, void* user, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern float* stbi_loadf([NativeTypeName("const char *")] sbyte* filename, int* x, int* y, int* channels_in_file, int desired_channels);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void stbi_hdr_to_ldr_gamma(float gamma);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void stbi_hdr_to_ldr_scale(float scale);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void stbi_ldr_to_hdr_gamma(float gamma);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void stbi_ldr_to_hdr_scale(float scale);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_is_hdr_from_callbacks([NativeTypeName("const stbi_io_callbacks *")] stbi_io_callbacks* clbk, void* user);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_is_hdr_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_is_hdr([NativeTypeName("const char *")] sbyte* filename);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("const char *")]
public static extern sbyte* stbi_failure_reason();
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void stbi_image_free(void* retval_from_stbi_load);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_info_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len, int* x, int* y, int* comp);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_info_from_callbacks([NativeTypeName("const stbi_io_callbacks *")] stbi_io_callbacks* clbk, void* user, int* x, int* y, int* comp);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_is_16_bit_from_memory([NativeTypeName("const stbi_uc *")] byte* buffer, int len);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_is_16_bit_from_callbacks([NativeTypeName("const stbi_io_callbacks *")] stbi_io_callbacks* clbk, void* user);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_info([NativeTypeName("const char *")] sbyte* filename, int* x, int* y, int* comp);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_is_16_bit([NativeTypeName("const char *")] sbyte* filename);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("char *")]
public static extern sbyte* stbi_zlib_decode_malloc_guesssize([NativeTypeName("const char *")] sbyte* buffer, int len, int initial_size, int* outlen);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("char *")]
public static extern sbyte* stbi_zlib_decode_malloc_guesssize_headerflag([NativeTypeName("const char *")] sbyte* buffer, int len, int initial_size, int* outlen, int parse_header);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("char *")]
public static extern sbyte* stbi_zlib_decode_malloc([NativeTypeName("const char *")] sbyte* buffer, int len, int* outlen);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_zlib_decode_buffer([NativeTypeName("char *")] sbyte* obuffer, int olen, [NativeTypeName("const char *")] sbyte* ibuffer, int ilen);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("char *")]
public static extern sbyte* stbi_zlib_decode_noheader_malloc([NativeTypeName("const char *")] sbyte* buffer, int len, int* outlen);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_zlib_decode_noheader_buffer([NativeTypeName("char *")] sbyte* obuffer, int olen, [NativeTypeName("const char *")] sbyte* ibuffer, int ilen);
[NativeTypeName("#define STBI_VERSION 1")]
public const int STBI_VERSION = 1;
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_write_png([NativeTypeName("const char *")] sbyte* filename, int w, int h, int comp, [NativeTypeName("const void *")] void* data, int stride_in_bytes);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_write_bmp([NativeTypeName("const char *")] sbyte* filename, int w, int h, int comp, [NativeTypeName("const void *")] void* data);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_write_tga([NativeTypeName("const char *")] sbyte* filename, int w, int h, int comp, [NativeTypeName("const void *")] void* data);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_write_hdr([NativeTypeName("const char *")] sbyte* filename, int w, int h, int comp, [NativeTypeName("const float *")] float* data);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_write_jpg([NativeTypeName("const char *")] sbyte* filename, int x, int y, int comp, [NativeTypeName("const void *")] void* data, int quality);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_write_png_to_func([NativeTypeName("stbi_write_func *")] delegate* unmanaged[Cdecl]<void*, void*, int, void> func, void* context, int w, int h, int comp, [NativeTypeName("const void *")] void* data, int stride_in_bytes);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_write_bmp_to_func([NativeTypeName("stbi_write_func *")] delegate* unmanaged[Cdecl]<void*, void*, int, void> func, void* context, int w, int h, int comp, [NativeTypeName("const void *")] void* data);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_write_tga_to_func([NativeTypeName("stbi_write_func *")] delegate* unmanaged[Cdecl]<void*, void*, int, void> func, void* context, int w, int h, int comp, [NativeTypeName("const void *")] void* data);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_write_hdr_to_func([NativeTypeName("stbi_write_func *")] delegate* unmanaged[Cdecl]<void*, void*, int, void> func, void* context, int w, int h, int comp, [NativeTypeName("const float *")] float* data);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int stbi_write_jpg_to_func([NativeTypeName("stbi_write_func *")] delegate* unmanaged[Cdecl]<void*, void*, int, void> func, void* context, int x, int y, int comp, [NativeTypeName("const void *")] void* data, int quality);
[DllImport("stbi", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void stbi_flip_vertically_on_write(int flip_boolean);
}

View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: DisableRuntimeMarshalling]

View File

@@ -0,0 +1,22 @@
using System;
using System.Diagnostics;
namespace Ghost.StbI;
/// <summary>Defines the annotation found in a native declaration.</summary>
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
[Conditional("DEBUG")]
internal sealed partial class NativeAnnotationAttribute : Attribute
{
private readonly string _annotation;
/// <summary>Initializes a new instance of the <see cref="NativeAnnotationAttribute" /> class.</summary>
/// <param name="annotation">The annotation that was used in the native declaration.</param>
public NativeAnnotationAttribute(string annotation)
{
_annotation = annotation;
}
/// <summary>Gets the annotation that was used in the native declaration.</summary>
public string Annotation => _annotation;
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Diagnostics;
namespace Ghost.StbI;
/// <summary>Defines the type of a member as it was used in the native signature.</summary>
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = false, Inherited = true)]
[Conditional("DEBUG")]
internal sealed partial class NativeTypeNameAttribute : Attribute
{
private readonly string _name;
/// <summary>Initializes a new instance of the <see cref="NativeTypeNameAttribute" /> class.</summary>
/// <param name="name">The name of the type that was used in the native signature.</param>
public NativeTypeNameAttribute(string name)
{
_name = name;
}
/// <summary>Gets the name of the type that was used in the native signature.</summary>
public string Name => _name;
}

View File

@@ -0,0 +1,13 @@
namespace Ghost.StbI;
public unsafe partial struct stbi_io_callbacks
{
[NativeTypeName("int (*)(void *, char *, int)")]
public delegate* unmanaged[Cdecl]<void*, sbyte*, int, int> read;
[NativeTypeName("void (*)(void *, int)")]
public delegate* unmanaged[Cdecl]<void*, int, void> skip;
[NativeTypeName("int (*)(void *)")]
public delegate* unmanaged[Cdecl]<void*, int> eof;
}

View File

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<None Update="runtimes\win-x64\native\stbi.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,648 @@
// <auto-generated>
// This file is generated by Ghost.NativeWrapperGen. Do not edit manually.
// </auto-generated>
namespace Ghost.StbI;
public unsafe partial struct StbIApi
{
/// <summary>
/// From: <see cref="Api.stbi_load_from_memory(byte*, int, int*, int*, int*, int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static byte* LoadFromMemory(ReadOnlySpan<byte> buffer, int* x, int* y, int* channels_in_file, int desired_channels)
{
fixed (byte* pbuffer = buffer)
{
return Api.stbi_load_from_memory(
(byte*)pbuffer,
buffer.Length,
x,
y,
channels_in_file,
desired_channels);
}
}
/// <summary>
/// From: <see cref="Api.stbi_load_from_callbacks(stbi_io_callbacks*, void*, int*, int*, int*, int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static byte* LoadFromCallbacks(stbi_io_callbacks* clbk, void* user, int* x, int* y, int* channels_in_file, int desired_channels)
{
return Api.stbi_load_from_callbacks(
clbk,
user,
x,
y,
channels_in_file,
desired_channels);
}
/// <summary>
/// From: <see cref="Api.stbi_load(sbyte*, int*, int*, int*, int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static byte* Load(sbyte* filename, int* x, int* y, int* channels_in_file, int desired_channels)
{
return Api.stbi_load(
filename,
x,
y,
channels_in_file,
desired_channels);
}
/// <summary>
/// From: <see cref="Api.stbi_load_gif_from_memory(byte*, int, int**, int*, int*, int*, int*, int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static byte* LoadGifFromMemory(ReadOnlySpan<byte> buffer, int** delays, int* x, int* y, int* z, int* comp, int req_comp)
{
fixed (byte* pbuffer = buffer)
{
return Api.stbi_load_gif_from_memory(
(byte*)pbuffer,
buffer.Length,
delays,
x,
y,
z,
comp,
req_comp);
}
}
/// <summary>
/// From: <see cref="Api.stbi_load_16_from_memory(byte*, int, int*, int*, int*, int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static ushort* Load16FromMemory(ReadOnlySpan<byte> buffer, int* x, int* y, int* channels_in_file, int desired_channels)
{
fixed (byte* pbuffer = buffer)
{
return Api.stbi_load_16_from_memory(
(byte*)pbuffer,
buffer.Length,
x,
y,
channels_in_file,
desired_channels);
}
}
/// <summary>
/// From: <see cref="Api.stbi_load_16_from_callbacks(stbi_io_callbacks*, void*, int*, int*, int*, int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static ushort* Load16FromCallbacks(stbi_io_callbacks* clbk, void* user, int* x, int* y, int* channels_in_file, int desired_channels)
{
return Api.stbi_load_16_from_callbacks(
clbk,
user,
x,
y,
channels_in_file,
desired_channels);
}
/// <summary>
/// From: <see cref="Api.stbi_load_16(sbyte*, int*, int*, int*, int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static ushort* Load16(sbyte* filename, int* x, int* y, int* channels_in_file, int desired_channels)
{
return Api.stbi_load_16(
filename,
x,
y,
channels_in_file,
desired_channels);
}
/// <summary>
/// From: <see cref="Api.stbi_loadf_from_memory(byte*, int, int*, int*, int*, int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static float* LoadfFromMemory(ReadOnlySpan<byte> buffer, int* x, int* y, int* channels_in_file, int desired_channels)
{
fixed (byte* pbuffer = buffer)
{
return Api.stbi_loadf_from_memory(
(byte*)pbuffer,
buffer.Length,
x,
y,
channels_in_file,
desired_channels);
}
}
/// <summary>
/// From: <see cref="Api.stbi_loadf_from_callbacks(stbi_io_callbacks*, void*, int*, int*, int*, int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static float* LoadfFromCallbacks(stbi_io_callbacks* clbk, void* user, int* x, int* y, int* channels_in_file, int desired_channels)
{
return Api.stbi_loadf_from_callbacks(
clbk,
user,
x,
y,
channels_in_file,
desired_channels);
}
/// <summary>
/// From: <see cref="Api.stbi_loadf(sbyte*, int*, int*, int*, int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static float* Loadf(sbyte* filename, int* x, int* y, int* channels_in_file, int desired_channels)
{
return Api.stbi_loadf(
filename,
x,
y,
channels_in_file,
desired_channels);
}
/// <summary>
/// From: <see cref="Api.stbi_hdr_to_ldr_gamma(float)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static void HdrToLdrGamma(float gamma)
{
Api.stbi_hdr_to_ldr_gamma(gamma);
}
/// <summary>
/// From: <see cref="Api.stbi_hdr_to_ldr_scale(float)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static void HdrToLdrScale(float scale)
{
Api.stbi_hdr_to_ldr_scale(scale);
}
/// <summary>
/// From: <see cref="Api.stbi_ldr_to_hdr_gamma(float)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static void LdrToHdrGamma(float gamma)
{
Api.stbi_ldr_to_hdr_gamma(gamma);
}
/// <summary>
/// From: <see cref="Api.stbi_ldr_to_hdr_scale(float)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static void LdrToHdrScale(float scale)
{
Api.stbi_ldr_to_hdr_scale(scale);
}
/// <summary>
/// From: <see cref="Api.stbi_is_hdr_from_callbacks(stbi_io_callbacks*, void*)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int IsHdrFromCallbacks(stbi_io_callbacks* clbk, void* user)
{
return Api.stbi_is_hdr_from_callbacks(
clbk,
user);
}
/// <summary>
/// From: <see cref="Api.stbi_is_hdr_from_memory(byte*, int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int IsHdrFromMemory(ReadOnlySpan<byte> buffer)
{
fixed (byte* pbuffer = buffer)
{
return Api.stbi_is_hdr_from_memory(
(byte*)pbuffer,
buffer.Length);
}
}
/// <summary>
/// From: <see cref="Api.stbi_is_hdr(sbyte*)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int IsHdr(sbyte* filename)
{
return Api.stbi_is_hdr(filename);
}
/// <summary>
/// From: <see cref="Api.stbi_failure_reason()" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static sbyte* FailureReason()
{
return Api.stbi_failure_reason();
}
/// <summary>
/// From: <see cref="Api.stbi_image_free(void*)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static void ImageFree(void* retval_from_stbi_load)
{
Api.stbi_image_free(retval_from_stbi_load);
}
/// <summary>
/// From: <see cref="Api.stbi_info_from_memory(byte*, int, int*, int*, int*)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int InfoFromMemory(ReadOnlySpan<byte> buffer, int* x, int* y, int* comp)
{
fixed (byte* pbuffer = buffer)
{
return Api.stbi_info_from_memory(
(byte*)pbuffer,
buffer.Length,
x,
y,
comp);
}
}
/// <summary>
/// From: <see cref="Api.stbi_info_from_callbacks(stbi_io_callbacks*, void*, int*, int*, int*)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int InfoFromCallbacks(stbi_io_callbacks* clbk, void* user, int* x, int* y, int* comp)
{
return Api.stbi_info_from_callbacks(
clbk,
user,
x,
y,
comp);
}
/// <summary>
/// From: <see cref="Api.stbi_is_16_bit_from_memory(byte*, int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int Is16BitFromMemory(ReadOnlySpan<byte> buffer)
{
fixed (byte* pbuffer = buffer)
{
return Api.stbi_is_16_bit_from_memory(
(byte*)pbuffer,
buffer.Length);
}
}
/// <summary>
/// From: <see cref="Api.stbi_is_16_bit_from_callbacks(stbi_io_callbacks*, void*)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int Is16BitFromCallbacks(stbi_io_callbacks* clbk, void* user)
{
return Api.stbi_is_16_bit_from_callbacks(
clbk,
user);
}
/// <summary>
/// From: <see cref="Api.stbi_info(sbyte*, int*, int*, int*)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int Info(sbyte* filename, int* x, int* y, int* comp)
{
return Api.stbi_info(
filename,
x,
y,
comp);
}
/// <summary>
/// From: <see cref="Api.stbi_is_16_bit(sbyte*)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int Is16Bit(sbyte* filename)
{
return Api.stbi_is_16_bit(filename);
}
/// <summary>
/// From: <see cref="Api.stbi_set_unpremultiply_on_load(int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static void SetUnpremultiplyOnLoad(int flag_true_if_should_unpremultiply)
{
Api.stbi_set_unpremultiply_on_load(flag_true_if_should_unpremultiply);
}
/// <summary>
/// From: <see cref="Api.stbi_convert_iphone_png_to_rgb(int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static void ConvertIphonePngToRgb(int flag_true_if_should_convert)
{
Api.stbi_convert_iphone_png_to_rgb(flag_true_if_should_convert);
}
/// <summary>
/// From: <see cref="Api.stbi_set_flip_vertically_on_load(int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static void SetFlipVerticallyOnLoad(int flag_true_if_should_flip)
{
Api.stbi_set_flip_vertically_on_load(flag_true_if_should_flip);
}
/// <summary>
/// From: <see cref="Api.stbi_set_unpremultiply_on_load_thread(int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static void SetUnpremultiplyOnLoadThread(int flag_true_if_should_unpremultiply)
{
Api.stbi_set_unpremultiply_on_load_thread(flag_true_if_should_unpremultiply);
}
/// <summary>
/// From: <see cref="Api.stbi_convert_iphone_png_to_rgb_thread(int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static void ConvertIphonePngToRgbThread(int flag_true_if_should_convert)
{
Api.stbi_convert_iphone_png_to_rgb_thread(flag_true_if_should_convert);
}
/// <summary>
/// From: <see cref="Api.stbi_set_flip_vertically_on_load_thread(int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static void SetFlipVerticallyOnLoadThread(int flag_true_if_should_flip)
{
Api.stbi_set_flip_vertically_on_load_thread(flag_true_if_should_flip);
}
/// <summary>
/// From: <see cref="Api.stbi_zlib_decode_malloc_guesssize(sbyte*, int, int, int*)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static sbyte* ZlibDecodeMallocGuesssize(ReadOnlySpan<sbyte> buffer, int initial_size, int* outlen)
{
fixed (sbyte* pbuffer = buffer)
{
return Api.stbi_zlib_decode_malloc_guesssize(
(sbyte*)pbuffer,
buffer.Length,
initial_size,
outlen);
}
}
/// <summary>
/// From: <see cref="Api.stbi_zlib_decode_malloc_guesssize_headerflag(sbyte*, int, int, int*, int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static sbyte* ZlibDecodeMallocGuesssizeHeaderflag(ReadOnlySpan<sbyte> buffer, int initial_size, int* outlen, int parse_header)
{
fixed (sbyte* pbuffer = buffer)
{
return Api.stbi_zlib_decode_malloc_guesssize_headerflag(
(sbyte*)pbuffer,
buffer.Length,
initial_size,
outlen,
parse_header);
}
}
/// <summary>
/// From: <see cref="Api.stbi_zlib_decode_malloc(sbyte*, int, int*)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static sbyte* ZlibDecodeMalloc(ReadOnlySpan<sbyte> buffer, int* outlen)
{
fixed (sbyte* pbuffer = buffer)
{
return Api.stbi_zlib_decode_malloc(
(sbyte*)pbuffer,
buffer.Length,
outlen);
}
}
/// <summary>
/// From: <see cref="Api.stbi_zlib_decode_buffer(sbyte*, int, sbyte*, int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int ZlibDecodeBuffer(ReadOnlySpan<sbyte> obuffer, ReadOnlySpan<sbyte> ibuffer)
{
fixed (sbyte* pobuffer = obuffer)
{
fixed (sbyte* pibuffer = ibuffer)
{
return Api.stbi_zlib_decode_buffer(
(sbyte*)pobuffer,
obuffer.Length,
(sbyte*)pibuffer,
ibuffer.Length);
}
}
}
/// <summary>
/// From: <see cref="Api.stbi_zlib_decode_noheader_malloc(sbyte*, int, int*)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static sbyte* ZlibDecodeNoheaderMalloc(ReadOnlySpan<sbyte> buffer, int* outlen)
{
fixed (sbyte* pbuffer = buffer)
{
return Api.stbi_zlib_decode_noheader_malloc(
(sbyte*)pbuffer,
buffer.Length,
outlen);
}
}
/// <summary>
/// From: <see cref="Api.stbi_zlib_decode_noheader_buffer(sbyte*, int, sbyte*, int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int ZlibDecodeNoheaderBuffer(ReadOnlySpan<sbyte> obuffer, ReadOnlySpan<sbyte> ibuffer)
{
fixed (sbyte* pobuffer = obuffer)
{
fixed (sbyte* pibuffer = ibuffer)
{
return Api.stbi_zlib_decode_noheader_buffer(
(sbyte*)pobuffer,
obuffer.Length,
(sbyte*)pibuffer,
ibuffer.Length);
}
}
}
/// <summary>
/// From: <see cref="Api.stbi_write_png(sbyte*, int, int, int, void*, int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int WritePng(sbyte* filename, int w, int h, int comp, void* data, int stride_in_bytes)
{
return Api.stbi_write_png(
filename,
w,
h,
comp,
data,
stride_in_bytes);
}
/// <summary>
/// From: <see cref="Api.stbi_write_bmp(sbyte*, int, int, int, void*)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int WriteBmp(sbyte* filename, int w, int h, int comp, void* data)
{
return Api.stbi_write_bmp(
filename,
w,
h,
comp,
data);
}
/// <summary>
/// From: <see cref="Api.stbi_write_tga(sbyte*, int, int, int, void*)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int WriteTga(sbyte* filename, int w, int h, int comp, void* data)
{
return Api.stbi_write_tga(
filename,
w,
h,
comp,
data);
}
/// <summary>
/// From: <see cref="Api.stbi_write_hdr(sbyte*, int, int, int, float*)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int WriteHdr(sbyte* filename, int w, int h, int comp, float* data)
{
return Api.stbi_write_hdr(
filename,
w,
h,
comp,
data);
}
/// <summary>
/// From: <see cref="Api.stbi_write_jpg(sbyte*, int, int, int, void*, int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int WriteJpg(sbyte* filename, int x, int y, int comp, void* data, int quality)
{
return Api.stbi_write_jpg(
filename,
x,
y,
comp,
data,
quality);
}
/// <summary>
/// From: <see cref="Api.stbi_write_png_to_func(delegate* unmanaged[Cdecl]<void*, void*, int, void>, void*, int, int, int, void*, int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int WritePngToFunc(delegate* unmanaged[Cdecl]<void*, void*, int, void> func, void* context, int w, int h, int comp, void* data, int stride_in_bytes)
{
return Api.stbi_write_png_to_func(
func,
context,
w,
h,
comp,
data,
stride_in_bytes);
}
/// <summary>
/// From: <see cref="Api.stbi_write_bmp_to_func(delegate* unmanaged[Cdecl]<void*, void*, int, void>, void*, int, int, int, void*)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int WriteBmpToFunc(delegate* unmanaged[Cdecl]<void*, void*, int, void> func, void* context, int w, int h, int comp, void* data)
{
return Api.stbi_write_bmp_to_func(
func,
context,
w,
h,
comp,
data);
}
/// <summary>
/// From: <see cref="Api.stbi_write_tga_to_func(delegate* unmanaged[Cdecl]<void*, void*, int, void>, void*, int, int, int, void*)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int WriteTgaToFunc(delegate* unmanaged[Cdecl]<void*, void*, int, void> func, void* context, int w, int h, int comp, void* data)
{
return Api.stbi_write_tga_to_func(
func,
context,
w,
h,
comp,
data);
}
/// <summary>
/// From: <see cref="Api.stbi_write_hdr_to_func(delegate* unmanaged[Cdecl]<void*, void*, int, void>, void*, int, int, int, float*)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int WriteHdrToFunc(delegate* unmanaged[Cdecl]<void*, void*, int, void> func, void* context, int w, int h, int comp, float* data)
{
return Api.stbi_write_hdr_to_func(
func,
context,
w,
h,
comp,
data);
}
/// <summary>
/// From: <see cref="Api.stbi_write_jpg_to_func(delegate* unmanaged[Cdecl]<void*, void*, int, void>, void*, int, int, int, void*, int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static int WriteJpgToFunc(delegate* unmanaged[Cdecl]<void*, void*, int, void> func, void* context, int x, int y, int comp, void* data, int quality)
{
return Api.stbi_write_jpg_to_func(
func,
context,
x,
y,
comp,
data,
quality);
}
/// <summary>
/// From: <see cref="Api.stbi_flip_vertically_on_write(int)" />
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static void FlipVerticallyOnWrite(int flip_boolean)
{
Api.stbi_flip_vertically_on_write(flip_boolean);
}
}

Binary file not shown.

View File

@@ -44,6 +44,7 @@ public sealed class DerivesFromConfig
public string ParamPrefix { get; init; } = string.Empty;
/// <summary>The suffix of the sibling parameter name to consume (e.g. "_len").</summary>
public string ParamSuffix { get; init; } = string.Empty;
public string RegexName { get; init; } = string.Empty;
/// <summary>Expression to pass in place of the consumed parameter. $arg is replaced with the source param name.</summary>
public required string Expr { get; init; }
}

View File

@@ -484,10 +484,20 @@ public sealed class WrapperGeneratorEmitter
continue;
}
var expectedSiblingName = remap.DerivesFrom.ParamPrefix + param.Name + remap.DerivesFrom.ParamSuffix;
var isExactName = !string.IsNullOrWhiteSpace(remap.DerivesFrom.RegexName);
var expectedSiblingName = isExactName
? remap.DerivesFrom.RegexName
: remap.DerivesFrom.ParamPrefix + param.Name + remap.DerivesFrom.ParamSuffix;
for (var j = i + 1; j < func.Parameters.Count; j++)
{
if (string.Equals(func.Parameters[j].Name, expectedSiblingName, StringComparison.Ordinal))
var match = isExactName
? Regex.IsMatch(func.Parameters[j].Name, expectedSiblingName)
: string.Equals(func.Parameters[j].Name, expectedSiblingName, StringComparison.Ordinal);
if (match)
{
consumedByDerivesFrom.Add(j);
derivedExprs[j] = remap.DerivesFrom.Expr.Replace("$arg", param.Name, StringComparison.Ordinal);

View File

@@ -2,7 +2,7 @@
"profiles": {
"Ghost.NativeWrapperGen": {
"commandName": "Project",
"commandLineArgs": "--config \"F:/csharp/GhostEngine/src/Tools/Ghost.NativeWrapperGen/configs/ufbx.json\" --input \"F:/csharp/GhostEngine/src/ThridParty/Ghost.Ufbx/Generated\" --output \"F:/csharp/GhostEngine/src/ThridParty/Ghost.Ufbx/Wrapper\""
"commandLineArgs": "--config \"F:/csharp/GhostEngine/src/Tools/Ghost.NativeWrapperGen/configs/stbi.json\" --input \"F:/csharp/GhostEngine/src/ThridParty/Ghost.StbI/Generated\" --output \"F:/csharp/GhostEngine/src/ThridParty/Ghost.StbI/Wrapper\""
}
}
}

View File

@@ -0,0 +1,48 @@
{
"libraryName": "stbi",
"nativeNamespace": "Ghost.StbImage",
"outputNamespace": "Ghost.StbImage",
"nativeTypePrefix": "stbi_",
"skipTypes": [
"NativeAnnotationAttribute",
"NativeTypeNameAttribute"
],
"skipFunctions": [],
"remaps": [
{
"src": "$TYPE*",
"dst": "ReadOnlySpan<$TYPE>",
"scope": [ "parameter" ],
"filter": [ ".*buffer" ],
"derivesFrom": {
"regexName": ".*len",
"expr": "$arg.Length"
},
"adapter": {
"convertBack": {
"wrapCall": "fixed ($TYPE* p$arg = $arg) { $CALL }",
"passAs": "($TYPE*)p$arg"
}
}
}
],
"actions": [
{
"filter": "EXTERN_API",
"targetType": "StbIApi",
"apply": {
"type": "STATIC_METHOD",
"opts": {
"name": {
"remove": [
"PREFIX"
],
"style": "PascalCase"
}
}
}
}
]
}

6062
src/ufbx_temp.h Normal file

File diff suppressed because it is too large Load Diff