Handmade Hero»Forums»Code
18 posts
Performance of IMGUI

Hi, I'm writing a small UI library in IMGUI style, and I wonder how to reduce the number of draw calls. For example, if I draw a button, it will have a background and a text on top, so the code looks like:

bool button(char *text)
{
  // ..
  drawRect(..);
  drawText(..);
  // ..
}

So if I have a lot of buttons, the render commands will be rect, text, rect, text, .. The problem is that I have to change shaders between rects and text. I would like to be able to draw all rectangles at once first and then all the texts. How can I do that in an IMGUI style? In retained mode I think that we would just maintain the order of the commands and just hand that off to the GPU at once.

Benjamin Pedersen
44 posts
Mathematician
Performance of IMGUI

I haven't written an immediate mode gui myself, but from what I understand most don't do it like your example. They queue up draw calls to be done in a rendering pass. I guess you could have a first rendering pass for the rectangles and a another for text. Makes sense?

Mārtiņš Možeiko
2406 posts / 2 projects
Performance of IMGUI
Edited by Mārtiņš Možeiko on

If you're drawing text from font atlas then you can get rectangle drawing very cheaply with same shader. Put a solid (1,1,1,1) color as one pixel into your atlas. And use it's uv coordinats for fake text "glyph" to draw in place of rectangle. Because it is same color the actual size of rectangle does not matter - shader will sample same color across whole rectangle you draw.

Then all it is left is to multiply sampled color with color from vertex attribute - but that part is the same for both font glyphs or your fake rectangle-glyph.

If you open default DearImgui example, for example, from here: https://pthom.github.io/imgui_manual_online/manual/imgui_manual.html

and go to Widgets -> Images you'll see font atlas texture they created. Look above mouse cursor on first row (directly to the right of first triangle). The dot above is this pixel I've talked about.

image.png

The code that uses is to draw rect is here: https://github.com/ocornut/imgui/blob/24dfe6db8ac2ffefce8cf02a6b585a05ebfcbe0a/imgui_draw.cpp#L668-L681

You can see it uses same uv value (TexUvWhitePixel) for all 4 vertices regardless for rectangle size (a/b coordinates).

18 posts
Performance of IMGUI
Replying to mmozeiko (#26656)

Thanks, this is an interesting technique. But if we have very specialized shaders for each shape, like circles, rects, what can we do? Do I have to use a single shader for everything? Or maybe I should record the rendering commands and sort them each frame?

Simon Anciaux
1241 posts
Performance of IMGUI
Edited by Simon Anciaux on
Replying to Terans (#26657)

Like Benjipede said, you want to send a single drawcall for all rectangles, and a single drawcall for all text.

In my codebase, every call to drawText and drawRectangle adds a entry in a batch (command buffer), and at the end of the frame I call render, and that fills the index and vertex buffers and do 1 draw call for text, and 1 for rectangles.

I found that this isn't a very good way to do things, because if you have a popup window (for a dropdown list for example) than you want the rectangle and text of the popup to be drawn on top of other things, and you need two other draw calls. A simple solution is to have back and front batches for text and rectangles, but that's just a bit more complication to handle.

So having a single shader and draw call (like mmozeiko said) for every thing is probably better. You can have a look at Ryan Fleury's example: https://www.rfleury.com/p/ui-part-6-rendering

Mārtiņš Možeiko
2406 posts / 2 projects
Performance of IMGUI
Edited by Mārtiņš Možeiko on
Replying to Terans (#26657)

If you start adding more customized shaders for drawing different primitives, then you should do branching on its type. As long as same branch is taken for whole triangle it will be almost for free.

Basically in fragment shader it should look like different cases calculating output color from inputs (uv/screen pos/other attributes) differently:

void main()
{
  if (type == rectangle) {
    out_color = RectColor(..);
  } else if (type == circle) {
    out_color = CircleColor(..);
  } else if (type == whatever) {
    out_color = WhateverColor(..);
}
18 posts
Performance of IMGUI
Edited by Terans on

Ok so having a single shader that handles everything seems to be the best thing to do.