make your code base a home base

a reasonable api for debug drawing with OpenGL

actually enjoy drawing points, lines, triangles, and quads

when i learned graphics in college, we used the legacy OpenGL glBegin(...) / glEnd() api this was a good api for "debug drawing" -- not trying to draw stuff beautifully, just visualizing where data is in space :) the implementation might have been slow because something something submitting vertices to the gpu one by one... -- but i'm talking about the api. and this was a good api! just look how easy it was to make a rainbow triangle! glBegin(GL_TRIANGLES); glColor3f(1.0f, 0.0f, 0.0f); glVertex3f(-0.5f, -0.5f, 0.0f); glColor3f(0.0f, 1.0f, 0.0f); glVertex3f(0.5f, -0.5f, 0.0f); glColor3f(0.0f, 0.0f, 1.0f); glVertex3f(0.0f, 0.5f, 0.0f); glEnd(); sidenote: add a little macro fun1, and you could even be living this life: BOOKEND(glBegin(GL_TRIANGLES), glEnd()) { glColor3f(1.0f, 0.0f, 0.0f); glVertex3f(-0.5f, -0.5f, 0.0f); glColor3f(0.0f, 1.0f, 0.0f); glVertex3f(0.5f, -0.5f, 0.0f); glColor3f(0.0f, 0.0f, 1.0f); glVertex3f(0.0f, 0.5f, 0.0f); } unfortunately, modern OpenGL deprecated/removed this api and didn't provide anything similarly ergonomic to use to take its place. hello triangle ballooned to 196 lines and now required two different languages2 ( and want to draw points? lines? line strips?? -- glhf :( ) this decision was, in my opinion, pedagogically disastrous; Khronos upgraded the ladder but forgot to include the bottom 10 rungs. in my opinion, the old api had real value! it was sufficient for making Quake! plus a whole bunch of people (students) just want to draw something! luckily, you can implement your own, even better version of the legacy OpenGL glBegin(...) / glEnd() api i call mine eso, which stands for "Easy SOup" :) like implementing your own printf, or defeating the machines, this primary barrier to implementing your own debug drawing api is believing such a thing is possible luckily, a simple reference implementation is available here, but the really important thing is the usage code here is usage code of my current api ( it includes a hello triangle :D ) Void main(Void) { ... while (begin_frame()) { BOOKEND(eso_begin(...), eso_end()) { eso_primitive(EsoPrimitiveTriangles); eso_color_rgb(1.0, 0.0, 0.0); eso_vertex_xyz(-0.5, -0.5, 0.0); eso_color_rgb(0.0, 1.0, 0.0); eso_vertex_xyz(0.5, -0.5, 0.0); eso_color_rgb(0.0, 0.0, 1.0); eso_vertex_xyz(0.0, 0.5, 0.0); eso_sim(&sim, &state); } } } note that the function eso_sim(...) -- which draws my soft robot simulation -- is written so it can be called in the middle of an eso_begin(...) { ... } eso_end() block Void eso_sim(Sim *sim, State *state) { ... if (u64_is_on(state->enabled, SimTermCables)) { eso_primitive(EsoPrimitiveLines); eso_size(2.0); VIEW_FOR(View_Uint *, cable, &sim->cables.outer) { ... BOOKEND(eso_compound_begin(EsoCompoundLineStrip), eso_compound_end()) { eso_color_r3(color_pastel_hot_purple); VIEW_FOR(Uint *, i, cable) { Real2 p_i = ...; eso_vertex_r2(p_i); } } } } if (u64_is_on(state->enabled, SimTermGravity)) { eso_primitive(EsoPrimitivePoints); eso_color_r3(color_monokai_orange); eso_size(2.0); VIEW_FOR(Real2 *, x, x_) { eso_vertex_r2(*x); } } ... } in summary, in a single eso_begin / eso_end-block, we can - draw multiple different primitives -- e.g., GL_TRIANGLES and GL_POINTS - draw multiple "compound primitives" -- e.g., GL_LINE_STRIP the only important implementation note is that nothing gets sent to the gpu until eso_end() rather, eso_vertex*(...) buffers data in massive global arrays on the cpu GL_Uint vertex_primitives[ESO_MAX_GL_VERTICES]; GL_Uint vertex_inner_indices[ESO_MAX_GL_VERTICES]; GL_Real3 vertex_positions[ESO_MAX_GL_VERTICES]; GL_Real4 vertex_colors[ESO_MAX_GL_VERTICES]; ... and eso_end() makes one massive glDrawArrays(GL_TRIANGLES, ...); call final note: when drawing points, lines, and quads, eso_vertex*(...) duplicates data on the cpu -- duplicated vertex positions get offset later in a single combined vertex shader (to support compiling to wasm, i avoid geometry shaders)
1 Ryan Fleury calls this construct a "defer loop"; it executes BEFORE(...); { ... } AFTER(...); using a for loop and comma expressions #define BOOKEND(BEFORE, AFTER) \ for ( \ int XCONCAT(_, __LINE__) = (BEFORE, 0); \ XCONCAT(_, __LINE__) == 0; \ (AFTER, XCONCAT(_, __LINE__) = 1) \ ) note: i'm naming the indexing variable using a typical two-layer "extended" concat macro #define CONCAT(a, b) a ## b #define XCONCAT(a, b) CONCAT(a, b) 2 unfortunately, Vulkan continued this trend, bringing a 1379 line hello triangle that requires 7 standard library headers plus the external glm math library D: ( the "hello world" of graphics programming is now longer than the longest program many undergrads ever write )