MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VolumeFieldProcessor.cpp
Go to the documentation of this file.
3
4namespace MayaFlux::Buffers {
5
6namespace {
7 constexpr uint32_t k_workgroup_x = 8;
8 constexpr uint32_t k_workgroup_y = 8;
9 constexpr uint32_t k_workgroup_z = 4;
10
11 /**
12 * @struct LatticePrefix
13 * @brief Byte layout of the parameter block's leading eight words.
14 */
15 struct LatticePrefix {
16 uint32_t width;
17 uint32_t height;
18 uint32_t depth;
19 uint32_t word3;
23 float word7;
24 };
25
26 constexpr size_t k_word3_offset = offsetof(LatticePrefix, word3);
27 constexpr size_t k_word7_offset = offsetof(LatticePrefix, word7);
28 constexpr size_t k_prefix_size = sizeof(LatticePrefix);
29}
30
32 std::vector<FieldBinding> bindings, const std::string& shader_path)
33 : ComputeProcessor(shader_path, k_workgroup_x)
34 , m_bindings(std::move(bindings))
35{
37}
38
40 std::vector<FieldBinding> bindings, const Portal::Graphics::ShaderSpec& spec)
41 : ComputeProcessor(spec)
42 , m_bindings(std::move(bindings))
43{
45}
46
48{
49 for (const auto& entry : m_bindings) {
50 m_config.bindings[entry.name]
51 = ShaderBinding(0, entry.binding, vk::DescriptorType::eStorageBuffer);
52 }
53}
54
56{
57 m_swap_fields.push_back(std::move(field));
58}
59
61{
62 if (m_config.push_constant_size < size) {
64 }
65}
66
68{
69 for (const auto& entry : m_bindings) {
70 if (!m_volume->has_field(entry.field)) {
72 "VolumeFieldProcessor: volume has no field '{}' for binding '{}'",
73 entry.field, entry.name);
74 return false;
75 }
76 }
77
78 for (const auto& entry : m_bindings) {
79 if (entry.access != FieldAccess::WRITE) {
80 continue;
81 }
82
83 const bool also_read = std::ranges::any_of(m_bindings, [&](const FieldBinding& other) {
84 return other.access == FieldAccess::READ && other.field == entry.field;
85 });
86
87 if (!also_read) {
88 continue;
89 }
90
91 if (m_volume->read_handle(entry.field) == m_volume->write_handle(entry.field)) {
93 "VolumeFieldProcessor: field '{}' is bound for both read and write "
94 "but is not double-buffered",
95 entry.field);
96 return false;
97 }
98 }
99
100 return true;
101}
102
104{
105 const glm::uvec3 res = m_volume->get_lattice().resolution;
106
107 set_workgroup_size(k_workgroup_x, k_workgroup_y, k_workgroup_z);
110 (res.x + k_workgroup_x - 1) / k_workgroup_x,
111 (res.y + k_workgroup_y - 1) / k_workgroup_y,
112 (res.z + k_workgroup_z - 1) / k_workgroup_z);
113}
114
115void VolumeFieldProcessor::on_attach(const std::shared_ptr<Buffer>& buffer)
116{
118
119 m_volume = std::dynamic_pointer_cast<VolumeGridBuffer>(buffer);
120 if (!m_volume) {
121 return;
122 }
123
124 if (!validate_fields()) {
125 m_volume.reset();
126 return;
127 }
128
130 reserve_param_size(k_prefix_size);
132
134}
135
137{
138 if (!m_volume) {
139 return;
140 }
141
142 auto& data = get_push_constant_data();
143 if (data.size() < m_config.push_constant_size) {
144 data.resize(m_config.push_constant_size);
145 }
146
147 const Kinesis::Lattice3D& lattice = m_volume->get_lattice();
148 const glm::vec3 cell = lattice.cell_size();
149
150 uint32_t preserved_3 = 0;
151 float preserved_7 = 0.0F;
152 std::memcpy(&preserved_3, data.data() + k_word3_offset, sizeof(uint32_t));
153 std::memcpy(&preserved_7, data.data() + k_word7_offset, sizeof(float));
154
155 const LatticePrefix prefix {
156 .width = lattice.resolution.x,
157 .height = lattice.resolution.y,
158 .depth = lattice.resolution.z,
159 .word3 = preserved_3,
160 .cell_size_x = cell.x,
161 .cell_size_y = cell.y,
162 .cell_size_z = cell.z,
163 .word7 = preserved_7,
164 };
165
166 std::memcpy(data.data(), &prefix, k_prefix_size);
167}
168
170{
171 auto& data = get_push_constant_data();
172 if (data.size() < k_prefix_size) {
173 return;
174 }
175
176 std::memcpy(data.data() + k_word3_offset, &value, sizeof(uint32_t));
177}
178
180{
181 auto& data = get_push_constant_data();
182 if (data.size() < k_prefix_size) {
183 return;
184 }
185
186 std::memcpy(data.data() + k_word7_offset, &value, sizeof(float));
187}
188
189void VolumeFieldProcessor::write_param_tail(size_t offset, const void* data_in, size_t size)
190{
191 if (offset < k_prefix_size) {
193 "VolumeFieldProcessor: parameter offset {} overlaps the prefix", offset);
194 return;
195 }
196
197 auto& data = get_push_constant_data();
198 if (data.size() < offset + size) {
199 data.resize(offset + size);
200 }
201
202 std::memcpy(data.data() + offset, data_in, size);
203}
204
206{
207 if (!m_volume || m_descriptor_set_ids.empty()) {
208 return;
209 }
210
211 auto& foundry = Portal::Graphics::get_shader_foundry();
212
213 for (const auto& entry : m_bindings) {
214 const vk::Buffer handle = entry.access == FieldAccess::READ
215 ? m_volume->read_handle(entry.field)
216 : m_volume->write_handle(entry.field);
217
218 foundry.update_descriptor_buffer(
219 m_descriptor_set_ids[0], entry.binding, vk::DescriptorType::eStorageBuffer,
220 handle, 0, m_volume->get_field_bytes(entry.field));
221 }
222}
223
228
229void VolumeFieldProcessor::processing_function(const std::shared_ptr<Buffer>& buffer)
230{
233 }
234
236
237 if (!m_volume || !wants_swap()) {
238 return;
239 }
240
241 for (const auto& field : m_swap_fields) {
242 m_volume->swap_field(field);
243 }
244}
245
248 const std::shared_ptr<VKBuffer>& buffer)
249{
250 return m_volume && std::dynamic_pointer_cast<VolumeGridBuffer>(buffer) != nullptr;
251}
252
253} // namespace MayaFlux::Buffers
#define MF_ERROR(comp, ctx,...)
float value
float offset
uint32_t width
uint32_t height
float cell_size_y
uint32_t word3
float cell_size_x
float cell_size_z
uint32_t depth
float word7
virtual void on_attach(const std::shared_ptr< Buffer > &)
Called when this processor is attached to a buffer.
virtual void processing_function(const std::shared_ptr< Buffer > &buffer)=0
The core processing function that must be implemented by derived classes.
void set_workgroup_size(uint32_t x, uint32_t y=1, uint32_t z=1)
Set workgroup size (should match shader local_size)
void set_dispatch_mode(ShaderDispatchConfig::DispatchMode mode)
Set dispatch mode.
void set_manual_dispatch(uint32_t x, uint32_t y=1, uint32_t z=1)
Set manual dispatch group counts.
Specialized ShaderProcessor for Compute Pipelines.
const std::vector< uint8_t > & get_push_constant_data() const
Get current push constant data.
void set_push_constant_size()
Set push constant size from type.
bool are_descriptors_ready() const
Check if descriptors are initialized.
std::vector< Portal::Graphics::DescriptorSetID > m_descriptor_set_ids
void write_param_tail(size_t offset, const void *data, size_t size)
Write subclass parameters past the shared prefix.
bool validate_fields()
Check every named field exists and that read-write fields carry two slots.
void reserve_param_size(size_t size)
Raise the push constant block to at least this size.
void write_lattice_word7(float value)
Write word seven of the parameter prefix, at byte offset 28.
void size_dispatch()
Size the manual dispatch to cover the lattice.
VolumeFieldProcessor(std::vector< FieldBinding > bindings, const std::string &shader_path)
Construct from a shader path.
void processing_function(const std::shared_ptr< Buffer > &buffer) override
Rewrite field descriptors, run the shader, then swap.
void write_lattice_word3(uint32_t value)
Write word three of the shared prefix.
std::shared_ptr< VolumeGridBuffer > m_volume
The attached volume, null until on_attach validates it.
void register_bindings()
Register the binding table into m_config.bindings.
void on_attach(const std::shared_ptr< Buffer > &buffer) override
Cache and validate the volume, size the dispatch, write the shared parameter prefix,...
bool on_before_execute(Portal::Graphics::CommandBufferID cmd_id, const std::shared_ptr< VKBuffer > &buffer) override
Reject buffers that are not the validated volume.
void write_field_descriptors()
Issue descriptor writes for every entry in the binding table.
virtual void on_volume_ready()
Hook for subclass parameters, called at the end of on_attach.
@ READ
Resolves to VolumeGridBuffer::read_handle.
@ WRITE
Resolves to VolumeGridBuffer::write_handle.
void write_lattice_params()
Write the lattice dimensions and cell size into the staged push constant block, preserving word three...
virtual bool wants_swap() const
Whether this cycle's dispatch should be followed by a swap.
void add_swap_field(std::string field)
Register a field to swap after each dispatch.
void on_descriptors_created() override
Write every binding in the table for the current slot assignment.
@ BufferProcessing
Buffer processing (Buffers::BufferManager, processing chains)
@ Buffers
Buffers, Managers, processors and processing chains.
MAYAFLUX_API ShaderFoundry & get_shader_foundry()
Get the global shader compiler instance.
Describes how a VKBuffer binds to a shader descriptor.
std::unordered_map< std::string, ShaderBinding > bindings
std::string field
Field name on the attached volume.
One shader binding and the field slot it draws from.
glm::vec3 cell_size() const noexcept
Edge length of one cell along each axis.
Definition Lattice.hpp:30
glm::uvec3 resolution
Cell count per axis. Zero on any axis is invalid.
Definition Lattice.hpp:26
A regular subdivision of an AABB3D into a cell count per axis.
Definition Lattice.hpp:25
Complete declarative description of a generated compute shader.