VisuTwin Canvas
C++ 3D Engine — Metal Backend
Loading...
Searching...
No Matches
metalParticleComputePass.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 particle advection.
5//
6// Advects millions of particles through a 3D velocity field entirely
7// on the GPU using a Metal compute kernel. The output particle buffer
8// can be rendered directly as point primitives.
9//
10// Pipeline (per frame):
11// 1. Encode compute command: bind particle buffer, velocity 3D texture,
12// uniforms, dispatch one thread per particle.
13// 2. Commit command buffer. Metal's implicit barriers ensure the
14// vertex read in the subsequent render pass sees the compute output.
15//
16// Double-buffered particle storage avoids read/write hazards when
17// the previous frame's render pass hasn't finished yet.
18//
19// Follows the MetalLICPass / MetalTaaPass decomposition pattern:
20// - Embedded MSL source as string literal
21// - Lazy resource creation
22// - Friend access to MetalGraphicsDevice for command queue
23//
24// Custom shader — no upstream GLSL equivalent exists for GPU
25// particle advection.
26//
27#pragma once
28
29#include <cstdint>
30#include <memory>
31#include <Metal/Metal.hpp>
32
33namespace visutwin::canvas
34{
36 class Texture;
37
39 struct alignas(16) ParticleComputeUniforms
40 {
41 float domainMin[3];
42 float dt;
43 float domainMax[3];
44 uint32_t particleCount;
45 float invDomainSize[3];
46 float time;
47 float speedMin;
48 float speedMax;
49 float fadeStart;
50 float padding;
51 };
52 static_assert(sizeof(ParticleComputeUniforms) == 64,
53 "ParticleComputeUniforms must be 64 bytes");
54
63 {
64 public:
67
68 // Non-copyable
71
74 void initialize(uint32_t maxParticles);
75
79 void uploadParticles(const void* data, uint32_t count);
80
85 void advect(Texture* velocityTexture, const ParticleComputeUniforms& uniforms);
86
89 [[nodiscard]] MTL::Buffer* currentParticleBuffer() const;
90
92 [[nodiscard]] uint32_t maxParticles() const { return maxParticles_; }
93
95 [[nodiscard]] bool isInitialized() const { return initialized_; }
96
97 private:
98 void ensureResources();
99
100 MetalGraphicsDevice* device_;
101
102 MTL::ComputePipelineState* computePipeline_ = nullptr;
103 MTL::Buffer* particleBufferA_ = nullptr; // ping
104 MTL::Buffer* particleBufferB_ = nullptr; // pong
105 MTL::Buffer* uniformBuffer_ = nullptr;
106 MTL::SamplerState* fieldSampler_ = nullptr;
107
108 uint32_t maxParticles_ = 0;
109 uint32_t currentBuffer_ = 0; // 0 = A is current, 1 = B
110 bool initialized_ = false;
111 bool resourcesReady_ = false;
112 };
113
114} // namespace visutwin::canvas
uint32_t maxParticles() const
Get the particle count (set during initialize).
bool isInitialized() const
True once initialize() has been called successfully.
MetalParticleComputePass(const MetalParticleComputePass &)=delete
void uploadParticles(const void *data, uint32_t count)
void advect(Texture *velocityTexture, const ParticleComputeUniforms &uniforms)
MetalParticleComputePass & operator=(const MetalParticleComputePass &)=delete
GPU texture resource supporting 2D, cubemap, volume, and array formats with mipmap management.
Definition texture.h:57
Uniform data uploaded to the compute kernel each frame.
float domainMin[3]
Field domain minimum (world space).
float speedMax
Maximum speed for TF mapping.
float domainMax[3]
Field domain maximum (world space).
float time
Current simulation time (for seeding noise).
float speedMin
Minimum speed for TF mapping.
float invDomainSize[3]
1 / (domainMax - domainMin).
float fadeStart
Age ratio where alpha fade begins.
uint32_t particleCount
Number of active particles.