MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
GraphicsOperator.hpp
Go to the documentation of this file.
1#pragma once
2
4#include "NetworkOperator.hpp"
5
7
9enum class PrimitiveTopology : uint8_t;
10}
11
13
16
17/**
18 * @class GraphicsOperator
19 * @brief Operator that produces GPU-renderable geometry
20 *
21 * Adds graphics-specific interface (vertex data, position extraction)
22 * on top of base NetworkOperator. Uses glm::vec3 for positions since
23 * that's the graphics domain standard.
24 */
25class MAYAFLUX_API GraphicsOperator : public NetworkOperator {
26public:
27 /**
28 * @brief Get vertex data for GPU upload
29 */
30 [[nodiscard]] virtual std::span<const uint8_t> get_vertex_data() const = 0;
31
32 /**
33 * @brief Get vertex data for specific collection (if multiple)
34 * @param idx Collection index
35 */
36 [[nodiscard]] virtual std::span<const uint8_t> get_vertex_data_for_collection(uint32_t idx = 0) const = 0;
37
38 /**
39 * @brief Get vertex layout describing vertex structure
40 */
41 [[nodiscard]] virtual Kakshya::VertexLayout get_vertex_layout() const = 0;
42
43 /**
44 * @brief Get number of vertices (may differ from point count for topology/path)
45 */
46 [[nodiscard]] virtual size_t get_vertex_count() const = 0;
47
48 /**
49 * @brief Check if geometry changed this frame
50 */
51 [[nodiscard]] virtual bool is_vertex_data_dirty() const = 0;
52
53 /**
54 * @brief Clear dirty flag after GPU upload
55 */
56 virtual void mark_vertex_data_clean() = 0;
57
58 /**
59 * @struct DirtyVertexRange
60 * @brief A contiguous span of vertex records that changed since the last
61 * mark_vertex_data_clean().
62 *
63 * group_index selects which vertex-array pack the span's bytes come from,
64 * for a consumer that reads one pack at a time through
65 * get_vertex_data_for_collection(); it is 0 for an operator holding a
66 * single pack. vertex_offset and vertex_count are in global vertex-index
67 * order, the same concatenation order get_vertex_data() and
68 * build_cluster_ids() produce.
69 */
71 uint32_t group_index {};
72 uint32_t vertex_offset {};
73 uint32_t vertex_count {};
74 };
75
76 /**
77 * @brief Vertex sub-ranges that changed since the last
78 * mark_vertex_data_clean().
79 * @return One entry per contiguous dirty region, in global vertex-index
80 * order, or empty when nothing changed.
81 *
82 * The default reports a single range spanning every vertex whenever
83 * is_vertex_data_dirty() is true, so a consumer that uploads per-range
84 * stays identical to one that re-uploads the whole buffer. An operator
85 * holding several independent vertex-array packs (PathOperator's paths,
86 * TopologyOperator's graphs) overrides this to report only the packs
87 * whose own geometry changed this cycle, so an edit to one pack leaves
88 * the uploaded bytes of the others, and any GPU-side accumulation already
89 * written into them, untouched.
90 */
91 [[nodiscard]] virtual std::vector<DirtyVertexRange> dirty_vertex_ranges() const
92 {
93 if (!is_vertex_data_dirty()) {
94 return {};
95 }
96 return { DirtyVertexRange { .vertex_count = static_cast<uint32_t>(get_vertex_count()) } };
97 }
98
99 /**
100 * @brief Whether every vertex mutation on this operator sets a group's
101 * dirty flag, so dirty_vertex_ranges() is the complete record of
102 * what changed since the last mark_vertex_data_clean().
103 *
104 * False by default: a consumer must re-upload the whole buffer. True for
105 * operators (PathOperator, TopologyOperator) whose per-group flags are
106 * authoritative, letting a consumer upload only the reported ranges and
107 * then call mark_vertex_data_clean(). An operator that reports true must
108 * also route every path through which its vertices change (add, edit,
109 * clear, interpolation-parameter changes) to a group dirty flag.
110 */
111 [[nodiscard]] virtual bool supports_incremental_upload() const { return false; }
112
113 /**
114 * @brief Get source point count (before topology expansion)
115 */
116 [[nodiscard]] virtual size_t get_point_count() const = 0;
117
118 /**
119 * @brief Per-vertex collection index, global index order.
120 * @return get_vertex_count() elements, not get_point_count(): the
121 * consumer indexes it in lockstep with the vertex buffer a
122 * GpuFieldOperator dispatch or the spatial hash actually reads
123 * (vertices[i], not points[i]), and for an operator whose
124 * rendered vertex count differs from its source point count
125 * (TopologyOperator/PathOperator after interpolation) those are
126 * two different numbers. Default: every entry 0, meaning "one
127 * population, no cluster distinction" -- correct for every
128 * operator that carries no notion of multiple complete
129 * vertex-array packs, which is every GraphicsOperator except
130 * PhysicsOperator today. get_point_count() and get_vertex_count()
131 * coincide for PhysicsOperator's point-sprite geometry, so this
132 * default is exactly as correct there as a point_count-sized
133 * array would have been.
134 *
135 * The source NetworkGeometryBuffer::ensure_cluster_ids() reads from when
136 * declaring and populating the hash_cluster_id state field a
137 * cluster-aware GPU stage (ClaimProcessor, HashDensityColorProcessor, a
138 * cluster-scoped GpuFieldOperator binding) consumes. An operator that
139 * does carry multiple collections overrides this once, here, and every
140 * such consumer picks it up with no further wiring: none of them
141 * dynamic_cast to a concrete operator type to get it. An override must
142 * size and order its result the same way: by rendered vertex, in the
143 * same concatenation order get_vertex_data() itself produces.
144 */
145 [[nodiscard]] virtual std::vector<uint32_t> build_cluster_ids() const
146 {
147 return std::vector<uint32_t>(get_vertex_count(), 0U);
148 }
149
150 /**
151 * @brief Extra named, per-vertex attributes beyond position/color/size,
152 * for an external cache/export consumer.
153 * @return One entry per attribute, in the same global vertex-index
154 * order get_vertex_data() itself produces. Empty by default.
155 *
156 * Deliberately returns plain named Kakshya::DataVariant channels
157 * rather than anything IO- or Alembic-shaped: GraphicsOperator has no
158 * business depending on IO. A caller that does (e.g. a SpatialCache
159 * producer) wraps each entry itself.
160 */
161 [[nodiscard]] virtual std::vector<std::pair<std::string, Kakshya::DataVariant>>
162 extract_vertex_attributes() const { return {}; }
163
164 /**
165 * @brief Per-vertex velocity, for a consumer that wants to write it as
166 * a native channel (e.g. Alembic Points' own .velocities)
167 * rather than a generic attribute.
168 * @return One entry per vertex, in the same order as
169 * extract_vertex_attributes(). Empty by default (no velocity).
170 */
171 [[nodiscard]] virtual std::vector<glm::vec3> extract_vertex_velocities() const { return {}; }
172
173 /**
174 * @brief Topology this operator's vertex data is authored for, if it
175 * holds a node to ask.
176 * @return The (first) underlying node's own
177 * GeometryWriterNode::get_primitive_topology(); nullopt when
178 * this operator holds no node at all. Default nullopt, matching
179 * every operator with no notion of a specific node (or that
180 * never overrides this).
181 *
182 * get_vertex_count()/get_vertex_layout() describe the vertex record,
183 * not what the vertices mean as primitives, so a consumer choosing
184 * between an Alembic Points and Curves schema (or any other
185 * topology-shaped decision) needs this instead.
186 */
187 [[nodiscard]] virtual std::optional<Portal::Graphics::PrimitiveTopology>
188 declared_topology() const { return std::nullopt; }
189
190 /**
191 * @brief Contiguous vertex spans grouped by primitive topology.
192 * @return One run per maximal contiguous stretch of same-topology
193 * collections whose topology permits concatenation, one run per
194 * collection otherwise, in the order get_vertex_data()
195 * produces. Default: a single run covering the whole operator at
196 * get_primitive_topology()'s value -- meaning every
197 * GraphicsOperator that has no notion of multiple collections, or
198 * whose collections all declare the same topology, needs no
199 * override at all.
200 *
201 * Adjacent collections of equal topology are coalesced into one run only
202 * when that topology is concatenable, meaning each primitive is assembled
203 * from its own group of consecutive vertices. Strip and fan collections are
204 * always reported separately, since concatenating two of them fabricates a
205 * primitive spanning the join: a PathOperator holding five LINE_STRIP paths
206 * back to back yields five runs, not one. A consumer draws each run as one
207 * draw call at that run's topology, or expands each run independently;
208 * sizing and ordering follow get_vertex_data() exactly, the same contract
209 * build_cluster_ids() and dirty_vertex_ranges() already keep.
210 */
211 [[nodiscard]] virtual std::vector<DrawRun> topology_runs() const;
212
213 /**
214 * @brief Apply ONE_TO_ONE parameter mapping
215 *
216 * Default implementation handles common graphics properties:
217 * - "color": Per-point color
218 * - "size": Per-point size (for point rendering)
219 */
220 void apply_one_to_one(
221 std::string_view param,
222 const std::shared_ptr<NodeNetwork>& source) override;
223
224 /**
225 * @brief Get human-readable vertex type name (for validation/debugging)
226 */
227 [[nodiscard]] virtual const char* get_vertex_type_name() const = 0;
228
229 /**
230 * @brief Whether this operator contributes a vertex slice to rendering.
231 *
232 * Default true. Set false for transform-only chain operators (e.g. a
233 * FieldOperator deforming upstream vertices) that must not add an
234 * independent render slice.
235 */
236 [[nodiscard]] bool participates_in_rendering() const { return m_participates_in_rendering; }
237 void set_participates_in_rendering(bool value) { m_participates_in_rendering = value; }
238
239 /**
240 * @brief Whether this operator requests upstream vertex state before process().
241 *
242 * Default false. Set true for operators that derive their initial vertex
243 * data from the preceding operator in the chain rather than from an
244 * explicit initialize() call.
245 */
246 [[nodiscard]] bool consumes_upstream() const { return m_consumes_upstream; }
247 void set_consumes_upstream(bool value) { m_consumes_upstream = value; }
248
249 /**
250 * @brief Receive upstream vertex state before process() is called.
251 *
252 * Called by OperatorChain::process() only when consumes_upstream() is true.
253 * Implementations seed their internal vertex buffer from the upstream
254 * operator's current output. Default no-op.
255 *
256 * @param upstream Last GraphicsOperator that ran before this one in the
257 * chain, or the primary operator if this is the first chain
258 * entry. Null if no upstream GraphicsOperator exists.
259 */
260 virtual void seed_from_upstream(const GraphicsOperator* upstream) { }
261
262 /**
263 * @brief Override the dt passed by the caller with a fixed internal value.
264 *
265 * Useful when the owning network passes 0.0F or a sample-count dt that is
266 * meaningless for time-based integration (e.g. PhysicsOperator in a
267 * PointCloudNetwork chain). When true, process() ignores the incoming dt
268 * and substitutes m_internal_dt instead.
269 */
270 void set_force_internal_dt(bool value) { m_force_internal_dt = value; }
271 [[nodiscard]] bool uses_force_internal_dt() const { return m_force_internal_dt; }
272
273protected:
274 /**
275 * @brief Get mutable access to point at global index
276 * @return Pointer to vertex data, or nullptr if index invalid
277 *
278 * Subclasses must implement to provide per-point access
279 */
280 virtual void* get_data_at(size_t global_index) = 0;
281
282 bool m_participates_in_rendering { true };
283 bool m_consumes_upstream {};
284 bool m_force_internal_dt {};
285};
286
287} // namespace MayaFlux::Nodes::Network::Operators
glm::vec3 value
virtual std::vector< std::pair< std::string, Kakshya::DataVariant > > extract_vertex_attributes() const
Extra named, per-vertex attributes beyond position/color/size, for an external cache/export consumer.
bool participates_in_rendering() const
Whether this operator contributes a vertex slice to rendering.
virtual void * get_data_at(size_t global_index)=0
Get mutable access to point at global index.
virtual size_t get_point_count() const =0
Get source point count (before topology expansion)
virtual std::span< const uint8_t > get_vertex_data_for_collection(uint32_t idx=0) const =0
Get vertex data for specific collection (if multiple)
virtual size_t get_vertex_count() const =0
Get number of vertices (may differ from point count for topology/path)
virtual std::vector< uint32_t > build_cluster_ids() const
Per-vertex collection index, global index order.
virtual void seed_from_upstream(const GraphicsOperator *upstream)
Receive upstream vertex state before process() is called.
virtual Kakshya::VertexLayout get_vertex_layout() const =0
Get vertex layout describing vertex structure.
virtual bool is_vertex_data_dirty() const =0
Check if geometry changed this frame.
virtual bool supports_incremental_upload() const
Whether every vertex mutation on this operator sets a group's dirty flag, so dirty_vertex_ranges() is...
virtual std::optional< Portal::Graphics::PrimitiveTopology > declared_topology() const
Topology this operator's vertex data is authored for, if it holds a node to ask.
bool consumes_upstream() const
Whether this operator requests upstream vertex state before process().
void set_force_internal_dt(bool value)
Override the dt passed by the caller with a fixed internal value.
virtual std::vector< glm::vec3 > extract_vertex_velocities() const
Per-vertex velocity, for a consumer that wants to write it as a native channel (e....
virtual std::span< const uint8_t > get_vertex_data() const =0
Get vertex data for GPU upload.
virtual const char * get_vertex_type_name() const =0
Get human-readable vertex type name (for validation/debugging)
virtual std::vector< DirtyVertexRange > dirty_vertex_ranges() const
Vertex sub-ranges that changed since the last mark_vertex_data_clean().
virtual void mark_vertex_data_clean()=0
Clear dirty flag after GPU upload.
Operator that produces GPU-renderable geometry.
Domain-agnostic interpretive lens for network processing.
constexpr bool is_concatenable_topology(PrimitiveTopology topology) noexcept
Whether two adjacent spans of this topology can be concatenated without altering the primitives they ...
PrimitiveTopology
Vertex assembly primitive topology.
Complete description of vertex data layout in a buffer.
A contiguous span of vertex records that changed since the last mark_vertex_data_clean().
One contiguous span of vertices sharing a primitive topology.