/***************************************************************************** * * File: VDP.cpp * * Project: Osmose emulator. * * Description: This class will handle VDP (Video Display Processor)operation. * * Author: Vedder Bruno * Date: 11/10/2004, 08h30 * * URL: http://bcz.emu-france.com/ *****************************************************************************/ #include "VDP.h" /* Uncomment this to display sprites */ #define DISPLAY_SPRITES // Pre calculated Vertical count values, for 192 line NTSC video. unsigned char vcount_ntsc_192[262] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF }; // Pre calculated Vertical count values, for 192 line NTSC video. unsigned char vcount_palsecam_192[313] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF }; /* Constructor */ VDP::VDP(bool ntsc) { VRAM = new unsigned char[0x4000]; CRAM = new unsigned char[64]; colors = new unsigned short[64]; gfx_buffer = new unsigned short[240*256]; // Select appropriate vcount array, depending on ntsc/pal option. if (ntsc == true) { v_cnt = vcount_ntsc_192; } else { v_cnt = vcount_palsecam_192; } if ((VRAM !=NULL) && (CRAM !=NULL) && (gfx_buffer !=NULL)) { for(int i=0; i < 64; i++) { CRAM[i] = 0x0; } for(int i=0; i < 0x4000; i++) { VRAM[i] = 0x0; } reset(); } else { cerr << "Unable to allocate memory for ROM banks !" << endl; cerr << "Exiting." << endl; exit(-1); } } /*-------------------------------------------------------------*/ /* This method handles write operation on the VDP data port. */ /*-------------------------------------------------------------*/ void VDP::writeDataPort(unsigned char data) /* Port 0xBE written */ { cmd_flag = false; rd_data_port_buffer = data; // CMD docs says that write, load buffer with it's value. // destination is VRAM or CRAM ? if (cmd_type == 3) { CRAM[addr & 0x1F] = data; // data not anded with 1f. It's done with rgb rate. #ifdef VDP_VERBOSE cout << "CRAM written: at 0x" << hex << setw(4) << setfill('0')<< addr << " with value "<< setw(2) << setfill('0') << (int)data <> 2) &3]; b = b_col[(data >> 4) &3]; colors[addr & 0x1f] = (r << 11) | (g <<5) | (b); addr++; addr &=0x3FFF; } else // Destination is VRAM { VRAM[addr] = data; #ifdef VDP_VERBOSE cout << "VRAM written: at 0x" << hex << setw(4) << setfill('0')<< addr << " with value "<< setw(2) << setfill('0') <<(int)data < map default to 0x3800 in VRAM */ map_addr = 0x3800; /* REG2 converted into VRAM address. */ REG5 = 0x7E; /* x111111x -> sit default to 0x3F00 in VRAM */ sit_addr = 0x3F00; /* REG5 converted into VRAM address. */ REG10 = 0xFF; /* No Line Interrupt */ } /*------------------------------------------------------------*/ /* This is a debugging purpose function. */ /* Note that this method will dump VDP VRAM. */ /*------------------------------------------------------------*/ void VDP::dumpVRAM(unsigned int sa, int nb_lines) { cout << "Dumping VDP RAM:"<> 1) & 0x3F) << 8 ); #ifdef VDP_VERBOSE if (r == 2 || r==5) { cout << "VDP REGISTER: tile map address set to 0x" << hex << setw(4) << setfill('0') << map_addr << endl; cout << "VDP REGISTER: sprite information table set to 0x" << hex << setw(4) << setfill('0') << sit_addr << endl; } #endif } /*------------------------------------------------------------*/ /* This method is called when a scanline has been drawn. */ /* It draws one line in screen. Set sms_irq if VDP triggers */ /* an interrupt. */ /* If drawline is true, line render is done else it's skipped */ /* This is for frame skip, to handle interrupt system, but to */ /* avoid drawing line is frame is not displayed. */ /*------------------------------------------------------------*/ void VDP::update(SDL_Surface *s, bool drawline) { bool line_int = false; if (line <= 0xC0) { if (line < 0xC0) { if (drawline == true) { SDL_LockSurface(s); traceBackGroundLine(s); SDL_UnlockSurface(s); } } if (line == 0xC0) { vdp_status |= BIT7; } if (line == 0) { i_counter = REG10; } if (i_counter == 0) { i_counter = REG10; line_int = true; } else { i_counter--; } if((line_int) && (REG0 & BIT4)) { sms_irq = true; } } else { i_counter = REG10; // Line < 224 && VSynch raised && VSynch gen. enabled if ((line < 0xE0) && (vdp_status & BIT7) && (REG1 & BIT5)) { sms_irq = true; } } v_counter = v_cnt[line]; } /*--------------------------------------------------------------*/ /* This method draws a scanline. */ /* */ /* Drawing is done in two pass: */ /* - Render tile background */ /* (sprite are rendered, then) */ /* - Render tile that cover sprites */ /*--------------------------------------------------------------*/ void VDP::traceBackGroundLine(SDL_Surface *s) { unsigned int c,pos; unsigned short *dst; unsigned short *scr; unsigned short currentTile; unsigned char i, o, x, y, col_index, attrib; int tmp; unsigned int p; // scr ptr in our SDL_Surface points line to be drawn. scr = (unsigned short*) s->pixels + (256 * line); dst = line_buffer; // Draw a blank line directly in screen if display is disabled. if ((!(REG1 & BIT6)) /* || ((REG2 & BIT1) == 0)*/) { memset(scr,0x00, 0x200); // 0x200 means 256 16bits pixels. return; } // Clear our tileMask: memset(tile_mask,0x00, 0x100); // x = X scroll register, y = Y scroll register. y = REG9; x = REG8; // Top 2 rows of screen not affected by horizontal scrolling if (((REG0 & BIT6) && (line<=15))) { x = 0; } // y + x*2 (16bit entrie) tmp = ((line+y)% 224); pos = (tmp>>3)<<6; // Note that x is never tested for >255 since it automaticaly wraps // due to it's unsigned char declaration. for (o=0; o<32;o++) { currentTile = VRAM[ map_addr + pos++]; attrib = VRAM[ map_addr + pos++]; if(attrib & BIT0) { currentTile |=0x100; // 9th tile index bit. } // line in tile converted to VRAM ind if (attrib & BIT2) { // Verticaly flipped tile. c = (7 - ((tmp & 7) )<<2) + (currentTile<<5); } else { c = ((tmp & 7)<<2) + (currentTile<<5); } // Four bytes are read into one 32bits variable. This avoid 3 memory access. // Bits plan are like this in the variable (intel architecture): // P3P2P1P0 which is inverse order or ram content. This is due to intel // endianness unsigned int *cst = (unsigned int *) &VRAM[c]; p = *cst; c += 4; // Draw 8 horizontals pixels. switch ((attrib>>1) & 3) { case 0: // Tile not flipped for ( i = 0; i<8; i++) { col_index = (p >>7)&1 | ((p >> 15)<<1)&2 | ((p >> 23)<<2)&4 | ((p >> 31)<<3); if(attrib & BIT3) col_index|=0x10; // Then use sprite palete dst[x] = colors[col_index]; if ((attrib & BIT4) && (col_index != 0x10) && (col_index !=0x0)) { tile_mask[x] = 1; } x++; p<<=1; } break; case 1: // Tile flipped on x for ( i = 0; i<8; i++) { col_index = (p&1) | ((p>>8) & 1)<<1 | ((p>>16) & 1 )<<2 | ((p >>24) &1)<<3; if(attrib & BIT3) col_index|=0x10; // Then use sprite palete dst[x] = colors[col_index]; if ((attrib & BIT4) && (col_index != 0x10) && (col_index !=0x0)) { tile_mask[x] = 1; } x++; p>>=1; } break; case 2: // Tile flipped on y for (int i=0; i<8;i++) { col_index = ((p>>7) &1)| ((p >> 15)&1)<<1 | ((p >> 23)&1)<<2 | ((p >> 31)&1)<<3; if(attrib & BIT3) col_index|=0x10; // Then use sprite palete dst[x] = colors[col_index]; if ((attrib & BIT4) && (col_index != 0x10) && (col_index !=0x0)) { tile_mask[x] = 1; } x++; p<<=1; } break; case 3: // Tile flipped on x and y for (int i=0; i<8;i++) { col_index = (p & 1) | ((p>>8) & 1)<<1 | ((p>>16) & 1)<<2 | ((p>>24) & 1)<<3; if(attrib & BIT3) col_index|=0x10; // Then use sprite palete dst[x] = colors[col_index]; if ((attrib & BIT4) && (col_index != 0x10) && (col_index !=0x0)) { tile_mask[x] = 1; } x++; p>>=1; } break; } // switch attrib flip x/y } #ifdef DISPLAY_SPRITES displaySpritesLine(); #endif if (REG0 & BIT5) // Do not display (clear) leftmost column of the screen { unsigned short c = colors[(REG7 & 0xF)+16]; for (int u=0; u < 8; u++) { dst[u] = c; } } // Copy buffer_line in screen line: memcpy(scr,dst, 512); } /*-------------------------------------------------------------*/ /* This method will sprites on a given scanline on SDL_Surface */ /* The scanline is the line variable into VDP Class, which is */ /* the current line drawn. */ /*-------------------------------------------------------------*/ void VDP::displaySpritesLine() { unsigned char sprite_height = 8; unsigned char displayedSprites; unsigned short c; unsigned char col_index; int y; unsigned int *cst; unsigned int p; int x_spr[8]; int y_spr[8]; unsigned short ind_spr[8]; unsigned char line_spr[8]; unsigned char start_spr[8]; unsigned char width_spr[8]; if ((!(REG1 & BIT6)) /* || ((REG2 & BIT1) == 0)*/) { return; } if (REG1 & BIT1) // 8*16 sprites { sprite_height = 16; } displayedSprites = 0; // For all sprite information table for (int j=0; j<64; j++) { y = (int)VRAM[sit_addr+j]; // y=208 mean stop displaying sprites. if (y == 208) { break; } // Y position 0 means scanline 1. y++; if (y>240) { y-=256; } // Found one sprite to draw. if ( (line>=y) && (line<(y+sprite_height)) ) { if (displayedSprites == 8) { vdp_status |= BIT6; /* Sprite overflow bit */ break; } ind_spr[displayedSprites] = VRAM[(sit_addr+129)+(j<<1)]; if (REG6 & BIT2) // 9th bit sprite nbr { ind_spr[displayedSprites] |= 0x100; } if (sprite_height==16) { ind_spr[displayedSprites] &=0x01FE; } y_spr[displayedSprites] = y; line_spr[displayedSprites] = line - y; x_spr[displayedSprites] = VRAM[(sit_addr+128)+(j<<1)]; // Sprites moved 8 pixels left. start_spr[displayedSprites] = 0; // First pixel in sprite line to draw. if (REG0 & BIT3) { x_spr[displayedSprites]-=8; if (x_spr[displayedSprites]<0) { start_spr[displayedSprites] = -x_spr[displayedSprites]; } } width_spr[displayedSprites] = 8; // Nbr of pixels in sprite line to draw. if (x_spr[displayedSprites]>248) { width_spr[displayedSprites] = 256 - x_spr[displayedSprites]; } displayedSprites++; } } // Draw in reverse order. for (int i= 0; i < 256; i++) { spr_col[i] = 0; } for (int r = displayedSprites-1; r >= 0; r--) { c = (line_spr[r]<<2) + (ind_spr[r]<<5); cst = (unsigned int *) &VRAM[c]; p = *cst; c += 4; for (int i=start_spr[r]; i>7)&1 | ((p >> 15)<<1)&2 | ((p >> 23)<<2)&4 | ((p >> 31)<<3); col_index|=0x10; // Always use sprite palete if(col_index != 0x10) { if (tile_mask[x_spr[r]+i] == 0) { unsigned short u = x_spr[r]+i; line_buffer[u] = colors[col_index]; if(spr_col[u] == 1) { vdp_status |= BIT5; // Force collision to true } spr_col[u] = 1; } } p<<=1; } } }