VisuTwin Canvas
C++ 3D Engine — Metal Backend
Loading...
Searching...
No Matches
metalInstanceCullPass.h
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025-2026 Arnis Lektauers
3//
4// Metal compute pass for GPU frustum culling of instanced draws.
5//
6// Two-kernel pipeline:
7// Kernel 1 (instanceCull): Each thread tests one instance's bounding sphere
8// against 6 frustum planes. Visible instances are compacted into an output
9// buffer via atomic_fetch_add.
10// Kernel 2 (writeIndirectArgs): A single thread reads the atomic counter and
11// writes MTLDrawIndexedPrimitivesIndirectArguments (20 bytes).
12//
13// Follows the MetalMarchingCubesPass pattern:
14// - Embedded MSL source as string literal
15// - Lazy resource creation
16// - Friend access to MetalGraphicsDevice for command queue
17//
18// Custom shader -- no upstream GLSL equivalent exists for GPU instance culling.
19// Upstream GPU culling is GSplat-specific; this is a general-purpose extension.
20//
21#pragma once
22
23#include <cstdint>
24#include <Metal/Metal.hpp>
25
26namespace visutwin::canvas
27{
29
32 struct alignas(16) InstanceCullParams
33 {
34 float frustumPlanes[6][4];
36 uint32_t instanceCount;
37 uint32_t indexCount;
38 uint32_t indexStart;
39 int32_t baseVertex;
40 uint32_t baseInstance;
41 float _pad[2];
42 };
43 static_assert(sizeof(InstanceCullParams) == 128,
44 "InstanceCullParams must be 128 bytes to match MSL layout");
45
56 {
57 public:
60
61 // Non-copyable
64
67 void reserve(uint32_t maxInstances);
68
75 void cull(MTL::Buffer* inputBuffer, const InstanceCullParams& params);
76
79 [[nodiscard]] MTL::Buffer* compactedBuffer() const { return compactedBuffer_; }
80
83 [[nodiscard]] MTL::Buffer* indirectArgsBuffer() const { return indirectArgsBuffer_; }
84
86 [[nodiscard]] bool isReady() const { return resourcesReady_; }
87
90 static void extractFrustumPlanes(const float* vpMatrix4x4ColMajor, float outPlanes[6][4]);
91
92 private:
93 void ensureResources();
94
95 MetalGraphicsDevice* device_;
96
97 // Compute pipelines
98 MTL::ComputePipelineState* cullPipeline_ = nullptr;
99 MTL::ComputePipelineState* writeArgsPipeline_ = nullptr;
100
101 // Buffers (reused across frames)
102 MTL::Buffer* compactedBuffer_ = nullptr; // Visible instances (maxInstances * 80)
103 MTL::Buffer* indirectArgsBuffer_ = nullptr; // 20 bytes (MTLDrawIndexedPrimitivesIndirectArguments)
104 MTL::Buffer* counterBuffer_ = nullptr; // atomic_uint (4 bytes)
105 MTL::Buffer* uniformBuffer_ = nullptr; // InstanceCullParams (128 bytes)
106
107 uint32_t maxInstances_ = 0;
108
109 bool resourcesReady_ = false;
110 };
111
112} // namespace visutwin::canvas
static void extractFrustumPlanes(const float *vpMatrix4x4ColMajor, float outPlanes[6][4])
MetalInstanceCullPass & operator=(const MetalInstanceCullPass &)=delete
MetalInstanceCullPass(MetalGraphicsDevice *device)
bool isReady() const
True once ensureResources() has succeeded.
void cull(MTL::Buffer *inputBuffer, const InstanceCullParams &params)
MetalInstanceCullPass(const MetalInstanceCullPass &)=delete
uint32_t indexCount
Mesh Primitive.count -> indirect args.
float frustumPlanes[6][4]
6 planes: (nx, ny, nz, d). dot(n,p)+d >= 0 = inside.
uint32_t indexStart
Mesh Primitive.base -> indirect args.
int32_t baseVertex
Mesh Primitive.baseVertex -> indirect args.
float boundingSphereRadius
Bounding sphere radius for each instance.
uint32_t instanceCount
Total input instances.