Get RealtimeCSG native bridge generating meshes
This commit is contained in:
parent
71e2d823a7
commit
8be5a99539
3 changed files with 182 additions and 53 deletions
|
|
@ -51,6 +51,12 @@ surface mirrored by its managed Unity library:
|
|||
.\tools\run_csg_perf.ps1 -UseRealtimeCsgCpp
|
||||
```
|
||||
|
||||
To test the native bridge without running the full timing suite:
|
||||
|
||||
```powershell
|
||||
.\tools\run_realtimecsg_cpp_perf.ps1 -OutputPath .\experiments\generated\realtimecsg-cpp-health-latest.jsonl -Health
|
||||
```
|
||||
|
||||
That path builds `tools/realtimecsg_native_bridge`, copies
|
||||
`RealtimeCSG[1_559].dll` beside the bridge executable, calls only exported
|
||||
functions already declared by the plugin's managed P/Invoke layer, and writes
|
||||
|
|
@ -58,13 +64,12 @@ native timing records to `experiments/generated/realtimecsg-cpp-perf-latest.json
|
|||
It fails closed if the native plugin emits no mesh descriptions; zero-geometry
|
||||
timings are poison, not data.
|
||||
|
||||
Current direct-DLL status: the bridge successfully loads the native plugin and
|
||||
creates brush meshes, brushes, and trees through the mirrored P/Invoke surface,
|
||||
but outside the Unity-hosted plugin lifecycle the native query currently returns
|
||||
success with `meshDescriptionCount = 0` even for a single additive cube. The
|
||||
failure is useful: it proves the exported heart can be reached directly, and it
|
||||
also proves we still need the missing initialization or lifecycle surface before
|
||||
using the C++ plugin as a timing oracle.
|
||||
Current direct-DLL status: the bridge successfully loads the native plugin,
|
||||
creates brush meshes, brushes, and trees, verifies bounds, outlines, raycasts,
|
||||
and now extracts generated mesh descriptions. The fragile part was construction
|
||||
discipline: build child nodes before the tree, insert them through the public
|
||||
Foundation range-insert path, and avoid eagerly setting default brush operation
|
||||
or flag state.
|
||||
|
||||
The reference executable must emit JSONL with the same scenario names. Until
|
||||
that harness exists, the script appends a `kernel=reference,status=missing`
|
||||
|
|
@ -74,20 +79,25 @@ The public `LogicalError/Realtime-CSG-demo` source still exposes the relevant
|
|||
algorithmic surface: half-edge control meshes, polygon bounds, visible flags,
|
||||
inside/outside/aligned/reverse-aligned categories, crossing polygon splits, and
|
||||
logical routing tables. Use it as readable doctrine. Use the native bridge as
|
||||
the performance target once the missing initialization path is identified.
|
||||
the performance target.
|
||||
|
||||
## Latest Local Baseline
|
||||
|
||||
Captured on 2026-05-07 with `.\tools\run_csg_perf.ps1`:
|
||||
Captured on 2026-05-07 with `.\tools\run_csg_perf.ps1 -UseRealtimeCsgCpp`:
|
||||
|
||||
```jsonl
|
||||
{"kernel":"vg_csg","scenario":"single_center_cut","brushes":2,"iterations":64,"warmup_iterations":8,"mean_ns":37314,"min_ns":27300,"p50_ns":32400,"p95_ns":45600,"max_ns":203900,"triangles":72,"fragments":6,"warnings":0}
|
||||
{"kernel":"vg_csg","scenario":"room_grid_8x8_doors","brushes":192,"iterations":64,"warmup_iterations":8,"mean_ns":8413054,"min_ns":4710000,"p50_ns":8020100,"p95_ns":11060600,"max_ns":18551700,"triangles":3072,"fragments":256,"warnings":0}
|
||||
{"kernel":"vg_csg","scenario":"rotated_cut_stack_64","brushes":65,"iterations":64,"warmup_iterations":8,"mean_ns":14952303,"min_ns":8627100,"p50_ns":14678000,"p95_ns":19088600,"max_ns":28753400,"triangles":3404,"fragments":280,"warnings":0}
|
||||
{"kernel":"vg_csg","scenario":"common_box_chain_64","brushes":64,"iterations":64,"warmup_iterations":8,"mean_ns":891484,"min_ns":616700,"p50_ns":823700,"p95_ns":1312000,"max_ns":3726400,"triangles":45,"fragments":1,"warnings":0}
|
||||
{"kernel":"vg_csg","scenario":"distant_cutters_512","brushes":513,"iterations":64,"warmup_iterations":8,"mean_ns":552914,"min_ns":459100,"p50_ns":471500,"p95_ns":775900,"max_ns":1421600,"triangles":12,"fragments":1,"warnings":0}
|
||||
{"status":"missing","reason":"Set VIBEGEOMETRY_REFERENCE_CSG_PERF or pass -ReferenceCommand with an executable that emits the same JSONL scenario records.","kernel":"reference"}
|
||||
{"kernel":"vg_csg","scenario":"single_center_cut","brushes":2,"iterations":64,"warmup_iterations":8,"mean_ns":30817,"min_ns":21200,"p50_ns":25300,"p95_ns":41600,"max_ns":144900,"triangles":72,"fragments":6,"warnings":0}
|
||||
{"kernel":"vg_csg","scenario":"room_grid_8x8_doors","brushes":192,"iterations":64,"warmup_iterations":8,"mean_ns":6558703,"min_ns":4415500,"p50_ns":6232300,"p95_ns":8580100,"max_ns":10348300,"triangles":3072,"fragments":256,"warnings":0}
|
||||
{"kernel":"vg_csg","scenario":"rotated_cut_stack_64","brushes":65,"iterations":64,"warmup_iterations":8,"mean_ns":12701656,"min_ns":9057100,"p50_ns":12045700,"p95_ns":17285200,"max_ns":29792900,"triangles":3404,"fragments":280,"warnings":0}
|
||||
{"kernel":"vg_csg","scenario":"common_box_chain_64","brushes":64,"iterations":64,"warmup_iterations":8,"mean_ns":890965,"min_ns":617100,"p50_ns":871800,"p95_ns":1214900,"max_ns":1989700,"triangles":45,"fragments":1,"warnings":0}
|
||||
{"kernel":"vg_csg","scenario":"distant_cutters_512","brushes":513,"iterations":64,"warmup_iterations":8,"mean_ns":635082,"min_ns":460900,"p50_ns":578300,"p95_ns":873800,"max_ns":1361200,"triangles":12,"fragments":1,"warnings":0}
|
||||
{"kernel":"realtime_csg_cpp","scenario":"single_center_cut","brushes":2,"iterations":64,"warmup_iterations":8,"mean_ns":8300,"min_ns":5500,"p50_ns":7300,"p95_ns":11700,"max_ns":22000,"triangles":72,"vertices":144,"mesh_descriptions":3}
|
||||
{"kernel":"realtime_csg_cpp","scenario":"room_grid_8x8_doors","brushes":192,"iterations":64,"warmup_iterations":8,"mean_ns":530000,"min_ns":379700,"p50_ns":446300,"p95_ns":915500,"max_ns":1537100,"triangles":12800,"vertices":20992,"mesh_descriptions":4}
|
||||
{"kernel":"realtime_csg_cpp","scenario":"rotated_cut_stack_64","brushes":65,"iterations":64,"warmup_iterations":8,"mean_ns":112800,"min_ns":93600,"p50_ns":99900,"p95_ns":143600,"max_ns":314200,"triangles":3498,"vertices":5102,"mesh_descriptions":4}
|
||||
{"kernel":"realtime_csg_cpp","scenario":"common_box_chain_64","brushes":64,"iterations":64,"warmup_iterations":8,"mean_ns":76800,"min_ns":57200,"p50_ns":64400,"p95_ns":104900,"max_ns":428700,"triangles":1536,"vertices":2412,"mesh_descriptions":4}
|
||||
{"kernel":"realtime_csg_cpp","scenario":"distant_cutters_512","brushes":513,"iterations":64,"warmup_iterations":8,"mean_ns":324600,"min_ns":240400,"p50_ns":263400,"p95_ns":534100,"max_ns":1530400,"triangles":6180,"vertices":12360,"mesh_descriptions":4}
|
||||
```
|
||||
|
||||
Read these as smoke timings, not final benchmark gospel. The fixtures are stable
|
||||
enough to catch regressions and compare against a reference harness once wired.
|
||||
Read these as smoke timings, not final benchmark gospel. They are already sharp
|
||||
enough to show the target: our current ordered Rust backend is not yet competing
|
||||
with the C++ classifier/router on mesh-heavy workloads.
|
||||
|
|
|
|||
|
|
@ -3,13 +3,23 @@ using System.Runtime.InteropServices;
|
|||
|
||||
const int WarmupIterations = 8;
|
||||
const int MeasureIterations = 64;
|
||||
var debugNative = args.Any(arg => arg == "--debug-native");
|
||||
var healthOnly = args.Any(arg => arg == "--health");
|
||||
|
||||
var output = args.Length > 0
|
||||
? args[0]
|
||||
var outputArg = args.FirstOrDefault(arg => arg != "--debug-native" && arg != "--health");
|
||||
var output = outputArg is not null
|
||||
? outputArg
|
||||
: Path.GetFullPath(Path.Combine("experiments", "generated", "realtimecsg-cpp-perf-latest.jsonl"));
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(output)!);
|
||||
Native.RegisterNoopUnityMethods();
|
||||
if (healthOnly)
|
||||
{
|
||||
using var healthWriter = new StreamWriter(output, false);
|
||||
healthWriter.WriteLine(RunHealthProbe());
|
||||
return;
|
||||
}
|
||||
|
||||
var cases = new PerfCase[]
|
||||
{
|
||||
new("single_center_cut", 2, SingleCenterCut),
|
||||
|
|
@ -25,7 +35,7 @@ foreach (var perfCase in cases)
|
|||
writer.WriteLine(RunCase(perfCase));
|
||||
}
|
||||
|
||||
static string RunCase(PerfCase perfCase)
|
||||
string RunCase(PerfCase perfCase)
|
||||
{
|
||||
var timings = new List<long>(MeasureIterations);
|
||||
var triangles = 0;
|
||||
|
|
@ -38,12 +48,17 @@ static string RunCase(PerfCase perfCase)
|
|||
throw new InvalidOperationException($"Failed to build native RealtimeCSG tree for {perfCase.Name}");
|
||||
|
||||
Native.RebuildAll();
|
||||
if (debugNative)
|
||||
DumpTree(perfCase.Name, tree);
|
||||
if (Native.GetBrushCount() != perfCase.Brushes)
|
||||
throw new InvalidOperationException($"Native brush count mismatch for {perfCase.Name}: expected {perfCase.Brushes}, got {Native.GetBrushCount()}, nodes={Native.GetNodeCount()}, trees={Native.GetTreeCount()}, brushMeshes={Native.GetBrushMeshCount()}");
|
||||
for (var i = 0; i < WarmupIterations; i++)
|
||||
MeasureOnce(tree, out triangles, out vertices, out meshDescriptions);
|
||||
if (meshDescriptions == 0)
|
||||
{
|
||||
DumpTree(perfCase.Name, tree);
|
||||
throw new InvalidOperationException($"Native generated no mesh descriptions for {perfCase.Name}: nodes={Native.GetNodeCount()}, brushes={Native.GetBrushCount()}, trees={Native.GetTreeCount()}, brushMeshes={Native.GetBrushMeshCount()}, treeBrushes={Native.GetNumberOfBrushesInTree(tree)}, treeChildren={Native.GetChildNodeCount(tree)}, treeDirty={Native.IsNodeDirty(tree)}");
|
||||
}
|
||||
|
||||
for (var i = 0; i < MeasureIterations; i++)
|
||||
{
|
||||
|
|
@ -60,14 +75,76 @@ static string RunCase(PerfCase perfCase)
|
|||
$"{{\"kernel\":\"realtime_csg_cpp\",\"scenario\":\"{perfCase.Name}\",\"brushes\":{perfCase.Brushes},\"iterations\":{MeasureIterations},\"warmup_iterations\":{WarmupIterations},\"mean_ns\":{TicksToNs(mean)},\"min_ns\":{TicksToNs(timings[0])},\"p50_ns\":{TicksToNs(timings[(timings.Count - 1) * 50 / 100])},\"p95_ns\":{TicksToNs(timings[(timings.Count - 1) * 95 / 100])},\"max_ns\":{TicksToNs(timings[^1])},\"triangles\":{triangles},\"vertices\":{vertices},\"mesh_descriptions\":{meshDescriptions}}}";
|
||||
}
|
||||
|
||||
static string RunHealthProbe()
|
||||
{
|
||||
Native.ClearAllNodes();
|
||||
var tree = TreeFrom(() => new[]
|
||||
{
|
||||
Brush("native_health_cube", Vec3.Zero, Vec3.Splat(4), Mat4.Identity, Operation.Additive),
|
||||
});
|
||||
Native.RebuildAll();
|
||||
var updateOk = Native.UpdateAllTreeMeshes();
|
||||
var child = Native.GetChildNodeAtIndex(tree, 0);
|
||||
var bounds = new Aabb();
|
||||
var boundsOk = Native.GetBrushBounds(child, ref bounds);
|
||||
var outlineOk = Native.GetBrushOutlineSizes(child, out var outlineVertices, out var visibleOuter, out var visibleInner, out var invisibleOuter, out var invisibleInner, out var invalid);
|
||||
var rayStart = new Vec3(-8, 0, 0);
|
||||
var rayEnd = new Vec3(8, 0, 0);
|
||||
var matrix = Mat4.Identity;
|
||||
var rayHits = Native.RayCastIntoTreeMultiCount(tree, ref rayStart, ref rayEnd, ref matrix, 0, false, IntPtr.Zero, 0);
|
||||
var meshTypes = MeshQuery.RenderOnlyTypes;
|
||||
var meshTypesHandle = GCHandle.Alloc(meshTypes, GCHandleType.Pinned);
|
||||
var generated = Native.GenerateMeshDescriptions(tree, meshTypes.Length, meshTypesHandle.AddrOfPinnedObject(), VertexChannelFlags.All, out var meshDescriptions);
|
||||
meshTypesHandle.Free();
|
||||
var heartbeat = updateOk && Native.GetTreeEnabled(tree) && Native.GetChildNodeCount(tree) == 1 && boundsOk && outlineOk && rayHits > 0;
|
||||
var meshExtractionReady = generated && meshDescriptions > 0;
|
||||
var json =
|
||||
$"{{\"kernel\":\"realtime_csg_cpp\",\"scenario\":\"native_health_cube\",\"heartbeat\":{JsonBool(heartbeat)},\"mesh_extraction_ready\":{JsonBool(meshExtractionReady)},\"update_ok\":{JsonBool(updateOk)},\"tree\":{tree},\"tree_enabled\":{JsonBool(Native.GetTreeEnabled(tree))},\"nodes\":{Native.GetNodeCount()},\"brushes\":{Native.GetBrushCount()},\"trees\":{Native.GetTreeCount()},\"brush_meshes\":{Native.GetBrushMeshCount()},\"tree_children\":{Native.GetChildNodeCount(tree)},\"tree_brushes\":{Native.GetNumberOfBrushesInTree(tree)},\"tree_dirty\":{JsonBool(Native.IsNodeDirty(tree))},\"child_type\":{Native.GetTypeOfNode(child)},\"child_tree\":{Native.GetTreeOfNode(child)},\"child_mesh\":{Native.GetBrushMeshID(child)},\"child_operation\":{Native.GetBrushOperationType(child)},\"bounds_ok\":{JsonBool(boundsOk)},\"bounds_min\":[{bounds.MinX},{bounds.MinY},{bounds.MinZ}],\"bounds_max\":[{bounds.MaxX},{bounds.MaxY},{bounds.MaxZ}],\"outline_ok\":{JsonBool(outlineOk)},\"outline_vertices\":{outlineVertices},\"visible_outer_lines\":{visibleOuter},\"visible_inner_lines\":{visibleInner},\"invisible_outer_lines\":{invisibleOuter},\"invisible_inner_lines\":{invisibleInner},\"invalid_lines\":{invalid},\"raycast_hits\":{rayHits},\"mesh_descriptions_generated\":{JsonBool(generated)},\"mesh_descriptions\":{meshDescriptions}}}";
|
||||
Native.ClearAllNodes();
|
||||
return json;
|
||||
}
|
||||
|
||||
static void DumpTree(string name, int tree)
|
||||
{
|
||||
Native.RebuildAll();
|
||||
var updated = Native.UpdateAllTreeMeshes();
|
||||
Console.Error.WriteLine($"native debug {name}: updated={updated} tree={tree} type={Native.GetTypeOfNode(tree)} enabled={Native.GetTreeEnabled(tree)} children={Native.GetChildNodeCount(tree)} dirty={Native.IsNodeDirty(tree)} nodes={Native.GetNodeCount()} brushes={Native.GetBrushCount()} trees={Native.GetTreeCount()} brushMeshes={Native.GetBrushMeshCount()}");
|
||||
for (var i = 0; i < Native.GetChildNodeCount(tree); i++)
|
||||
{
|
||||
var child = Native.GetChildNodeAtIndex(tree, i);
|
||||
var bounds = new Aabb();
|
||||
var boundsOk = Native.GetBrushBounds(child, ref bounds);
|
||||
var outlineOk = Native.GetBrushOutlineSizes(child, out var outlineVertices, out var visibleOuter, out var visibleInner, out var invisibleOuter, out var invisibleInner, out var invalid);
|
||||
Console.Error.WriteLine($" child[{i}] id={child} type={Native.GetTypeOfNode(child)} tree={Native.GetTreeOfNode(child)} mesh={Native.GetBrushMeshID(child)} op={Native.GetBrushOperationType(child)} dirty={Native.IsNodeDirty(child)} boundsOk={boundsOk} bounds=({bounds.MinX},{bounds.MinY},{bounds.MinZ})..({bounds.MaxX},{bounds.MaxY},{bounds.MaxZ}) outlineOk={outlineOk} outlineVertices={outlineVertices} visibleOuter={visibleOuter} visibleInner={visibleInner} invisibleOuter={invisibleOuter} invisibleInner={invisibleInner} invalid={invalid}");
|
||||
}
|
||||
var rayStart = new Vec3(-8, 0, 0);
|
||||
var rayEnd = new Vec3(8, 0, 0);
|
||||
var matrix = Mat4.Identity;
|
||||
var rayHits = Native.RayCastIntoTreeMultiCount(tree, ref rayStart, ref rayEnd, ref matrix, 0, false, IntPtr.Zero, 0);
|
||||
Console.Error.WriteLine($" raycast hits={rayHits}");
|
||||
ProbeGeneratedMesh(tree);
|
||||
}
|
||||
|
||||
static void ProbeGeneratedMesh(int tree)
|
||||
{
|
||||
var indices = new int[8192];
|
||||
var positions = new Vec3[8192];
|
||||
var indexHandle = GCHandle.Alloc(indices, GCHandleType.Pinned);
|
||||
var positionHandle = GCHandle.Alloc(positions, GCHandleType.Pinned);
|
||||
var ok = Native.GetGeneratedMesh(tree, 0, 0, indices.Length, indexHandle.AddrOfPinnedObject(), positions.Length, positionHandle.AddrOfPinnedObject(), IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out var center, out var size);
|
||||
positionHandle.Free();
|
||||
indexHandle.Free();
|
||||
Console.Error.WriteLine($" direct mesh probe ok={ok} boundsCenter=({center.X},{center.Y},{center.Z}) boundsSize=({size.X},{size.Y},{size.Z}) firstIndex={indices[0]} firstPos=({positions[0].X},{positions[0].Y},{positions[0].Z})");
|
||||
}
|
||||
|
||||
static long TicksToNs(long ticks) => (long)(ticks * (1_000_000_000.0 / Stopwatch.Frequency));
|
||||
static string JsonBool(bool value) => value ? "true" : "false";
|
||||
|
||||
static void MeasureOnce(int tree, out int triangles, out int vertices, out int meshDescriptions)
|
||||
{
|
||||
Native.RebuildAll();
|
||||
Native.UpdateAllTreeMeshes();
|
||||
|
||||
var meshTypes = new[] { MeshQuery.RenderableAll };
|
||||
var meshTypes = MeshQuery.RenderOnlyTypes;
|
||||
var meshTypesHandle = GCHandle.Alloc(meshTypes, GCHandleType.Pinned);
|
||||
var generated = Native.GenerateMeshDescriptions(tree, meshTypes.Length, meshTypesHandle.AddrOfPinnedObject(), VertexChannelFlags.All, out meshDescriptions);
|
||||
if (!generated || meshDescriptions == 0)
|
||||
|
|
@ -126,9 +203,11 @@ static void MeasureOnce(int tree, out int triangles, out int vertices, out int m
|
|||
|
||||
static int SingleCenterCut()
|
||||
{
|
||||
return Tree(
|
||||
return TreeFrom(() => new[]
|
||||
{
|
||||
Brush("source", Vec3.Zero, Vec3.Splat(4), Mat4.Identity, Operation.Additive),
|
||||
Brush("void", Vec3.Zero, Vec3.Splat(2), Mat4.Identity, Operation.Subtractive));
|
||||
Brush("void", Vec3.Zero, Vec3.Splat(2), Mat4.Identity, Operation.Subtractive),
|
||||
});
|
||||
}
|
||||
|
||||
static int RoomGrid8x8Doors()
|
||||
|
|
@ -185,11 +264,18 @@ static int DistantCutters512()
|
|||
|
||||
static int Tree(params int[] children)
|
||||
{
|
||||
return TreeFrom(() => children);
|
||||
}
|
||||
|
||||
static int TreeFrom(Func<int[]> buildChildren)
|
||||
{
|
||||
var children = buildChildren();
|
||||
if (!Native.GenerateTree(0, out var tree))
|
||||
return 0;
|
||||
Native.SetTreeEnabled(tree, true);
|
||||
if (!SetChildNodes(tree, children))
|
||||
throw new InvalidOperationException($"SetChildNodes failed for tree {tree} with {children.Length} children");
|
||||
if (!Native.SetTreeEnabled(tree, true) || !Native.GetTreeEnabled(tree))
|
||||
throw new InvalidOperationException($"SetTreeEnabled failed for tree {tree}");
|
||||
if (!InsertChildNodes(tree, children))
|
||||
throw new InvalidOperationException($"InsertChildNodes failed for tree {tree} with {children.Length} children");
|
||||
foreach (var child in children)
|
||||
Native.SetDirty(child);
|
||||
Native.SetDirty(tree);
|
||||
|
|
@ -202,8 +288,6 @@ static int Brush(string name, Vec3 center, Vec3 size, Mat4 rotation, Operation o
|
|||
var meshId = CreateBrushMesh(mesh, name.GetHashCode());
|
||||
if (meshId == 0 || !Native.GenerateBrush(name.GetHashCode(), out var brush))
|
||||
throw new InvalidOperationException($"Failed to create brush {name}: meshId={meshId}");
|
||||
if (!UpdateBrushMesh(meshId, mesh))
|
||||
throw new InvalidOperationException($"UpdateBrushMesh failed for brush {name}: meshId={meshId}");
|
||||
if (!Native.IsBrushMeshIDValid(meshId))
|
||||
throw new InvalidOperationException($"Native rejected brush mesh {name}/{meshId}");
|
||||
var transform = rotation.WithTranslation(center);
|
||||
|
|
@ -211,7 +295,7 @@ static int Brush(string name, Vec3 center, Vec3 size, Mat4 rotation, Operation o
|
|||
throw new InvalidOperationException($"SetNodeLocalTransformation failed for brush {name}/{brush}");
|
||||
if (!Native.SetBrushMeshID(brush, meshId))
|
||||
throw new InvalidOperationException($"SetBrushMeshID failed for brush {name}/{brush}, mesh={meshId}");
|
||||
if (!Native.SetBrushOperationType(brush, operation))
|
||||
if (operation != Operation.Additive && !Native.SetBrushOperationType(brush, operation))
|
||||
throw new InvalidOperationException($"SetBrushOperationType failed for brush {name}/{brush}, op={operation}");
|
||||
if (Native.GetBrushMeshID(brush) != meshId)
|
||||
throw new InvalidOperationException($"Brush {name}/{brush} did not retain mesh id {meshId}");
|
||||
|
|
@ -219,11 +303,10 @@ static int Brush(string name, Vec3 center, Vec3 size, Mat4 rotation, Operation o
|
|||
return brush;
|
||||
}
|
||||
|
||||
static bool SetChildNodes(int node, int[] children)
|
||||
static bool InsertChildNodes(int node, int[] children)
|
||||
{
|
||||
var childNodes = children.Select(id => new CsgTreeNode(id)).ToArray();
|
||||
var handle = GCHandle.Alloc(childNodes, GCHandleType.Pinned);
|
||||
var ok = Native.SetChildNodes(node, childNodes.Length, handle.AddrOfPinnedObject());
|
||||
var handle = GCHandle.Alloc(children, GCHandleType.Pinned);
|
||||
var ok = Native.InsertChildNodeRange(node, 0, children.Length, handle.AddrOfPinnedObject());
|
||||
handle.Free();
|
||||
return ok;
|
||||
}
|
||||
|
|
@ -240,23 +323,11 @@ static int CreateBrushMesh(BrushMesh mesh, int userId)
|
|||
return id;
|
||||
}
|
||||
|
||||
static bool UpdateBrushMesh(int meshId, BrushMesh mesh)
|
||||
{
|
||||
var vh = GCHandle.Alloc(mesh.Vertices, GCHandleType.Pinned);
|
||||
var eh = GCHandle.Alloc(mesh.HalfEdges, GCHandleType.Pinned);
|
||||
var ph = GCHandle.Alloc(mesh.Polygons, GCHandleType.Pinned);
|
||||
var ok = Native.UpdateBrushMesh(meshId, mesh.Vertices.Length, vh.AddrOfPinnedObject(), mesh.HalfEdges.Length, eh.AddrOfPinnedObject(), mesh.Polygons.Length, ph.AddrOfPinnedObject());
|
||||
ph.Free();
|
||||
eh.Free();
|
||||
vh.Free();
|
||||
return ok;
|
||||
}
|
||||
|
||||
static BrushMesh Cube(Vec3 size)
|
||||
{
|
||||
var min = size * -0.5f;
|
||||
var max = size * 0.5f;
|
||||
var layers = new SurfaceLayers { LayerUsage = LayerUsageFlags.RenderReceiveCastShadows };
|
||||
var layers = new SurfaceLayers { LayerUsage = LayerUsageFlags.RenderReceiveCastShadows | LayerUsageFlags.Collidable };
|
||||
var surfaces = new[]
|
||||
{
|
||||
new SurfaceDescription { UV0 = new UvMatrix(new Vec4(-1, 0, 0, -min.X), new Vec4(0, 1, 0, min.Y)) },
|
||||
|
|
@ -295,6 +366,7 @@ record PerfCase(string Name, int Brushes, Func<int> Build);
|
|||
record BrushMesh(Vec3[] Vertices, HalfEdge[] HalfEdges, Polygon[] Polygons);
|
||||
|
||||
enum Operation : byte { Additive = 0, Subtractive = 1, Intersecting = 2 }
|
||||
enum BrushFlags : int { Default = 0, Infinite = 1 }
|
||||
enum VertexChannelFlags : byte { Position = 0, Tangent = 2, Normal = 4, UV0 = 8, All = 14 }
|
||||
enum LayerUsageFlags : int
|
||||
{
|
||||
|
|
@ -370,6 +442,14 @@ struct SurfaceLayers
|
|||
public int LayerParameter3;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
struct Aabb
|
||||
{
|
||||
public float MinX, MaxX;
|
||||
public float MinY, MaxY;
|
||||
public float MinZ, MaxZ;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
struct Polygon(int firstEdge, int edgeCount, int polygonId, SurfaceDescription surface, SurfaceLayers layers)
|
||||
{
|
||||
|
|
@ -387,9 +467,6 @@ struct HalfEdge(int vertexIndex, int twinIndex)
|
|||
public int TwinIndex = twinIndex;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
struct CsgTreeNode(int nodeId) { public int NodeId = nodeId; }
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
struct MeshQuery
|
||||
{
|
||||
|
|
@ -397,7 +474,29 @@ struct MeshQuery
|
|||
public uint Layers;
|
||||
public uint MaskAndChannels;
|
||||
public static MeshQuery RenderableAll => new(LayerUsageFlags.Renderable, LayerUsageFlags.RenderReceiveCastShadows, LayerParameterIndex.LayerParameter1, VertexChannelFlags.All);
|
||||
MeshQuery(LayerUsageFlags query, LayerUsageFlags mask, LayerParameterIndex parameterIndex, VertexChannelFlags vertexChannels)
|
||||
public static MeshQuery[] SimpleTypes => new[]
|
||||
{
|
||||
new MeshQuery(LayerUsageFlags.Renderable, LayerUsageFlags.Renderable, LayerParameterIndex.None, VertexChannelFlags.Position),
|
||||
new MeshQuery(LayerUsageFlags.RenderReceiveCastShadows, LayerUsageFlags.RenderReceiveCastShadows, LayerParameterIndex.None, VertexChannelFlags.Position),
|
||||
new MeshQuery(LayerUsageFlags.Renderable, LayerUsageFlags.RenderReceiveCastShadows, LayerParameterIndex.None, VertexChannelFlags.All),
|
||||
new MeshQuery(LayerUsageFlags.RenderReceiveCastShadows, LayerUsageFlags.RenderReceiveCastShadows, LayerParameterIndex.LayerParameter1, VertexChannelFlags.All),
|
||||
new MeshQuery(LayerUsageFlags.None, LayerUsageFlags.None, LayerParameterIndex.None, VertexChannelFlags.Position),
|
||||
new MeshQuery(LayerUsageFlags.Culled, LayerUsageFlags.Culled, LayerParameterIndex.None, VertexChannelFlags.Position),
|
||||
};
|
||||
public static MeshQuery[] RenderOnlyTypes => new[]
|
||||
{
|
||||
new MeshQuery(LayerUsageFlags.CastShadows, LayerUsageFlags.RenderCastShadows, LayerParameterIndex.LayerParameter1, VertexChannelFlags.All),
|
||||
new MeshQuery(LayerUsageFlags.Renderable, LayerUsageFlags.RenderReceiveCastShadows, LayerParameterIndex.LayerParameter1, VertexChannelFlags.All),
|
||||
new MeshQuery(LayerUsageFlags.RenderCastShadows, LayerUsageFlags.RenderReceiveCastShadows, LayerParameterIndex.LayerParameter1, VertexChannelFlags.All),
|
||||
new MeshQuery(LayerUsageFlags.RenderReceiveShadows, LayerUsageFlags.RenderReceiveCastShadows, LayerParameterIndex.LayerParameter1, VertexChannelFlags.All),
|
||||
new MeshQuery(LayerUsageFlags.RenderReceiveCastShadows, LayerUsageFlags.RenderReceiveCastShadows, LayerParameterIndex.LayerParameter1, VertexChannelFlags.All),
|
||||
new MeshQuery(LayerUsageFlags.None, LayerUsageFlags.Renderable, LayerParameterIndex.None, VertexChannelFlags.Position),
|
||||
new MeshQuery(LayerUsageFlags.CastShadows, LayerUsageFlags.None, LayerParameterIndex.None, VertexChannelFlags.Position),
|
||||
new MeshQuery(LayerUsageFlags.ReceiveShadows, LayerUsageFlags.None, LayerParameterIndex.None, VertexChannelFlags.Position),
|
||||
new MeshQuery(LayerUsageFlags.Culled, LayerUsageFlags.None, LayerParameterIndex.None, VertexChannelFlags.Position),
|
||||
};
|
||||
|
||||
public MeshQuery(LayerUsageFlags query, LayerUsageFlags mask, LayerParameterIndex parameterIndex, VertexChannelFlags vertexChannels)
|
||||
{
|
||||
if (mask == LayerUsageFlags.None)
|
||||
mask = query;
|
||||
|
|
@ -463,21 +562,32 @@ static partial class Native
|
|||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool GenerateTree(int userID, out int generatedTreeNodeID);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int GetNumberOfBrushesInTree(int nodeID);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool SetDirty(int nodeID);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool ClearDirty(int nodeID);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool IsNodeDirty(int nodeID);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int GetChildNodeCount(int nodeID);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int GetChildNodeAtIndex(int nodeID, int index);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int GetTreeOfNode(int nodeID);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern byte GetTypeOfNode(int nodeID);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool SetTreeEnabled(int modelNodeID, bool isEnabled);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool GetTreeEnabled(int modelNodeID);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool GenerateBrush(int userID, out int generatedNodeID);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool GenerateBranch(int userID, out int generatedOperationNodeID);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool SetBranchOperationType(int branchNodeID, Operation operation);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool SetBrushOperationType(int brushNodeID, Operation operation);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool SetBrushFlags(int brushNodeID, BrushFlags flags);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int GetBrushOperationType(int brushNodeID);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool SetBrushMeshID(int brushNodeID, int brushMeshID);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int GetBrushMeshID(int brushNodeID);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool GetBrushBounds(int brushNodeID, ref Aabb bounds);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool GetBrushOutlineSizes(int brushNodeID, out int vertexCount, out int visibleOuterLineCount, out int visibleInnerLineCount, out int invisibleOuterLineCount, out int invisibleInnerLineCount, out int invalidLineCount);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool SetNodeLocalTransformation(int brushNodeID, ref Mat4 brushToTreeSpace);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool SetChildNodes(int nodeID, int childCount, IntPtr childrenNodeIDs);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool InsertChildNodeRange(int nodeID, int index, int childCount, IntPtr childrenNodeIDs);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int CreateBrushMesh(int userID, int vertexCount, IntPtr vertices, int halfEdgeCount, IntPtr halfEdges, int polygonCount, IntPtr polygons);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool UpdateBrushMesh(int brushMeshIndex, int vertexCount, IntPtr vertices, int halfEdgeCount, IntPtr halfEdges, int polygonCount, IntPtr polygons);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool IsBrushMeshIDValid(int brushMeshIndex);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool GenerateMeshDescriptions(int treeNodeID, int meshTypeCount, IntPtr meshTypes, VertexChannelFlags vertexChannelMask, out int meshDescriptionCount);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool GetMeshDescriptions(int treeNodeID, int meshDescriptionCount, IntPtr meshDescriptions);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool GetGeneratedMesh(int treeNodeID, int meshIndex, int subMeshIndex, int indexCount, IntPtr indices, int vertexCount, IntPtr positions, IntPtr tangents, IntPtr normals, IntPtr uvs, out Vec3 boundsCenter, out Vec3 boundsSize);
|
||||
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int RayCastIntoTreeMultiCount(int modelNodeID, ref Vec3 worldRayStart, ref Vec3 worldRayEnd, ref Mat4 modelLocalToWorldMatrix, int inFilterLayerParameter0, bool ignoreInvisiblePolygons, IntPtr ignoreNodeIds, int ignoreNodeIdCount);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
param(
|
||||
[string]$OutputPath = ".\experiments\generated\realtimecsg-cpp-perf-latest.jsonl",
|
||||
[string]$PluginDll = ".\experiments\source-repos\realtime-CSG-for-unity\Plugins\Editor\External\x64\RealtimeCSG[1_559].dll"
|
||||
[string]$PluginDll = ".\experiments\source-repos\realtime-CSG-for-unity\Plugins\Editor\External\x64\RealtimeCSG[1_559].dll",
|
||||
[switch]$Health,
|
||||
[switch]$DebugNative
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
|
@ -21,7 +23,14 @@ if ($LASTEXITCODE -ne 0) {
|
|||
throw "RealtimeCSG native bridge build failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
Copy-Item -LiteralPath $resolvedDll -Destination (Join-Path $releaseDir "RealtimeCSG[1_559].dll") -Force
|
||||
& (Join-Path $releaseDir "RealtimeCsgNativeBridge.exe") $resolvedOutput
|
||||
$bridgeArgs = @($resolvedOutput)
|
||||
if ($Health) {
|
||||
$bridgeArgs += "--health"
|
||||
}
|
||||
if ($DebugNative) {
|
||||
$bridgeArgs += "--debug-native"
|
||||
}
|
||||
& (Join-Path $releaseDir "RealtimeCsgNativeBridge.exe") @bridgeArgs
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Remove-Item -LiteralPath $resolvedOutput -ErrorAction SilentlyContinue
|
||||
throw "RealtimeCSG native bridge failed with exit code $LASTEXITCODE"
|
||||
|
|
|
|||
Loading…
Reference in a new issue