List of nodes to be rendered to an output. More...
Detailed Description
List of nodes to be rendered to an output.
This references all the score::gfx::Node that have an effect on a given output, and manages all the matching renderers, as well as a few shared data, such as output-specific UBOs, shared textures and buffers, etc.
The score::gfx::Graph creates one RenderList per OutputNode in the graph.
Public Types | |
| using | Buffers = std::pair< const Mesh *const, MeshBuffers > |
Public Member Functions | |
| RenderList (OutputNode &output, const std::shared_ptr< RenderState > &state) | |
| void | init () |
| Initialize data for this renderer. | |
| QRhiResourceUpdateBatch * | initialBatch () const noexcept |
| Initial resource update batch. | |
| void | setInitialBatch (QRhiResourceUpdateBatch *batch) noexcept |
| Store a resource update batch to be submitted on the next render frame. | |
| void | flushInitialBatch () |
| Submit the pending initial batch before destroying resources it may name. | |
| MeshBuffers | initMeshBuffer (const Mesh &mesh, QRhiResourceUpdateBatch &res) |
| Create buffers for a mesh and mark them for upload. | |
| void | update (QRhiResourceUpdateBatch &res) |
| Update / upload this RenderList's shared data. | |
| void | render (QRhiCommandBuffer &commands, bool force=false) noexcept |
| Render every node in order. | |
| QRhiCommandBuffer * | currentCommandBuffer () const noexcept |
| void | release () |
| Release GPU resources owned by this render list. | |
| void | releaseBuffer (QRhiBuffer *buf) |
| Retire a buffer this render list's node owns. | |
| bool | checkBindingsLive (const QRhiShaderResourceBindings &srb, const char *where) const noexcept |
| bool | maybeRebuild (bool force=false) |
| Check if the render size has changed in order to rebuild the pipelines. | |
| bool | resizeSwapchainSizedTargets (QSize newOutputSize, QSize newRenderSize) |
| Fast-path for pure viewport resize. | |
| TextureRenderTarget | renderTargetForOutput (const Edge &edge) const noexcept |
| Obtain the texture corresponding to an output port. | |
| TextureRenderTarget | renderTargetForInputPort (const Port &p) const noexcept |
| Look up the render target for a given input port. | |
| BufferView | bufferForInput (const Edge &edge) const noexcept |
| BufferView | bufferForOutput (const Edge &edge) const noexcept |
| QImage | adaptImage (const QImage &in) |
| Adapts an image to the GPU limits / format. | |
| Buffers | acquireMesh (const ossia::geometry_spec &, QRhiResourceUpdateBatch &res, const Mesh *current, MeshBuffers currentbufs) noexcept |
| void | clearRenderers () |
| Clear the renderers so that they get reinitialized on the next frame. | |
| QRhiTexture & | emptyTexture () const noexcept |
| Texture to use when a texture is missing (2D) | |
| QRhiTexture & | emptyTexture3D () const noexcept |
| Texture to use when a 3D (sampler3D) texture is missing. | |
| QRhiTexture & | emptyTextureCube () const noexcept |
| Texture to use when a cubemap (samplerCube) is missing. | |
| QRhiTexture & | emptyTextureArray () const noexcept |
| Texture to use when a 2D array (sampler2DArray) is missing. | |
| QRhiBuffer & | outputUBO () const noexcept |
| UBO corresponding to the output parameters: | |
| GpuResourceRegistry & | registry () noexcept |
| Per-output GPU arena store for scene-graph source nodes. | |
| const GpuResourceRegistry & | registry () const noexcept |
| VertexFallbackPool & | vertexFallbackPool () noexcept |
| Per-RenderList pool of neutral fallback vertex buffers for "REQUIRED: false" VERTEX_INPUTS whose upstream geometry does not provide a matching attribute. | |
| GpuTimings & | gpuTimings () noexcept |
| Per-RenderList GPU-timing collector. | |
| const GpuTimings & | gpuTimings () const noexcept |
| Gfx::AssetTable * | assetTable () const noexcept |
| Session-wide asset decode cache. | |
| void | setAssetTable (Gfx::AssetTable *t) noexcept |
| const score::gfx::Mesh & | defaultQuad () const noexcept |
| A quad mesh correct for this API. | |
| const score::gfx::Mesh & | defaultTriangle () const noexcept |
| A triangle mesh correct for this API. | |
| bool | requiresDepth (const score::gfx::Port &p) const noexcept |
| Whether this list of rendering actions requires depth testing at all. | |
| bool | anyNodeRequiresDepth () const noexcept |
| int | samples () const noexcept |
| const Quirks & | quirks () const noexcept |
| void | isolateFollowingPasses () noexcept |
| Put the render passes that follow in a command buffer of their own. | |
| bool | canRender () const noexcept |
| QSize | renderSize (const Edge *e) const noexcept |
| void | createAllInputRenderTargets () |
| void | markBuilt () noexcept |
| Mark this render list as fully built. | |
| bool | isBuilt () const noexcept |
| Is this render list currently coherent with its output's GPU objects? | |
| void | markRequiresDepth (bool value) noexcept |
| void | onEdgeRemoved (Edge &edge, const ossia::hash_set< const Port * > *preserveSinks=nullptr) |
| void | removeInputRenderTarget (const Port *port) |
| Remove the render target for a specific input port. | |
| QSize | resolveDownstreamSize (const Node *node, const ossia::small_flat_map< const Port *, RenderTargetSpecs, 16 > &resolvedSpecs) const noexcept |
| Resolve the downstream render target size for a node. | |
Static Public Member Functions | |
| static void | adoptBuffer (QRhiBuffer *buf) |
| Borrow a buffer another node owns (a slot with owned == false). | |
| static void | dropAdoptedBuffer (QRhiBuffer *buf) |
| static int | adoptedBufferCount () noexcept |
| Adoptions currently held, process-wide. Tests assert on this. | |
| static bool | isRetiredBuffer (const QRhiBuffer *buf) noexcept |
| Buffers this render list has retired, for the bind-time liveness check. | |
| static bool | isRetiringBuffer (const QRhiBuffer *buf) noexcept |
| static QByteArray | retiredBufferName (const QRhiBuffer *buf) |
| static int | retiredBufferCount () noexcept |
| static void | noteBufferLive (const QRhiBuffer *buf) noexcept |
| static void | noteFrameCompleted () noexcept |
| Advance the shared frame sequence and prune old retirements. | |
| static bool | hasRetiredBinding (const QRhiShaderResourceBindings &srb) noexcept |
| static bool | strictBindingsEnabled () noexcept |
| static int | staleBindingCount () noexcept |
| static void | resetStaleBindingCount () noexcept |
Public Attributes | |
| OutputNode & | output |
| Output node to which this RenderList is rendering to. | |
| RenderState & | state |
| RenderState corresponding to this RenderList. | |
| std::vector< score::gfx::Node * > | nodes |
| Nodes present in this RenderList, in order. | |
| std::vector< score::gfx::NodeRenderer * > | renderers |
| Renderers - one per node. | |
| int64_t | frame = 0 |
| float | currentDate [4] {} |
Friends | |
| struct | Graph |
Member Function Documentation
◆ adaptImage()
| QImage score::gfx::RenderList::adaptImage | ( | const QImage & | in | ) |
Adapts an image to the GPU limits / format.
e.g. Y direction, texture size limits...
◆ adoptBuffer()
|
static |
Borrow a buffer another node owns (a slot with owned == false).
Every adoption is a reference in a process-wide registry: the pointer is unique per QRhi and the owner is routinely on another render list, so the registry is shared the same way the retirement registry is. The consumer calls dropAdoptedBuffer() when its slot stops naming the buffer – it re-adopts, falls back to its own allocation, or releases its state – and an owner that has already released the buffer by then is what frees it.
This is what makes the owned=false contract enforced rather than assumed: a buffer with a live adoption cannot be freed, whatever the owner does.
◆ assetTable()
|
inlinenoexcept |
Session-wide asset decode cache.
Set by Graph::createRenderList from GfxContext's AssetTable. May be null on test RenderLists or after teardown. Consumers must guard.
◆ checkBindingsLive()
|
noexcept |
True when no buffer bound by srb has been retired. Warns naming each offender. Reads only the binding list, never the buffer objects.
◆ currentCommandBuffer()
|
inlinenoexcept |
◆ flushInitialBatch()
| void score::gfx::RenderList::flushInitialBatch | ( | ) |
Submit the pending initial batch before destroying resources it may name.
A QRhiResourceUpdateBatch stores raw QRhiBuffer / QRhiTexture pointers until it is committed, and QRhiResource::deleteLater() outside a frame deletes immediately. Graph mutations run between frames, so any path that tears down renderer resources while this batch is outstanding must call this first; otherwise the next render() submits updates naming freed resources.
◆ gpuTimings()
|
inlinenoexcept |
Per-RenderList GPU-timing collector.
Renderers wrap their begin/endPass regions in ScopedGpuTimer to attribute the CB-wide lastCompletedGpuTime to the named pass. The result is one frame stale — see GpuTiming.hpp for details.
◆ hasRetiredBinding()
|
staticnoexcept |
Same question without the warning or the counter: for callers that are about to repair the binding rather than report it.
◆ initialBatch()
|
noexcept |
Initial resource update batch.
See QRhiResourceUpdateBatch::merge for documentation on this
◆ initMeshBuffer()
| MeshBuffers score::gfx::RenderList::initMeshBuffer | ( | const Mesh & | mesh, |
| QRhiResourceUpdateBatch & | res | ||
| ) |
Create buffers for a mesh and mark them for upload.
The meshes used by the nodes are cached (as most are just rendering on a full-screen triangle, which we can reuse).
◆ isBuilt()
|
inlinenoexcept |
Is this render list currently coherent with its output's GPU objects?
False between the moment something invalidates the list (a fast-path viewport resize: resizeSwapchainSizedTargets) and the maybeRebuild() that runs on the next render frame. In that window the output node has ALREADY destroyed and replaced its QRhiTextureRenderTarget / QRhiRenderPassDescriptor while the renderers still hold the pre-resize snapshot, so nothing outside the rebuild may ask a renderer for a render target.
◆ isolateFollowingPasses()
|
noexcept |
Put the render passes that follow in a command buffer of their own.
For passes whose sample count is not ours to choose and which may be empty. Must be called outside a pass.
◆ isRetiredBuffer()
|
staticnoexcept |
Buffers this render list has retired, for the bind-time liveness check.
A consumer that adopted a producer's buffer with owned=false keeps a raw pointer. If the producer retires it and the consumer never re-reads, the pointer stays in the consumer's shader resource bindings: the rebuild is gated on a hash of the pointers, and a pointer that goes dead without changing hashes the same. Qt cannot catch it either – its own generation check reads m_id off the resource, so a freed resource cannot report it.
Recording the retirement gives a liveness oracle that does not dereference the pointer, which is what makes the failure deterministic instead of dependent on whether the freed chunk has been reused yet. True once the buffer has actually been freed. deleteLater() keeps the object alive until the end of the frame that retired it, so a binding made in that same frame still dereferences a live object: only a use in a LATER frame is a use-after-free. Counting the same-frame case made the oracle over-report by roughly an order of magnitude.
◆ isRetiringBuffer()
|
staticnoexcept |
True from the moment of retirement, same frame included: the slot should stop pointing at it even while the object is still alive.
◆ markBuilt()
|
inlinenoexcept |
Mark this render list as fully built.
Prevents maybeRebuild() from unnecessarily tearing down and recreating all resources on the first render frame after createRenderList() has already fully initialized everything.
◆ markRequiresDepth()
|
inlinenoexcept |
Set the "any node requires depth" flag computed from the node graph. Either direction invalidates the build: a rise leaves targets allocated without depth, a fall leaves every sink carrying a depth attachment nothing reads – 33 MB per sink at 4K, for the rest of the session.
◆ noteBufferLive()
|
staticnoexcept |
Forget a retirement recorded at this address: the caller holds a buffer it created and has not released, so the allocator handed the freed address back and the retirement no longer describes the object there. Without this a recycled address reads as a use-after-free.
◆ onEdgeRemoved()
| void score::gfx::RenderList::onEdgeRemoved | ( | Edge & | edge, |
| const ossia::hash_set< const Port * > * | preserveSinks = nullptr |
||
| ) |
Notify that an edge was removed. Notifies renderers, releases RT if unused.
- Parameters
-
preserveSinks Optional set of sink Ports that should keep their input render target even if this edge was their only feed. Used by batched edge updates (see GfxContext::incrementalEdgeUpdate) so that inserting a filter between two nodes doesn't destroy and immediately re-allocate the same RT when the old and new edges share a sink port.
◆ outputUBO()
|
inlinenoexcept |
UBO corresponding to the output parameters:
- Render size
- Per-API adjustments and globals
◆ registry()
|
inlinenoexcept |
Per-output GPU arena store for scene-graph source nodes.
Returns a reference to the registry that owns the Camera / Light / Material / PerDraw arena buffers. Source nodes (Camera, Light, PBRMesh, …) allocate a slot from this registry at construction and write their packed bytes into it at their own update().
Persist-across-rebuild contract: the registry is owned by the OutputNode (OutputNode::m_registry) and survives RenderList rebuilds — the same registry pointer is observed by both the pre- and post-rebuild RenderList for a given OutputNode. Consumers that cache the registry pointer (e.g. ScenePreprocessor's m_registry) can compare it against the new RL's registry on init() and skip the cache wipe when it is unchanged.
◆ releaseBuffer()
| void score::gfx::RenderList::releaseBuffer | ( | QRhiBuffer * | buf | ) |
Retire a buffer this render list's node owns.
The free is deferred while any consumer still holds the buffer through adoptBuffer(): the owner's release then only marks it orphaned, and the last dropAdoptedBuffer() frees it. A consumer that borrowed the handle therefore keeps binding a live object – the last contents its producer wrote – until it re-reads the producer's geometry and lets go, instead of binding a freed one.
◆ render()
|
noexcept |
Render every node in order.
Never throws: a node that fails to build its pipeline, or any other exception raised while recording, is caught and reported here rather than escaping into the output node's frame bracket and, from there, into QCoreApplication::notify.
◆ renderTargetForInputPort()
|
noexcept |
Look up the render target for a given input port.
All input render targets are created centrally before any node init() so that they are available regardless of initialization order.
◆ renderTargetForOutput()
|
noexcept |
Obtain the texture corresponding to an output port.
This is done by looking for the render target which corresponds to a given port.
◆ requiresDepth()
|
noexcept |
Whether this list of rendering actions requires depth testing at all.
e.g. it's not needed if we're just doing some generative shaders.
◆ resizeSwapchainSizedTargets()
| bool score::gfx::RenderList::resizeSwapchainSizedTargets | ( | QSize | newOutputSize, |
| QSize | newRenderSize | ||
| ) |
Fast-path for pure viewport resize.
Update state.outputSize to newOutputSize and state.renderSize to newRenderSize, and mark every renderer's renderTargetSpecsChanged so the rt_changed block in renderInternal does the RT recreation and sampler rebinding on the next render frame.
The two sizes are distinct: the output node owns the render size (the /rendersize override, ScreenNode::setRenderSize), the platform owns the output size (the swapchain). Callers pass both from the RenderState the output node has already updated.
Skips the full recreateOutputRenderList teardown + rebuild (pipeline compiles, ScenePreprocessor rebuild, mesh slab uploads, texture array reallocation): the persistent registry and ScenePreprocessor caches make that work unnecessary for a pure size change. Cost is O(N renderers), with no GPU drain and no allocation until the next frame's rt_changed block recreates the RTs.
Returns true on success. Returns false, and the caller must fall back to recreateOutputRenderList, when:
- either size is invalid
- renderers vector is empty (RL not yet initialised)
◆ resolveDownstreamSize()
|
noexcept |
Resolve the downstream render target size for a node.
Returns the maximum size across all downstream render targets that this node renders to. Used as fallback when a node's input port has no explicit render target size.
◆ retiredBufferName()
|
static |
Name the buffer carried at retirement, while it was still alive: reading name() off a retired buffer is the dereference this check exists to avoid.
◆ setInitialBatch()
|
inlinenoexcept |
Store a resource update batch to be submitted on the next render frame.
Used by incremental edge additions that happen after the first render frame (when the original m_initialBatch has already been consumed).
◆ staleBindingCount()
|
staticnoexcept |
How many stale bindings checkBindingsLive has seen, process-wide. Tests assert on this: the render loop swallows the strict-mode exception and carries on, so a violation is invisible from the frame result alone.
◆ strictBindingsEnabled()
|
staticnoexcept |
True when SCORE_GFX_STRICT_BINDINGS is set: a failed liveness check then throws instead of only warning.
◆ vertexFallbackPool()
|
inlinenoexcept |
Per-RenderList pool of neutral fallback vertex buffers for "REQUIRED: false" VERTEX_INPUTS whose upstream geometry does not provide a matching attribute.
Valid between init() and release(). See VertexFallbackPool.hpp.
The documentation for this class was generated from the following files:
- RenderList.hpp
- RenderList.cpp