Efficiently rendering ordered GUI objects

Started by
2 comments, last by Pthalicus 12 years, 8 months ago
Hi everyone,

I'm writing my own GUI lib using D3D11 ( but that's irrelevant ). I understand there are existing libraries out there for this, but I'm doing it for fun. :)

Anyway, each renderable quad has position, size, texture uv's, and colour (rgba) properties.
Each quad is stored within a quadbuffer that can hold 256 quads. And each source image holds a list of these quad buffers.
The typical render path is:

  1. Set shaders/data etc
  2. For each source image:
  3. Set source image
  4. Render each quad buffer

This seems to be very efficient, or at least efficient enough. Unfortunately, when designing the system I completely forgot about depth sorting.
Currently depth checks are disable for the GUI, and quads are literally rendered on top of each other as they come. As quads can have transparency,
ideally I'd like to render them from back to front to accumulate colour, but how would I go about sorting the quads and still render them efficiently?

I've considered giving each quadbuffer a depth layer, and then sorting the quadbuffers for rendering. This would still enable me to batch the quads, instead of rendering each quad individually.
But then I'd require more quadbuffers - and so more quads, than is necessary.

I guess this is the common battle between depth sorting and batching. Any thoughts?


Thanks

Edit: I should point out that the rendering of the GUI elements is separated from the implementation of the functionality. e.g A button doesn't have a render method, but instead holds renderable quads/sprites which will be rendered by the GUI system.

Saving the world, one semi-colon at a time.

Advertisement
what kind of numbers you talking about i.e. if you rendered each element with one call a piece how many render calls would there be?

50?
250?
500?
more?

maybe you could steal some ideas from:
scaleform
Alot of items in GUI rendering have to be single draw calls to keep ordering correct. You have to sort the GUI so that the order of draw calls is correct. This is not very hard because usually all the controls are stored in a window, and each window is a layer. So, you dont really need to sort things like, buttons, text boxes, check boxes, etc because they should all be contained within a window. So, as long as you maintain a list of children of a window, you simply ensure the windows are layered correctly, then draw each window in back-to-front order. You can batch all the children of each window into a single draw call too. So, if you have 8 windows, you should be able to do 16 draw calls. This is how I organized my UI. If you want free floating controls that are not part of an actual window, that is easy too because you can cheat. Just create a window and make sure the background of that window is not drawn. So, when you place controls within it, they are drawn, but the actual background of the window is not. Now, you have free floating controls.

There are a few special cases where you will have to do a few extra checks, like on the textbox caret, which has to be drawn last. But, you can do this by keeping exactly one caret for your entire GUI and just drawing that after everything else is drawn --thus ensuring that it is over the top of the textbox. Another special case is for listboxes when they are opened. These have to be drawn last as well if they are open to make sure they layer over the top of any other controls.
Wisdom is knowing when to shut up, so try it.
--Game Development http://nolimitsdesigns.com: Reliable UDP library, Threading library, Math Library, UI Library. Take a look, its all free.
[font=arial, verdana, tahoma, sans-serif][size=2]Thanks for the response,


I've just taken a look at scaleform - it looks extremely useful. [/font]
Similar to the "Intelligent caching" in scaleform, my quadbuffers only update the GPU data when there's been a change. (Which makes perfect sense)

I guess for a single non-resizeable button 1, for a resizeable button 9, and for a "window" - about 20 quads?

Having a single draw call for each of these elements would produce a lot of drawcalls /overhead. At the moment, the quadbuffers render them in sets of 256. So 1 draw call to render 256 quads.
I guess allowing the quadbuffers to store an arbritrary number of quads would make sorting easier... so for example a scalable button would request 9 quads for a single quad buffer. And then I guess if each buffer had a "depth layer" it'd make sorting easier - seeing as they're only rendered in their natural order anyway.




Alot of items in GUI rendering have to be single draw calls to keep ordering correct. You have to sort the GUI so that the order of draw calls is correct. This is not very hard because usually all the controls are stored in a window, and each window is a layer. So, you dont really need to sort things like, buttons, text boxes, check boxes, etc because they should all be contained within a window. So, as long as you maintain a list of children of a window, you simply ensure the windows are layered correctly, then draw each window in back-to-front order. You can batch all the children of each window into a single draw call too. So, if you have 8 windows, you should be able to do 16 draw calls. This is how I organized my UI. If you want free floating controls that are not part of an actual window, that is easy too because you can cheat. Just create a window and make sure the background of that window is not drawn. So, when you place controls within it, they are drawn, but the actual background of the window is not. Now, you have free floating controls.

There are a few special cases where you will have to do a few extra checks, like on the textbox caret, which has to be drawn last. But, you can do this by keeping exactly one caret for your entire GUI and just drawing that after everything else is drawn --thus ensuring that it is over the top of the textbox. Another special case is for listboxes when they are opened. These have to be drawn last as well if they are open to make sure they layer over the top of any other controls.


Yes, this makes sense. I could create a buffer for each window - which would store the window + child elements. Which would result in 1 draw call per window. The only issue then would be that the buffers may need to be dynamic to account for adding components to the window. Hmm. Thanks for the tips, smasherprog.

Saving the world, one semi-colon at a time.

This topic is closed to new replies.

Advertisement