An IMGUI-style drawing API isn’t necessarily just immediate-mode graphics

Kragen Javier Sitaker, 2015-09-03 (3 minutes)

There’s this thing called “immediate-mode GUIs” floating around which is kind of related to immediate- versus retained-mode drawing systems. But it’s not really. In an immediate-mode GUI, you supply the library with a function which draws your UI, and it invokes it when necessary, influencing its execution as needed.

A real drawing equivalent of immediate-mode GUIs might be how you would draw if you only had a single scan line of framebuffer. You could have a function that represented the image you wanted to draw, invoked with a drawing context object of type dctx, once every scan line:

void draw_stuff(dctx *c)
{
    draw_rect(c, 10, 10, 100, 100, DC_GREY);
    draw_circle(c, 10, 10, 100, 100, DC_BLACK);
    int baseline = 55;
    if (bbox(c, baseline - font_height, 0, baseline, 100)) {
        char *s = "Welcome";
        int text_width = text_escapement(s);  // for centering
        show_text(c, 55 - text_width/2, baseline, s, DC_RED, DC_BLACK);
    }
}

Note that this is almost entirely abstract over the question of whether you’re drawing a scanline at a time or what.

The bbox function determines whether or not part of the currently-being-drawn area impinges on that bounding box. If not, the user-provided function above skips the potentially expensive calculations within. This is the same approach IMGUI libraries use to handle buttons being clicked, menus being open, and so on.

Serious IMGUI libraries do stuff to help you divide your GUI-drawing function into separately memoized parts, but I’m not going to touch on that here. Instead I’m going to focus on the idea of using this to make it usable to draw graphics scanline by scanline.

Let’s consider what show_text has to do. It’s being invoked with a dctx, a start point, some text, a foreground, and a background:

// in the dctx library:
void show_text(dctx *c, int x, int y, char *s, color fg, color bg)
{
    <<show_text implementation>>
}

Let’s suppose for the moment that we only have one font, and it’s a proportional bitmap font; and that this function in particular only has to draw a single line.

First, we ought to check to see if our bounding-box is being drawn, which means we have to compute it.

// in show_text implementation:
int width = text_escapement(s), max_x = x + width, min_y = y - font_height;
if (!bbox(c, x, min_y, max_x, y)) return;

Now, though, we need to copy the appropriate chunks of pixels into the scan line, in the appropriate color. This part inevitably depends on the fact that we’re drawing a single scanline, rather than half a scanline or four scanlines.

int font_line = c->y - min_y;
char k;
for (char *p = s; (k = *p) != '\0'; p++) {
    short pixels = get_font_pixel_slice(k, font_line);
    char width = get_font_pixel_width(k);
    for (int i = 0; i < width; i++) {
        c->buf[x] = pixels & 1 ? fg : bg;
        x++;
        pixels >>= 1;
    }
}

And that’s it, aside from some relatively mundane details about get_font_pixel_width and get_font_pixel_slice. That gives you the software equivalent of an old-style character generator, permitting color framebufferless rendering at arbitrary pixel offsets.

Topics