MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches

◆ build_spec()

std::optional< Portal::Graphics::ShaderSpec > MayaFlux::Nodes::Network::GpuFieldOperator::build_spec ( ) const

Assemble the compute spec for the current bindings.

Emits one GLSL function per distinct bound field, then a kernel that guards against the dispatch tail, reads the position once, and writes every touched target in a single pass. Fields sharing a target sum before the write; NORMAL and TANGENT normalise after the sum. POSITION accumulates onto the existing vertex, every other target is assigned, matching FieldOperator.

The kernel addresses a range rather than the whole buffer, since a NetworkGeometryBuffer aggregates one slice per producing operator. The range and the record stride arrive as push constants written by the processor, so neither the caller nor the field author offsets anything, and a layout or slice change needs no recompile. Attribute offsets are baked, since those describe the record the spec was built against.

Cached until a bind, unbind or configuration change invalidates it. Returns nullopt when nothing is bound or the layout carries no position attribute.

A cluster_id SSBO binding and the read that feeds it are emitted only when needs_cluster_id() is true: an operator with no cluster-scoped binding gets exactly the shader it always has, unchanged.

Definition at line 456 of file GpuFieldOperator.cpp.

457{
458 if (m_spec_cache.has_value())
459 return m_spec_cache;
460
461 if (m_bindings.empty())
462 return std::nullopt;
463
464 const auto position = resolve_target(Kinesis::FieldTarget::POSITION);
465 if (!position.has_value()) {
467 "GpuFieldOperator: layout carries no position attribute. Fields are "
468 "functions of position and cannot be evaluated without one.");
469 return std::nullopt;
470 }
471
472 const bool needs_cluster = needs_cluster_id();
473
474 Portal::Graphics::ShaderSpec::Assemble assemble;
475 assemble.start_binding(m_vertex_binding)
478
479 if (needs_cluster) {
480 assemble.ssbo("cluster_id", Portal::Graphics::BindingDirection::Input,
482 }
483
484 assemble.pc("first_vertex", Kakshya::GpuDataFormat::UINT32)
485 .pc("vertex_count", Kakshya::GpuDataFormat::UINT32)
486 .pc("stride_words", Kakshya::GpuDataFormat::UINT32)
488 .workgroup(m_workgroup_size);
489
490 std::vector<std::string_view> emitted;
491 for (const auto& b : m_bindings) {
492 const auto seen = std::ranges::find(emitted, b.source.name);
493 if (seen != emitted.end())
494 continue;
495 emitted.push_back(b.source.name);
496 assemble.function(b.source.return_type, b.source.name,
497 b.source.params, b.source.body);
498 }
499
500 const uint32_t pw = position->first;
501 std::string body;
502 body += " if (i >= vertex_count) { return; }\n";
503 body += " uint b = (first_vertex + i) * stride_words;\n";
504 body += " vec3 p = vec3(vertices[b + " + word(pw)
505 + "], vertices[b + " + word(pw + 1)
506 + "], vertices[b + " + word(pw + 2) + "]);\n";
507
508 if (needs_cluster) {
509 body += " uint my_cluster = cluster_id[first_vertex + i];\n";
510 }
511
513 const bool touched = std::ranges::any_of(m_bindings,
514 [t](const Binding& b) { return has_flag(b.targets, t); });
515 if (!touched)
516 continue;
517
518 const auto slot = resolve_target(t);
519 if (!slot.has_value())
520 continue;
521
522 const auto [w, components] = *slot;
523 const std::string acc = std::string("acc_") + target_name(t);
524
525 body += "\n " + std::string(glsl_type(components)) + " " + acc
526 + " = " + glsl_zero(components) + ";\n";
527
528 for (const auto& b : m_bindings) {
529 if (!has_flag(b.targets, t))
530 continue;
531
532 const std::string call = std::string(b.source.name)
533 + (b.temporal ? "(p, time)" : "(p)");
534
535 if (b.cluster.has_value()) {
536 body += " if (my_cluster == " + word(*b.cluster) + ") { "
537 + acc + " += " + call + "; }\n";
538 } else {
539 body += " " + acc + " += " + call + ";\n";
540 }
541 }
542
544 const std::string len = std::string("len_") + target_name(t);
545 body += " float " + len + " = length(" + acc + ");\n";
546 body += " " + acc + " = " + len + " > 1e-6f ? " + acc + " / "
547 + len + " : " + acc + ";\n";
548 }
549
550 const char* assign = target_accumulates(t) ? " += " : " = ";
551 for (uint32_t k = 0; k < components; ++k) {
552 body += " vertices[b + " + word(w + k) + "]" + assign
553 + acc + (components == 1 ? "" : k_swizzle[k]) + ";\n";
554 }
555 }
556
557 assemble.kernel(Portal::Graphics::KernelSource { .body = std::move(body) });
558
559 m_spec_cache = assemble.build();
560 return m_spec_cache;
561}
#define MF_ERROR(comp, ctx,...)
size_t b
float k
std::optional< Portal::Graphics::ShaderSpec > m_spec_cache
std::optional< std::pair< uint32_t, uint32_t > > resolve_target(FieldTarget target) const
Locate a target's word offset and component count in the layout.
bool needs_cluster_id() const
Whether build_spec() needs a per-vertex cluster id this cycle.
@ NodeProcessing
Node graph processing (Nodes::NodeGraphManager)
@ Nodes
DSP Generator and Filter Nodes, graph pipeline, node management.
@ Kinesis
General mathematical and physics algorithns.
FieldTarget
What a Tendency drives when applied to a vertex record.
constexpr std::array< FieldTarget, 6 > k_field_targets
Single-bit targets known to this version, in vertex layout order.

References b, MayaFlux::Portal::Graphics::KernelSource::body, MayaFlux::Portal::Graphics::ShaderSpec::Assemble::build(), MayaFlux::Kakshya::FLOAT32, MayaFlux::Portal::Graphics::ShaderSpec::Assemble::function(), MayaFlux::Portal::Graphics::InOut, MayaFlux::Portal::Graphics::Input, k, MayaFlux::Kinesis::k_field_targets, MayaFlux::Portal::Graphics::ShaderSpec::Assemble::kernel(), m_bindings, m_spec_cache, m_vertex_binding, m_workgroup_size, MF_ERROR, needs_cluster_id(), MayaFlux::Journal::NodeProcessing, MayaFlux::Journal::Nodes, MayaFlux::Kinesis::NORMAL, MayaFlux::Portal::Graphics::ShaderSpec::Assemble::pc(), MayaFlux::Kinesis::POSITION, resolve_target(), MayaFlux::Portal::Graphics::ShaderSpec::Assemble::ssbo(), MayaFlux::Portal::Graphics::ShaderSpec::Assemble::start_binding(), MayaFlux::Kinesis::TANGENT, MayaFlux::Kakshya::UINT32, and MayaFlux::Portal::Graphics::ShaderSpec::Assemble::workgroup().

+ Here is the call graph for this function: