Loading...
Searching...
No Matches
RenderedRawRasterPipelineNode.hpp
1#pragma once
2
3#include <Gfx/Graph/ISFNode.hpp>
4#include <Gfx/Graph/IsfBindingsBuilder.hpp>
5#include <Gfx/Graph/NodeRenderer.hpp>
6#include <Gfx/Graph/RenderedISFUtils.hpp>
7#include <Gfx/Graph/VertexFallbackPlan.hpp>
8
9#include <ossia/detail/small_flat_map.hpp>
10
11#include <span>
12
13namespace score::gfx
14{
15// Used for the simple case of a single, non-persistent pass (the most common case)
16
18{
19 explicit RenderedRawRasterPipelineNode(const ISFNode& node) noexcept;
20
22
23 void updateInputTexture(const Port& input, QRhiTexture* tex, QRhiTexture* depthTex = nullptr) override;
24 QRhiTexture* textureForOutput(const Port& output) override;
25
26 void init(RenderList& renderer, QRhiResourceUpdateBatch& res) override;
27 void update(RenderList& renderer, QRhiResourceUpdateBatch& res, Edge* edge) override;
28 bool updateMaterials(RenderList& renderer, QRhiResourceUpdateBatch& res, Edge* edge);
29 void release(RenderList& r) override;
30
31 void initState(RenderList& renderer, QRhiResourceUpdateBatch& res) override;
32 void releaseState(RenderList& renderer) override;
33 void addOutputPass(RenderList& renderer, Edge& edge, QRhiResourceUpdateBatch& res) override;
34 void removeOutputPass(RenderList& renderer, Edge& edge) override;
35 bool hasOutputPassForEdge(Edge& edge) const override;
36 void addInputEdge(RenderList& renderer, Edge& edge, QRhiResourceUpdateBatch& res) override;
37 void removeInputEdge(RenderList& renderer, Edge& edge) override;
38
39 void runInitialPasses(
40 RenderList&, QRhiCommandBuffer& commands, QRhiResourceUpdateBatch*& res,
41 Edge& edge) override;
42
43 void runRenderPass(RenderList&, QRhiCommandBuffer& commands, Edge& edge) override;
44
45 void process(int32_t port, const ossia::transform3d& v) override;
46
47private:
48 // Resolves every image-style INPUT against the incoming geometry's
49 // auxiliary_textures list and overrides the initial texture pointer in
50 // m_inputSamplers for matches. Also builds m_auxTextureBindings so
51 // update() can cheaply re-run the lookup when the geometry changes.
52 // Must be called AFTER initInputSamplers.
53 void bindAuxTexturesInit(RenderList& renderer);
54
55 // Per-frame update hook: walks m_auxTextureBindings, re-resolves each
56 // binding's texture pointer from the current geometry's aux textures,
57 // and returns true if at least one sampler's texture pointer changed
58 // (caller will flag mustRecreatePasses).
59 bool rebindAuxTextures();
60
61 void initPass(
62 const TextureRenderTarget& rt, RenderList& renderer,
63 QRhiResourceUpdateBatch& res, Edge& edge);
64 void initMRTPass(RenderList& renderer, QRhiResourceUpdateBatch& res);
65 void initMRTBlitPasses(RenderList& renderer, QRhiResourceUpdateBatch& res);
66 void initMRTBlitPass(RenderList& renderer, QRhiResourceUpdateBatch& res, Edge& edge);
67
68 // EXECUTION_MODEL=MANUAL: evaluate the COUNT expression against the
69 // live input state (first input image's $WIDTH / $HEIGHT / $DEPTH /
70 // $LAYERS, scalar input values as $<inputName>). Pure-integer literal
71 // fast path; otherwise delegate to ossia::math_expression with '$' →
72 // 'var_' rewrite — same convention as CSF STRIDE / image-size
73 // expressions. Returns >= 1; unparseable expressions degrade to 1.
74 int resolveManualInvocationCount() const;
75
76 // True when the shader renders procedurally: no VERTEX_INPUTS, driven by
77 // gl_VertexIndex, with PIPELINE_STATE.VERTEX_COUNT set. m_mesh stays null and
78 // the draw skips vertex-buffer bindings, which relaxes the "no mesh, bail out"
79 // guards for fullscreen passes, VSA-style draws and IBL precompute shaders.
80 bool isProceduralDraw() const noexcept;
81
82 // Evaluate an integer-valued expression against the same variable
83 // surface as resolveManualInvocationCount ($WIDTH_<inp> / $HEIGHT /
84 // scalar inputs). Used for OUTPUTS.WIDTH / HEIGHT at init time.
85 // Returns `fallback` when the expression is empty, >=1 otherwise.
86 int resolveIntExpression(const std::string& expr, int fallback) const;
87
88 // Issue the draw for the currently bound pipeline and SRB. When the input
89 // geometry carries several sub-meshes with per-mesh aux buffers, this iterates
90 // them and re-points the SRB bindings before each draw; single-sub-mesh and MDI
91 // geometries delegate to the mesh's own draw(). The SRB is left on the last
92 // sub-mesh's bindings, and the next runRenderPass rebinds from scratch.
93 void drawWithPerMeshAuxRebind(
94 QRhiShaderResourceBindings& srb, QRhiCommandBuffer& cb,
95 const FallbackBindingPlan& plan = {});
96
97 std::vector<Sampler> allSamplers() const noexcept;
98
99 ossia::small_vector<std::pair<Edge*, Pass>, 2> m_passes;
100
101 ISFNode& n;
102
103 std::vector<Sampler> m_inputSamplers;
104 std::vector<Sampler> m_audioSamplers;
105 ossia::small_flat_map<Edge*, QRhiSampler*, 4> m_blitSamplersByEdge;
106
107 int64_t meshChangedIndex{-1};
108 const Mesh* m_mesh{};
109 MeshBuffers m_meshbufs;
110
111 // Quad mesh used for MRT blit passes (separate from the geometry mesh)
112 const Mesh* m_blitMesh{};
113 MeshBuffers m_blitMeshbufs;
114
115 QRhiBuffer* m_materialUBO{};
116 int m_materialSize{};
117
118 QRhiBuffer* m_modelUBO{};
119
120 struct AuxiliarySSBO
121 {
122 QRhiBuffer* buffer{};
123 QRhiBuffer* prev_buffer{};
124 int64_t size{};
125 bool owned{true}; // false when adopted from upstream geometry / upstream port
126 bool is_uniform{false}; // true for uniform_input, false for storage_input
127 bool persistent{false};
128 std::string name;
129 std::string access;
130 // Index into n.input[] for the score port that may carry an upstream-
131 // supplied QRhiBuffer*. -1 when the buffer can only come from the
132 // input geometry's auxiliary list (e.g. desc.auxiliary entries without
133 // a matching INPUTS port).
134 int input_port_index{-1};
135 // SRB binding slot assigned at pipeline build time. Needed so the per-
136 // sub-mesh draw loop can patch `per_draw` (and any other per-mesh aux)
137 // to point at mesh[i]'s buffer before drawing sub-mesh i. -1 when the
138 // aux was filtered out of the SRB (e.g. visibility==none).
139 int binding{-1};
140 // For persistent aux only: binding slot of the <name>_prev (read-only)
141 // half of the ping-pong pair. prev_binding + 1 == binding.
142 int prev_binding{-1};
147 int64_t declared_size{};
148 };
149 std::vector<AuxiliarySSBO> m_auxiliarySSBOs;
150
151 // Storage images and the rest of the INPUTS storage trio -- storage_input for
152 // SSBOs, csf_image_input for image2D/3D, uniform_input for UBOs -- declared in
153 // the top-level INPUTS array. Wired through the shared IsfBindingsBuilder
154 // helpers so the SRB binding type matches isf_emit_graphics_storage's GLSL.
155 // m_auxiliarySSBOs carries only the AUXILIARY-block entries; the dual
156 // population is deliberate while the AUXILIARY path still has its own dispatch.
157 GraphicsStorageResources m_storage;
158 int m_firstStorageBinding{-1};
159
160 // Texture auxes carried on the input geometry (see
161 // ossia::geometry::auxiliary_textures). Each entry records a sampler
162 // slot in m_inputSamplers that auto-resolves its texture pointer from
163 // the incoming geometry's aux-texture list by name at init() time and
164 // again every time the geometry changes. Eliminates the need for a
165 // dedicated texture cable (base_color_array / skybox / ...).
166 struct AuxTextureBinding
167 {
168 int sampler_idx{-1}; // index into m_inputSamplers
169 std::string name; // INPUT name, matched against auxiliary_texture::name
170 };
171 std::vector<AuxTextureBinding> m_auxTextureBindings;
172
173 // Non-owning per-port sampler overrides published by the upstream geometry's
174 // auxiliary_texture::sampler_handle, parallel to m_inputSamplers. Kept separate
175 // because m_inputSamplers entries are owned and released with
176 // `delete sampler.sampler`, which would double-free a registry-owned sampler.
177 std::vector<QRhiSampler*> m_inputSamplerOverrides;
178
179 // Textures declared in the top-level AUXILIARY array (TYPE: image /
180 // texture / cubemap / image_cube). Do NOT create a score input port —
181 // resolved only from ossia::geometry::auxiliary_textures by name, with
182 // a placeholder bound until the first matching handle arrives.
183 struct AuxTextureAuxSampler
184 {
185 QRhiSampler* sampler{}; // Null for storage-image entries.
186 QRhiTexture* texture{};
187 // Shape-matched empty fallback (one of the RenderList-owned empty
188 // textures). Set at init from is_cubemap / dimensions / is_array and
189 // never changes. When rebindAuxTextures stops finding a matching
190 // aux_texture upstream (producer stopped publishing the name, got
191 // disconnected, etc.) `texture` reverts to this placeholder rather
192 // than keeping the previous, possibly-freed, upstream handle.
193 // Never owned here.
194 QRhiTexture* placeholder{};
195 std::string name;
196 int binding{-1};
197 // Storage-image variant: bound with imageLoad / imageStore /
198 // imageLoadStore instead of sampledTexture. `access` distinguishes
199 // which of the three — "read_only" / "write_only" / "read_write".
200 bool is_storage{false};
201 std::string access;
202 };
203 std::vector<AuxTextureAuxSampler> m_auxTextureSamplers;
204
205 std::optional<AudioTextureUpload> m_audioTex;
206
207 // MRT: internally-owned render target with multiple attachments
208 TextureRenderTarget m_mrtRenderTarget;
209 bool m_hasMRT{false};
210 bool m_mrtRenderedThisFrame{false};
211
212 // EXECUTION_MODEL (top-level, RAW_RASTER only).
213 // Single default, one invocation, no extra loop.
214 // PerMip one invocation per mip level of the TARGET output, each binding a
215 // per-mip render target so the draw writes only that mip;
216 // ProcessUBO.passIndex carries the mip index. Drives the
217 // prefiltered-GGX roughness sweep.
218 // PerLayer one invocation per array layer. Colour targets bind setLayer(i);
219 // depth targets render to a shared scratch and copyTexture into
220 // layer i after the pass, since Qt RHI 6.11 has no per-layer depth
221 // attachment. Drives shadow_cascades.frag.
222 // Manual count decided every frame by evaluating a COUNT expression through
223 // the math_expression parser, with the same variable surface as CSF
224 // STRIDE and image-size expressions. All invocations share the MRT
225 // render target and the shader branches on ProcessUBO.passIndex.
226 enum class ExecutionMode : std::uint8_t
227 {
228 Single,
229 PerMip,
230 PerCubeFace, // Iterate 6 cube faces; target = CubeMap + setLayer(i)
231 PerLayer, // Iterate N array layers; target = TextureArray + setLayer(i)
232 Manual
233 };
234 ExecutionMode m_executionMode{ExecutionMode::Single};
235
236 // PerCubeFace state. The target OUTPUT is allocated with
237 // QRhiTexture::CubeMap (6 implicit layers) and six per-face render
238 // targets are built at init; runInitialPasses iterates them in order,
239 // stamping the face index into ProcessUBO.passIndex. Shares the
240 // m_perMipOutputIndex resolution path (same "which colour output is
241 // the target" question) and reuses the m_mipRTs vector for storage
242 // — interpretation is mode-dependent (mip level vs face index).
243 int m_perCubeFaceOutputIndex{-1};
244
245 // PerMip state. When PerMip is active the MRT target texture is
246 // allocated with QRhiTexture::MipMapped and m_mipCount / m_mipRTs
247 // point at per-level render-pass views of it. m_perMipOutputIndex is
248 // the index into m_mrtRenderTarget{.texture, .additionalColorTextures}
249 // being iterated. -1 in other modes.
250 int m_perMipOutputIndex{-1};
251 int m_mipCount{0};
252 struct MipRT
253 {
254 QRhiTextureRenderTarget* renderTarget{};
255 QRhiRenderPassDescriptor* renderPass{};
256 QRhiTexture* depth{}; // per-level depth — owned here.
257 };
258 std::vector<MipRT> m_mipRTs;
259
260 // PerLayer state. m_perLayerOutputIndex is the raw index into
261 // descriptor().outputs[], depth-inclusive, unlike the colour-only
262 // m_perMipOutputIndex / m_perCubeFaceOutputIndex. m_perLayerIsDepth picks the
263 // path:
264 //
265 // colour: m_mipRTs holds one entry per layer with a setLayer(i) attachment,
266 // structurally PER_CUBE_FACE with a variable layer count.
267 //
268 // depth: identical, using QRhiTextureRenderTargetDescription::setDepthLayer
269 // (Qt >= 6.12) so the pass writes straight into layer i of the OUTPUT
270 // depth array. Below 6.12 that API does not exist and the mode is REFUSED
271 // with a diagnostic rather than silently producing an empty array -- see
272 // initMRTPass. m_perLayerDummyColor is the placeholder colour attachment
273 // some backends (GLES) require, shared by every layer's RT and sized to
274 // the render extent, never 1x1.
275 int m_perLayerOutputIndex{-1};
276 bool m_perLayerIsDepth{false};
277 QRhiTexture* m_perLayerDummyColor{nullptr};
278
279 // Manual state. Re-evaluated every frame in runInitialPasses.
280 int m_manualCount{1};
281
282 // Per-invocation UBO + SRB pool for PER_MIP / PER_CUBE_FACE / MANUAL.
283 //
284 // A QRhi Dynamic UBO has a single slot per frame-in-flight: several
285 // updateDynamicBuffer calls to one buffer within a frame overwrite each other,
286 // so every draw that frame reads the last value and distinct PASSINDEX values
287 // collapse to one.
288 //
289 // One UBO and one SRB per invocation, pre-built at init, lets the render loop
290 // swap which SRB it binds. Index 0 is the main pass (pass.processUBO /
291 // pass.p.srb); the vectors hold 1..N-1 and grow lazily, which MANUAL needs
292 // since its count is per-frame dynamic.
293 std::vector<QRhiBuffer*> m_perInvocationUBOs;
294 std::vector<QRhiShaderResourceBindings*> m_perInvocationSRBs;
295
296 // Transparent CUBEMAP + MULTIVIEW shim: QRhi forbids setMultiViewCount on a
297 // cube texture, so a shader declaring both renders into a hidden 2D
298 // TextureArray -- the only shape multiview accepts -- and each layer is blitted
299 // onto the matching cube face at the end of runInitialPasses. Downstream sees a
300 // real samplerCube through textureForOutput(); the shadow array never leaves
301 // this class.
302 //
303 // m_cubeCopyShadowArray 6-layer TextureArray, UsedAsTransferSource, the
304 // multiview render target
305 // m_cubeCopyCube the public CubeMap
306 // m_cubeCopyOutputIdx colour-attachment index taking the array-then-copy
307 // path, -1 otherwise; one output per shader
308 QRhiTexture* m_cubeCopyShadowArray{};
309 QRhiTexture* m_cubeCopyCube{};
310 int m_cubeCopyOutputIdx{-1};
311
312 // The part of the m_materialUBO for which changes
313 // trigger a pipeline recreation (blend status etc.)
314 static constexpr int size_of_pipeline_material = 32;
315 alignas(4) char m_prevPipelineChangingMaterial[size_of_pipeline_material]{0};
316 struct PipelineChangingMaterial
317 {
318 int32_t mode; // tri, point, line
319 int32_t enable_blend; // bool
320 QRhiGraphicsPipeline::BlendFactor src_color;
321 QRhiGraphicsPipeline::BlendFactor dst_color;
322 QRhiGraphicsPipeline::BlendOp op_color;
323 QRhiGraphicsPipeline::BlendFactor src_alpha;
324 QRhiGraphicsPipeline::BlendFactor dst_alpha;
325 QRhiGraphicsPipeline::BlendOp op_alpha;
326 };
327 static_assert(sizeof(PipelineChangingMaterial) == size_of_pipeline_material);
328
329 ossia::transform3d m_modelTransform;
330};
331}
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
Connection between two score::gfx::Port.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:103
Definition VertexFallbackPlan.hpp:27
Aggregate of all graphics-visible storage resources for a node.
Definition IsfBindingsBuilder.hpp:115
Definition Mesh.hpp:94
Data model for meshes.
Definition Mesh.hpp:179
Port of a score::gfx::Node.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:82
Definition RenderedRawRasterPipelineNode.hpp:18
QRhiTexture * textureForOutput(const Port &output) override
Definition RenderedRawRasterPipelineNode.cpp:168
bool hasOutputPassForEdge(Edge &edge) const override
Check if this renderer already has an output pass for the given edge.
Definition RenderedRawRasterPipelineNode.cpp:2355
void removeInputEdge(RenderList &renderer, Edge &edge) override
Notify the renderer that an input edge was disconnected.
Definition RenderedRawRasterPipelineNode.cpp:2530
void addInputEdge(RenderList &renderer, Edge &edge, QRhiResourceUpdateBatch &res) override
Definition RenderedRawRasterPipelineNode.cpp:2513
void initState(RenderList &renderer, QRhiResourceUpdateBatch &res) override
Definition RenderedRawRasterPipelineNode.cpp:1832
void removeOutputPass(RenderList &renderer, Edge &edge) override
Definition RenderedRawRasterPipelineNode.cpp:2309
void releaseState(RenderList &renderer) override
Definition RenderedRawRasterPipelineNode.cpp:2361
void updateInputTexture(const Port &input, QRhiTexture *tex, QRhiTexture *depthTex=nullptr) override
Definition RenderedRawRasterPipelineNode.cpp:101
void addOutputPass(RenderList &renderer, Edge &edge, QRhiResourceUpdateBatch &res) override
Create a pass for a new output edge (pipeline, SRB, processUBO).
Definition RenderedRawRasterPipelineNode.cpp:2275
Useful abstraction for storing all the data related to a render target.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:152