I can confirm that it wasn't a problem with MCLaunch. After spending way too much time trying to locate the offending piece of code, one of the problems turned out to be code like this:
#define GPU_DATA_ADDR (0x1f801810)
#define GPU_CONTROL_ADDR (0x1f801814)
#define GPU_DATA *((volatile u32 *)GPU_DATA_ADDR)
#define GPU_CONTROL *((volatile u32 *)GPU_CONTROL_ADDR)
...
gpu_wait();
GPU_CONTROL = 0x01000000;
GPU_DATA = ...
GPU_DATA = ...
...
Notice the control write above? I have no idea why I put it there in the first place, it's been there for years. But what it should do is tell the GPU to stop rendering and clear the command buffer. But since I first wait for the GPU to be finished, it really shouldn't do anything at all. This works on every PS1 I've tested, from the very first SCPH-1002 to the very latest PSone, as well as on any emulator. On the PS2 however, the primitive following the control write will not be rendered *. This is why I always ended up with a black screen, since code like that was used in the functions I use to print characters to the screen... After removing the useless control write, I no longer get black screens
edit:
*) I don't know if that's actually the case, or if it's an issue with the busy-flags in the GPU status register or something else. But in any case, nothing got rendered.