feat(wrapper): span-based interop, resource API refactor
Refactored native wrappers to use ReadOnlySpan<T> for pointer parameters, improving .NET safety and interop. Enhanced wrapper generator with $TYPE and prefix/suffix-based parameter remapping. Added platform-specific native library loading for meshoptimizer, nvtt, and ufbx. Updated D3D12GraphicsEngineFactory for native DLL resolution and removed redundant logic from UnitTestApp. Changed RenderGraphBuilder's resource extraction API to use QueryTextureExtraction/QueryBufferExtraction with explicit handles and flags. Removed IRenderer and D3D12Renderer, moving RenderContext to RenderPipeline. Improved mesh loading, resource management, and updated test shader conventions. Updated project references, build settings, and added launchSettings.json for tooling. BREAKING CHANGE: Native wrapper APIs now use ReadOnlySpan<T> instead of pointers. RenderGraphBuilder resource extraction API has changed. IRenderer and D3D12Renderer have been removed.
This commit is contained in:
@@ -40,8 +40,10 @@ public sealed class RemapConfig
|
||||
/// </summary>
|
||||
public sealed class DerivesFromConfig
|
||||
{
|
||||
/// <summary>The prefix of the sibling parameter name to consume (e.g. "name_").</summary>
|
||||
public string ParamPrefix { get; init; } = string.Empty;
|
||||
/// <summary>The suffix of the sibling parameter name to consume (e.g. "_len").</summary>
|
||||
public required string ParamSuffix { get; init; }
|
||||
public string ParamSuffix { get; init; } = string.Empty;
|
||||
/// <summary>Expression to pass in place of the consumed parameter. $arg is replaced with the source param name.</summary>
|
||||
public required string Expr { get; init; }
|
||||
}
|
||||
|
||||
@@ -140,18 +140,36 @@ public sealed class WrapperGeneratorEmitter
|
||||
{
|
||||
foreach (var condition in conditions)
|
||||
{
|
||||
// Handle parameterised conditions before the switch.
|
||||
if (condition.StartsWith("NAME_CONDITION(", StringComparison.Ordinal) &&
|
||||
condition.EndsWith(')'))
|
||||
var inverseCondition = false;
|
||||
|
||||
var conditionSpan = condition.AsSpan();
|
||||
if (conditionSpan[0] == '!')
|
||||
{
|
||||
var pattern = condition["NAME_CONDITION(".Length..^1];
|
||||
inverseCondition = true;
|
||||
}
|
||||
|
||||
var remainingCondition = conditionSpan[(inverseCondition ? 1 : 0)..].Trim();
|
||||
|
||||
// Handle parameterised conditions before the switch.
|
||||
if (remainingCondition.StartsWith("NAME_CONDITION(", StringComparison.Ordinal) &&
|
||||
remainingCondition.EndsWith(')'))
|
||||
{
|
||||
var pattern = remainingCondition["NAME_CONDITION(".Length..^1];
|
||||
|
||||
// $TSelf → full struct name (e.g. "NvttSurface")
|
||||
// $TBare → struct name with the library prefix stripped (e.g. "Surface")
|
||||
var bareName = StripPrefix(targetStructName, config.NativeTypePrefix);
|
||||
var resolvedPattern = pattern
|
||||
var resolvedPattern = pattern.ToString()
|
||||
.Replace("$TBare", bareName, StringComparison.Ordinal)
|
||||
.Replace("$TSelf", targetStructName, StringComparison.Ordinal);
|
||||
if (!Regex.IsMatch(func.Name, resolvedPattern, RegexOptions.IgnoreCase))
|
||||
|
||||
var match = Regex.IsMatch(func.Name, resolvedPattern, RegexOptions.IgnoreCase);
|
||||
if (inverseCondition)
|
||||
{
|
||||
match = !match;
|
||||
}
|
||||
|
||||
if (!match)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -159,13 +177,13 @@ public sealed class WrapperGeneratorEmitter
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (condition)
|
||||
switch (remainingCondition)
|
||||
{
|
||||
case "SELF_PTR":
|
||||
if (func.Parameters.Count == 0 ||
|
||||
resolver.TryGetBindingStructName(func.Parameters[0].TypeName) is null)
|
||||
{
|
||||
return false;
|
||||
return inverseCondition;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -174,7 +192,7 @@ public sealed class WrapperGeneratorEmitter
|
||||
if (func.Parameters.Count > 0 &&
|
||||
resolver.TryGetBindingStructName(func.Parameters[0].TypeName) is not null)
|
||||
{
|
||||
return false;
|
||||
return inverseCondition;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -182,7 +200,7 @@ public sealed class WrapperGeneratorEmitter
|
||||
case "RETURN_BINDING_TYPE":
|
||||
if (resolver.TryGetBindingStructName(func.ReturnType) is null)
|
||||
{
|
||||
return false;
|
||||
return inverseCondition;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -190,7 +208,7 @@ public sealed class WrapperGeneratorEmitter
|
||||
case "VOID_RETURN":
|
||||
if (!string.Equals(func.ReturnType, "void", StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
return inverseCondition;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -466,7 +484,7 @@ public sealed class WrapperGeneratorEmitter
|
||||
continue;
|
||||
}
|
||||
|
||||
var expectedSiblingName = param.Name + remap.DerivesFrom.ParamSuffix;
|
||||
var expectedSiblingName = remap.DerivesFrom.ParamPrefix + param.Name + remap.DerivesFrom.ParamSuffix;
|
||||
for (var j = i + 1; j < func.Parameters.Count; j++)
|
||||
{
|
||||
if (string.Equals(func.Parameters[j].Name, expectedSiblingName, StringComparison.Ordinal))
|
||||
@@ -522,17 +540,17 @@ public sealed class WrapperGeneratorEmitter
|
||||
var convertBack = matchedRemap.Adapter.ConvertBack;
|
||||
var publicParam = new PublicParam
|
||||
{
|
||||
PublicType = matchedRemap.Dst,
|
||||
PublicType = matchedRemap.Dst.Replace("$TYPE", param.RawTypeName),
|
||||
PublicName = param.Name,
|
||||
WrapCall = convertBack.WrapCall,
|
||||
PassAs = convertBack.PassAs,
|
||||
WrapCall = convertBack.WrapCall.Replace("$TYPE", param.RawTypeName),
|
||||
PassAs = convertBack.PassAs.Replace("$TYPE", param.RawTypeName),
|
||||
};
|
||||
publicParams.Add(publicParam);
|
||||
allNativeParams.Add(new NativeParamInfo
|
||||
{
|
||||
NativeName = param.Name,
|
||||
PublicName = param.Name,
|
||||
PassAs = convertBack.PassAs,
|
||||
PassAs = convertBack.PassAs.Replace("$TYPE", param.RawTypeName),
|
||||
});
|
||||
}
|
||||
else
|
||||
@@ -581,7 +599,8 @@ public sealed class WrapperGeneratorEmitter
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!string.Equals(remap.Src, param.TypeName, StringComparison.Ordinal))
|
||||
var src = remap.Src.Replace("$TYPE", param.RawTypeName);
|
||||
if (!string.Equals(src, param.TypeName, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@ public sealed class NativeParameter
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
public required string TypeName { get; init; }
|
||||
|
||||
private string? _rawTypeName;
|
||||
public string RawTypeName => _rawTypeName ??= TypeName.TrimEnd('*', '&');
|
||||
}
|
||||
|
||||
public sealed class NativeMember
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Ghost.NativeWrapperGen": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "--config \"F:/csharp/GhostEngine/src/Tools/Ghost.NativeWrapperGen/configs/ufbx.json\" --input \"F:/csharp/GhostEngine/src/ThridParty/Ghost.Ufbx/Generated\" --output \"F:/csharp/GhostEngine/src/ThridParty/Ghost.Ufbx/Wrapper\""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,13 +17,31 @@
|
||||
"dst": "ReadOnlySpan<byte>",
|
||||
"scope": [ "parameter" ],
|
||||
"filter": [ ".*name", ".*filename", ".*path", ".*prop$" ],
|
||||
"derivesFrom": { "paramSuffix": "_len", "expr": "(nuint)$arg.Length" },
|
||||
"derivesFrom": {
|
||||
"paramSuffix": "_len",
|
||||
"expr": "(nuint)$arg.Length"
|
||||
},
|
||||
"adapter": {
|
||||
"convertBack": {
|
||||
"wrapCall": "fixed (byte* p$arg = $arg) { $CALL }",
|
||||
"passAs": "(sbyte*)p$arg"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"src": "$TYPE*",
|
||||
"dst": "ReadOnlySpan<$TYPE>",
|
||||
"scope": [ "parameter" ],
|
||||
"derivesFrom": {
|
||||
"paramPrefix": "num_",
|
||||
"expr": "(nuint)$arg.Length"
|
||||
},
|
||||
"adapter": {
|
||||
"convertBack": {
|
||||
"wrapCall": "fixed ($TYPE* p$arg = $arg) { $CALL }",
|
||||
"passAs": "($TYPE*)p$arg"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -57,14 +75,13 @@
|
||||
// Instance method: first param is T* of a known binding struct → method on T
|
||||
"comment": "First param is T* of a known binding struct → instance method on T",
|
||||
"filter": "EXTERN_API",
|
||||
"conditions": [ "SELF_PTR" ],
|
||||
"conditions": [ "SELF_PTR", "!NAME_CONDITION(ufbx_generate_indices)", "!NAME_CONDITION(ufbx_topo_next_vertex_edge)", "!NAME_CONDITION(ufbx_topo_prev_vertex_edge)" ],
|
||||
"targetType": "FIRST_PARAM_TYPE",
|
||||
"apply": {
|
||||
"type": "INSTANCE_METHOD",
|
||||
"opts": {
|
||||
"removeFirstParam": true,
|
||||
"passAs": "($TSelf*)System.Runtime.CompilerServices.Unsafe.AsPointer(ref this)",
|
||||
// Change "ufbx_free_scene" to "free"
|
||||
"name": {
|
||||
"remove": [
|
||||
"PREFIX",
|
||||
|
||||
Reference in New Issue
Block a user