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) private static PipelineState MeragePipeline(PipelineSemantic? semantic, PipelineState parent)
{ {
@@ -141,21 +141,27 @@ internal static class DSLShaderCompiler
var descriptor = new GraphicsShaderDescriptor var descriptor = new GraphicsShaderDescriptor
{ {
name = semantics.name, Name = semantics.name,
propertyBufferSize = propertyInfo.size, PropertyBufferSize = propertyInfo.size,
shaderModel = semantics.shaderModel, ShaderModel = semantics.shaderModel,
passes = passes 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; 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) public static Result<GraphicsShaderDescriptor> CompileGraphicsShader(string shaderPath)
{ {
try try
@@ -163,7 +169,8 @@ internal static class DSLShaderCompiler
var source = File.ReadAllText(shaderPath); var source = File.ReadAllText(shaderPath);
// Use ANTLR4 parser // 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) 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) public static Result<ComputeShaderDescriptor> CompileComputeShader(string shaderPath)
{ {
try try
{ {
var source = File.ReadAllText(shaderPath); 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) if (parseErrors.Count != 0)
{ {
@@ -281,12 +295,12 @@ internal static class DSLShaderCompiler
return new ComputeShaderDescriptor return new ComputeShaderDescriptor
{ {
name = semantics.name, Name = semantics.name,
propertyBufferSize = propertyInfo.size, PropertyBufferSize = propertyInfo.size,
shaderModel = semantics.shaderModel, ShaderModel = semantics.shaderModel,
shaderCodes = shaderCodes, ShaderCodes = shaderCodes,
defines = semantics.defines?.ToArray() ?? Array.Empty<string>(), Defines = semantics.defines?.ToArray() ?? Array.Empty<string>(),
keywords = semantics.keywords?.ToArray() ?? Array.Empty<KeywordsGroup>() Keywords = semantics.keywords?.ToArray() ?? Array.Empty<KeywordsGroup>()
}; };
} }
} }

View File

@@ -7,10 +7,8 @@ namespace Ghost.DSL.ShaderParser;
public class AntlrShaderCompiler 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 try
{ {
var inputStream = new AntlrInputStream(source); 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>(); errors = new List<DSLShaderError>();

View File

@@ -6,7 +6,7 @@ namespace Ghost.Editor.Core.AssetHandler;
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public sealed class CustomAssetHandlerAttribute : Attribute 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 public interface IAssetHandler
{ {
bool CanExport => false;
AssetType RuntimeAssetType { get; } AssetType RuntimeAssetType { get; }
Guid EditorAssetTypeID { get; } Guid EditorAssetTypeID { get; }
IAssetSettings? CreateDefaultSettings(); IAssetSettings? CreateDefaultSettings();
ValueTask<Result<IAsset>> LoadAssetAsync(Stream assetStream, Guid id, IAssetSettings? settings, CancellationToken token = default); ValueTask<Result<IAsset>> LoadAssetAsync(FileStream assetStream, Guid id, IAssetSettings? settings, CancellationToken token = default);
ValueTask<Result> SaveAssetAsync(Stream targetStream, IAsset asset, CancellationToken token = default); ValueTask<Result> SaveAssetAsync(FileStream 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);
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; 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 public static class AssetHandlerRegistry
{ {
private static readonly Dictionary<string, IAssetHandler> s_byExtension; private static readonly Dictionary<string, IAssetHandler> s_byExtension;
@@ -21,10 +17,10 @@ public static class AssetHandlerRegistry
s_versionByTypeId = new Dictionary<Guid, int>(); 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_byTypeId[assetTypeId] = handler;
s_versionByTypeId[typeId] = version; s_versionByTypeId[assetTypeId] = version;
foreach (var ext in extensions) foreach (var ext in extensions)
{ {
@@ -46,13 +42,13 @@ public static class AssetHandlerRegistry
return handler; return handler;
} }
public static IAssetHandler? GetByTypeId(Guid typeId) public static IAssetHandler? GetByAssetTypeId(Guid typeId)
{ {
s_byTypeId.TryGetValue(typeId, out var handler); s_byTypeId.TryGetValue(typeId, out var handler);
return handler; return handler;
} }
public static int GetVersionByTypeId(Guid typeId) public static int GetVersionByAssetTypeId(Guid typeId)
{ {
s_versionByTypeId.TryGetValue(typeId, out var version); s_versionByTypeId.TryGetValue(typeId, out var version);
return version; return version;
@@ -63,7 +59,7 @@ public static class AssetHandlerRegistry
return s_byExtension.Keys; return s_byExtension.Keys;
} }
public static AssetType GetAssetTypeByExtension(string extension) public static AssetType GetRuntimeAssetTypeByExtension(string extension)
{ {
if (string.IsNullOrEmpty(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.Core;
using Ghost.Engine; using Ghost.Engine;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using ImageMagick; using Ghost.StbI;
using Misaki.HighPerformance.LowLevel; using Misaki.HighPerformance.LowLevel;
using System.IO.MemoryMappedFiles;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using TerraFX.Interop.Windows;
namespace Ghost.Editor.Core.AssetHandler; namespace Ghost.Editor.Core.AssetHandler;
@@ -48,7 +51,7 @@ public enum MipmapFilter : uint
} }
[Guid(GUID)] [Guid(GUID)]
public class TextureAsset : IAsset public unsafe class TextureAsset : IAsset
{ {
public const string GUID = "27965FFF-860C-40EF-9123-1874D7DE9CDC"; public const string GUID = "27965FFF-860C-40EF-9123-1874D7DE9CDC";
@@ -57,7 +60,7 @@ public class TextureAsset : IAsset
private readonly Guid _id; private readonly Guid _id;
private readonly IAssetSettings _settings; private readonly IAssetSettings _settings;
private readonly MagickImage _textureData; private readonly IntPtr _textureData;
private readonly uint _width; private readonly uint _width;
private readonly uint _height; private readonly uint _height;
private readonly uint _depth; private readonly uint _depth;
@@ -65,17 +68,17 @@ public class TextureAsset : IAsset
private readonly uint _dimension; private readonly uint _dimension;
public Guid ID => _id; public Guid ID => _id;
public Guid TypeID => s_typeID; public Guid TypeID => typeof(TextureAsset).GUID;
public IAssetSettings Settings => _settings; public IAssetSettings Settings => _settings;
public MagickImage TextureData => _textureData; public IntPtr TextureData => _textureData;
public uint Width => _width; public uint Width => _width;
public uint Height => _height; public uint Height => _height;
public uint Depth => _depth; public uint Depth => _depth;
public uint Dimension => _dimension; public uint Dimension => _dimension;
public uint ColorComponents => _colorComponents; 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; _id = id;
_settings = settings; _settings = settings;
@@ -95,7 +98,7 @@ public class TextureAsset : IAsset
public void Dispose() public void Dispose()
{ {
_textureData.Dispose(); StbIApi.ImageFree((void*)_textureData);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
} }
@@ -253,8 +256,19 @@ public class TextureAssetSettings : IAssetSettings
} }
[CustomAssetHandler(TextureAsset.GUID, [".png", ".jpg", ".jpeg", ".tga", ".bmp", ".hdr"], 1)] [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 AssetType RuntimeAssetType => AssetType.Texture;
public Guid EditorAssetTypeID => typeof(TextureAsset).GUID; public Guid EditorAssetTypeID => typeof(TextureAsset).GUID;
@@ -291,23 +305,86 @@ internal class TextureAssetHandler : IAssetHandler
return TextureDimension.Texture2D; 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 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 textureSettings = settings as TextureAssetSettings ?? new TextureAssetSettings();
var contentHeader = new TextureContentHeader var contentHeader = new TextureContentHeader
{ {
width = image.Width, width = (uint)info.width,
height = image.Height, height = (uint)info.height,
depth = image.Depth, depth = (uint)info.depth,
colorComponents = image.ChannelCount, colorComponents = (uint)info.colorComponents,
dimension = (uint)GetTextureDimension(textureSettings) 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) 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) if (asset is not TextureAsset textureAsset)
{ {
return Result.Failure("Asset type is not TextureAsset"); return Result.Failure("Asset type is not TextureAsset");
} }
return await Task.Run(() =>
{
var gcHandle = GCHandle.Alloc(targetStream, GCHandleType.Normal);
try 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(); return Result.Success();
} }
catch (Exception ex) catch (Exception ex)
{ {
return Result.Failure(ex.Message); 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 try
{ {
using var image = new MagickImage(sourceStream); var textureSettings = settings as TextureAssetSettings ?? new TextureAssetSettings();
var pixels = image.GetPixelsUnsafe().GetAreaPointer(0, 0, image.Width, image.Height);
if (pixels == 0) 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 info = infoResult.Value;
var (path, mip) = await TextureProcessor.CompressToCacheAsync(EditorApplication.CacheFolderPath, id, pixels, image.Width, image.Height, image.Depth, textureSettings, token) var result = await TextureProcessor.CompressToCacheAsync(EditorApplication.CacheFolderPath, id,
info,
textureSettings, token)
.ConfigureAwait(false); .ConfigureAwait(false);
if (result.IsFailure)
{
return result;
}
var (cachePath, mip) = result.Value;
targetStream.Seek(0, SeekOrigin.Begin); targetStream.Seek(0, SeekOrigin.Begin);
var contentHeader = new TextureContentHeader var header = new TextureContentHeader
{ {
width = image.Width, width = (uint)info.width,
height = image.Height, height = (uint)info.height,
depth = image.Depth, depth = (uint)info.depth,
colorComponents = image.ChannelCount, colorComponents = (uint)info.colorComponents,
mipLevels = (uint)mip, mipLevels = (uint)mip,
dimension = (uint)GetTextureDimension(textureSettings) 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 ddsStream.CopyToAsync(targetStream, token).ConfigureAwait(false);
await targetStream.FlushAsync(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.")); 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 Ghost.Nvtt;
using Misaki.HighPerformance.LowLevel; using Misaki.HighPerformance.LowLevel;
using System.IO.Hashing; using System.IO.Hashing;
@@ -24,27 +25,19 @@ internal static class TextureProcessor
{ {
private readonly string _outputPath; private readonly string _outputPath;
private readonly nint _image; private readonly TextureAssetHandler.TextureInfo _textureInfo;
private readonly uint _depth;
private readonly uint _width;
private readonly uint _height;
private readonly TextureAssetSettings _settings; 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, TextureAssetHandler.TextureInfo textureInfo, TextureAssetSettings settings)
public NvttPipelineTask(string outputPath, nint image, uint width, uint height, uint depth, TextureAssetSettings settings)
{ {
_outputPath = outputPath; _outputPath = outputPath;
_image = image; _textureInfo = textureInfo;
_width = width;
_height = height;
_depth = depth;
_settings = settings; _settings = settings;
_completionSource = new TaskCompletionSource(); _completionSource = new TaskCompletionSource<Result<int>>();
} }
public unsafe void Execute() public unsafe void Execute()
@@ -54,15 +47,22 @@ internal static class TextureProcessor
using var pOutOpts = new DisposablePtr<NvttOutputOptions>(NvttOutputOptions.Create()); using var pOutOpts = new DisposablePtr<NvttOutputOptions>(NvttOutputOptions.Create());
using var pCtx = new DisposablePtr<NvttContext>(NvttContext.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_RGBA_32F
: NvttInputFormat.NVTT_InputFormat_BGRA_8UB; // we'll swizzle RB below : 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, // 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). // 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); pSurface.Get()->Swizzle(2, 1, 0, 3, null);
} }
@@ -101,7 +101,7 @@ internal static class TextureProcessor
pSurface.Get()->PremultiplyAlpha(null); pSurface.Get()->PremultiplyAlpha(null);
} }
pCompOpts.Get()->SetFormat(SelectFormat(_settings)); pCompOpts.Get()->SetFormat(SelectFormat(_settings, _textureInfo.isHDR));
pCompOpts.Get()->SetQuality(SelectQuality(_settings.Advanced.CompressionLevel)); pCompOpts.Get()->SetQuality(SelectQuality(_settings.Advanced.CompressionLevel));
if (_settings.Advanced.CutoutAlpha) if (_settings.Advanced.CutoutAlpha)
@@ -117,6 +117,7 @@ internal static class TextureProcessor
var nvttFilter = SelectMipmapFilter(_settings.Advanced.MipmapFilter); var nvttFilter = SelectMipmapFilter(_settings.Advanced.MipmapFilter);
int mipmapCount;
if (!_settings.Advanced.GenerateMipmaps) if (!_settings.Advanced.GenerateMipmaps)
{ {
mipmapCount = 1; 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 settingsHash = ComputeSettingsHash(settings);
var cacheFileName = $"texturecache_{assetId:N}_{settingsHash:X16}.dds"; var cacheFileName = $"texturecache_{assetId:N}_{settingsHash:X16}.dds";
@@ -202,15 +205,21 @@ internal static class TextureProcessor
} }
ScheduleWork: ScheduleWork:
var workItem = new NvttPipelineTask(cachePath, image, width, height, depth, settings); var workItem = new NvttPipelineTask(cachePath, textureInfo, settings);
ThreadPool.UnsafeQueueUserWorkItem(workItem, true); ThreadPool.UnsafeQueueUserWorkItem(workItem, true);
await workItem.Task.WaitAsync(cancellationToken).ConfigureAwait(false); var result = await workItem.Task.WaitAsync(cancellationToken).ConfigureAwait(false);
if (result.IsFailure)
return (cachePath, workItem.mipmapCount); {
return Result.Failure(result.Message);
} }
private static NvttFormat SelectFormat(TextureAssetSettings settings) return (cachePath, result.Value);
=> settings.Basic.TextureType switch }
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.Normal => NvttFormat.NVTT_Format_BC5, // RG normal map
TextureType.SingleChannel => NvttFormat.NVTT_Format_BC4, // single channel TextureType.SingleChannel => NvttFormat.NVTT_Format_BC4, // single channel

View File

@@ -1,5 +1,6 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Editor.Core.AssetHandler; using Ghost.Editor.Core.AssetHandler;
using Ghost.Editor.Core.Services;
using Ghost.Engine.AssetLoader; using Ghost.Engine.AssetLoader;
namespace Ghost.Editor.Core.Contracts; namespace Ghost.Editor.Core.Contracts;
@@ -40,12 +41,14 @@ public sealed class AssetChangedEventArgs : EventArgs
public interface IAssetRegistry : IDisposable public interface IAssetRegistry : IDisposable
{ {
event EventHandler<AssetChangedEventArgs>? OnAssetChanged;
event EventHandler<Guid>? OnAssetImported;
AssetCatalog GetAssetCatalog();
string? GetAssetPath(Guid id); string? GetAssetPath(Guid id);
Guid GetAssetGuid(string assetPath); Guid GetAssetGuid(string assetPath);
event EventHandler<AssetChangedEventArgs>? OnAssetChanged;
ValueTask<Result<Guid>> ImportAssetAsync(string sourceFilePath, string targetAssetPath, CancellationToken token = default); ValueTask<Result<Guid>> ImportAssetAsync(string sourceFilePath, string targetAssetPath, CancellationToken token = default);
ValueTask<Result> ReimportAssetAsync(Guid assetId, string sourceFilePath, CancellationToken token = default); ValueTask<Result> ReimportAssetAsync(Guid assetId, string sourceFilePath, CancellationToken token = default);
ValueTask<Result<IAsset>> LoadAssetAsync(Guid id, 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) internal static void Initialize(IServiceProvider serviceProvider, string projectPath, string projectName)
{ {
Environment.CurrentDirectory = projectPath;
s_serviceProvider = serviceProvider; s_serviceProvider = serviceProvider;
s_currentProjectPath = projectPath; s_currentProjectPath = projectPath;
s_currentProjectName = projectName; s_currentProjectName = projectName;

View File

@@ -15,7 +15,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentIcons.WinUI" Version="2.1.324" /> <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.Data.Sqlite" Version="10.0.6" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.28000.1721" /> <PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.28000.1721" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.260317003" /> <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="..\..\Runtime\Ghost.Generator\Ghost.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\..\ThridParty\Ghost.DXC\Ghost.DXC.csproj" /> <ProjectReference Include="..\..\ThridParty\Ghost.DXC\Ghost.DXC.csproj" />
<ProjectReference Include="..\..\ThridParty\Ghost.Nvtt\Ghost.Nvtt.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>
<ItemGroup> <ItemGroup>

View File

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

View File

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

View File

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

View File

@@ -24,7 +24,7 @@ internal readonly record struct ImportJob(
ImportReason Reason 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_NAME = "Imported";
public const string IMPORTED_EXTENSION = ".imported"; public const string IMPORTED_EXTENSION = ".imported";
@@ -34,9 +34,7 @@ internal sealed class ImportCoordinator : IDisposable
private readonly CancellationTokenSource _cts; private readonly CancellationTokenSource _cts;
private readonly Task[] _workers; private readonly Task[] _workers;
// In a real implementation, this event would be used to notify the UI/Rest of engine public event EventHandler<Guid>? OnImportCompleted;
// For now we just focus on the core logic
// public event EventHandler<AssetChangedEventArgs>? OnAssetChanged;
public ImportCoordinator(AssetCatalog catalog, int workerCount = 2) 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); var meta = await AssetMetaIO.ReadAsync(job.MetaPath, token);
if (meta is null) if (meta is null)
{ {
@@ -87,27 +95,27 @@ internal sealed class ImportCoordinator : IDisposable
} }
var handler = meta.HandlerTypeId.HasValue var handler = meta.HandlerTypeId.HasValue
? AssetHandlerRegistry.GetByTypeId(meta.HandlerTypeId.Value) ? AssetHandlerRegistry.GetByAssetTypeId(meta.HandlerTypeId.Value)
: AssetHandlerRegistry.GetByExtension(Path.GetExtension(job.SourcePath)); : AssetHandlerRegistry.GetByExtension(Path.GetExtension(job.SourcePath));
var contentHash = await ComputeFileHashAsync(fullSourcePath, token); var contentHash = await ComputeFileHashAsync(job.SourcePath, token);
var settingsHash = ComputeSettingsHash(meta.Settings); var settingsHash = ComputeSettingsHash(meta.Settings);
// Check if we can skip (if not a manual reimport) // Check if we can skip (if not a manual reimport)
if (job.Reason != ImportReason.ManualReimport && if (job.Reason != ImportReason.ManualReimport &&
meta.ContentHash == contentHash && meta.ContentHash == contentHash &&
meta.SettingsHash == settingsHash && meta.SettingsHash == settingsHash &&
meta.HandlerVersion == AssetHandlerRegistry.GetVersionByTypeId(meta.HandlerTypeId ?? Guid.Empty)) meta.HandlerVersion == AssetHandlerRegistry.GetVersionByAssetTypeId(meta.HandlerTypeId ?? Guid.Empty))
{ {
return; return;
} }
var importResult = Result.Success(); 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); await using var targetStream = new FileStream(targetPath, FileMode.Create, FileAccess.Write, FileShare.None);
importResult = await importable.ImportAsync(sourceStream, targetStream, job.AssetGuid, meta.Settings, token); importResult = await importable.ImportAsync(sourceStream, targetStream, job.AssetGuid, meta.Settings, token);
@@ -117,7 +125,7 @@ internal sealed class ImportCoordinator : IDisposable
{ {
meta.ContentHash = contentHash; meta.ContentHash = contentHash;
meta.SettingsHash = settingsHash; meta.SettingsHash = settingsHash;
meta.HandlerVersion = AssetHandlerRegistry.GetVersionByTypeId(meta.HandlerTypeId ?? Guid.Empty); meta.HandlerVersion = AssetHandlerRegistry.GetVersionByAssetTypeId(meta.HandlerTypeId ?? Guid.Empty);
meta.LastImportedUtc = DateTime.UtcNow; meta.LastImportedUtc = DateTime.UtcNow;
await AssetMetaIO.WriteAsync(job.MetaPath, meta, token); 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) 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 var config = new ShaderCompilationConfig
{ {
@@ -141,11 +141,11 @@ internal static class ShaderCompilerUtility
stage = ShaderStage.ComputeShader, stage = ShaderStage.ComputeShader,
}; };
var compiled = new UnsafeArray<UnsafeArray<byte>>(descriptor.shaderCodes.Length, allocationHandle); var compiled = new UnsafeArray<UnsafeArray<byte>>(descriptor.ShaderCodes.Length, allocationHandle);
for (int i = 0; i < descriptor.shaderCodes.Length; i++) for (int i = 0; i < descriptor.ShaderCodes.Length; i++)
{ {
config.shaderCode = descriptor.shaderCodes[i].code; config.shaderCode = descriptor.ShaderCodes[i].code;
config.entryPoint = descriptor.shaderCodes[i].entryPoint; config.entryPoint = descriptor.ShaderCodes[i].entryPoint;
var result = shaderCompiler.Compile(ref config, allocationHandle); var result = shaderCompiler.Compile(ref config, allocationHandle);
if (result.IsFailure) if (result.IsFailure)

View File

@@ -1,3 +1,4 @@
using Ghost.Editor.Core.Contracts;
using Ghost.Editor.Core.Utilities; using Ghost.Editor.Core.Utilities;
using Ghost.Editor.Models; using Ghost.Editor.Models;
using Ghost.Engine; using Ghost.Engine;
@@ -65,7 +66,13 @@ internal static class ActivationHandler
AllocationManager.Initialize(opts); 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; return ValueTask.CompletedTask;
} }

View File

@@ -50,10 +50,102 @@
</Style> </Style>
<!-- Named Style --> <!-- Named Style -->
<Style <Style x:Key="ToolbarButton" TargetType="Button">
x:Key="ToolbarButton" <Setter Property="Padding" Value="2" />
BasedOn="{StaticResource SubtleButtonStyle}" <Setter Property="Background" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
TargetType="Button" /> <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"> <Style x:Key="VerticalDivider" TargetType="Border">
<Setter Property="BorderBrush" Value="{ThemeResource DividerStrokeColorDefaultBrush}" /> <Setter Property="BorderBrush" Value="{ThemeResource DividerStrokeColorDefaultBrush}" />

View File

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

View File

@@ -33,8 +33,6 @@ internal sealed partial class ContentBrowser : UserControl
Loaded += ProjectBrowser_Loaded; Loaded += ProjectBrowser_Loaded;
Unloaded += ProjectBrowser_Unloaded; Unloaded += ProjectBrowser_Unloaded;
GettingFocus += ProjectBrowser_GettingFocus;
} }
private void ProjectBrowser_GettingFocus(UIElement sender, GettingFocusEventArgs args) 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) private void ProjectBrowser_Loaded(object sender, RoutedEventArgs e)
{ {
_inspectorService.OnSelectionChanged += _inspectorService_OnSelectionChanged; _inspectorService.OnSelectionChanged += _inspectorService_OnSelectionChanged;
GettingFocus += ProjectBrowser_GettingFocus;
} }
private void ProjectBrowser_Unloaded(object sender, RoutedEventArgs e) private void ProjectBrowser_Unloaded(object sender, RoutedEventArgs e)
{ {
_inspectorService.OnSelectionChanged -= _inspectorService_OnSelectionChanged; _inspectorService.OnSelectionChanged -= _inspectorService_OnSelectionChanged;
GettingFocus -= ProjectBrowser_GettingFocus;
if (LastFocused == this) if (LastFocused == this)
{ {

View File

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

View File

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

View File

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

View File

@@ -21,6 +21,7 @@
<Project Path="ThridParty/Ghost.FMOD/Ghost.FMOD.csproj" /> <Project Path="ThridParty/Ghost.FMOD/Ghost.FMOD.csproj" />
<Project Path="ThridParty/Ghost.MeshOptimizer/Ghost.MeshOptimizer.csproj" /> <Project Path="ThridParty/Ghost.MeshOptimizer/Ghost.MeshOptimizer.csproj" />
<Project Path="ThridParty/Ghost.Nvtt/Ghost.Nvtt.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" /> <Project Path="ThridParty/Ghost.Ufbx/Ghost.Ufbx.csproj" Id="c4bd647c-6d77-49d8-ba81-6ed4946474d1" />
</Folder> </Folder>
<Folder Name="/Runtime/"> <Folder Name="/Runtime/">

View File

@@ -60,18 +60,56 @@ public struct PassDescriptor
public class GraphicsShaderDescriptor public class GraphicsShaderDescriptor
{ {
public required string name = string.Empty; public required string Name
public required uint propertyBufferSize; {
public required ShaderModel shaderModel; get; init;
public required PassDescriptor[] passes = Array.Empty<PassDescriptor>(); }
public required uint PropertyBufferSize
{
get; init;
}
public required ShaderModel ShaderModel
{
get; init;
}
public required PassDescriptor[] Passes
{
get; init;
}
} }
public class ComputeShaderDescriptor public class ComputeShaderDescriptor
{ {
public required string name = string.Empty; public required string Name
public required uint propertyBufferSize; {
public required ShaderModel shaderModel; get; init;
public required ShaderCode[] shaderCodes; }
public required string[] defines;
public required KeywordsGroup[] keywords; 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)) 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; return entry;
} }
public void InvalidateAsset(Guid guid) public void ReimportAsset(Guid guid)
{ {
if (!_entries.TryGetValue(guid, out var entry)) if (!_entries.TryGetValue(guid, out var entry))
{ {

View File

@@ -4,6 +4,12 @@ using Misaki.HighPerformance.Jobs;
namespace Ghost.Engine; namespace Ghost.Engine;
public interface IRuntimeInitializeCallback
{
void Initialize();
void Shutdown();
}
public sealed partial class EngineCore : IDisposable public sealed partial class EngineCore : IDisposable
{ {
private readonly IContentProvider _contentProvider; private readonly IContentProvider _contentProvider;
@@ -13,6 +19,10 @@ public sealed partial class EngineCore : IDisposable
private readonly RenderSystem _renderSystem; private readonly RenderSystem _renderSystem;
private readonly AssetManager _assetManager; private readonly AssetManager _assetManager;
internal JobScheduler JobScheduler => _jobScheduler;
internal RenderSystem RenderSystem => _renderSystem;
internal AssetManager AssetManager => _assetManager;
public EngineCore(IContentProvider contentProvider) public EngineCore(IContentProvider contentProvider)
{ {
_contentProvider = 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) 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 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) if (hr.SUCCEEDED)
{ {
pResource = pAllocation->GetResource(); pResource = pAllocation->GetResource();
@@ -249,11 +254,16 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
if (isSubAllocation) 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 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) if (hr.SUCCEEDED)
{ {
pResource = pAllocation->GetResource(); pResource = pAllocation->GetResource();

View File

@@ -75,15 +75,15 @@ public partial struct Shader : IResourceReleasable
internal Shader(GraphicsShaderDescriptor descriptor) internal Shader(GraphicsShaderDescriptor descriptor)
{ {
_nameHash = RHIUtility.GetShaderID(descriptor.name); _nameHash = RHIUtility.GetShaderID(descriptor.Name);
_propertyBufferSize = descriptor.propertyBufferSize; _propertyBufferSize = descriptor.PropertyBufferSize;
_shaderPasses = new UnsafeArray<ShaderPass>(descriptor.passes.Length, AllocationHandle.Persistent); _shaderPasses = new UnsafeArray<ShaderPass>(descriptor.Passes.Length, AllocationHandle.Persistent);
_passIDToLocal = new UnsafeHashMap<int, int>(descriptor.passes.Length, AllocationHandle.Persistent); _passIDToLocal = new UnsafeHashMap<int, int>(descriptor.Passes.Length, AllocationHandle.Persistent);
_keywordIDToLocal = new UnsafeHashMap<int, int>(32, 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(); var keywords = new LocalKeywordSet();
@@ -189,7 +189,7 @@ public partial struct Shader : IResourceReleasable
public unsafe partial struct ComputeShader : IResourceReleasable public unsafe partial struct ComputeShader : IResourceReleasable
{ {
private readonly ulong _nameHash; 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 readonly uint _propertyBufferSize;
private LocalKeywordSet _localKeywordSet; private LocalKeywordSet _localKeywordSet;
@@ -200,20 +200,20 @@ public unsafe partial struct ComputeShader : IResourceReleasable
internal ComputeShader(ComputeShaderDescriptor descriptor) internal ComputeShader(ComputeShaderDescriptor descriptor)
{ {
_nameHash = RHIUtility.GetShaderID(descriptor.name); _nameHash = RHIUtility.GetShaderID(descriptor.Name);
_propertyBufferSize = descriptor.propertyBufferSize; _propertyBufferSize = descriptor.PropertyBufferSize;
_keywordIDToLocal = new UnsafeHashMap<int, int>(32, AllocationHandle.Persistent); _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; 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) if (group.keywords == null)
{ {
continue; continue;
@@ -236,7 +236,7 @@ public unsafe partial struct ComputeShader : IResourceReleasable
public ulong GetEntryID(int entryIndex) public ulong GetEntryID(int entryIndex)
{ {
Logger.DebugAssert(entryIndex >= 0 && entryIndex < 8, "Entry index out of bounds."); Logger.DebugAssert(entryIndex >= 0 && entryIndex < 8, "Entry index out of bounds.");
return entryHashes[entryIndex]; return _entryHashes[entryIndex];
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

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

View File

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

View File

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

View File

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

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; using System.Runtime.InteropServices;
namespace Ghost.Nvtt namespace Ghost.Nvtt
{ {
public static unsafe partial class Api 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)] [DllImport("nvtt", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern NvttBoolean nvttIsCudaSupported(); public static extern NvttBoolean nvttIsCudaSupported();

View File

@@ -15,6 +15,9 @@ namespace Ghost.Nvtt
private readonly int _value; private readonly int _value;
public readonly bool IsTrue => _value != 0;
public readonly bool IsFalse => _value == 0;
public NvttBoolean(int value) public NvttBoolean(int value)
{ {
_value = 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; public string ParamPrefix { get; init; } = string.Empty;
/// <summary>The suffix of the sibling parameter name to consume (e.g. "_len").</summary> /// <summary>The suffix of the sibling parameter name to consume (e.g. "_len").</summary>
public string ParamSuffix { get; init; } = string.Empty; 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> /// <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; } public required string Expr { get; init; }
} }

View File

@@ -484,10 +484,20 @@ public sealed class WrapperGeneratorEmitter
continue; 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++) 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); consumedByDerivesFrom.Add(j);
derivedExprs[j] = remap.DerivesFrom.Expr.Replace("$arg", param.Name, StringComparison.Ordinal); derivedExprs[j] = remap.DerivesFrom.Expr.Replace("$arg", param.Name, StringComparison.Ordinal);

View File

@@ -2,7 +2,7 @@
"profiles": { "profiles": {
"Ghost.NativeWrapperGen": { "Ghost.NativeWrapperGen": {
"commandName": "Project", "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