Focus: rendering

Published 2021-09-06

First order of business is to put things on the screen. I chose to render to opengl rather than make a terminal app because a) I rarely need to run an editor over ssh b) it's hard to do smooth scrolling in a terminal app c) terminal protocols are insane.

SDL is a library that abstracts over the details of various different windowing and input systems and makes it easy to eg open a window. Consuming C libraries fromm zig is amazingly easy - just add a single import statement and include the library when compiling.

On startup I use freetype to generate a single bitmap containing all the ascii characters and then send that to the gpu. From then on I can render characters by just referring to their coordinates in that image.

When I started this project I had essentially zero experience putting pixels on the screen. I learned a great deal from microui and from reading the Our Machinery blog.

The core idea I got from those was to avoid being too chatty with the graphics driver - there is apparently a large overhead to each draw call. So we want to plan out all our work for the frame and then send it all at once in a single draw call.

The draw call in question is glDrawElements. This can do any of roughly 1 billion different things depending on the current values of a myriad of global variables that we need to set at startup. In this case, we're going to use it to draw a whole bunch of 2d triangles each of which has a set of source coordinates in the image we made earlier, destination coordinates in the window we're drawing to and an RGB colour to map over the original black+white image.

That's literally everything I know about opengl and I will be glad if I never have to touch that code again. The predominant feature of opengl is never telling you if you made a mistake - you have to just intuitively infer what you did wrong from the sullenly empty window.

The triangles themselves are added to the list by calls to queueQuad which lets us draw arbitrary rectangles from the source image, and queueText which takes a string and finds the correct rectangle for each character. Note the half-hearted and not quite correct attempt at clipping the characters to stop them poking out the edges of their container.

When should we issue the draw call? Ideally, right before the monitor is about to draw, to minimize end-to-end latency. The feature that enables this synchronization is vsync and as far as I can tell noone knows how to consistently make this work in an app with more than one window because the opengl spec does not specify the behaviour in sufficient detail so all the various graphics drivers interpret it differently.