So, I learned about double buffering yesterday, and this solves the problem (surprise surprise). I still want to know why the problem occurred in the first place, but it's probably to do with the VSync timing system that's deeper within libogc than I want to dig at this time.
For any interested parties with a similar problem, double buffering involves initializing two frame buffers. At any time one is set to be displayed and the other is being modified. On each frame cycle you copy the "display" buffer to the screen, modify the "background" buffer, and then switch the roles of the buffers: once the "background" buffer is modified it is ready to be displayed, so it becomes the "display" buffer, and once the "display" buffer is copied over it becomes the new "background" buffer.
My code now basically looks like this. Its double buffering stuff is based on the sprites example code from devkitPro, so look at that for details.
Code: Select all
// initialization stuff from template.c, including initializing rmode and xfb pointer array
while (1)
{
// get WPAD buttons, WPAD_IR structure, exit if home is pressed
VIDEO_ClearFrameBuffer(rmode, xfb[xfb_index], COLOR_BLACK);
drawBorder(); // a function that loops through four sections of the frame buffer, setting individual pixels
VIDEO_SetNextFramebuffer(xfb[xfb_index])
VIDEO_Flush();
VIDEO_WaitVSync();
xfb_index ^= 1; // toggle active frame buffer
}