Introduce SimplifyOptions and SimplifyVertexOptions enums for mesh simplification, replacing magic numbers with type-safe flags. Update MeshOptApi with strongly-typed wrapper methods. Refactor MeshletUtility to use new enums and nullable delegates, and fix stride calculation for pointer arithmetic. Rename NamingConventions.GetMethodName to GetName, update name removal logic to use "$TBare", and add ALL_CAPS style for constants. Update config files to match new naming conventions and add ALL_CAPS constant rule for meshopt. Refactor BindingParser and related classes to support constant member kind. Apply minor bug fixes and code style improvements throughout.
Ghost.NativeWrapperGen
Ghost.NativeWrapperGen is a CLI tool that reads low-level generated C# native bindings (e.g. ClangSharp output) and emits a thin, config-driven wrapper layer that routes DllImport functions from the Api class onto the native struct types they belong to.
It is built for bindings that look like ClangSharp output:
- many
unsafe partial structnative types - raw pointer parameters like
NvttSurface* - native methods inside a static
Apiclass decorated with[DllImport]
What it generates
For each routed function the generator emits a method on the appropriate partial struct in a *.nativegen.cs file. No wrapper classes, no properties, no owned/marshalled types — just methods.
Example: nvttDestroySurface(NvttSurface*) → public void Dispose() on NvttSurface
// NvttSurface.nativegen.cs (auto-generated)
public unsafe partial struct NvttSurface : System.IDisposable
{
// From: nvttCreateSurface()
public static NvttSurface* Create() => Api.nvttCreateSurface();
// From: nvttDestroySurface(NvttSurface*)
public void Dispose()
{
Api.nvttDestroySurface((NvttSurface*)System.Runtime.CompilerServices.Unsafe.AsPointer(ref this));
}
// From: nvttSurfaceWidth(NvttSurface*)
public int Width()
{
return Api.nvttSurfaceWidth((NvttSurface*)System.Runtime.CompilerServices.Unsafe.AsPointer(ref this));
}
}
CLI usage
dotnet run --project src/Tools/Ghost.NativeWrapperGen/Ghost.NativeWrapperGen.csproj -- \
--config <config-path> \
--input <generated-binding-folder> \
--output <wrapper-output-folder>
Arguments:
--config— JSON config for one library--input— folder containing generated.csbinding files (ClangSharp output)--output— folder where*.nativegen.csfiles are written
The generator deletes all existing *.nativegen.cs files in the output folder before writing new ones.
Validated commands
dotnet run --project src/Tools/Ghost.NativeWrapperGen/Ghost.NativeWrapperGen.csproj -- --config src/Tools/Ghost.NativeWrapperGen/configs/nvtt.json --input src/ThridParty/Ghost.Nvtt/Generated --output src/ThridParty/Ghost.Nvtt/WrapperGen
dotnet run --project src/Tools/Ghost.NativeWrapperGen/Ghost.NativeWrapperGen.csproj -- --config src/Tools/Ghost.NativeWrapperGen/configs/ufbx.json --input src/ThridParty/Ghost.Ufbx/Generated --output src/ThridParty/Ghost.Ufbx/Wrapper
Important files
src/Tools/Ghost.NativeWrapperGen/Program.cssrc/Tools/Ghost.NativeWrapperGen/Config/WrapperConfig.cssrc/Tools/Ghost.NativeWrapperGen/Parsing/BindingParser.cssrc/Tools/Ghost.NativeWrapperGen/Transform/NamingConventions.cssrc/Tools/Ghost.NativeWrapperGen/Transform/JsonConverter.cssrc/Tools/Ghost.NativeWrapperGen/Emit/WrapperGeneratorEmitter.cssrc/Tools/Ghost.NativeWrapperGen/configs/nvtt.jsonsrc/Tools/Ghost.NativeWrapperGen/configs/ufbx.json
Config schema
Top-level fields:
| Field | Type | Description |
|---|---|---|
libraryName |
string | Human-readable library name |
nativeNamespace |
string | Namespace of the low-level generated bindings |
outputNamespace |
string | Namespace used in emitted files |
nativeTypePrefix |
string | Prefix stripped when deriving method names (e.g. "Nvtt", "ufbx_") |
skipTypes |
string[] | Types ignored by parsing/emission |
skipFunctions |
string[] | Native function names excluded from routing |
remaps |
RemapConfig[] | Parameter type remappings (e.g. sbyte* → ReadOnlySpan<byte>) |
actions |
ActionConfig[] | Routing rules, evaluated in order (first match wins) |
remaps
Each remap entry replaces a native parameter type with a C# type at the call site.
{
"src": "sbyte*",
"dst": "ReadOnlySpan<byte>",
"scope": ["parameter"],
"filter": [".*name", ".*path"],
"adapter": {
"convertBack": {
"wrapCall": "fixed (byte* p$arg = $arg) { $CALL }",
"passAs": "(sbyte*)p$arg"
}
}
}
Fields:
src— native C# type to matchdst— public C# type to expose in the generated signaturescope—"parameter"and/or"return"filter— optional regex list applied to parameter names; only matching parameters are remappedadapter.convertBack.wrapCall— wraps the whole call site;$arg= param name,$CALL= the Api call expressionadapter.convertBack.passAs— expression passed as the native argument;$arg= param namederivesFrom— if set, a sibling parameter (matched by name suffix) is consumed and replaced by an expression
actions
Actions are evaluated in order; the first one whose conditions all match is used.
{
"filter": "EXTERN_API",
"conditions": ["VOID_RETURN", "SELF_PTR", "NAME_CONDITION(.*Destroy$TBare)"],
"targetType": "FIRST_PARAM_TYPE",
"apply": [
{
"type": "INSTANCE_METHOD",
"opts": {
"removeFirstParam": true,
"passAs": "($TSelf*)System.Runtime.CompilerServices.Unsafe.AsPointer(ref this)",
"name": { "set": "Dispose" }
}
},
{
"type": "INHERITANCE",
"opts": { "baseType": ["System.IDisposable"] }
}
]
}
filter
Only "EXTERN_API" is supported — matches all [DllImport] methods on the Api class.
conditions
All conditions must be true for the action to match. Evaluated left-to-right.
| Condition | Description |
|---|---|
SELF_PTR |
First parameter is T* where T is a known binding struct |
FIRST_PARAM_OTHER_TYPE |
First parameter is NOT a known struct pointer |
RETURN_BINDING_TYPE |
Return type is T* where T is a known binding struct |
VOID_RETURN |
Return type is void |
NAME_CONDITION(regex) |
Native function name matches the regex (case-insensitive). Supports $TSelf (full struct name, e.g. NvttSurface) and $TBare (struct name with nativeTypePrefix stripped, e.g. Surface) |
targetType
Determines which struct the method is emitted on.
| Value | Description |
|---|---|
FIRST_PARAM_TYPE |
The binding struct type of the first parameter |
RETURN_TYPE |
The binding struct type of the return value |
apply
Can be a single object or an array of objects. Each apply step has a type and optional opts.
opts is dynamic — fields are read directly from the JSON object by name. Missing fields return null.
Apply type: INSTANCE_METHOD
Emits a public instance method on the target struct.
opts field |
Type | Description |
|---|---|---|
removeFirstParam |
bool | If true, skip the first native parameter from the public signature (it becomes the self pointer) |
passAs |
string | Expression used as the first native argument. $TSelf is replaced with the struct name |
name.set |
string | Fixed method name (overrides name.remove) |
name.remove |
array | Ordered list of removal tokens applied to derive the method name (see Naming) |
Apply type: STATIC_METHOD
Same as INSTANCE_METHOD but emits a public static method. No self pointer is injected.
opts field |
Type | Description |
|---|---|---|
name.set |
string | Fixed method name |
name.remove |
array | Removal token list |
Apply type: INHERITANCE
Adds one or more base types to the target struct's partial declaration header. Does not emit a method.
opts field |
Type | Description |
|---|---|---|
baseType |
string[] | Fully-qualified interface/type names to add (e.g. ["System.IDisposable"]) |
Naming
When name.set is given, it is used verbatim.
Otherwise name.remove is a list of tokens applied in sequence, each followed by underscore-trimming:
| Token | Effect |
|---|---|
PREFIX |
Strip nativeTypePrefix from the start of the name (case-insensitive) |
NO_PREFIX($TSelf) |
Strip the struct's bare name (struct name minus nativeTypePrefix) from the start or end of the name (case-insensitive) |
Examples with nativeTypePrefix = "Nvtt", struct = NvttSurface (bare = Surface):
| Native name | Tokens | Result |
|---|---|---|
nvttSurfaceWidth |
["PREFIX", "NO_PREFIX($TSelf)"] |
Width |
nvttCreateSurface |
["PREFIX", "NO_PREFIX($TSelf)"] |
Create |
Full config example
{
"libraryName": "nvtt",
"nativeNamespace": "Ghost.Nvtt",
"outputNamespace": "Ghost.Nvtt",
"nativeTypePrefix": "Nvtt",
"skipTypes": ["NativeAnnotationAttribute", "NativeTypeNameAttribute"],
"skipFunctions": [],
"remaps": [
{
"src": "sbyte*",
"dst": "ReadOnlySpan<byte>",
"scope": ["parameter"],
"filter": [".*name", ".*filename", ".*path", ".*prop$"],
"adapter": {
"convertBack": {
"wrapCall": "fixed (byte* p$arg = $arg) { $CALL }",
"passAs": "(sbyte*)p$arg"
}
}
}
],
"actions": [
{
"comment": "Dispose pattern: void return + T* param + name matches .*Destroy<Bare>",
"filter": "EXTERN_API",
"conditions": ["VOID_RETURN", "SELF_PTR", "NAME_CONDITION(.*Destroy$TBare)"],
"targetType": "FIRST_PARAM_TYPE",
"apply": [
{
"type": "INSTANCE_METHOD",
"opts": {
"removeFirstParam": true,
"passAs": "($TSelf*)System.Runtime.CompilerServices.Unsafe.AsPointer(ref this)",
"name": { "set": "Dispose" }
}
},
{
"type": "INHERITANCE",
"opts": { "baseType": ["System.IDisposable"] }
}
]
},
{
"comment": "First param is T* → instance method on T",
"filter": "EXTERN_API",
"conditions": ["SELF_PTR"],
"targetType": "FIRST_PARAM_TYPE",
"apply": {
"type": "INSTANCE_METHOD",
"opts": {
"removeFirstParam": true,
"passAs": "($TSelf*)System.Runtime.CompilerServices.Unsafe.AsPointer(ref this)",
"name": { "remove": ["PREFIX", "NO_PREFIX($TSelf)"] }
}
}
},
{
"comment": "Return type is T* → static method on T",
"filter": "EXTERN_API",
"conditions": ["FIRST_PARAM_OTHER_TYPE", "RETURN_BINDING_TYPE"],
"targetType": "RETURN_TYPE",
"apply": {
"type": "STATIC_METHOD",
"opts": {
"name": { "remove": ["PREFIX", "NO_PREFIX($TSelf)"] }
}
}
}
]
}
Recommended workflow
# 1. Build the tool
dotnet build src/Tools/Ghost.NativeWrapperGen/Ghost.NativeWrapperGen.csproj
# 2. Regenerate wrappers
dotnet run --project src/Tools/Ghost.NativeWrapperGen/Ghost.NativeWrapperGen.csproj -- --config src/Tools/Ghost.NativeWrapperGen/configs/nvtt.json --input src/ThridParty/Ghost.Nvtt/Generated --output src/ThridParty/Ghost.Nvtt/WrapperGen
dotnet run --project src/Tools/Ghost.NativeWrapperGen/Ghost.NativeWrapperGen.csproj -- --config src/Tools/Ghost.NativeWrapperGen/configs/ufbx.json --input src/ThridParty/Ghost.Ufbx/Generated --output src/ThridParty/Ghost.Ufbx/Wrapper
# 3. Build the libraries
dotnet build src/ThridParty/Ghost.Nvtt/Ghost.Nvtt.csproj
dotnet build src/ThridParty/Ghost.Ufbx/Ghost.Ufbx.csproj
Adding a new library
- Create
src/Tools/Ghost.NativeWrapperGen/configs/mylib.jsonfollowing the schema above. - Run the generator against your ClangSharp output folder.
- Add the output folder to your
.csprojas a glob:<Compile Include="WrapperGen\*.nativegen.cs" />. - Build and iterate.
The key design principle: keep policy in config, keep the emitter generic.