MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VertexFieldProcessor.cpp
Go to the documentation of this file.
2
4#include "RenderProcessor.hpp"
5
8
9namespace MayaFlux::Buffers {
10
11namespace {
12
13 /**
14 * @brief Extract a spec from the operator or fail construction.
15 *
16 * The shader is compiled by ShaderConfig's ShaderSpec constructor, which
17 * runs in the member initialiser list, so an operator that yields nothing
18 * cannot produce a usable processor. Failing here beats constructing one
19 * that logs on every cycle.
20 */
21 [[nodiscard]] Portal::Graphics::ShaderSpec require_spec(
22 const std::shared_ptr<Nodes::Network::GpuFieldOperator>& op)
23 {
24 if (!op) {
25 error<std::invalid_argument>(
28 std::source_location::current(),
29 "VertexFieldProcessor: null field operator");
30 }
31
32 auto spec = op->build_spec();
33 if (!spec.has_value()) {
34 error<std::invalid_argument>(
37 std::source_location::current(),
38 "VertexFieldProcessor: operator produced no spec. Bind at least "
39 "one field, and supply a layout carrying a position attribute, "
40 "before constructing the processor.");
41 }
42
43 return *spec;
44 }
45
46 [[nodiscard]] glm::mat4 geometry_transform(const std::shared_ptr<VKBuffer>& buffer)
47 {
48 glm::mat4 transform { 1.0F };
49 if (const auto render = buffer->get_render_processor()) {
50 if (const auto& source = render->get_geometry_transform_source()) {
51 transform = source();
52 } else if (const auto& fixed = render->get_geometry_transform()) {
53 transform = *fixed;
54 }
55 }
56
57 const float determinant = glm::determinant(glm::mat3(transform));
58 return std::isfinite(determinant) && determinant != 0.0F ? transform : glm::mat4 { 1.0F };
59 }
60
61} // namespace
62
64 std::shared_ptr<Nodes::Network::GpuFieldOperator> field_operator)
65 : ComputeProcessor(require_spec(field_operator))
66 , m_operator(std::move(field_operator))
67 , m_built_revision(m_operator->revision())
68 , m_needs_cluster_id(m_operator->needs_cluster_id())
69{
71
72 add_binding("vertices",
73 ShaderBinding(0, m_operator->get_vertex_binding(),
75
77 add_binding("cluster_id",
78 ShaderBinding(0, m_operator->get_vertex_binding() + 1,
80 }
81
82 m_params.stride_words = m_operator->get_layout().stride_bytes / 4;
83
85}
86
87void VertexFieldProcessor::set_vertex_range(uint32_t first_vertex, uint32_t vertex_count)
88{
89 m_explicit_first = first_vertex;
90 m_explicit_count = vertex_count;
91 m_range_set = true;
92}
93
100
101void VertexFieldProcessor::on_attach(const std::shared_ptr<Buffer>& buffer)
102{
103 auto vk_buf = std::dynamic_pointer_cast<VKBuffer>(buffer);
104 if (!vk_buf) {
106 "VertexFieldProcessor requires a VKBuffer");
107 return;
108 }
109
110 bind_buffer("vertices", vk_buf);
111
112 m_network_buffer = std::dynamic_pointer_cast<NetworkGeometryBuffer>(buffer);
114
116
117 m_epoch = std::chrono::steady_clock::now();
118
120 "VertexFieldProcessor attached: {} bytes, stride {} words, {} bound fields",
121 vk_buf->get_size_bytes(),
123 m_operator->binding_count());
124}
125
127{
128 return std::chrono::duration<float>(
129 std::chrono::steady_clock::now() - m_epoch)
130 .count();
131}
132
134 const std::shared_ptr<VKBuffer>& buffer) const
135{
136 const uint32_t stride_bytes = m_params.stride_words * 4;
137 if (stride_bytes == 0)
138 return 0;
139
140 return static_cast<uint32_t>(buffer->get_size_bytes() / stride_bytes);
141}
142
144{
145 if (m_operator->revision() == m_built_revision)
146 return true;
147
148 auto spec = m_operator->build_spec();
149 if (!spec.has_value()) {
151 "VertexFieldProcessor: operator no longer yields a spec after rebind. "
152 "Dispatch suppressed; the previous pipeline is not reused because it "
153 "no longer describes the bindings.");
154 m_built_revision = m_operator->revision();
155 return false;
156 }
157
158 set_config(ShaderConfig(*spec));
159
160 add_binding("vertices",
161 ShaderBinding(0, m_operator->get_vertex_binding(),
163
164 m_needs_cluster_id = m_operator->needs_cluster_id();
165 if (m_needs_cluster_id) {
166 add_binding("cluster_id",
167 ShaderBinding(0, m_operator->get_vertex_binding() + 1,
169 }
170
171 m_params.stride_words = m_operator->get_layout().stride_bytes / 4;
172 m_built_revision = m_operator->revision();
173
175 "VertexFieldProcessor: rebuilt for revision {} ({} bound fields)",
176 m_built_revision, m_operator->binding_count());
177
178 return ensure_cluster_binding();
179}
180
183 const std::shared_ptr<VKBuffer>& buffer)
184{
185 if (!buffer)
186 return false;
187
188 if (!sync_revision())
189 return false;
190
191 const uint32_t capacity = buffer_capacity(buffer);
192 if (capacity == 0)
193 return false;
194
195 if (m_range_set) {
196 if (m_explicit_first >= capacity) {
198 "VertexFieldProcessor: range starts at {} but the buffer holds {} "
199 "records. Dispatch suppressed.",
200 m_explicit_first, capacity);
201 return false;
202 }
203
206 } else {
208 m_params.vertex_count = capacity;
209 }
210
211 if (m_params.vertex_count == 0)
212 return false;
213
215 m_params.transform = geometry_transform(buffer);
216
218 "VertexFieldProcessor dispatch: [{}, {}) of {} records, stride {} words, t={:.3f}",
221 capacity,
223 m_params.time);
224
225 if (m_operator->uses_params()) {
226 const glm::vec4& params = m_operator->get_params();
227 set_push_constant_data(ParamsBlock { m_params, { params.x, params.y, params.z, params.w } });
228 } else {
230 }
231 return true;
232}
233
235 const std::shared_ptr<VKBuffer>& /*buffer*/)
236{
237 const auto& dispatch = get_dispatch_config();
238 const uint32_t local_x = std::max(1U, dispatch.workgroup_x);
239 const uint32_t groups = (m_params.vertex_count + local_x - 1) / local_x;
240
241 return { std::max(1U, groups), 1U, 1U };
242}
243
245{
246 if (!m_needs_cluster_id) {
247 return true;
248 }
249
250 if (!m_network_buffer) {
252 "VertexFieldProcessor: operator has a cluster-scoped binding but the "
253 "attached buffer is not a NetworkGeometryBuffer, so hash_cluster_id "
254 "has nowhere to live. Dispatch suppressed.");
255 return false;
256 }
257
258 if (!m_network_buffer->ensure_cluster_ids()) {
260 "VertexFieldProcessor: operator has a cluster-scoped binding but "
261 "hash_cluster_id could not be derived (no primary GraphicsOperator, "
262 "or it reports zero points). Dispatch suppressed.");
263 return false;
264 }
265
266 return true;
267}
268
270{
272 return;
273 }
274
275 auto& foundry = Portal::Graphics::get_shader_foundry();
276 const auto handle = m_network_buffer->read_state_handle("hash_cluster_id");
277 const auto bytes = m_network_buffer->get_state_bytes("hash_cluster_id");
278
279 foundry.update_descriptor_buffer(
280 m_descriptor_set_ids[0], m_operator->get_vertex_binding() + 1,
281 vk::DescriptorType::eStorageBuffer, handle, 0, bytes);
282}
283
284} // namespace MayaFlux::Buffers
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_RT_ERROR(comp, ctx,...)
#define MF_RT_TRACE(comp, ctx,...)
std::shared_ptr< NetworkGeometryBuffer > buffer
virtual void on_attach(const std::shared_ptr< Buffer > &)
Called when this processor is attached to a buffer.
const ShaderDispatchConfig & get_dispatch_config() const
Get current dispatch configuration.
Specialized ShaderProcessor for Compute Pipelines.
void set_push_constant_data(const T &data)
Update push constant data (type-safe)
void add_binding(const std::string &descriptor_name, const ShaderBinding &binding)
Add descriptor binding configuration.
void set_config(const ShaderConfig &config)
Update entire configuration.
void bind_buffer(const std::string &descriptor_name, const std::shared_ptr< VKBuffer > &buffer)
Bind a VKBuffer to a named shader descriptor.
std::vector< Portal::Graphics::DescriptorSetID > m_descriptor_set_ids
bool ensure_cluster_binding()
Ensure the backing hash_cluster_id state field exists, when m_needs_cluster_id is true.
bool on_before_execute(Portal::Graphics::CommandBufferID cmd_id, const std::shared_ptr< VKBuffer > &buffer) override
Rebuild on a stale revision, resolve the range, write push constants.
std::chrono::steady_clock::time_point m_epoch
std::shared_ptr< NetworkGeometryBuffer > m_network_buffer
std::array< uint32_t, 3 > calculate_dispatch_size(const std::shared_ptr< VKBuffer > &buffer) override
One thread per record in the resolved range.
void clear_vertex_range()
Drop an explicit range and cover the whole buffer again.
uint32_t buffer_capacity(const std::shared_ptr< VKBuffer > &buffer) const
Records the buffer can hold at the operator's stride.
std::shared_ptr< Nodes::Network::GpuFieldOperator > m_operator
void set_vertex_range(uint32_t first_vertex, uint32_t vertex_count)
Restrict the dispatch to a slice of the buffer.
bool sync_revision()
Recompile and rebuild the pipeline when the operator has changed.
void on_descriptors_created() override
Write the cluster_id descriptor when the operator needs one.
void on_attach(const std::shared_ptr< Buffer > &buffer) override
Called when this processor is attached to a buffer.
VertexFieldProcessor(std::shared_ptr< Nodes::Network::GpuFieldOperator > field_operator)
float elapsed() const
Seconds since construction, the value bound to the time parameter.
@ GRAPHICS_BACKEND
Standard graphics processing backend configuration.
@ BufferProcessing
Buffer processing (Buffers::BufferManager, processing chains)
@ Buffers
Buffers, Managers, processors and processing chains.
Source source()
Begin a Source chain.
Definition Plot.hpp:127
@ STORAGE
Large arrays or buffers the shader may write (SSBO)
MAYAFLUX_API ShaderFoundry & get_shader_foundry()
Get the global shader compiler instance.
Describes how a VKBuffer binds to a shader descriptor.
Push constant block when the operator carries parametric fields.