MayaFlux 1.0.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 516 of file GpuFieldOperator.cpp.

517{
518 if (m_spec_cache.has_value())
519 return m_spec_cache;
520
521 if (m_bindings.empty())
522 return std::nullopt;
523
525 if (!position.has_value()) {
527 "GpuFieldOperator: layout carries no position attribute. Fields are "
528 "functions of position and cannot be evaluated without one.");
529 return std::nullopt;
530 }
531
532 const bool needs_cluster = needs_cluster_id();
533
534 Portal::Graphics::ShaderSpec::Assemble assemble;
535 assemble.start_binding(m_vertex_binding)
538
539 if (needs_cluster) {
540 assemble.ssbo("cluster_id", Portal::Graphics::BindingDirection::Input,
542 }
543
544 assemble.pc("first_vertex", Kakshya::GpuDataFormat::UINT32)
545 .pc("vertex_count", Kakshya::GpuDataFormat::UINT32)
546 .pc("stride_words", Kakshya::GpuDataFormat::UINT32)
548 .pc("geometry", Kakshya::GpuDataFormat::MAT4_F32);
549
550 if (uses_params()) {
551 assemble.pc("params", Kakshya::GpuDataFormat::VEC4_F32);
552 }
553
554 assemble.workgroup(m_workgroup_size);
555
556 std::vector<std::string_view> emitted;
557 for (const auto& b : m_bindings) {
558 const auto seen = std::ranges::find(emitted, b.source.name);
559 if (seen != emitted.end())
560 continue;
561 emitted.push_back(b.source.name);
562 assemble.function(b.source.return_type, b.source.name,
563 b.source.params, b.source.body);
564 }
565
566 const uint32_t pw = position->first;
567 std::string body;
568 body += " if (i >= vertex_count) { return; }\n";
569 body += " uint b = (first_vertex + i) * stride_words;\n";
570 body += " vec3 p = (geometry * vec4(vertices[b + " + word(pw)
571 + "], vertices[b + " + word(pw + 1)
572 + "], vertices[b + " + word(pw + 2) + "], 1.0f)).xyz;\n";
573
574 if (needs_cluster) {
575 body += " uint my_cluster = cluster_id[first_vertex + i];\n";
576 }
577
579 const bool touched = std::ranges::any_of(m_bindings,
580 [t](const Binding& b) { return has_flag(b.targets, t); });
581 if (!touched)
582 continue;
583
584 const auto slot = resolve_target(t);
585 if (!slot.has_value())
586 continue;
587
588 const auto [w, components] = *slot;
589 const std::string acc = std::string("acc_") + target_name(t);
590
591 body += "\n " + std::string(glsl_type(components)) + " " + acc
592 + " = " + glsl_zero(components) + ";\n";
593
594 for (const auto& b : m_bindings) {
595 if (!has_flag(b.targets, t))
596 continue;
597
598 const char* arguments = "(p)";
599 if (b.parametric) {
600 arguments = "(p, time, params)";
601 } else if (b.temporal) {
602 arguments = "(p, time)";
603 }
604 const std::string call = std::string(b.source.name) + arguments;
605
606 if (b.cluster.has_value()) {
607 body += " if (my_cluster == " + word(*b.cluster) + ") { "
608 + acc + " += " + call + "; }\n";
609 } else {
610 body += " " + acc + " += " + call + ";\n";
611 }
612 }
613
615 body += " " + acc + " = inverse(mat3(geometry)) * " + acc + ";\n";
616 } else if (t == Kinesis::FieldTarget::NORMAL) {
617 body += " " + acc + " = transpose(mat3(geometry)) * " + acc + ";\n";
618 }
619
621 const std::string len = std::string("len_") + target_name(t);
622 body += " float " + len + " = length(" + acc + ");\n";
623 body += " " + acc + " = " + len + " > 1e-6f ? " + acc + " / "
624 + len + " : " + acc + ";\n";
625 }
626
627 const char* assign = target_accumulates(t) ? " += " : " = ";
628 for (uint32_t k = 0; k < components; ++k) {
629 body += " vertices[b + " + word(w + k) + "]" + assign
630 + acc + (components == 1 ? "" : k_swizzle[k]) + ";\n";
631 }
632 }
633
634 assemble.kernel(Portal::Graphics::KernelSource { .body = std::move(body) });
635
636 m_spec_cache = assemble.build();
637 return m_spec_cache;
638}
#define MF_ERROR(comp, ctx,...)
std::optional< glm::vec3 > position
Definition Decoder.cpp:93
size_t b
uint32_t pw
std::optional< Portal::Graphics::ShaderSpec > m_spec_cache
bool uses_params() const
Whether any current binding is parametric, so the spec carries the block.
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.
@ VEC4_F32
glm::vec4 (32-bit components)
@ FLOAT32
32-bit float (standard GPU)
@ MAT4_F32
glm::mat4 (column-major), not a sampled image format
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, MayaFlux::Kinesis::k_field_targets, MayaFlux::Portal::Graphics::ShaderSpec::Assemble::kernel(), m_bindings, m_spec_cache, m_vertex_binding, m_workgroup_size, MayaFlux::Kakshya::MAT4_F32, MF_ERROR, needs_cluster_id(), MayaFlux::Journal::NodeProcessing, MayaFlux::Journal::Nodes, MayaFlux::Kinesis::NORMAL, MayaFlux::Portal::Graphics::ShaderSpec::Assemble::pc(), MayaFlux::Kinesis::POSITION, position, pw, resolve_target(), MayaFlux::Portal::Graphics::ShaderSpec::Assemble::ssbo(), MayaFlux::Portal::Graphics::ShaderSpec::Assemble::start_binding(), MayaFlux::Kinesis::TANGENT, MayaFlux::Kakshya::UINT32, uses_params(), MayaFlux::Kakshya::VEC4_F32, and MayaFlux::Portal::Graphics::ShaderSpec::Assemble::workgroup().

+ Here is the call graph for this function: