/* Konami Twin16 Hardware - Video TODO: - convert background to tilemap - clean up sprite system - bad sprites in devilw, eg. odd colours for the mud/lava monster in the 1st level, or wrong sprite-sprite priority sometimes -- check real arcade first - unsure about some sprite preprocessor attributes (see twin16_spriteram_process) */ #include "emu.h" #include "includes/twin16.h" static TIMER_CALLBACK( twin16_sprite_tick ); enum { TWIN16_SCREEN_FLIPY = 0x01, TWIN16_SCREEN_FLIPX = 0x02, // confirmed: Hard Puncher Intro TWIN16_UNKNOWN1 = 0x04, // ? Hard Puncher uses this TWIN16_PLANE_ORDER = 0x08, // confirmed: Devil Worlds TWIN16_TILE_FLIPX = 0x10, // unused? TWIN16_TILE_FLIPY = 0x20 // confirmed? Vulcan Venture }; enum { // user-defined priorities TWIN16_BG_LAYER1 = 0x01, TWIN16_SPRITE_PRI_L1 = 0x02, TWIN16_BG_LAYER2 = 0x04, TWIN16_SPRITE_PRI_L2 = 0x08, TWIN16_SPRITE_OCCUPIED = 0x10, // sprite on screen pixel TWIN16_SPRITE_CAST_SHADOW = 0x20 }; WRITE16_HANDLER( twin16_text_ram_w ) { twin16_state *state = space->machine().driver_data(); COMBINE_DATA(&state->m_text_ram[offset]); tilemap_mark_tile_dirty(state->m_text_tilemap, offset); } WRITE16_HANDLER( twin16_paletteram_word_w ) { // identical to tmnt_paletteram_w COMBINE_DATA(space->machine().generic.paletteram.u16 + offset); offset &= ~1; data = ((space->machine().generic.paletteram.u16[offset] & 0xff) << 8) | (space->machine().generic.paletteram.u16[offset + 1] & 0xff); palette_set_color_rgb(space->machine(), offset / 2, pal5bit(data >> 0), pal5bit(data >> 5), pal5bit(data >> 10)); } WRITE16_HANDLER( fround_gfx_bank_w ) { twin16_state *state = space->machine().driver_data(); COMBINE_DATA(&state->m_gfx_bank); } WRITE16_HANDLER( twin16_video_register_w ) { twin16_state *state = space->machine().driver_data(); switch (offset) { case 0: COMBINE_DATA( &state->m_video_register ); flip_screen_x_set(space->machine(), state->m_video_register & TWIN16_SCREEN_FLIPX); flip_screen_y_set(space->machine(), state->m_video_register & TWIN16_SCREEN_FLIPY); break; case 1: COMBINE_DATA( &state->m_scrollx[0] ); break; case 2: COMBINE_DATA( &state->m_scrolly[0] ); break; case 3: COMBINE_DATA( &state->m_scrollx[1] ); break; case 4: COMBINE_DATA( &state->m_scrolly[1] ); break; case 5: COMBINE_DATA( &state->m_scrollx[2] ); break; case 6: COMBINE_DATA( &state->m_scrolly[2] ); break; default: logerror("unknown video_register write:%d", data ); break; } } /* * Sprite Format * ---------------------------------- * preprocessor (not much data to test with): * Word | Bit(s) | Use * -----+-fedcba9876543210-+---------------- * 0 | x--------------- | enable * 0 | -xxxxxxx-------- | ? * 0 | --------xxxxxxxx | sprite-sprite priority * -----+------------------+ * 1 | xxxxxxxxxxxxxxxx | ? * -----+------------------+ * 2 | xxxxxx---------- | ? * 2 | ------x--------- | yflip (devilw) * 2 | -------x-------- | xflip * 2 | --------xx------ | height * 2 | ----------xx---- | width * 2 | ------------xxxx | color * -----+------------------+ * 3 | -xxxxxxxxxxxxxxx | code * -----+------------------+ * 4 | -------xxxxxxxxx | xpos high, other bits probably no effect * 5 | xxxxxxxx-------- | xpos low, other bits probably no effect * 6 | -------xxxxxxxxx | xpos high, other bits probably no effect * 7 | xxxxxxxx-------- | ypos low, other bits probably no effect * * ---------------------------------- * normal/after preprocessing: * Word | Bit(s) | Use * -----+-fedcba9876543210-+---------------- * 0 | -xxxxxxxxxxxxxxx | code * -----+------------------+ * 1 | -------xxxxxxxxx | ypos * -----+------------------+ * 2 | -------xxxxxxxxx | xpos * -----+------------------+ * 3 | x--------------- | enable * 3 | -x-------------- | priority ? * 3 | -----x---------- | no shadow ? * 3 | ------x--------- | yflip ? * 3 | -------x-------- | xflip * 3 | --------xx------ | height * 3 | ----------xx---- | width * 3 | ------------xxxx | color */ READ16_HANDLER( twin16_sprite_status_r ) { twin16_state *state = space->machine().driver_data(); // bit 0: busy, other bits: dunno return state->m_sprite_busy; } static TIMER_CALLBACK( twin16_sprite_tick ) { twin16_state *state = machine.driver_data(); state->m_sprite_busy = 0; } static int twin16_set_sprite_timer( running_machine &machine ) { twin16_state *state = machine.driver_data(); if (state->m_sprite_busy) return 1; // sprite system busy, maybe a dma? time is guessed, assume 4 scanlines state->m_sprite_busy = 1; state->m_sprite_timer->adjust(machine.primary_screen->frame_period() / machine.primary_screen->height() * 4); return 0; } void twin16_spriteram_process( running_machine &machine ) { twin16_state *state = machine.driver_data(); UINT16 *spriteram16 = machine.generic.spriteram.u16; UINT16 dx = state->m_scrollx[0]; UINT16 dy = state->m_scrolly[0]; const UINT16 *source = &spriteram16[0x0000]; const UINT16 *finish = &spriteram16[0x1800]; twin16_set_sprite_timer(machine); memset(&spriteram16[0x1800],0xff,0x800*sizeof(UINT16)); while( source>4| // no-shadow? (gradius2 level 7 boss sets this bit and appears to expect pen $F to be solid) (priority&0x200)<<5; // sprite-background priority? dest[0] = source[3]; /* gfx data */ dest[1] = ((xpos>>8) - dx)&0xffff; dest[2] = ((ypos>>8) - dy)&0xffff; dest[3] = attributes; } source += 0x50/2; } state->m_need_process_spriteram = 0; } static void draw_sprites( running_machine &machine, bitmap_t *bitmap ) { twin16_state *state = machine.driver_data(); const UINT16 *source = 0x1800+machine.generic.buffered_spriteram.u16 + 0x800 - 4; const UINT16 *finish = 0x1800+machine.generic.buffered_spriteram.u16; for (; source >= finish; source -= 4) { UINT16 attributes = source[3]; UINT16 code = source[0]; if((code!=0xffff) && (attributes&0x8000)) { int xpos = source[1]; int ypos = source[2]; int x,y; int pal_base = ((attributes&0xf)+0x10)*16; int height = 16<<((attributes>>6)&0x3); int width = 16<<((attributes>>4)&0x3); const UINT16 *pen_data = 0; int flipy = attributes&0x0200; int flipx = attributes&0x0100; int priority = (attributes&0x4000)?TWIN16_SPRITE_PRI_L1:TWIN16_SPRITE_PRI_L2; if( state->m_custom_video ) { /* fround board */ pen_data = state->m_gfx_rom + 0x80000; } else { switch( (code>>12)&0x3 ) { /* bank select */ case 0: pen_data = state->m_gfx_rom; break; case 1: pen_data = state->m_gfx_rom + 0x40000; break; case 2: pen_data = state->m_gfx_rom + 0x80000; if( code&0x4000 ) pen_data += 0x40000; break; case 3: pen_data = state->m_sprite_gfx_ram; break; } code &= 0xfff; } /* some code masking */ if ((height&width) == 64) code &= ~8; // gradius2 ending sequence 64*64 else if ((height&width) == 32) code &= ~3; // devilw 32*32 else if ((height|width) == 48) code &= ~1; // devilw 32*16 / 16*32 pen_data += code*0x40; if( state->m_video_register&TWIN16_SCREEN_FLIPY ) { if (ypos>65000) ypos=ypos-65536; /* Bit hacky */ ypos = 256-ypos-height; flipy = !flipy; } if( state->m_video_register&TWIN16_SCREEN_FLIPX ) { if (xpos>65000) xpos=xpos-65536; /* Bit hacky */ xpos = 320-xpos-width; flipx = !flipx; } if( xpos>=320 ) xpos -= 65536; if( ypos>=256 ) ypos -= 65536; /* slow slow slow, but it's ok for now */ for( y=0; y=16 && sy<256-16 ) { UINT16 *dest = BITMAP_ADDR16(bitmap, sy, 0); UINT8 *pdest = BITMAP_ADDR8(machine.priority_bitmap, sy, 0); for( x=0; x=0 && sx<320 ) { UINT16 pen = pen_data[x>>2]>>((~x&3)<<2)&0xf; if( pen ) { int shadow = (pen==0xf) & ((attributes&0x400)==0); if (pdest[sx](); UINT16 *videoram = state->m_videoram; const UINT16 *gfx_base; const UINT16 *source = videoram; int i, xxor, yxor; int bank_table[4]; int dx, dy, palette; int tile_flipx = state->m_video_register&TWIN16_TILE_FLIPX; int tile_flipy = state->m_video_register&TWIN16_TILE_FLIPY; if( ((state->m_video_register&TWIN16_PLANE_ORDER)?1:0) != opaque ) { source += 0x1000; dx = state->m_scrollx[2]; dy = state->m_scrolly[2]; palette = 1; } else { source += 0x0000; dx = state->m_scrollx[1]; dy = state->m_scrolly[1]; palette = 0; } if( state->m_custom_video ) { /* fround board */ gfx_base = state->m_gfx_rom; bank_table[3] = (state->m_gfx_bank>>(4*3))&0xf; bank_table[2] = (state->m_gfx_bank>>(4*2))&0xf; bank_table[1] = (state->m_gfx_bank>>(4*1))&0xf; bank_table[0] = (state->m_gfx_bank>>(4*0))&0xf; } else { gfx_base = state->m_tile_gfx_ram; bank_table[0] = 0; bank_table[1] = 1; bank_table[2] = 2; bank_table[3] = 3; } if( state->m_video_register&TWIN16_SCREEN_FLIPX ) { dx = 256-dx-64; tile_flipx = !tile_flipx; } if( state->m_video_register&TWIN16_SCREEN_FLIPY ) { dy = 256-dy; tile_flipy = !tile_flipy; } xxor = tile_flipx ? 7 : 0; yxor = tile_flipy ? 7 : 0; for( i=0; i<64*64; i++ ) { int y1, y2, x1, x2; int sx = (i%64)*8; int sy = (i/64)*8; int xpos,ypos; if( state->m_video_register&TWIN16_SCREEN_FLIPX ) sx = 63*8 - sx; if( state->m_video_register&TWIN16_SCREEN_FLIPY ) sy = 63*8 - sy; xpos = (sx-dx)&0x1ff; ypos = (sy-dy)&0x1ff; if( xpos>=320 ) xpos -= 512; if( ypos>=256 ) ypos -= 512; x1 = MAX(xpos, 0); x2 = MIN(xpos+7, bitmap->width-1); y1 = MAX(ypos, 0); y2 = MIN(ypos+7, bitmap->height-1); if (x1 <= x2 && y1 <= y2) { int code = source[i]; /* fedcba9876543210 xxx------------- color ---xx----------- tile bank -----xxxxxxxxxxx tile number */ const UINT16 *gfx_data = gfx_base + (code&0x7ff)*16 + bank_table[(code>>11)&0x3]*0x8000; int color = (code>>13); int pal_base = 16*(0x20+color+8*palette); int x, y; if( opaque ) { for (y = y1; y <= y2; y++) { const UINT16 *gfxptr = gfx_data + ((y - ypos) ^ yxor) * 2; UINT16 *dest = BITMAP_ADDR16(bitmap, y, 0); UINT8 *pdest = BITMAP_ADDR8(machine.priority_bitmap, y, 0); for (x = x1; x <= x2; x++) { int effx = (x - xpos) ^ xxor; UINT16 data = gfxptr[effx / 4]; dest[x] = pal_base + ((data >> 4*(~effx & 3)) & 0x0f); pdest[x] |= TWIN16_BG_LAYER1; } } } else { for (y = y1; y <= y2; y++) { const UINT16 *gfxptr = gfx_data + ((y - ypos) ^ yxor) * 2; UINT16 *dest = BITMAP_ADDR16(bitmap, y, 0); UINT8 *pdest = BITMAP_ADDR8(machine.priority_bitmap, y, 0); for (x = x1; x <= x2; x++) { int effx = (x - xpos) ^ xxor; UINT16 data = gfxptr[effx / 4]; UINT8 pen = (data >> 4*(~effx & 3)) & 0x0f; if (pen) { dest[x] = pal_base + pen; pdest[x] |= TWIN16_BG_LAYER2; } } } } } } } static TILE_GET_INFO( get_text_tile_info ) { twin16_state *state = machine.driver_data(); const UINT16 *source = state->m_text_ram; int attr = source[tile_index]; /* fedcba9876543210 -x-------------- yflip --x------------- xflip ---xxxx--------- color -------xxxxxxxxx tile number */ int code = attr & 0x1ff; int color = (attr >> 9) & 0x0f; int flags=0; if (attr&0x2000) flags|=TILE_FLIPX; if (attr&0x4000) flags|=TILE_FLIPY; SET_TILE_INFO(0, code, color, flags); } VIDEO_START( twin16 ) { twin16_state *state = machine.driver_data(); state->m_text_tilemap = tilemap_create(machine, get_text_tile_info, tilemap_scan_rows, 8, 8, 64, 32); tilemap_set_transparent_pen(state->m_text_tilemap, 0); palette_set_shadow_factor(machine,0.4); // screenshots estimate memset(state->m_sprite_buffer,0xff,0x800*sizeof(UINT16)); state->m_sprite_busy = 0; state->m_sprite_timer = machine.scheduler().timer_alloc(FUNC(twin16_sprite_tick)); state->m_sprite_timer->adjust(attotime::never); /* register for savestates */ state_save_register_global_array(machine, state->m_sprite_buffer); state_save_register_global_array(machine, state->m_scrollx); state_save_register_global_array(machine, state->m_scrolly); state_save_register_global(machine, state->m_need_process_spriteram); state_save_register_global(machine, state->m_gfx_bank); state_save_register_global(machine, state->m_video_register); state_save_register_global(machine, state->m_sprite_busy); } SCREEN_UPDATE( twin16 ) { twin16_state *state = screen->machine().driver_data(); int text_flip=0; if (state->m_video_register&TWIN16_SCREEN_FLIPX) text_flip|=TILEMAP_FLIPX; if (state->m_video_register&TWIN16_SCREEN_FLIPY) text_flip|=TILEMAP_FLIPY; bitmap_fill(screen->machine().priority_bitmap,cliprect,0); draw_layer( screen->machine(), bitmap, 1 ); draw_layer( screen->machine(), bitmap, 0 ); draw_sprites( screen->machine(), bitmap ); if (text_flip) tilemap_set_flip(state->m_text_tilemap, text_flip); tilemap_draw(bitmap, cliprect, state->m_text_tilemap, 0, 0); return 0; } SCREEN_EOF( twin16 ) { twin16_state *state = machine.driver_data(); twin16_set_sprite_timer(machine); if (twin16_spriteram_process_enable(machine)) { if (state->m_need_process_spriteram) twin16_spriteram_process(machine); state->m_need_process_spriteram = 1; /* if the sprite preprocessor is used, sprite ram is copied to an external buffer first, as evidenced by 1-frame sprite lag in gradius2 and devilw otherwise, though there's probably more to it than that */ memcpy(&machine.generic.buffered_spriteram.u16[0x1800],state->m_sprite_buffer,0x800*sizeof(UINT16)); memcpy(state->m_sprite_buffer,&machine.generic.spriteram.u16[0x1800],0x800*sizeof(UINT16)); } else { address_space *space = machine.device("maincpu")->memory().space(AS_PROGRAM); buffer_spriteram16_w(space,0,0,0xffff); } }