MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VolumeSurfaceProcessor.hpp
Go to the documentation of this file.
1#pragma once
2
5
6namespace MayaFlux::Buffers {
7
8class VolumeGridBuffer;
9
10/**
11 * @class VolumeSurfaceProcessor
12 * @brief ComputeProcessor resampling one scalar field of a VolumeGridBuffer
13 * into the corner grid layout the marching cubes stage consumes.
14 *
15 * VolumeGridBuffer stores one value per cell centre; mc_emit.comp reads one
16 * value per lattice corner, of which there are (res+1) on each axis. This
17 * stage bridges the two by trilinear resampling, which also decouples the
18 * extraction resolution from the simulation resolution: surfacing a 128
19 * cube simulation at 64 costs a quarter of the triangles.
20 *
21 * Marching cubes crosses at iso_level with the interior conventionally
22 * negative, while a density field is high inside. The shader therefore
23 * writes threshold minus density, placing the surface where density equals
24 * threshold with negative interior, so the consuming SDFMeshProcessor runs
25 * at iso_level zero.
26 *
27 * Owns the corner grid buffer. SDFPrepProcessor is not needed on this path:
28 * every corner is written each cycle, and SDFMeshProcessor zeroes the
29 * atomic counter itself.
30 *
31 * Chain order:
32 * flat[n] - the volume simulation stages
33 * flat[n+1] - VolumeSurfaceProcessor (field -> corner grid)
34 * flat[n+2] - SDFMeshProcessor (corner grid -> vertices)
35 * final - RenderProcessor
36 *
37 * The attached buffer must be the VolumeGridBuffer for the field read to
38 * resolve, but the vertex output belongs to whichever buffer
39 * SDFMeshProcessor is attached to.
40 */
41class MAYAFLUX_API VolumeSurfaceProcessor : public ComputeProcessor {
42public:
43 /**
44 * @struct SurfaceParams
45 * @brief Push constant block the resample shader receives.
46 *
47 * Lattice dimensions occupy the leading fields, matching the convention
48 * AdvectProcessor::AdvectParams establishes for volume stages,
49 * followed by the extraction resolution and the surface threshold.
50 */
52 uint32_t width;
53 uint32_t height;
54 uint32_t depth;
55 uint32_t pad0;
56 uint32_t res_x;
57 uint32_t res_y;
58 uint32_t res_z;
59 float threshold;
60 };
61
62 static_assert(sizeof(SurfaceParams) % 16 == 0);
63
64 /**
65 * @brief Construct a surface extraction bridge.
66 * @param volume Volume whose field is resampled.
67 * @param field_name Name of the scalar field resampled. Must have
68 * stride sizeof(float).
69 * @param lattice Extraction lattice, normally the volume's own lattice
70 * resampled to a different resolution over the same bounds.
71 * @param threshold Field value the surface is placed at.
72 * @param shader_path Path to the compute shader.
73 */
75 std::shared_ptr<VolumeGridBuffer> volume,
76 std::string field_name,
77 Kinesis::Lattice3D lattice,
78 float threshold,
79 const std::string& shader_path = "volume_to_sdf_grid.comp.spv");
80
81 ~VolumeSurfaceProcessor() override = default;
82
83 /**
84 * @brief The corner grid this stage writes.
85 *
86 * Pass to SDFMeshProcessor's externally-owned-buffers constructor.
87 * Valid immediately after construction.
88 */
89 [[nodiscard]] std::shared_ptr<VKBuffer> grid_buf() const { return m_grid_buf; }
90
91 /**
92 * @brief Set the field value the surface is placed at.
93 * @param threshold Surface level. Takes effect next cycle.
94 */
95 void set_threshold(float threshold);
96
97 /** @brief Field value the surface is placed at. */
98 [[nodiscard]] float get_threshold() const { return m_threshold; }
99
100 /** @brief Extraction cell count along X. */
101 [[nodiscard]] uint32_t get_res_x() const { return m_lattice.resolution.x; }
102
103 /** @brief Extraction cell count along Y. */
104 [[nodiscard]] uint32_t get_res_y() const { return m_lattice.resolution.y; }
105
106 /** @brief Extraction cell count along Z. */
107 [[nodiscard]] uint32_t get_res_z() const { return m_lattice.resolution.z; }
108
109 /** @brief The extraction lattice. */
110 [[nodiscard]] const Kinesis::Lattice3D& get_lattice() const { return m_lattice; }
111
112 /**
113 * @brief Corner count of the grid, one greater per axis than the
114 * extraction resolution. The grid buffer holds this many floats.
115 */
116 [[nodiscard]] uint32_t corner_count() const noexcept
117 {
118 return static_cast<uint32_t>(m_lattice.corner_count());
119 }
120
121 /**
122 * @brief Upper bound on vertices mc_emit can produce at this resolution.
123 *
124 * Fifteen per voxel, the maximum five triangles the triangle table
125 * encodes. Size the vertex buffer to at least this many vertices:
126 * mc_emit allocates slots via atomicAdd without a capacity check.
127 */
128 [[nodiscard]] uint32_t worst_case_vertices() const noexcept
129 {
130 return static_cast<uint32_t>(m_lattice.cell_count()) * 15U;
131 }
132
133protected:
134 /**
135 * @brief Validate the named field, bind the grid, and size the dispatch.
136 * @param buffer The attached buffer, expected to be a VolumeGridBuffer.
137 */
138 void on_attach(const std::shared_ptr<Buffer>& buffer) override;
139
140 /**
141 * @brief Write the field descriptor for the current slot assignment.
142 */
143 void on_descriptors_created() override;
144
145 /**
146 * @brief Reject buffers that are not VolumeGridBuffer.
147 * @param cmd_id Command buffer this cycle's dispatch will be recorded into.
148 * @param buffer The attached buffer, received as VKBuffer.
149 * @return True if the attached buffer is a VolumeGridBuffer.
150 */
151 bool on_before_execute(Portal::Graphics::CommandBufferID cmd_id, const std::shared_ptr<VKBuffer>& buffer) override;
152
153 /**
154 * @brief Write the field descriptor for this cycle's slot assignment,
155 * then run the normal shader processing path.
156 *
157 * The field's read slot changes whenever an upstream stage swaps it, so
158 * the binding is rewritten every cycle rather than once.
159 */
160 void processing_function(const std::shared_ptr<Buffer>& buffer) override;
161
162private:
163 /**
164 * @brief Issue the direct ShaderFoundry descriptor write for the field
165 * read slot. The grid buffer is a real VKBuffer and binds through
166 * the normal bind_buffer path.
167 */
168 void write_field_descriptor();
169
170 /**
171 * @brief Size the push constant block to at least SurfaceParams and
172 * write the lattice dimensions, extraction resolution, and
173 * threshold.
174 */
175 void write_params();
176
177 /**
178 * @brief Allocate the corner grid buffer for the current resolution.
179 */
180 void rebuild_grid_buffer();
181
182 std::string m_field_name;
185
186 std::shared_ptr<VKBuffer> m_grid_buf;
187 std::shared_ptr<VolumeGridBuffer> m_volume; ///< The attached volume, cached for the descriptor write.
188};
189
190} // namespace MayaFlux::Buffers
float threshold
Specialized ShaderProcessor for Compute Pipelines.
uint32_t get_res_z() const
Extraction cell count along Z.
uint32_t get_res_x() const
Extraction cell count along X.
std::shared_ptr< VolumeGridBuffer > m_volume
The attached volume, cached for the descriptor write.
const Kinesis::Lattice3D & get_lattice() const
The extraction lattice.
uint32_t get_res_y() const
Extraction cell count along Y.
float get_threshold() const
Field value the surface is placed at.
std::shared_ptr< VKBuffer > grid_buf() const
The corner grid this stage writes.
uint32_t worst_case_vertices() const noexcept
Upper bound on vertices mc_emit can produce at this resolution.
uint32_t corner_count() const noexcept
Corner count of the grid, one greater per axis than the extraction resolution.
ComputeProcessor resampling one scalar field of a VolumeGridBuffer into the corner grid layout the ma...
Push constant block the resample shader receives.
A regular subdivision of an AABB3D into a cell count per axis.
Definition Lattice.hpp:25