C API unexpectedly switches bitmaps for Lua API

Versions

OS: macOS Monterey 12.6.2 (21G320)
PlayDate Simulator: 1.12.3 (140884)
PlayDate SDK: 1.12.3

Issue in a nutshell

Calling pd->graphics->clearBitmap(pd->graphics->getDebugBitmap(), kColorBlack); from C makes Lua API to draw on the debug bitmap.

Setup

I have a computation-heavy C function, which is exposed to Lua. This function does roughly the following:

// This code is called once
uint8_t* lcd_bitmap = pd->graphics->getFrame();
uint8_t* debug_bitmap;
playdate->graphics->getBitmapData(pd->graphics->getDebugBitmap(), NULL, NULL, NULL, NULL, &debug_bitmap);

// This code is called once per frame
pd->graphics->clearBitmap(pd->graphics->getDebugBitmap(), kColorBlack);
pd->graphics->clear(kColorBlack);

drawStuff(lcd_bitmap);
drawDebugStuff(debug_bitmap);

pd->graphics->markUpdatedRows(0, LCD_ROWS-1);

This function is called from Lua like this:

function playdate.update()
    my_heave_c_function()
    playdate.drawFPS(0, 0)
end

All is working correctly, FPS is drawn on a correct bitmap.

Experiment

I want to draw simple things like UI using Lua API, since it is much more convenient than C. My intention was to render heavy 3D stuff in C and then display UI on top of it. However, an attempt to draw something after calling my_heave_c_function() results in shapes being drawn in red (meaning, they are put onto the debug bitmap).

If you try to draw stuff from Lua before calling my_heave_c_function(), it works just fine.

Through trial and error I figured out that removing a call to pd->graphics->clearBitmap(pd->graphics->getDebugBitmap(), kColorBlack); solves the problem. After removing it, I can draw from Lua after drawing from C and it displays on the correct bitmap. However, it is not really a solution because now all debug drawings from previous frames are preserved, which is really inconvenient.

Conclusion

I can do some hacks to make drawing UI before rendering 3D stuff from C possible, but ideally I would like to keep the order of "heavy stuff first, UI later". This seems like a bug because the developer should have control over what bitmaps they draw on.

I do not have a PlayDate yet, so I am not sure what happens on the device in this case.

The same happens if you attempt to push the debug bitmap on the context and then clear it:

pd->graphics->pushContext(pd->graphics->getDebugBitmap());
pd->graphics->clear(kColorBlack);
// Draw debug stuff
pd->graphics->popContext();

So clearBitmap() itself is not necessarily a problem.

Another interesting point is that if you do not clear the debug bitmap in any way, then the issue does not reproduce anymore. So the action of clearing the bitmap itself might be problematic.

Clearing the debug bitmap manually using memset also resolves the problem. Given this fact, I am pretty confident that something is wrong with the bitmap clearing API.

memset(debug_bitmap, 0, 240 * ROW_STRIDE);

When you get the debug bitmap 1) it should be set to black automatically if there is no clip rect, calling clearBitmap shouldn't be needed. 2) when called, the OS sets the frame buffer to the debug buffer so anything draw after that will draw into that buffer until it's drawn to the screen. That is by design, so a slight code reorg may be needed.

I am getting the pointer to the debug bitmap once during the startup and reusing it on each frame. In this setup, there are shapes left on the bitmap from previous frame.

Does the getDebugBitmap do clearing before returning the bitmap you mentioned in (1)?

Yup, when you call the API it clears it before returning it. Edit: the debug bitmap is not made to hang on to across frames since it clears and sets the frame buffer when you call the API. That should probably be noted in the docs.