/* For more information, please see: - http://cgfm2.emuviews.com/txt/msvdp.txt - http://www.smspower.org/forums/viewtopic.php?p=44198 A scanline contains the following sections: - horizontal sync 1 ED => HSYNC high/increment line counter/generate interrupts/etc - left blanking 2 ED-EE - color burst 14 EE-EF - left blanking 8 F5-F9 - left border 13 F9-FF - active display 256 00-7F - right border 15 80-87 - right blanking 8 87-8B - horizontal sync 25 8B-97 => HSYNC low NTSC frame timing 256x192 256x224 256x240 (doesn't work on real hardware) - vertical blanking 3 D5-D7 3 E5-E7 3 EE-F0 - top blanking 13 D8-E4 13 E8-F4 13 F1-FD - top border 27 E5-FF 11 F5-FF 3 FD-FF - active display 192 00-BF 224 00-DF 240 00-EF - bottom border 24 C0-D7 8 E0-E7 0 F0-F0 - bottom blanking 3 D8-DA 3 E8-EA 3 F0-F2 PAL frame timing 256x192 256x224 256x240 - vertical blanking 3 BA-BC 3 CA-CC 3 D2-D4 - top blanking 13 BD-C9 13 CD-D9 13 D5-E1 - top border 54 CA-FF 38 DA-FF 30 E2-FF - active display 192 00-BF 224 00-DF 240 00-EF - bottom border 48 C0-EF 32 E0-FF 24 F0-07 - bottom blanking 3 F0-F2 3 00-02 3 08-0A */ #include "driver.h" #include "video/smsvdp.h" #include "includes/sms.h" #include "video/generic.h" #include "cpu/z80/z80.h" #define IS_SMS1_VDP ( features & MODEL_315_5124 ) #define IS_SMS2_VDP ( features & MODEL_315_5246 ) #define IS_GAMEGEAR_VDP ( features & MODEL_315_5378 ) #define STATUS_VINT (0x80) /* Pending vertical interrupt flag */ #define STATUS_SPROVR (0x40) /* Sprite overflow flag */ #define STATUS_SPRCOL (0x20) /* Object collision flag */ #define STATUS_HINT (0x02) /* Pending horizontal interrupt flag */ #define GG_CRAM_SIZE (0x40) /* 32 colors x 2 bytes per color = 64 bytes */ #define SMS_CRAM_SIZE (0x20) /* 32 colors x 1 bytes per color = 32 bytes */ #define MAX_CRAM_SIZE 0x40 #define VRAM_SIZE (0x4000) #define PRIORITY_BIT 0x1000 #define BACKDROP_COLOR ( ( vdp_mode == 4 ? 0x10 : 0x00 ) + (reg[0x07] & 0x0F)) #define NUM_OF_REGISTER (0x10) /* 16 registers */ #define INIT_VCOUNT 0 #define VERTICAL_BLANKING 1 #define TOP_BLANKING 2 #define TOP_BORDER 3 #define ACTIVE_DISPLAY_V 4 #define BOTTOM_BORDER 5 #define BOTTOM_BLANKING 6 static UINT8 sms_ntsc_192[7] = { 0xD5, 3, 13, 27, 192, 24, 3 }; static UINT8 sms_ntsc_224[7] = { 0xE5, 3, 13, 11, 224, 8, 3 }; static UINT8 sms_ntsc_240[7] = { 0xEE, 3, 13, 3, 240, 0, 3 }; static UINT8 sms_pal_192[7] = { 0xBA, 3, 13, 54, 192, 48, 3 }; static UINT8 sms_pal_224[7] = { 0xCA, 3, 13, 38, 224, 32, 3 }; static UINT8 sms_pal_240[7] = { 0xD2, 3, 13, 30, 240, 24, 3 }; UINT8 *sms_frame_timing; UINT8 reg[NUM_OF_REGISTER]; UINT8 *VRAM = NULL; UINT8 *CRAM = NULL; int CRAMMask; UINT8 *lineCollisionBuffer; UINT8 reg9copy; int addr; int code; int pending; int latch; int buffer; int statusReg; int ggSmsMode; UINT32 features; int isCRAMDirty; int lineCountDownCounter; int irqState; /* The status of the IRQ line, as seen by the VDP */ int y_pixels; /* 192, 224, 240 */ int vdp_mode; /* current mode of the VDP: 0,1,2,3,4 */ mame_bitmap *prevBitMap; /* lineBuffer will be used to hold 5 lines of line data. Line #0 is the regular blitting area. Lines #1-#4 will be used as a kind of cache to be used for vertical scaling in the gamegear sms compatibility mode. */ int *lineBuffer = NULL; int currentPalette[32]; int prevBitMapSaved; void (*int_callback)(int); mame_timer *smsvdp_display_timer = NULL; static TIMER_CALLBACK(smsvdp_display_callback); void sms_refresh_line(mame_bitmap *bitmap, int offsetx, int offsety, int line); void sms_update_palette(void); static void set_display_settings( void ) { int M1, M2, M3, M4; M1 = reg[0x01] & 0x10; M2 = reg[0x00] & 0x02; M3 = reg[0x01] & 0x08; M4 = reg[0x00] & 0x04; y_pixels = 192; if ( M4 ) { /* mode 4 */ vdp_mode = 4; if ( M2 && ( IS_SMS2_VDP || IS_GAMEGEAR_VDP ) ) { /* Is it 224-line display */ if ( M1 && ! M3 ) { y_pixels = 224; } else if ( ! M1 && M3 ) { /* 240-line display */ y_pixels = 240; } } } else { /* original TMS9918 mode */ if ( ! M1 && ! M2 && ! M3 ) { vdp_mode = 0; } else // if ( M1 && ! M2 && ! M3 ) { // vdp_mode = 1; // } else if ( ! M1 && M2 && ! M3 ) { vdp_mode = 2; // } else // if ( ! M1 && ! M2 && M3 ) { // vdp_mode = 3; } else { logerror( "Unknown video mode detected (M1=%c, M2=%c, M3=%c, M4=%c)\n", M1 ? '1' : '0', M2 ? '1' : '0', M3 ? '1' : '0', M4 ? '1' : '0'); } } switch( y_pixels ) { case 192: sms_frame_timing = ( Machine->screen[0].height == PAL_Y_PIXELS ) ? sms_pal_192 : sms_ntsc_192; break; case 224: sms_frame_timing = ( Machine->screen[0].height == PAL_Y_PIXELS ) ? sms_pal_224 : sms_ntsc_224; break; case 240: sms_frame_timing = ( Machine->screen[0].height == PAL_Y_PIXELS ) ? sms_pal_240 : sms_ntsc_240; break; } isCRAMDirty = 1; } READ8_HANDLER(sms_vdp_vcount_r) { return ( sms_frame_timing[ INIT_VCOUNT ] + video_screen_get_vpos(0) ) & 0xFF; } READ8_HANDLER(sms_vdp_hcount_r) { return video_screen_get_hpos(0) >> 1; } void sms_set_ggsmsmode( int mode ) { ggSmsMode = mode; } int smsvdp_video_init( const smsvdp_configuration *config ) { features = config->model; int_callback = config->int_callback; /* Allocate video RAM In theory the driver could have a REGION_GFX1 and/or REGION_GFX2 memory region of it's own. So this code could potentially cause a clash. */ VRAM = new_memory_region( Machine, REGION_GFX1, VRAM_SIZE, ROM_REQUIRED ); CRAM = new_memory_region( Machine, REGION_GFX2, MAX_CRAM_SIZE, ROM_REQUIRED ); lineBuffer = auto_malloc( 256 * 5 * sizeof(int) ); memset( lineBuffer, 0, 256 * 5 * sizeof(int) ); /* Clear RAM */ memset(reg, 0, NUM_OF_REGISTER); isCRAMDirty = 1; memset(VRAM, 0, VRAM_SIZE); memset(CRAM, 0, MAX_CRAM_SIZE); reg[0x01] |= 0x20; reg[0x02] = 0x0E; /* power up default */ reg[0x0a] = 0xff; CRAMMask = ( IS_GAMEGEAR_VDP && ! ggSmsMode ) ? ( GG_CRAM_SIZE - 1 ) : ( SMS_CRAM_SIZE - 1 ); /* Initialize VDP state variables */ addr = code = pending = latch = buffer = statusReg = \ lineCountDownCounter = irqState = 0; lineCollisionBuffer = auto_malloc(SMS_X_PIXELS); /* Make temp bitmap for rendering */ tmpbitmap = auto_bitmap_alloc(Machine->screen[0].width, Machine->screen[0].height, BITMAP_FORMAT_INDEXED32); prevBitMapSaved = 0; prevBitMap = auto_bitmap_alloc(Machine->screen[0].width, Machine->screen[0].height, BITMAP_FORMAT_INDEXED32); set_display_settings(); smsvdp_display_timer = mame_timer_alloc( smsvdp_display_callback ); mame_timer_adjust( smsvdp_display_timer, video_screen_get_time_until_pos( 0, 0, 0 ), 0, video_screen_get_scan_period( 0 ) ); return (0); } static TIMER_CALLBACK(smsvdp_display_callback) { rectangle rec; int vpos = video_screen_get_vpos(0); int vpos_limit = sms_frame_timing[VERTICAL_BLANKING] + sms_frame_timing[TOP_BLANKING] + sms_frame_timing[TOP_BORDER] + sms_frame_timing[ACTIVE_DISPLAY_V] + sms_frame_timing[BOTTOM_BORDER] + sms_frame_timing[BOTTOM_BLANKING]; rec.min_y = rec.max_y = vpos; /* Check if we're on the last line of a frame */ if ( vpos == vpos_limit - 1 ) { check_pause_button(); return; } vpos_limit -= sms_frame_timing[BOTTOM_BLANKING]; /* Check if we're below the bottom border */ if ( vpos >= vpos_limit ) { return; } vpos_limit -= sms_frame_timing[BOTTOM_BORDER]; /* Check if we're in the bottom border area */ if ( vpos >= vpos_limit ) { if ( vpos == vpos_limit ) { if ( lineCountDownCounter == 0x00 ) { lineCountDownCounter = reg[0x0A]; statusReg |= STATUS_HINT; irqState = 1; if ( reg[0x00] & 0x10 ) { if ( int_callback ) { int_callback( ASSERT_LINE ); } } } } if ( vpos == vpos_limit + 1 ) { statusReg |= STATUS_VINT; irqState = 1; if ( reg[0x01] & 0x20 ) { if ( int_callback ) { int_callback( ASSERT_LINE ); } } } if ( video_skip_this_frame() ) { return; } sms_update_palette(); /* Draw left border */ rec.min_x = LBORDER_START; rec.max_x = LBORDER_START + LBORDER_X_PIXELS - 1; fillbitmap( tmpbitmap, Machine->pens[currentPalette[BACKDROP_COLOR]], &rec ); /* Draw right border */ rec.min_x = LBORDER_START + LBORDER_X_PIXELS + 256; rec.max_x = rec.min_x + RBORDER_X_PIXELS - 1; fillbitmap( tmpbitmap, Machine->pens[currentPalette[BACKDROP_COLOR]], &rec ); /* Draw middle of the border */ /* We need to do this through the regular drawing function so it will */ /* be included in the gamegear scaling functions */ sms_refresh_line( tmpbitmap, LBORDER_START + LBORDER_X_PIXELS, vpos_limit - sms_frame_timing[ACTIVE_DISPLAY_V], vpos - ( vpos_limit - sms_frame_timing[ACTIVE_DISPLAY_V] ) ); return; } vpos_limit -= sms_frame_timing[ACTIVE_DISPLAY_V]; /* Check if we're in the active display area */ if ( vpos >= vpos_limit ) { if ( vpos == vpos_limit ) { lineCountDownCounter = reg[0x0A]; reg9copy = reg[0x09]; } if ( lineCountDownCounter == 0x00 ) { lineCountDownCounter = reg[0x0A]; statusReg |= STATUS_HINT; irqState = 1; if ( reg[0x00] & 0x10 ) { if ( int_callback ) { int_callback( ASSERT_LINE ); } } } else { lineCountDownCounter -= 1; } if ( video_skip_this_frame() ) { return; } sms_update_palette(); /* Check if display is disabled */ if ( ! ( reg[0x01] & 0x40 ) ) { /* set whole line to backdrop color */ rec.min_x = LBORDER_START; rec.max_x = LBORDER_START + LBORDER_X_PIXELS + 255 + RBORDER_X_PIXELS; fillbitmap( tmpbitmap, Machine->pens[currentPalette[BACKDROP_COLOR]], &rec ); } else { /* Draw left border */ rec.min_x = LBORDER_START; rec.max_x = LBORDER_START + LBORDER_X_PIXELS - 1; fillbitmap( tmpbitmap, Machine->pens[currentPalette[BACKDROP_COLOR]], &rec ); /* Draw right border */ rec.min_x = LBORDER_START + LBORDER_X_PIXELS + 256; rec.max_x = rec.min_x + RBORDER_X_PIXELS - 1; fillbitmap( tmpbitmap, Machine->pens[currentPalette[BACKDROP_COLOR]], &rec ); sms_refresh_line( tmpbitmap, LBORDER_START + LBORDER_X_PIXELS, vpos_limit, vpos - vpos_limit ); } return; } vpos_limit -= sms_frame_timing[TOP_BORDER]; /* Check if we're in the top border area */ if ( vpos >= vpos_limit ) { if ( video_skip_this_frame() ) { return; } sms_update_palette(); /* Draw left border */ rec.min_x = LBORDER_START; rec.max_x = LBORDER_START + LBORDER_X_PIXELS - 1; fillbitmap( tmpbitmap, Machine->pens[currentPalette[BACKDROP_COLOR]], &rec ); /* Draw right border */ rec.min_x = LBORDER_START + LBORDER_X_PIXELS + 256; rec.max_x = rec.min_x + RBORDER_X_PIXELS - 1; fillbitmap( tmpbitmap, Machine->pens[currentPalette[BACKDROP_COLOR]], &rec ); /* Draw middle of the border */ /* We need to do this through the regular drawing function so it will */ /* be included in the gamegear scaling functions */ sms_refresh_line( tmpbitmap, LBORDER_START + LBORDER_X_PIXELS, vpos_limit + sms_frame_timing[TOP_BORDER], vpos - ( vpos_limit + sms_frame_timing[TOP_BORDER] ) ); return; } } READ8_HANDLER(sms_vdp_data_r) { int temp; /* SMS 2 & GG behaviour. Seems like the latched data is passed straight through */ /* to the address register when in the middle of doing a command. */ /* Cosmic Spacehead needs this, among others */ if ( pending ) { addr = ( addr & 0xff00 ) | latch; /* Clear pending write flag */ pending = 0; } /* Return read buffer contents */ temp = buffer; /* Load read buffer */ buffer = VRAM[(addr & 0x3FFF)]; /* Bump internal address register */ addr += 1; return (temp); } READ8_HANDLER(sms_vdp_ctrl_r) { int temp = statusReg; /* Clear pending write flag */ pending = 0; statusReg &= ~( STATUS_VINT | STATUS_SPROVR | STATUS_SPRCOL | STATUS_HINT ); if (irqState == 1) { irqState = 0; if ( int_callback ) { int_callback( CLEAR_LINE ); } } return (temp); } WRITE8_HANDLER(sms_vdp_data_w) { /* SMS 2 & GG behaviour. Seems like the latched data is passed straight through */ /* to the address register when in the middle of doing a command. */ /* Cosmic Spacehead needs this, among others */ if ( pending ) { addr = ( addr & 0xff00 ) | latch; /* Clear pending write flag */ pending = 0; } switch(code) { case 0x00: case 0x01: case 0x02: { int address = (addr & 0x3FFF); VRAM[address] = data; } break; case 0x03: { int address = addr & CRAMMask; if (data != CRAM[address]) { CRAM[address] = data; isCRAMDirty = 1; } } break; } //buffer = data; addr += 1; } WRITE8_HANDLER(sms_vdp_ctrl_w) { int regNum; if (pending == 0) { latch = data; pending = 1; } else { /* Clear pending write flag */ pending = 0; code = (data >> 6) & 0x03; switch( code ) { case 0: /* VRAM reading mode */ addr = ( data << 8 ) | latch; buffer = VRAM[ addr & 0x3FFF ]; addr += 1; break; case 1: /* VRAM writing mode */ addr = ( data << 8 ) | latch; break; case 2: /* VDP register write */ regNum = data & 0x0F; reg[regNum] = latch; if (regNum == 0 && latch & 0x02) { logerror("overscan enabled.\n"); } if ( regNum == 0 || regNum == 1 ) { set_display_settings(); } if ( ( regNum == 1 ) && ( reg[0x01] & 0x20 ) && ( statusReg & STATUS_VINT ) ) { int_callback( ASSERT_LINE ); } code = 0; break; case 3: /* CRAM writing mode */ addr = ( data << 8 ) | latch; break; } } } void sms_refresh_line_mode4(int *lineBuffer, int line) { int tileColumn; int xScroll, yScroll, xScrollStartColumn; int spriteIndex; int pixelX, pixelPlotX, prioritySelected[256]; int spriteX, spriteY, spriteLine, spriteTileSelected, spriteHeight, spriteZoom; int spriteBuffer[8], spriteBufferCount, spriteBufferIndex; int bitPlane0, bitPlane1, bitPlane2, bitPlane3; UINT16 nameTableAddress; UINT8 *nameTable; UINT8 *spriteTable = &VRAM[(reg[0x05] << 7) & 0x3F00]; if ( y_pixels != 192 ) { nameTableAddress = (((reg[0x02] & 0x0C) << 10) | 0x0700) + ((((line + reg9copy) % 256) >> 3) << 6); } else { nameTableAddress = ((reg[0x02] << 10) & 0x3800) + ((((line + reg9copy) % 224) >> 3) << 6); } if ( IS_SMS1_VDP ) { nameTableAddress = nameTableAddress & (((reg[0x02] & 0x01) << 10) | 0x3BFF); } nameTable = VRAM + nameTableAddress; /* if top 2 rows of screen not affected by horizontal scrolling, then xScroll = 0 */ /* else xScroll = reg[0x08] (SMS Only) */ if ( IS_GAMEGEAR_VDP ) { xScroll = 0x0100 - reg[0x08]; } else { xScroll = (((reg[0x00] & 0x40) && (line < 16)) ? 0 : 0x0100 - reg[0x08]); } xScrollStartColumn = (xScroll >> 3); /* x starting column tile */ /* Draw background layer */ for (tileColumn = 0; tileColumn < 33; tileColumn++) { UINT16 tileData; int tileSelected, palletteSelected, horizSelected, vertSelected, prioritySelect; int tileLine; /* Rightmost 8 columns for SMS (or 2 columns for GG) not affected by */ /* vertical scrolling when bit 7 of reg[0x00] is set */ if ( IS_GAMEGEAR_VDP ) { yScroll = (((reg[0x00] & 0x80) && (tileColumn > 29)) ? 0 : (reg9copy) % 224); } else { yScroll = (((reg[0x00] & 0x80) && (tileColumn > 23)) ? 0 : (reg9copy) % 224); } tileLine = ( ( tileColumn + xScrollStartColumn ) & 0x1F ) * 2; tileData = nameTable[ tileLine ] | ( nameTable[ tileLine + 1 ] << 8 ); tileSelected = (tileData & 0x01FF); prioritySelect = tileData & PRIORITY_BIT; palletteSelected = (tileData >> 11) & 0x01; vertSelected = (tileData >> 10) & 0x01; horizSelected = (tileData >> 9) & 0x01; tileLine = line - ((0x07 - (yScroll & 0x07)) + 1); if (vertSelected) { tileLine = 0x07 - tileLine; } bitPlane0 = VRAM[((tileSelected << 5) + ((tileLine & 0x07) << 2)) + 0x00]; bitPlane1 = VRAM[((tileSelected << 5) + ((tileLine & 0x07) << 2)) + 0x01]; bitPlane2 = VRAM[((tileSelected << 5) + ((tileLine & 0x07) << 2)) + 0x02]; bitPlane3 = VRAM[((tileSelected << 5) + ((tileLine & 0x07) << 2)) + 0x03]; for (pixelX = 0; pixelX < 8 ; pixelX++) { UINT8 penBit0, penBit1, penBit2, penBit3; UINT8 penSelected; penBit0 = (bitPlane0 >> (7 - pixelX)) & 0x01; penBit1 = (bitPlane1 >> (7 - pixelX)) & 0x01; penBit2 = (bitPlane2 >> (7 - pixelX)) & 0x01; penBit3 = (bitPlane3 >> (7 - pixelX)) & 0x01; penSelected = (penBit3 << 3 | penBit2 << 2 | penBit1 << 1 | penBit0); if (palletteSelected) { penSelected |= 0x10; } if (!horizSelected) { pixelPlotX = pixelX; } else { pixelPlotX = 7 - pixelX; } pixelPlotX = (0 - (xScroll & 0x07) + (tileColumn << 3) + pixelPlotX); if (pixelPlotX >= 0 && pixelPlotX < 256) { // logerror("%x %x\n", pixelPlotX + pixelOffsetX, pixelPlotY); lineBuffer[pixelPlotX] = currentPalette[penSelected]; prioritySelected[pixelPlotX] = prioritySelect | ( penSelected & 0x0F ); } } } /* Draw sprite layer */ spriteHeight = (reg[0x01] & 0x02 ? 16 : 8); spriteZoom = 1; if (reg[0x01] & 0x01) { /* sprite doubling */ spriteZoom = 2; } spriteBufferCount = 0; for (spriteIndex = 0; (spriteIndex < 64) && (spriteTable[spriteIndex] != 0xD0 || y_pixels != 192) && (spriteBufferCount < 9); spriteIndex++) { spriteY = spriteTable[spriteIndex] + 1; /* sprite y position starts at line 1 */ if (spriteY > 240) { spriteY -= 256; /* wrap from top if y position is > 240 */ } if ((line >= spriteY) && (line < (spriteY + spriteHeight * spriteZoom))) { if (spriteBufferCount < 8) { spriteBuffer[spriteBufferCount] = spriteIndex; } else { /* Too many sprites per line */ statusReg |= STATUS_SPROVR; } spriteBufferCount++; } } if ( spriteBufferCount > 8 ) { spriteBufferCount = 8; } memset(lineCollisionBuffer, 0, SMS_X_PIXELS); spriteBufferCount--; for (spriteBufferIndex = spriteBufferCount; spriteBufferIndex >= 0; spriteBufferIndex--) { spriteIndex = spriteBuffer[spriteBufferIndex]; spriteY = spriteTable[spriteIndex] + 1; /* sprite y position starts at line 1 */ if (spriteY > 240) { spriteY -= 256; /* wrap from top if y position is > 240 */ } spriteX = spriteTable[0x80 + (spriteIndex << 1)]; if (reg[0x00] & 0x08) { spriteX -= 0x08; /* sprite shift */ } spriteTileSelected = spriteTable[0x81 + (spriteIndex << 1)]; if (reg[0x06] & 0x04) { spriteTileSelected += 256; /* pattern table select */ } if (reg[0x01] & 0x02) { spriteTileSelected &= 0x01FE; /* force even index */ } spriteLine = ( line - spriteY ) / spriteZoom; if (spriteLine > 0x07) { spriteTileSelected += 1; } bitPlane0 = VRAM[((spriteTileSelected << 5) + ((spriteLine & 0x07) << 2)) + 0x00]; bitPlane1 = VRAM[((spriteTileSelected << 5) + ((spriteLine & 0x07) << 2)) + 0x01]; bitPlane2 = VRAM[((spriteTileSelected << 5) + ((spriteLine & 0x07) << 2)) + 0x02]; bitPlane3 = VRAM[((spriteTileSelected << 5) + ((spriteLine & 0x07) << 2)) + 0x03]; for (pixelX = 0; pixelX < 8 ; pixelX++) { UINT8 penBit0, penBit1, penBit2, penBit3; int penSelected; penBit0 = (bitPlane0 >> (7 - pixelX)) & 0x01; penBit1 = (bitPlane1 >> (7 - pixelX)) & 0x01; penBit2 = (bitPlane2 >> (7 - pixelX)) & 0x01; penBit3 = (bitPlane3 >> (7 - pixelX)) & 0x01; penSelected = (penBit3 << 3 | penBit2 << 2 | penBit1 << 1 | penBit0) | 0x10; if ( penSelected == 0x10 ) { /* Transparent palette so skip draw */ continue; } if (reg[0x01] & 0x01) { /* sprite doubling is enabled */ pixelPlotX = spriteX + (pixelX << 1); /* check to prevent going outside of active display area */ if ( pixelPlotX > 256 ) { continue; } if ( ! ( prioritySelected[pixelPlotX] & PRIORITY_BIT ) ) { lineBuffer[pixelPlotX] = currentPalette[penSelected]; lineBuffer[pixelPlotX+1] = currentPalette[penSelected]; } else { if ( prioritySelected[pixelPlotX] == PRIORITY_BIT ) { lineBuffer[pixelPlotX] = currentPalette[penSelected]; } if ( prioritySelected[pixelPlotX + 1] == PRIORITY_BIT ) { lineBuffer[pixelPlotX+1] = currentPalette[penSelected]; } } if (lineCollisionBuffer[pixelPlotX] != 1) { lineCollisionBuffer[pixelPlotX] = 1; } else { /* sprite collision occurred */ statusReg |= STATUS_SPRCOL; } if (lineCollisionBuffer[pixelPlotX + 1] != 1) { lineCollisionBuffer[pixelPlotX + 1] = 1; } else { /* sprite collision occurred */ statusReg |= STATUS_SPRCOL; } } else { pixelPlotX = spriteX + pixelX; /* check to prevent going outside of active display area */ if ( pixelPlotX > 256 ) { continue; } if ( ! ( prioritySelected[pixelPlotX] & PRIORITY_BIT ) ) { lineBuffer[pixelPlotX] = currentPalette[penSelected]; } else { if ( prioritySelected[pixelPlotX] == PRIORITY_BIT ) { lineBuffer[pixelPlotX] = currentPalette[penSelected]; } } if (lineCollisionBuffer[pixelPlotX] != 1) { lineCollisionBuffer[pixelPlotX] = 1; } else { /* sprite collision occurred */ statusReg |= STATUS_SPRCOL; } } } } /* Fill column 0 with overscan color from reg[0x07] */ if (reg[0x00] & 0x20) { lineBuffer[0] = currentPalette[BACKDROP_COLOR]; lineBuffer[1] = currentPalette[BACKDROP_COLOR]; lineBuffer[2] = currentPalette[BACKDROP_COLOR]; lineBuffer[3] = currentPalette[BACKDROP_COLOR]; lineBuffer[4] = currentPalette[BACKDROP_COLOR]; lineBuffer[5] = currentPalette[BACKDROP_COLOR]; lineBuffer[6] = currentPalette[BACKDROP_COLOR]; lineBuffer[7] = currentPalette[BACKDROP_COLOR]; } } void sms_refresh_tms9918_sprites(int *lineBuffer, int line) { int pixelPlotX; int spriteHeight, spriteBufferCount, spriteIndex, spriteBuffer[4], spriteBufferIndex; UINT8 *spriteTable, *spritePatternTable; /* Draw sprite layer */ spriteTable = VRAM + ( ( reg[0x05] & 0x7F ) << 7 ); spritePatternTable = VRAM + ( ( reg[0x06] & 0x07 ) << 11 ); spriteHeight = 8; if ( reg[0x01] & 0x02 ) /* Check if SI is set */ spriteHeight = spriteHeight * 2; if ( reg[0x01] & 0x01 ) /* Check if MAG is set */ spriteHeight = spriteHeight * 2; spriteBufferCount = 0; for ( spriteIndex = 0; (spriteIndex < 32*4 ) && ( spriteTable[spriteIndex] != 0xD0 ) && ( spriteBufferCount < 5); spriteIndex+= 4 ) { int spriteY = spriteTable[spriteIndex] + 1; if ( spriteY > 240 ) { spriteY -= 256; } if ( ( line >= spriteY ) && ( line < ( spriteY + spriteHeight ) ) ) { if ( spriteBufferCount < 5 ) { spriteBuffer[spriteBufferCount] = spriteIndex; } else { /* Too many sprites per line */ statusReg |= STATUS_SPROVR; } spriteBufferCount++; } } if ( spriteBufferCount > 4 ) { spriteBufferCount = 4; } memset( lineCollisionBuffer, 0, SMS_X_PIXELS ); spriteBufferCount--; for ( spriteBufferIndex = spriteBufferCount; spriteBufferIndex >= 0; spriteBufferIndex-- ) { int penSelected; int spriteLine, pixelX, spriteX, spriteTileSelected; int spriteY; UINT8 pattern; spriteIndex = spriteBuffer[ spriteBufferIndex ]; spriteY = spriteTable[ spriteIndex ] + 1; if ( spriteY > 240 ) { spriteY -= 256; } spriteX = spriteTable[ spriteIndex + 1 ]; penSelected = spriteTable[ spriteIndex + 3 ] & 0x0F; if ( IS_GAMEGEAR_VDP ) { penSelected |= 0x10; } if ( spriteTable[ spriteIndex + 3 ] & 0x80 ) { spriteX -= 32; } spriteTileSelected = spriteTable[ spriteIndex + 2 ]; spriteLine = line - spriteY; if ( reg[0x01] & 0x01 ) { spriteLine >>= 1; } if ( reg[0x01] & 0x02 ) { spriteTileSelected &= 0xFC; if ( spriteLine > 0x07 ) { spriteTileSelected += 1; spriteLine -= 8; } } pattern = spritePatternTable[ spriteTileSelected * 8 + spriteLine ]; for ( pixelX = 0; pixelX < 8; pixelX++ ) { if ( reg[0x01] & 0x01 ) { pixelPlotX = spriteX + pixelX * 2; if ( pixelPlotX < 0 || pixelPlotX > 255 ) { continue; } if ( penSelected && ( pattern & ( 1 << ( 7 - pixelX ) ) ) ) { lineBuffer[pixelPlotX] = currentPalette[penSelected]; if ( lineCollisionBuffer[pixelPlotX] != 1 ) { lineCollisionBuffer[pixelPlotX] = 1; } else { statusReg |= STATUS_SPRCOL; } lineBuffer[pixelPlotX+1] = currentPalette[penSelected]; if ( lineCollisionBuffer[pixelPlotX + 1] != 1 ) { lineCollisionBuffer[pixelPlotX + 1] = 1; } else { statusReg |= STATUS_SPRCOL; } } } else { pixelPlotX = spriteX + pixelX; if ( pixelPlotX < 0 || pixelPlotX > 255 ) { continue; } if ( penSelected && ( pattern & ( 1 << ( 7 - pixelX ) ) ) ) { lineBuffer[pixelPlotX] = currentPalette[penSelected]; if ( lineCollisionBuffer[pixelPlotX] != 1 ) { lineCollisionBuffer[pixelPlotX] = 1; } else { statusReg |= STATUS_SPRCOL; } } } } if ( reg[0x01] & 0x02 ) { spriteTileSelected += 2; pattern = spritePatternTable[ spriteTileSelected * 8 + spriteLine ]; spriteX += ( reg[0x01] & 0x01 ? 16 : 8 ); for ( pixelX = 0; pixelX < 8; pixelX++ ) { if ( reg[0x01] & 0x01 ) { pixelPlotX = spriteX + pixelX * 2; if ( pixelPlotX < 0 || pixelPlotX > 255 ) { continue; } if ( penSelected && ( pattern & ( 1 << ( 7 - pixelX ) ) ) ) { lineBuffer[pixelPlotX] = currentPalette[penSelected]; if ( lineCollisionBuffer[pixelPlotX] != 1 ) { lineCollisionBuffer[pixelPlotX] = 1; } else { statusReg |= STATUS_SPRCOL; } lineBuffer[pixelPlotX+1] = currentPalette[penSelected]; if ( lineCollisionBuffer[pixelPlotX + 1] != 1 ) { lineCollisionBuffer[pixelPlotX + 1] = 1; } else { statusReg |= STATUS_SPRCOL; } } } else { pixelPlotX = spriteX + pixelX; if ( pixelPlotX < 0 || pixelPlotX > 255 ) { continue; } if ( penSelected && ( pattern & ( 1 << ( 7 - pixelX ) ) ) ) { lineBuffer[pixelPlotX] = currentPalette[penSelected]; if ( lineCollisionBuffer[pixelPlotX] != 1 ) { lineCollisionBuffer[pixelPlotX] = 1; } else { statusReg |= STATUS_SPRCOL; } } } } } } } void sms_refresh_line_mode2(int *lineBuffer, int line) { int tileColumn; int pixelX, pixelPlotX; UINT8 *nameTable, *colorTable, *patternTable; int patternMask, colorMask, patternOffset; /* Draw background layer */ nameTable = VRAM + ( ( reg[0x02] & 0x0F ) << 10 ) + ( ( line >> 3 ) * 32 ); colorTable = VRAM + ( ( reg[0x03] & 0x80 ) << 6 ); colorMask = ( ( reg[0x03] & 0x7F ) << 3 ) | 0x07; patternTable = VRAM + ( ( reg[0x04] & 0x04 ) << 11 ); patternMask = ( ( reg[0x04] & 0x03 ) << 8 ) | 0xFF; patternOffset = ( line & 0xC0 ) << 2; for ( tileColumn = 0; tileColumn < 32; tileColumn++ ) { UINT8 pattern; UINT8 colors; pattern = patternTable[ ( ( ( patternOffset + nameTable[tileColumn] ) & patternMask ) * 8 ) + ( line & 0x07 ) ]; colors = colorTable[ ( ( ( patternOffset + nameTable[tileColumn] ) & colorMask ) * 8 ) + ( line & 0x07 ) ]; for ( pixelX = 0; pixelX < 8; pixelX++ ) { UINT8 penSelected; if ( pattern & ( 1 << ( 7 - pixelX ) ) ) { penSelected = colors >> 4; } else { penSelected = colors & 0x0F; } if ( ! penSelected ) { penSelected = BACKDROP_COLOR; } pixelPlotX = ( tileColumn << 3 ) + pixelX; if ( IS_GAMEGEAR_VDP ) { penSelected |= 0x10; } lineBuffer[pixelPlotX] = currentPalette[penSelected]; } } /* Draw sprite layer */ sms_refresh_tms9918_sprites( lineBuffer, line ); } void sms_refresh_line_mode0(int *lineBuffer, int line) { int tileColumn; int pixelX, pixelPlotX; UINT8 *nameTable, *colorTable, *patternTable; /* Draw background layer */ nameTable = VRAM + ( ( reg[0x02] & 0x0F ) << 10 ) + ( ( line >> 3 ) * 32 ); colorTable = VRAM + ( ( reg[0x03] << 6 ) & ( VRAM_SIZE - 1 ) ); patternTable = VRAM + ( ( reg[0x04] << 11 ) & ( VRAM_SIZE - 1 ) ); for ( tileColumn = 0; tileColumn < 32; tileColumn++ ) { UINT8 pattern; UINT8 colors; pattern = patternTable[ ( nameTable[tileColumn] * 8 ) + ( line & 0x07 ) ]; colors = colorTable[ nameTable[tileColumn] >> 3 ]; for ( pixelX = 0; pixelX < 8; pixelX++ ) { int penSelected; if ( pattern & ( 1 << ( 7 - pixelX ) ) ) { penSelected = colors >> 4; } else { penSelected = colors & 0x0F; } if ( IS_GAMEGEAR_VDP ) { penSelected |= 0x10; } pixelPlotX = ( tileColumn << 3 ) + pixelX; lineBuffer[pixelPlotX] = currentPalette[penSelected]; } } /* Draw sprite layer */ sms_refresh_tms9918_sprites( lineBuffer, line ); } void sms_refresh_line( mame_bitmap *bitmap, int pixelOffsetX, int pixelPlotY, int line ) { int x; int *blitLineBuffer = lineBuffer; if ( line >= 0 && line < sms_frame_timing[ACTIVE_DISPLAY_V] ) { switch( vdp_mode ) { case 0: sms_refresh_line_mode0( blitLineBuffer, line ); break; case 2: sms_refresh_line_mode2( blitLineBuffer, line ); break; case 4: default: sms_refresh_line_mode4( blitLineBuffer, line ); break; } } else { for ( x = 0; x < 256; x++ ) { blitLineBuffer[x] = currentPalette[BACKDROP_COLOR]; } } if ( IS_GAMEGEAR_VDP && ggSmsMode ) { int *combineLineBuffer = lineBuffer + ( ( line & 0x03 ) + 1 ) * 256; int plotX = 48; /* Do horizontal scaling */ for( x = 8; x < 248; ) { int combined; /* Take red and green from first pixel, and blue from second pixel */ combined = ( blitLineBuffer[x] & 0x00FF ) | ( blitLineBuffer[x+1] & 0x0F00 ); combineLineBuffer[plotX] = combined; /* Take red from second pixel, and green and blue from third pixel */ combined = ( blitLineBuffer[x+1] & 0x000F ) | ( blitLineBuffer[x+2] & 0x0FF0 ); combineLineBuffer[plotX+1] = combined; x += 3; plotX += 2; } /* Do vertical scaling for a screen with 192 or 224 lines Lines 0-2 and 221-223 have no effect on the output on the GG screen. We will calculate the gamegear lines as follows: GG_0 = 1/6 * SMS_3 + 1/3 * SMS_4 + 1/3 * SMS_5 + 1/6 * SMS_6 GG_1 = 1/6 * SMS_4 + 1/3 * SMS_5 + 1/3 * SMS_6 + 1/6 * SMS_7 GG_2 = 1/6 * SMS_6 + 1/3 * SMS_7 + 1/3 * SMS_8 + 1/6 * SMS_9 GG_3 = 1/6 * SMS_7 + 1/3 * SMS_8 + 1/3 * SMS_9 + 1/6 * SMS_10 GG_4 = 1/6 * SMS_9 + 1/3 * SMS_10 + 1/3 * SMS_11 + 1/6 * SMS_12 ..... GG_142 = 1/6 * SMS_216 + 1/3 * SMS_217 + 1/3 * SMS_218 + 1/6 * SMS_219 GG_143 = 1/6 * SMS_217 + 1/3 * SMS_218 + 1/3 * SMS_219 + 1/6 * SMS_220 */ { int ggLine; int myLine = pixelPlotY + line - ( TBORDER_START + NTSC_224_TBORDER_Y_PIXELS ); int *line1, *line2, *line3, *line4; /* First make sure there's enough data to draw anything */ /* We need one more line of data if we're on line 8, 11, 14, 17, etc */ if ( myLine < 6 || myLine > 220 || ( ( myLine - 8 ) % 3 == 0 ) ) { return; } ggLine = ( ( myLine - 6 ) / 3 ) * 2; /* If we're on SMS line 7, 10, 13, etc we're on an odd GG line */ if ( myLine % 3 ) { ggLine++; } /* Calculate the line we will be drawing on */ pixelPlotY = TBORDER_START + NTSC_192_TBORDER_Y_PIXELS + 24 + ggLine; /* Setup our source lines */ line1 = lineBuffer + ( ( ( myLine - 3 ) & 0x03 ) + 1 ) * 256; line2 = lineBuffer + ( ( ( myLine - 2 ) & 0x03 ) + 1 ) * 256; line3 = lineBuffer + ( ( ( myLine - 1 ) & 0x03 ) + 1 ) * 256; line4 = lineBuffer + ( ( ( myLine - 0 ) & 0x03 ) + 1 ) * 256; for( x = 0+48; x < 160+48; x++ ) { rgb_t c1 = Machine->pens[line1[x]]; rgb_t c2 = Machine->pens[line2[x]]; rgb_t c3 = Machine->pens[line3[x]]; rgb_t c4 = Machine->pens[line4[x]]; *BITMAP_ADDR32( bitmap, pixelPlotY, pixelOffsetX + x) = MAKE_RGB( ( RGB_RED(c1)/6 + RGB_RED(c2)/3 + RGB_RED(c3)/3 + RGB_RED(c4)/6 ), ( RGB_GREEN(c1)/6 + RGB_GREEN(c2)/3 + RGB_GREEN(c3)/3 + RGB_GREEN(c4)/6 ), ( RGB_BLUE(c1)/6 + RGB_BLUE(c2)/3 + RGB_BLUE(c3)/3 + RGB_BLUE(c4)/6 ) ); } return; } blitLineBuffer = combineLineBuffer; } for( x = 0; x < 256; x++ ) { *BITMAP_ADDR32( bitmap, pixelPlotY + line, pixelOffsetX + x) = Machine->pens[blitLineBuffer[x]]; } } void sms_update_palette(void) { int i; /* Exit if palette is has no changes */ if (isCRAMDirty == 0) { return; } isCRAMDirty = 0; if ( vdp_mode != 4 && ! IS_GAMEGEAR_VDP ) { for( i = 0; i < 16; i++ ) { currentPalette[i] = 64+i; } return; } if ( IS_GAMEGEAR_VDP ) { if ( ggSmsMode ) { for ( i = 0; i < 32; i++ ) { currentPalette[i] = ( ( CRAM[i] & 0x30 ) << 6 ) | ( ( CRAM[i] & 0x0C ) << 4 ) | ( ( CRAM[i] & 0x03 ) << 2 ); } } else { for ( i = 0; i < 32; i++ ) { currentPalette[i] = ( ( CRAM[i*2+1] << 8 ) | CRAM[i*2] ) & 0x0FFF; } } } else { for ( i = 0; i < 32; i++ ) { currentPalette[i] = CRAM[i] & 0x3F; } } } VIDEO_UPDATE(sms) { int x, y; if (prevBitMapSaved) { for (y = 0; y < Machine->screen[0].height; y++) { for (x = 0; x < Machine->screen[0].width; x++) { *BITMAP_ADDR32(bitmap, y, x) = (*BITMAP_ADDR32(tmpbitmap, y, x) + *BITMAP_ADDR32(prevBitMap, y, x)) >> 2; logerror("%x %x %x\n", *BITMAP_ADDR32(tmpbitmap, y, x), *BITMAP_ADDR32(prevBitMap, y, x), (*BITMAP_ADDR32(tmpbitmap, y, x) + *BITMAP_ADDR32(prevBitMap, y, x)) >> 2); } } } else { copybitmap(bitmap, tmpbitmap, 0, 0, 0, 0, &Machine->screen[0].visarea, TRANSPARENCY_NONE, 0); } if (!prevBitMapSaved) { copybitmap(prevBitMap, tmpbitmap, 0, 0, 0, 0, &Machine->screen[0].visarea, TRANSPARENCY_NONE, 0); //prevBitMapSaved = 1; } return 0; }