feat(meshopt): add typed enums and improve naming logic
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.
This commit is contained in:
@@ -9,13 +9,12 @@
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<IsAotCompatible>True</IsAotCompatible>
|
||||
<DefineConstants>$(DefineConstants);PLATEFORME_WIN64</DefineConstants>
|
||||
<DefineConstants>$(DefineConstants);ENABLE_DEBUG_LAYER</DefineConstants>
|
||||
<IsTrimmable>True</IsTrimmable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<IsAotCompatible>True</IsAotCompatible>
|
||||
<DefineConstants>$(DefineConstants);PLATEFORME_WIN64</DefineConstants>
|
||||
<IsTrimmable>True</IsTrimmable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -259,13 +259,11 @@ public static unsafe class MeshletUtility
|
||||
return clusters;
|
||||
}
|
||||
|
||||
// CHANGED parameters: UnsafeList -> UnsafeArray (because UnsafeList with 0 count skips logic loops)
|
||||
internal static void LockBoundary(UnsafeArray<byte> locks, UnsafeList<UnsafeList<int>> groups, UnsafeList<Cluster> clusters, UnsafeArray<uint> remap, byte* vertexLock)
|
||||
{
|
||||
var pLocks = (byte*)locks.GetUnsafePtr();
|
||||
var pRemap = (uint*)remap.GetUnsafePtr();
|
||||
|
||||
// CHANGED: locks.Count -> locks.Length
|
||||
for (var i = 0; i < locks.Length; i++)
|
||||
{
|
||||
pLocks[i] = unchecked((byte)(pLocks[i] & ~((1 << 0) | (1 << 7))));
|
||||
@@ -294,11 +292,10 @@ public static unsafe class MeshletUtility
|
||||
}
|
||||
}
|
||||
|
||||
// CHANGED: locks.Count -> locks.Length
|
||||
for (var i = 0; i < locks.Length; i++)
|
||||
{
|
||||
var r = pRemap[i];
|
||||
pLocks[i] = (byte)((pLocks[r] & 1) | (pLocks[i] & Api.meshopt_SimplifyVertex_Protect & 0xFF));
|
||||
pLocks[i] = (byte)((pLocks[r] & 1) | (pLocks[i] & (byte)SimplifyVertexOptions.Protect & 0xFF));
|
||||
if (vertexLock != null)
|
||||
{
|
||||
pLocks[i] |= vertexLock[i];
|
||||
@@ -365,7 +362,7 @@ public static unsafe class MeshletUtility
|
||||
return partitions;
|
||||
}
|
||||
|
||||
private static int OutputGroup(ClodConfig config, ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> group, ClodBounds simplified, int depth, void* outputContext, ClodOutputDelegate outputCallback)
|
||||
private static int OutputGroup(ClodConfig config, ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> group, ClodBounds simplified, int depth, void* outputContext, ClodOutputDelegate? outputCallback)
|
||||
{
|
||||
using var groupClusters = new UnsafeList<ClodCluster>(group.Count, Allocator.FreeList);
|
||||
|
||||
@@ -402,15 +399,15 @@ public static unsafe class MeshletUtility
|
||||
return lod;
|
||||
}
|
||||
|
||||
var options = (uint)(Api.meshopt_SimplifySparse | Api.meshopt_SimplifyErrorAbsolute);
|
||||
var options = SimplifyOptions.Sparse | SimplifyOptions.ErrorAbsolute;
|
||||
if (config.simplifyPermissive)
|
||||
{
|
||||
options |= Api.meshopt_SimplifyPermissive;
|
||||
options |= SimplifyOptions.Permissive;
|
||||
}
|
||||
|
||||
if (config.simplifyRegularize)
|
||||
{
|
||||
options |= Api.meshopt_SimplifyRegularize;
|
||||
options |= SimplifyOptions.Regularize;
|
||||
}
|
||||
|
||||
var resultSize = MeshOptApi.SimplifyWithAttributes(
|
||||
@@ -435,7 +432,7 @@ public static unsafe class MeshletUtility
|
||||
|
||||
if ((nuint)lod.Length > targetCount && config.simplifyFallbackPermissive && !config.simplifyPermissive)
|
||||
{
|
||||
options |= Api.meshopt_SimplifyPermissive;
|
||||
options |= SimplifyOptions.Permissive;
|
||||
resultSize = MeshOptApi.SimplifyWithAttributes(
|
||||
(uint*)lod.GetUnsafePtr(),
|
||||
(uint*)indices.GetUnsafePtr(),
|
||||
@@ -466,14 +463,14 @@ public static unsafe class MeshletUtility
|
||||
{
|
||||
float maxEdgeSq = 0;
|
||||
var pIdx = (uint*)indices.GetUnsafePtr();
|
||||
var posStride = (int)(mesh.vertexPositionsStride / sizeof(float));
|
||||
var posStride = mesh.vertexPositionsStride / (nuint)sizeof(float);
|
||||
|
||||
for (var i = 0; i < indices.Count; i += 3)
|
||||
{
|
||||
uint a = pIdx[i], b = pIdx[i + 1], c = pIdx[i + 2];
|
||||
var va = mesh.vertexPositions + (a * (uint)posStride);
|
||||
var vb = mesh.vertexPositions + (b * (uint)posStride);
|
||||
var vc = mesh.vertexPositions + (c * (uint)posStride);
|
||||
var va = mesh.vertexPositions + (a * posStride);
|
||||
var vb = mesh.vertexPositions + (b * posStride);
|
||||
var vc = mesh.vertexPositions + (c * posStride);
|
||||
|
||||
float dx, dy, dz;
|
||||
dx = va[0] - vb[0]; dy = va[1] - vb[1]; dz = va[2] - vb[2];
|
||||
@@ -502,13 +499,13 @@ public static unsafe class MeshletUtility
|
||||
/// <param name="outputContext">Optional context pointer passed to the output callback.</param>
|
||||
/// <param name="outputCallback">Delegate invoked for each generated LOD group.</param>
|
||||
/// <returns>The total count of generated clusters.</returns>
|
||||
public static nuint Build(ClodConfig config, ClodMesh mesh, void* outputContext, ClodOutputDelegate outputCallback)
|
||||
public static nuint Build(ClodConfig config, ClodMesh mesh, void* outputContext, ClodOutputDelegate? outputCallback)
|
||||
{
|
||||
Debug.Assert(mesh.vertexAttributesStride % sizeof(float) == 0, "vertexAttributesStride must be a multiple of sizeof(float)");
|
||||
|
||||
using var locks = new UnsafeArray<byte>((int)mesh.vertexCount, Allocator.FreeList, AllocationOption.Clear);
|
||||
using var remap = new UnsafeArray<uint>((int)mesh.vertexCount, Allocator.FreeList);
|
||||
|
||||
|
||||
MeshOptApi.GeneratePositionRemap((uint*)remap.GetUnsafePtr(), mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride);
|
||||
|
||||
if (mesh.attributeProtectMask != 0)
|
||||
@@ -523,7 +520,7 @@ public static unsafe class MeshletUtility
|
||||
{
|
||||
if (mesh.vertexAttributes[i * maxAttributes + j] != mesh.vertexAttributes[r * maxAttributes + j])
|
||||
{
|
||||
((byte*)locks.GetUnsafePtr())[(int)i] |= Api.meshopt_SimplifyVertex_Protect & 0xFF;
|
||||
((byte*)locks.GetUnsafePtr())[i] |= (byte)SimplifyVertexOptions.Protect & 0xFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
90
src/ThridParty/Ghost.MeshOptimizer/MeshOptApi.cs
Normal file
90
src/ThridParty/Ghost.MeshOptimizer/MeshOptApi.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
namespace Ghost.MeshOptimizer;
|
||||
|
||||
[Flags]
|
||||
public enum SimplifyOptions : uint
|
||||
{
|
||||
LockBorder = 1 << 0,
|
||||
Sparse = 1 << 1,
|
||||
ErrorAbsolute = 1 << 2,
|
||||
Prune = 1 << 3,
|
||||
Regularize = 1 << 4,
|
||||
Permissive = 1 << 5
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum SimplifyVertexOptions : byte
|
||||
{
|
||||
Lock = 1 << 0,
|
||||
Protect = 1 << 1
|
||||
}
|
||||
|
||||
public unsafe partial struct MeshOptApi
|
||||
{
|
||||
public const int VERSION = Api.MESHOPTIMIZER_VERSION;
|
||||
|
||||
/// <summary>
|
||||
/// From: <see cref="Api.meshopt_simplify(uint*, uint*, nuint, float*, nuint, nuint, nuint, float, uint, float*)" />
|
||||
/// </summary>
|
||||
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
public static nuint Simplify(uint* destination, uint* indices, nuint index_count, float* vertex_positions, nuint vertex_count, nuint vertex_positions_stride, nuint target_index_count, float target_error, SimplifyOptions options, float* result_error)
|
||||
{
|
||||
return Api.meshopt_simplify(
|
||||
destination,
|
||||
indices,
|
||||
index_count,
|
||||
vertex_positions,
|
||||
vertex_count,
|
||||
vertex_positions_stride,
|
||||
target_index_count,
|
||||
target_error,
|
||||
(uint)options,
|
||||
result_error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// From: <see cref="Api.meshopt_simplifyWithAttributes(uint*, uint*, nuint, float*, nuint, nuint, float*, nuint, float*, nuint, byte*, nuint, float, uint, float*)" />
|
||||
/// </summary>
|
||||
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
public static nuint SimplifyWithAttributes(uint* destination, uint* indices, nuint index_count, float* vertex_positions, nuint vertex_count, nuint vertex_positions_stride, float* vertex_attributes, nuint vertex_attributes_stride, float* attribute_weights, nuint attribute_count, byte* vertex_lock, nuint target_index_count, float target_error, SimplifyOptions options, float* result_error)
|
||||
{
|
||||
return Api.meshopt_simplifyWithAttributes(
|
||||
destination,
|
||||
indices,
|
||||
index_count,
|
||||
vertex_positions,
|
||||
vertex_count,
|
||||
vertex_positions_stride,
|
||||
vertex_attributes,
|
||||
vertex_attributes_stride,
|
||||
attribute_weights,
|
||||
attribute_count,
|
||||
vertex_lock,
|
||||
target_index_count,
|
||||
target_error,
|
||||
(uint)options,
|
||||
result_error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// From: <see cref="Api.meshopt_simplifyWithUpdate(uint*, nuint, float*, nuint, nuint, float*, nuint, float*, nuint, byte*, nuint, float, uint, float*)" />
|
||||
/// </summary>
|
||||
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
public static nuint SimplifyWithUpdate(uint* indices, nuint index_count, float* vertex_positions, nuint vertex_count, nuint vertex_positions_stride, float* vertex_attributes, nuint vertex_attributes_stride, float* attribute_weights, nuint attribute_count, byte* vertex_lock, nuint target_index_count, float target_error, SimplifyOptions options, float* result_error)
|
||||
{
|
||||
return Api.meshopt_simplifyWithUpdate(
|
||||
indices,
|
||||
index_count,
|
||||
vertex_positions,
|
||||
vertex_count,
|
||||
vertex_positions_stride,
|
||||
vertex_attributes,
|
||||
vertex_attributes_stride,
|
||||
attribute_weights,
|
||||
attribute_count,
|
||||
vertex_lock,
|
||||
target_index_count,
|
||||
target_error,
|
||||
(uint)options,
|
||||
result_error);
|
||||
}
|
||||
}
|
||||
@@ -276,7 +276,7 @@ public sealed class WrapperGeneratorEmitter
|
||||
{
|
||||
var func = routed.Function;
|
||||
var nameOpts = routed.Apply.Opts?.name;
|
||||
var methodName = naming.GetMethodName(func.Name, nameOpts, routed.TargetStructName);
|
||||
var methodName = naming.GetName(func.Name, nameOpts, routed.TargetStructName);
|
||||
|
||||
// Build the parameter plan: for each native parameter, determine the public type
|
||||
// and how to pass it to the Api call (applying remaps).
|
||||
|
||||
@@ -18,7 +18,6 @@ public sealed class NativeStruct
|
||||
public required bool IsList { get; init; }
|
||||
public required bool IsPointerList { get; init; }
|
||||
public string? ListElementType { get; init; }
|
||||
public required bool IsElementLike { get; init; }
|
||||
}
|
||||
|
||||
public sealed class NativeEnum
|
||||
@@ -52,4 +51,5 @@ public enum NativeMemberKind
|
||||
{
|
||||
Field,
|
||||
Property,
|
||||
Constant,
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ public sealed class BindingParser
|
||||
{
|
||||
public NativeLibrary Parse(string inputDirectory, WrapperConfig config)
|
||||
{
|
||||
var members = new List<NativeMember>();
|
||||
var structs = new List<NativeStruct>();
|
||||
var enums = new List<NativeEnum>();
|
||||
var functions = new List<NativeFunction>();
|
||||
@@ -33,18 +34,17 @@ public sealed class BindingParser
|
||||
continue;
|
||||
}
|
||||
|
||||
var members = ParseMembers(@struct);
|
||||
var listInfo = TryMatchList(members);
|
||||
var structMembers = ParseMembers(@struct);
|
||||
var listInfo = TryMatchList(structMembers);
|
||||
|
||||
structs.Add(new NativeStruct
|
||||
{
|
||||
Name = @struct.Identifier.ValueText,
|
||||
Namespace = namespaceName,
|
||||
Members = members,
|
||||
Members = structMembers,
|
||||
IsList = listInfo.IsList,
|
||||
IsPointerList = listInfo.IsPointerList,
|
||||
ListElementType = listInfo.ListElementType,
|
||||
IsElementLike = members.Any(static m => m.Name == "element" && m.TypeName == "ufbx_element"),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -17,13 +17,13 @@ public sealed class NamingConventions
|
||||
///
|
||||
/// Supported remove tokens:
|
||||
/// "PREFIX" — strip the config's NativeTypePrefix from the start (e.g. "nvtt", "ufbx_")
|
||||
/// "NO_PREFIX($TSelf)" — strip the target struct name minus its type prefix from the start,
|
||||
/// "$TBare" — strip the target struct name minus its type prefix from the start,
|
||||
/// case-insensitively (e.g. NvttSurface → "Surface" stripped from "SurfaceWidth")
|
||||
///
|
||||
/// nameOpts is the dynamic opts.name object from JSON (may be null).
|
||||
/// If no nameOpts are provided, the name is returned with only the library prefix stripped.
|
||||
/// </summary>
|
||||
public string GetMethodName(string nativeFunctionName, dynamic? nameOpts, string targetStructName)
|
||||
public string GetName(string nativeFunctionName, dynamic? nameOpts, string targetStructName)
|
||||
{
|
||||
var name = nativeFunctionName;
|
||||
|
||||
@@ -47,13 +47,8 @@ public sealed class NamingConventions
|
||||
{
|
||||
name = StripPrefixIgnoreCase(name, _config.NativeTypePrefix);
|
||||
}
|
||||
else if (token.StartsWith("NO_PREFIX(", StringComparison.Ordinal) && token.EndsWith(')'))
|
||||
else if (string.Equals(token, "$TBare", StringComparison.Ordinal))
|
||||
{
|
||||
// Extract $TSelf — it's the literal token "NO_PREFIX($TSelf)", so the struct name
|
||||
// is resolved from the targetStructName argument passed in.
|
||||
// Strip the config prefix from the struct name to get the "bare" part.
|
||||
// Try prefix first, then suffix (handles both nvtt "SurfaceWidth"→"Width"
|
||||
// and ufbx "free_scene"→"free_" styles).
|
||||
var bareStructName = StripPrefixIgnoreCase(targetStructName, _config.NativeTypePrefix);
|
||||
|
||||
// Remove directly, the name maybe nvttSetOutputOptionsOutputHeader, if we only remove prefix and suffix, OutputOptions in the middle will be ignored, so we remove the bare struct name directly, case-insensitively.
|
||||
@@ -66,7 +61,7 @@ public sealed class NamingConventions
|
||||
var style = nameOpts.style as string;
|
||||
if (!string.IsNullOrEmpty(style))
|
||||
{
|
||||
if (string.Equals(style, "PascalCase", StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(style, "PascalCase", StringComparison.Ordinal))
|
||||
{
|
||||
int counter = 0;
|
||||
Span<char> nameSpan = stackalloc char[name.Length];
|
||||
@@ -83,10 +78,10 @@ public sealed class NamingConventions
|
||||
|
||||
if (name[i] == '_')
|
||||
{
|
||||
while (name[i] == '_' && i < name.Length)
|
||||
do
|
||||
{
|
||||
i++;
|
||||
}
|
||||
} while (i < name.Length && name[i] == '_');
|
||||
|
||||
nameSpan[counter] = char.ToUpperInvariant(name[i]);
|
||||
counter++;
|
||||
@@ -98,6 +93,54 @@ public sealed class NamingConventions
|
||||
counter++;
|
||||
}
|
||||
|
||||
name = nameSpan[..counter].ToString();
|
||||
}
|
||||
else if (string.Equals(style, "ALL_CAPS", StringComparison.Ordinal))
|
||||
{
|
||||
int counter = 0;
|
||||
Span<char> nameSpan = stackalloc char[name.Length * 2]; // Worst case, every character is uppercase and followed by an underscore.
|
||||
|
||||
for (int i = 0; i < name.Length; i++)
|
||||
{
|
||||
// ___ to _
|
||||
if (name[i] == '_')
|
||||
{
|
||||
while (i + 1 < name.Length && name[i + 1] == '_')
|
||||
{
|
||||
i++;
|
||||
}
|
||||
|
||||
nameSpan[counter] = '_';
|
||||
counter++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// AbC to AB_C
|
||||
if (i > 0 && char.IsUpper(name[i]) && char.IsLower(name[i - 1]))
|
||||
{
|
||||
nameSpan[counter] = '_';
|
||||
counter++;
|
||||
}
|
||||
|
||||
// ABC to ABC
|
||||
while (i < name.Length && char.IsUpper(name[i]))
|
||||
{
|
||||
nameSpan[counter] = name[i];
|
||||
|
||||
counter++;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i == name.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
nameSpan[counter] = char.ToUpperInvariant(name[i]);
|
||||
counter++;
|
||||
}
|
||||
|
||||
name = nameSpan[..counter].ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"name": {
|
||||
"remove": [
|
||||
"PREFIX",
|
||||
"NO_PREFIX($TSelf)" // NO_PREFIX(NvttSurface) will change "NvttSurface" to "Surface", the prefix is determined by the "nativeTypePrefix" field at the top level of this config
|
||||
"$TBare" // NO_PREFIX(NvttSurface) will change "NvttSurface" to "Surface", the prefix is determined by the "nativeTypePrefix" field at the top level of this config
|
||||
],
|
||||
"style": "PascalCase"
|
||||
}
|
||||
@@ -43,7 +43,7 @@
|
||||
"name": {
|
||||
"remove": [
|
||||
"PREFIX",
|
||||
"NO_PREFIX($TSelf)"
|
||||
"$TBare"
|
||||
],
|
||||
"style": "PascalCase"
|
||||
}
|
||||
@@ -64,6 +64,21 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"filter": "CONST",
|
||||
"targetType": "MeshOptApi",
|
||||
"apply": {
|
||||
"type": "CONST",
|
||||
"opts": {
|
||||
"name": {
|
||||
"remove": [
|
||||
"PREFIX"
|
||||
],
|
||||
"style": "ALL_CAPS"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"name": {
|
||||
"remove": [
|
||||
"PREFIX",
|
||||
"NO_PREFIX($TSelf)" // NO_PREFIX(NvttSurface) will change "NvttSurface" to "Surface", the prefix is determined by the "nativeTypePrefix" field at the top level of this config
|
||||
"$TBare" // NO_PREFIX(NvttSurface) will change "NvttSurface" to "Surface", the prefix is determined by the "nativeTypePrefix" field at the top level of this config
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -86,21 +86,7 @@
|
||||
"name": {
|
||||
"remove": [
|
||||
"PREFIX",
|
||||
"NO_PREFIX($TSelf)"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"filter": "EXTERN_API",
|
||||
"targetType": "NvttApi",
|
||||
"apply": {
|
||||
"type": "STATIC_METHOD",
|
||||
"opts": {
|
||||
"name": {
|
||||
"remove": [
|
||||
"PREFIX"
|
||||
"$TBare"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"name": {
|
||||
"remove": [
|
||||
"PREFIX",
|
||||
"NO_PREFIX($TSelf)" // NO_PREFIX(ufbx_scene) will output "scene" change "free_scene" to "free", the prefix is determined by the "nativeTypePrefix" field at the top level of this config
|
||||
"$TBare" // NO_PREFIX(ufbx_scene) will output "scene" change "free_scene" to "free", the prefix is determined by the "nativeTypePrefix" field at the top level of this config
|
||||
],
|
||||
"style": "PascalCase"
|
||||
}
|
||||
@@ -87,7 +87,7 @@
|
||||
"name": {
|
||||
"remove": [
|
||||
"PREFIX",
|
||||
"NO_PREFIX($TSelf)"
|
||||
"$TBare"
|
||||
],
|
||||
"style": "PascalCase"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user