Handling a graph of RenderTargets and a CommandBuffer


#1

While writing qml graph renderer + RenderExtension, I’ve encountered quite a few cases where a qml-scene-graph node/sub-graph is rendered to a texture to be later rendered by another scene-graph-node with a specific effect ( animation, shaders ). Now potentially this might create a DAG of dependencies between scene graph nodes.

The Qt’s D3D12 scene-graph example is pretty involved, but from what I’ve managed to decode, they use various texture memory barriers/fences and multiple command queues.

To the question then: How can I ensure that one of the dependent textures have already been rendered, should I force a swapBuffers calls on them in order ? Or should I split the graph into multiple CommandBuffers and use CommandSyncMask to encode the dependencies ?


#2

I’m not sure I understand the situation. You can render to a target multiple times, and you can use a target you rendered to for reading right after as well. There’s no special logic required. The only limitation is you can’t have it bound both for writing and reading.


#3

For an example of the complexity that can get appear in QML:

    Rectangle {
        id: gradientRect;
        width: 10
        height: 10
        gradient: Gradient {
            GradientStop { position: 0; color: "white" }
            GradientStop { position: 1; color: "steelblue" }
        }
        visible: false; // should not be visible on screen.
        layer.enabled: true;
        layer.smooth: true
    }

    Text {
        id: textItem
        font.pixelSize: 48
        text: "Gradient Text"
        anchors.centerIn: parent
        layer.enabled: true
        // This item should be used as the 'mask'
        layer.samplerName: "maskSource"
        layer.effect: ShaderEffect {
            property var colorSource: gradientRect;
            fragmentShader: "
                uniform lowp sampler2D colorSource;
                uniform lowp sampler2D maskSource;
                uniform lowp float qt_Opacity;
                varying highp vec2 qt_TexCoord0;
                void main() {
                    gl_FragColor =
                        texture2D(colorSource, qt_TexCoord0)
                        * texture2D(maskSource, qt_TexCoord0).a
                        * qt_Opacity;
                }
            "
        }
    }

So in single-command-queue case the code should first render gradientRect to an offscreen texture target ( layer.enabled ), then a text node is using the gradientRect.
In case of multiple command queues, I’d build and dispatch rendering of gradientRect on CQ1, and build a second command queue that ‘depends’ on CQ1’s output at specific ‘point’ ( just before using CQ1’s output ?)

Now this might be me not understanding underlying concepts well enough, but it seems like we might encounter some corruption while gradientRect is not ‘finished’ and the rendering that uses it starts ?

As a side not, this is the output the above QML produces:


#4

There is no benefit to using multiple queues for rendering. All GPU’s I’m aware of are only able to render to one target at a time, because switching between them requires extensive state changes. This might change in future hardware but for now multiple queues are only useful for “side jobs” like upload and compute (and even support for compute is questionable on nVidia). But if you were to use multiple queues you would need to sync their submissions (using the sync mask you mentioned) so they execute in order since they depend on each other.

You are still able to use multiple command buffers and get a performance benefit on the CPU side. Although it remains to be seen how useful is that considering the nodes seem to be very simple, the overhead of a CB might be higher than just using a single one. Command buffers still need to be submitted from a single thread and will be executed in order so handling of dependencies is up to your code to submit them in the right order.


#5

Thank You for answering.

So if I stick with single CQ, dispatch multiple drawing operations, where some of them depend on the results of previous ones ( gradientRect drawn to texture and then used as a shader input ) BSF will ‘know’ of the dependency ( in case of vulkan rendering ) add proper image transitions and in general ‘just-work’ ? :slight_smile:


#6

Exactly, bsf keeps track of all that stuff and handles the necessary barriers/transitions under the hood.