Loading...
Searching...
No Matches
RenderedCSFNode.hpp
1#pragma once
2#include <Gfx/Graph/GPUBufferScatter.hpp>
3#include <Gfx/Graph/ISFNode.hpp>
4#include <Gfx/Graph/NodeRenderer.hpp>
5
6#include <ossia/detail/small_flat_map.hpp>
7#include <ossia/detail/small_vector.hpp>
8
9namespace ossia { class math_expression; }
10
11namespace score::gfx
12{
13
15{
16 explicit RenderedCSFNode(const ISFNode& node) noexcept;
17
18 virtual ~RenderedCSFNode();
19
20 void updateInputTexture(const Port& input, QRhiTexture* tex, QRhiTexture* depthTex = nullptr) override;
21 QRhiTexture* textureForOutput(const Port& output) override;
22
23 void init(RenderList& renderer, QRhiResourceUpdateBatch& res) override;
24 void update(RenderList& renderer, QRhiResourceUpdateBatch& res, Edge* edge) override;
25 void release(RenderList& r) override;
26
27 void initState(RenderList& renderer, QRhiResourceUpdateBatch& res) override;
28 void releaseState(RenderList& renderer) override;
29 void addOutputPass(
30 RenderList& renderer, Edge& edge, QRhiResourceUpdateBatch& res) override;
31 void removeOutputPass(RenderList& renderer, Edge& edge) override;
32 bool hasOutputPassForEdge(Edge& edge) const override;
33 void
34 addInputEdge(RenderList& renderer, Edge& edge, QRhiResourceUpdateBatch& res) override;
35 void removeInputEdge(RenderList& renderer, Edge& edge) override;
36
37 void runInitialPasses(
38 RenderList&, QRhiCommandBuffer& commands, QRhiResourceUpdateBatch*& res,
39 Edge& edge) override;
40
41 void runRenderPass(RenderList&, QRhiCommandBuffer& commands, Edge& edge) override;
42
43private:
44 void initComputeSRBAndPasses(RenderList& renderer, QRhiResourceUpdateBatch& res);
45 void createComputePipeline(RenderList& renderer);
46 void createGraphicsPass(const TextureRenderTarget& rt, RenderList& renderer, Edge& edge, QRhiResourceUpdateBatch& res);
47 void updateDescriptorSet(RenderList& renderer, Edge& edge);
48 std::vector<Sampler> allSamplers() const noexcept;
49
50 // Expression evaluation helper
51 void registerCommonExpressionVariables(
52 ossia::math_expression& e, ossia::small_pod_vector<double, 16>& data) const;
53
54 // Upper bound on the number of doubles registerCommonExpressionVariables (+
55 // the small extra a caller adds, e.g. $USER) will emplace into the backing
56 // vector. ossia::math_expression::add_constant stores a double& INTO that
57 // vector, so the reserve MUST cover the full count: any emplace_back past
58 // capacity reallocates and dangles every previously-registered reference.
59 std::size_t expressionSymbolReserveCount() const noexcept;
60
61 // Image management
62 std::optional<QSize> getImageSize(const isf::csf_image_input&) const noexcept;
63 QSize computeTextureSize(const isf::csf_image_input& img) const noexcept;
64
65 // Buffer management methods
66 int calculateStorageBufferSize(std::span<const isf::storage_input::layout_field> layout, int arrayCount) const;
67 BufferView createStorageBuffer(
68 RenderList& renderer, const QString& name, const QString& access, int size);
69 void updateStorageBuffers(RenderList& renderer, QRhiResourceUpdateBatch& res);
70 void recreateShaderResourceBindings(RenderList& renderer, QRhiResourceUpdateBatch& res);
71
72 // Single source of truth for the CSF compute SRB binding list. Walks the
73 // descriptor's INPUTS / RESOURCES / AUXILIARIES in order and emits one
74 // QRhiShaderResourceBinding per shader binding slot. Both
75 // initComputeSRBAndPasses (init path) and recreateShaderResourceBindings
76 // (re-emit path) call this so the two paths can never drift in their
77 // emission order, indices, or fallback-on-missing-resource policy.
78 // Binding 1 (ProcessUBO) is left as a nullptr placeholder; each caller
79 // patches it per-pass. Output: appended to `bindings`.
80 void buildComputeSrbBindings(
81 RenderList& renderer, QRhiResourceUpdateBatch& res,
82 QList<QRhiShaderResourceBinding>& bindings);
83 int getArraySizeFromUI(const QString& bufferName) const;
84 QString updateShaderWithImageFormats(QString current);
85
86 // Geometry buffer management
87 void updateGeometryBindings(RenderList& renderer, QRhiResourceUpdateBatch& res);
88
89 void pushOutputGeometry(RenderList& renderer, QRhiResourceUpdateBatch& res, Edge& edge);
90 int resolveCountExpression(
91 const std::string& expr, const isf::geometry_input& geo,
92 const std::string& fieldName) const;
93 int resolveDispatchExpression(const std::string& expr) const;
94
95 BufferView bufferForOutput(const score::gfx::Port& output) override;
96
97 struct ComputePass
98 {
99 QRhiComputePipeline* pipeline{};
100 QRhiShaderResourceBindings* srb{};
101 QRhiBuffer* processUBO{};
102 // Hash of the last bindings vector applied to `srb`. Compared in
103 // recreateShaderResourceBindings to skip a destroy+setBindings+
104 // create cycle when the bindings haven't actually changed since the
105 // previous frame. 0 = "never built / unknown" — first call always
106 // rebuilds. See RenderedCSFNode.cpp recreateShaderResourceBindings.
107 size_t srbBindingsHash{0};
108 };
109
110 struct GraphicsPass
111 {
112 Pipeline pipeline;
113 QRhiSampler* outputSampler{};
114 MeshBuffers meshBuffers;
115 };
116
117 ossia::small_vector<std::pair<Edge*, ComputePass>, 2> m_computePasses;
118 ossia::small_vector<std::pair<Edge*, GraphicsPass>, 2> m_graphicsPasses;
119
120 ISFNode& n;
121
122 std::vector<Sampler> m_inputSamplers;
123
124 // Storage buffers for compute shaders
125 struct StorageBuffer
126 {
127 QRhiBuffer* buffer{};
128 int64_t size{};
129 int64_t lastKnownSize{}; // For dynamic resizing
130 QString name;
131 QString access; // "read_only", "write_only", "read_write"
132 std::vector<isf::storage_input::layout_field> layout; // For size calculation
133 bool owned{true}; // false when buffer comes from geometry auxiliary
134 std::string buffer_usage; // "", "indirect_draw", "indirect_draw_indexed", "dispatch_args"
135 };
136 std::vector<StorageBuffer> m_storageBuffers; // Contains both ins and outs
137
138 // Only outs, matched with index in m_storageBuffers
139 std::vector<std::pair<const score::gfx::Port*, int>> m_outStorageBuffers;
140
141 // Storage images for compute shaders
142 struct StorageImage
143 {
144 QRhiTexture* texture{};
145 QRhiTexture* read_texture{};
146 QString name;
147 QString access; // "read_only", "write_only", "read_write"
148 QRhiTexture::Format format{QRhiTexture::RGBA8};
149 bool is3D{false};
150 bool isCube{false};
151 bool persistent{false};
152 bool pending_initial_copy{false};
153 bool generate_mips{false};
154
155 // Recorded binding slots in the compute SRB so that end-of-frame
156 // swapping can call replaceTexture() without having to re-walk the
157 // descriptor layout.
158 int binding{-1};
159 int prev_binding{-1};
160 };
161 std::vector<StorageImage> m_storageImages;
162
163 // Only outs, matched with index in m_storageImages
164 std::vector<std::pair<const score::gfx::Port*, int>> m_outStorageImages;
165
166 // Geometry input bindings: SoA SSBOs created from incoming ossia::geometry
167 struct GeometryBinding
168 {
169 // One SSBO per declared attribute in the geometry_input
171 {
172 QRhiBuffer* buffer{}; // GPU SSBO for this attribute (write target / primary)
173 QRhiBuffer* read_buffer{}; // Separate read buffer for ping-pong (nullptr = use buffer for both)
174 int64_t size{}; // Current buffer size in bytes
175 bool owned{true}; // true = we created it; false = referencing upstream gpu_buffer
176 std::string name; // e.g. "position", "velocity"
177 std::string access; // "read_only", "write_only", "read_write"
178 bool per_instance{false}; // true = sized by instance_count, false = sized by vertex_count
179 const void* lastUploadSrc{};// CPU data pointer from last upload (for dedup)
180
181 // GPU scatter state (used when format conversion is needed)
182 QRhiBuffer* scatterStaging{}; // Staging SSBO for raw CPU data
183 int64_t scatterStagingSize{};
185 GPUBufferScatter::Params scatterParams;
186 bool scatterPending{false}; // true = needs dispatch this frame
187 };
188
189 // Structured SSBOs (or UBOs) that travel with the geometry (matched
190 // by name against ossia::geometry::auxiliary_buffer entries). The
191 // `is_uniform` flag mirrors the AUXILIARY request's kind: when true,
192 // the buffer is bound as a std140 uniform block via
193 // QRhiShaderResourceBinding::uniformBuffer; when false, as an std430
194 // SSBO via bufferLoad / bufferStore / bufferLoadStore.
196 {
197 QRhiBuffer* buffer{}; // GPU SSBO/UBO (write target / primary)
198 QRhiBuffer* read_buffer{}; // Separate read buffer for ping-pong (nullptr = use buffer for both)
199 int64_t size{};
200 bool owned{true};
201 bool is_uniform{false}; // true = std140 UBO, false = std430 SSBO
202 std::string name;
203 std::string access;
204 std::vector<isf::storage_input::layout_field> layout;
205 std::string size_expr; // expression for flexible array count, may contain $USER
206 };
207
208 // Auxiliary textures that travel with the geometry (resolved from
209 // ossia::geometry::auxiliary_textures by name). Either sampled
210 // (sampler*) or storage-image (image*). Shape-matched placeholder
211 // used as fallback when no match exists on the incoming geometry.
213 {
214 QRhiSampler* sampler{}; // null for storage-image entries
215 QRhiTexture* texture{}; // current bound handle (placeholder or upstream)
216 QRhiTexture* placeholder{}; // shape-matched empty from RenderList
217 std::string name;
218 int binding{-1}; // assigned at SRB build
219 bool is_storage{false};
220 std::string access; // "read_only" / "write_only" / "read_write"
221
222 // True when this binding allocated `texture` itself (write_only /
223 // read_write storage image declared as a nested aux on a geometry
224 // input — same lifecycle role as m_storageImages plays for top-
225 // level csf_image_input outputs). Owned textures:
226 // - skip the per-frame upstream-resolution overwrite (we own
227 // the data, no upstream contributes);
228 // - get pushed into out_geo.auxiliary_textures by name so
229 // downstream consumers can resolve the live handle;
230 // - get deleted on release().
231 bool owned{false};
232 };
233
234 std::vector<AttributeSSBO> attribute_ssbos;
235 std::vector<AuxiliarySSBO> auxiliary_ssbos;
236 std::vector<AuxiliaryTexture> auxiliary_textures;
237 std::string input_name; // RESOURCES[].NAME (e.g. "geoIn", "geoOut") — used by PER_VERTEX/PER_INSTANCE TARGET filtering
238 int vertex_count{0}; // Number of elements (vertices) in the geometry
239 int instance_count{1}; // Number of instances
240 int input_port_index{-1}; // Input port index for this binding (-1 = no input port, e.g. write_only generator)
241 bool has_output{false}; // true if any attribute is writable
242 bool has_vertex_count_spec{false}; // true if vertex_count expression is set
243 bool has_instance_count_spec{false}; // true if instance_count expression is set
244 bool is_feedback_receiver{false}; // true = uses ping-pong double buffering for read_write attrs
245 bool pending_initial_copy{false}; // first frame after read_buffer allocated: use same-buffer mode, then copy buffer→read_buffer
246
247 // GPU buffers allocated by COPY_FROM (CPU→GPU upload). Owned by this binding,
248 // must be released via renderer.releaseBuffer() since they escape into output geometry.
249 std::vector<QRhiBuffer*> copyFromBuffers;
250
251 // Persistent output geometry — reused across frames to avoid per-frame shared_ptr allocation.
252 // Updated in-place; dirty_index incremented when structure or handles change.
253 ossia::geometry_spec outputGeometry;
254 int prev_vertex_count{-1}; // Track structural changes
255 int prev_instance_count{-1};
256 int prev_attribute_count{-1};
257 int prev_upstream_attr_count{-1};
258
259 QRhiBuffer* indirectBuffer{}; // StorageBuffer (+ IndirectBuffer on Qt 6.12+)
260 int64_t indirectBufferSize{};
261 int indirectCountResult{0}; // Resolved command count
262 std::string indirectCountExpr; // Expression string for dynamic re-resolve
263 bool uses_indirect_draw{false};
264 // INDIRECT: { DRAW_COUNT: true } — 16-byte buffer whose word 0 is the
265 // GPU-written draw count, published via the "_indirect_draw_count"
266 // auxiliary and consumed by the drawIndexedIndirectCount rung.
267 QRhiBuffer* indirectCountBuffer{};
268 bool uses_indirect_count{false};
269 };
270 std::vector<GeometryBinding> m_geometryBindings;
271
272 // One-time "CSF indirect dispatch: gpu|cpu-fallback" log guard (per node
273 // instance, so per test session); see the dispatch site.
274 bool m_loggedIndirectDispatch{false};
275
276 QRhiBuffer* m_materialUBO{};
277 int m_materialSize{};
278
279 // Output texture for compute shader results
280 QRhiTexture* m_outputTexture{};
281 QRhiTexture::Format m_outputFormat{QRhiTexture::RGBA8};
282
283 // Compute shader specifics
284 QRhiComputePipeline* m_computePipeline{}; // Points to first pass pipeline (backward compat)
285 QShader m_computeShader;
286 QString m_computeShaderSource; // Template with ISF_LOCAL_SIZE_X/Y/Z placeholders
287 std::vector<QRhiComputePipeline*> m_perPassPipelines; // One entry per pass (may share pipelines)
288 std::vector<QRhiComputePipeline*> m_ownedPipelines; // Unique pipelines for cleanup
289 bool m_pipelinesDirty{true};
290
291 // GPU buffer scatter (format conversion on GPU)
292 GPUBufferScatter m_gpuScatter;
293 bool m_gpuScatterAvailable{false};
294
295 // True once at least one frame's worth of upstream rendering has happened
296 // for this renderer's input textures. Used to gate generateMips() so we
297 // don't trip a Vulkan validation error on freshly-allocated textures whose
298 // layout is still PREINITIALIZED. Reset on init() / after release() so a
299 // RenderList rebuild starts the cycle over.
300 bool m_inputsHaveBeenWritten{false};
301
302 // Once-per-frame guard for runInitialPasses. RenderList calls update() +
303 // runInitialPasses() once per incoming edge of every sink port, so a CSF
304 // feeding >=2 sinks would otherwise re-dispatch every compute pass and
305 // double-swap the feedback SSBOs / persistent images per frame (simulation
306 // advancing at N x). Keyed on renderer.frame (a monotonic counter) rather
307 // than a reset-in-update() bool, because update() is interleaved per-port
308 // before each runInitialPasses and would reset such a bool between edges.
309 // Mirrors SimpleRenderedISFNode::m_lastMRTRenderFrame. Reset in release().
310 int64_t m_lastRunFrame{-1};
311};
312
313}
Data model for Interactive Shader Format filters.
Definition ISFNode.hpp:22
Renderer for a given node.
Definition NodeRenderer.hpp:11
List of nodes to be rendered to an output.
Definition RenderList.hpp:30
Graphics rendering pipeline for ossia score.
Definition Filter/PreviewWidget.hpp:11
Definition Mesh.hpp:18
Connection between two score::gfx::Port.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:103
Definition GPUBufferScatter.hpp:30
Create or update an SRB+UBO for a specific scatter operation.
Definition GPUBufferScatter.hpp:45
Definition Mesh.hpp:94
Useful abstraction for storing a graphics pipeline and associated resource bindings.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:132
Port of a score::gfx::Node.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:82
Definition RenderedCSFNode.hpp:15
void initState(RenderList &renderer, QRhiResourceUpdateBatch &res) override
Definition RenderedCSFNode.cpp:3977
bool hasOutputPassForEdge(Edge &edge) const override
Check if this renderer already has an output pass for the given edge.
Definition RenderedCSFNode.cpp:4448
void removeInputEdge(RenderList &renderer, Edge &edge) override
Notify the renderer that an input edge was disconnected.
Definition RenderedCSFNode.cpp:4602
QRhiTexture * textureForOutput(const Port &output) override
Definition RenderedCSFNode.cpp:281
void addInputEdge(RenderList &renderer, Edge &edge, QRhiResourceUpdateBatch &res) override
Definition RenderedCSFNode.cpp:4584
void releaseState(RenderList &renderer) override
Definition RenderedCSFNode.cpp:4455
void removeOutputPass(RenderList &renderer, Edge &edge) override
Definition RenderedCSFNode.cpp:4436
void updateInputTexture(const Port &input, QRhiTexture *tex, QRhiTexture *depthTex=nullptr) override
Definition RenderedCSFNode.cpp:239
void addOutputPass(RenderList &renderer, Edge &edge, QRhiResourceUpdateBatch &res) override
Create a pass for a new output edge (pipeline, SRB, processUBO).
Definition RenderedCSFNode.cpp:4423
Useful abstraction for storing all the data related to a render target.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:152