In this blog series I’m listing out different performance tips and tricks for Qt Quick to make sure you’re getting the best performance out of the scene graph renderer. In this post let’s talk about how the default render in Qt Quick batches draw commands to OpenGL.
When you write an application in Qt Quick you are using OpenGL to render the elements in your user interface on screen. That is each button, label, drop down menu is rendered on screen using OpenGL (this is similar to if you wrote your application in WPF then your UI would be rendered on screen using DirectX).
OpenGL is a pure hardware API and performs best when the number of draw calls are very low and state changes are kept to a minimum.
Take for example a ListView which shows three items, each item uses a delegate that shows an icon and some text. Also the items background colour is alternating. To render this on screen OpenGL needs to do quite a bit of work.
When the render starts to draw a single delegate on screen the first thing it does is spin up a shader program so that it can draw a solid colour fill (in the above its either using White or Grey as the colour). After its prepared the shader in order to use it the next thing it has to do is draw the rectangle in which to fill. It does this by using
glDrawArrays() to draw four vertices (which creates two triangles that together form the rectangle). Lastly it applies the shader to the two newly created triangles and now we have a solidly filled in rectangle to use as the background of our delegate. Next it goes about drawing the icon which is a similar process (use
glDrawArrys() to create a rectangle then apply a shader to said rectangle); the difference being that instead of using a shader which does solid colour fills it will use a shader that applies a texture (in this case a texture that comprise of the icon image). Here however because the icon is not a solid rectangle (that is parts of it is transparent so we can see the background) the shader has to do some extra work applying an alpha-blend to the texture. The last thing that the render has to do in order to render a single delegate on screen is to render the text, which essentially does the same thing we just did for the icon (shader, alpha-blend, draw vertices, apply) but the texture will be the text instead of an image.
Like I said a lot of work, and it has to do it for each delegate!
Because this would make the application completely unusable (rendering each delegate would be super slow if you had a lot of them) the default render in Qt Quick is able to batch together these draw commands. Instead of spinning up a shader, applying alpha-blend, draw vertices, apply, rinse-wash-repeat for each delegate Qt Quick can draw all the verities together with a single glDrawArrays() command, patch together multiple delegate textures so that we can apply an alpha-blend to all of them in one go and use a single shader to apply them. This is called Batching.
Taking a look at the same example again here we can see that the number of OpenGL draw commands has dropped dramatically.
In the above instead of rendering the background colour for each delegate independently the render has batched them together and now only draws a single rectangle for all of the delegates and uses a shader that applies three solid colour fill textures which have been patched together. So now instead of drawing 6 triangles and painting each one we can draw and paint only two.
Secondly all the visible icon textures have been patched together and applied again to a single rectangle, all three icon images were loaded into their own texture but the textures were patched together into one texture. And the same goes for the text.
This greatly improves performance of the render but it does have some limitations.
For starters the above diagram is a little misleading as its showing that all the text is being rendered together in one set of OpenGL draw commands however, since the text alternates colours this is not actually possible. Only if the text is using all the same decoration information (i.e. same font, colour, weight, decoration/italic, etc. ) can a single shader program be used to render the text. In the above since we are using two different colours we need to use two different shader programs and therefore need to draw them in separate OpenGL draw commands. In other words we can’t batch them together.
Secondly if we set the clip property to true on the delegate Rectangle (the component which makes the grey or white background) we would be forcing each delegate into its own context (see Qt Quick Performance Tips – Clipping) and therefore would not be able to batch their draw commands together.
In order to visualize how on screen elements are being batched together set the environment variable QSG_VISUALIZE to batches. This will colourize your UI to show you the different batches. So the more colours you see the more batches were required to draw the view. Visual elements which share the same colour were drawn together by the same set of draw commands.
Here we have the same model driving two separate ListViews. The ListView on the left produces delegates with alternating text colours where the ListView on the right is using the same text colour for each entry but is alternating the icon.
By turning on batch colourization we can see that the ListView on the right is using far less draw commands to render its contents by the fact that all the rendered text is being shown in the same colour indicating that they are all drawn at the same time. This is in contrast with the ListView on the left which shows the use of two different batches to render all the text due to the fact that the delegate is alternating the text colours. Notice that the black text in the left ListView is actually being rendered at the same time as all the text in the right ListView; OpenGL draw command batching is not limited to views or layouts but applied to the entire scene as a whole.
Here is what happens to the batching if we enable clipping on the base Rectangle (the one used to generate the different background colours for each delegate) of the delegate used for the ListView on the right hand side. By enabling clipping we push each delegate into its own context therefore each delegate needs to be rendered independently. Notice here that now not only does each text in the right ListView need to be rendered independently but so are the icons and also there is no longer any batching between the two ListViews (it might be hard to tell as the use of the colour green is rampant but they are all slightly different shades of green indicating that they are all different batches of draw commands).
You can enable the colourization of batches in Qt Creator by going to the Projects tab, expand the Build Environment section, click Add, then enter as the variable name QSG_VISUALIZE and set the value for that variable to batches.
Hope that helps,
You can download a sample application which illustrates the above here:
Until next time think imaginatively and design creatively