diff --git a/src/dusksaturn/display/displaysaturn.c b/src/dusksaturn/display/displaysaturn.c index 0ac98832..a2f89029 100644 --- a/src/dusksaturn/display/displaysaturn.c +++ b/src/dusksaturn/display/displaysaturn.c @@ -25,12 +25,29 @@ errorret_t displaySaturnInit(void) { VDP2_TVMD_HORZ_NORMAL_A, VDP2_TVMD_VERT_224 ); - vdp2_tvmd_display_set(); + + /* Back-screen color shown where no VDP1/VDP2 plane draws a pixel. + * MSB=1 so VDP2 treats it as direct RGB in mixed-color mode. */ + vdp2_scrn_back_color_set( + VDP2_VRAM_ADDR(3, 0x01FFFE), + RGB1555(1, 0, 3, 15) + ); + + /* Sprite priority 0 → invisible in VDP2. Set slot 0 to priority 6 so + * VDP1 pixels are composited above the back-screen. */ + vdp2_sprite_priority_set(0, 6); /* Disable all NBG/RBG scroll planes; game content drawn entirely via VDP1. */ vdp2_scrn_display_set(VDP2_SCRN_DISP_NONE); + vdp2_tvmd_display_set(); + errorChain(renderSaturnInit()); + + /* Commit all VDP2 shadow registers to hardware before the first frame. */ + vdp2_sync(); + vdp2_sync_wait(); + errorOk(); } @@ -45,6 +62,12 @@ errorret_t displaySaturnSwap(void) { vdp1_sync(); vdp2_sync(); vdp2_sync_wait(); + /* Wait for VDP1's VBlank-OUT handler to complete. In auto mode VDP1 begins + * rendering at VBlank-IN; vdp2_sync_wait() returns shortly after that point, + * while VDP1 is still reading from VDP1 VRAM. vdp1_sync_wait() blocks until + * VBlank-OUT, by which time VDP1 has finished and swapped framebuffers. This + * prevents the next frame's DMA from overwriting VDP1 VRAM mid-render. */ + vdp1_sync_wait(); DISPLAY.whichBuffer ^= 1; errorOk(); } diff --git a/src/dusksaturn/display/render/rendersaturn.c b/src/dusksaturn/display/render/rendersaturn.c index 1b0a5c02..631db0e4 100644 --- a/src/dusksaturn/display/render/rendersaturn.c +++ b/src/dusksaturn/display/render/rendersaturn.c @@ -85,6 +85,16 @@ _Static_assert(sizeof(saturncmd_t) == sizeof(vdp1_cmdt_t), #define SATURNCMD_PMOD_RGB_DIRECT (5u << 3) /* RGB1555 in COLR, no palette */ #define SATURNCMD_PMOD_SPD (0x0040u) /* do not skip index 0 */ #define SATURNCMD_PMOD_ECD (0x0080u) /* end-code disable */ +/* Bit 15 of PMOD: forces the MSB of every pixel written to the VDP1 framebuffer + * to 1. In VDP2 mixed-color mode (SPCLMD=1, set by vdp1_env_default_set), a + * framebuffer pixel with MSB=1 is treated as a direct RGB555 color; MSB=0 is a + * CRAM palette index. Always set this for direct-color drawing. */ +#define SATURNCMD_PMOD_MSB (0x8000u) + +/* Last 16-word-aligned CRAM slot (word 2032 of 2048): reserved for the clear + * polygon color. Placed well above any texture palette range (max 8 × 256 + * = 2048 words, but we only have a handful of textures in practice). */ +#define SATURN_CLEAR_CRAM_ADDR (0x07F0u) /* ---- Texture table ------------------------------------------------------- */ @@ -118,15 +128,23 @@ static float saturnViewTgtX = 0.0f, saturnViewTgtY = 0.0f, saturnViewTgtZ = 0.0f /* ---- Helpers ------------------------------------------------------------- */ -/* Convert color_t RGBA → VDP1/VDP2 RGB1555. */ +/* Convert color_t RGBA → VDP1/VDP2 RGB1555 (no MSB). + * Saturn RGB1555: bit[15]=MSB, bits[14:10]=R, bits[9:5]=G, bits[4:0]=B. */ static uint16_t toRGB1555(color_t c) { return (uint16_t)( - ((uint16_t)(c.b >> 3) << 10) | + ((uint16_t)(c.r >> 3) << 10) | ((uint16_t)(c.g >> 3) << 5) | - ((uint16_t)(c.r >> 3)) + ((uint16_t)(c.b >> 3)) ); } +/* Convert color_t → RGB1555 with bit[15]=1 (direct-color flag). + * For VDP1 polygon CMDCOLR: bit[15]=1 tells VDP1 to use the color as direct + * RGB555 rather than a CRAM palette index. */ +static uint16_t toRGB1555Direct(color_t c) { + return 0x8000u | toRGB1555(c); +} + /* Write palette into VDP2 CRAM at the texture's slot. * CRAM is mapped at 0x25F00000. Each 256-entry slot = 512 bytes. */ static void uploadPalette(saturntexentry_t *e) { @@ -368,8 +386,8 @@ errorret_t renderSaturnFlush(ropbuffer_t *buf) { sc = &saturnCmdBuf[0]; memoryZero(sc, sizeof(saturncmd_t)); sc->ctrl = SATURNCMD_CTRL_SYSCLIP; - sc->xb = (int16_t)(DUSK_DISPLAY_WIDTH - 1); - sc->yb = (int16_t)(DUSK_DISPLAY_HEIGHT - 1); + sc->xc = (int16_t)(DUSK_DISPLAY_WIDTH - 1); + sc->yc = (int16_t)(DUSK_DISPLAY_HEIGHT - 1); /* [1] User clip – mirrors full screen; disabled per-command by default. */ sc = &saturnCmdBuf[1]; @@ -394,14 +412,20 @@ errorret_t renderSaturnFlush(ropbuffer_t *buf) { switch(op) { case ROP_CLEAR: { const ropclear_t *c = (const ropclear_t *)hdr; - /* Fill the entire screen with a solid RGB polygon. - * RGB direct mode (PMOD = SATURNCMD_PMOD_RGB_DIRECT) places the - * RGB1555 color in COLR directly — no palette lookup required. */ + /* VDP1 polygon CM=0: CMDCOLR is a CRAM word address (16-aligned), + * not a direct color value. Write the desired RGB1555 color into our + * reserved CRAM slot so VDP1 reads it during rasterization. + * PMOD MSB_ENABLE then ORs bit[15]=1 into the framebuffer pixel so + * VDP2 (SPCLMD=1) treats it as direct RGB555 rather than a palette + * index. */ + volatile uint16_t *cram = (volatile uint16_t *)0x25F00000; + cram[SATURN_CLEAR_CRAM_ADDR] = toRGB1555(c->color); + saturncmd_t *poly = allocCmd(); if(poly) { poly->ctrl = SATURNCMD_CTRL_POLYGON; - poly->pmod = SATURNCMD_PMOD_RGB_DIRECT; - poly->colr = toRGB1555(c->color); + poly->pmod = SATURNCMD_PMOD_MSB | SATURNCMD_PMOD_ECD | SATURNCMD_PMOD_SPD; + poly->colr = SATURN_CLEAR_CRAM_ADDR; poly->xa = 0; poly->ya = 0; poly->xb = (int16_t)(DUSK_DISPLAY_WIDTH - 1);