#include "driver.h"
#include "video/vdc.h"

/* VDC segments */
#define STATE_VSW		0
#define STATE_VDS		1
#define STATE_VDW		2
#define STATE_VCR		3

/* todo: replace this with the PAIR structure from 'osd_cpu.h' */
typedef union
{
#ifdef LSB_FIRST
	struct { unsigned char l,h; } b;
#else
	struct { unsigned char h,l; } b;
#endif
	unsigned short int w;
}pair;

/* the VDC context */

typedef struct
{
	int dvssr_write;			/* Set when the DVSSR register has been written to */
	int physical_width;			/* Width of the display */
	int physical_height;		/* Height of the display */
	UINT16 sprite_ram[64*4];	/* Sprite RAM */
	int curline;				/* the current scanline we're on */
	int current_segment;		/* current segment of display */
	int current_segment_line;	/* current line inside a segment of display */
	int vblank_triggered;		/* to indicate whether vblank has been triggered */
	int raster_count;			/* counter to compare RCR against */
	int satb_countdown;			/* scanlines to wait to trigger the SATB irq */
	UINT8 *vram;
	UINT8   inc;
	UINT8 vdc_register;
	UINT8 vdc_latch;
	pair vdc_data[32];
	int status;
	int y_scroll;
}VDC;

typedef struct {
	UINT8	vce_control;			/* VCE control register */
	pair	vce_address;			/* Current address in the palette */
	pair	vce_data[512];			/* Palette data */
	int		current_bitmap_line;	/* The current line in the display we are on */
	bitmap_t	*bmp;
}VCE;

typedef struct {
	UINT8 prio;
	UINT8 vdc0_enabled;
	UINT8 vdc1_enabled;
} VPC_PRIO;

typedef struct {
	VPC_PRIO vpc_prio[4];
	UINT8	prio_map[512];		/* Pre-calculated priority map */
	pair	priority;			/* Priority settings registers */
	pair	window1;			/* Window 1 setting */
	pair	window2;			/* Window 2 setting */
	UINT8	vdc_select;			/* Which VDC do the ST0, ST1, and ST2 instructions write to */
}VPC;

static VDC vdc[2];
static VCE vce;
static VPC vpc;

/* Function prototypes */

static void vdc_advance_line(running_machine *machine, int which);
static void draw_black_line(running_machine *machine, int line);
static void draw_overscan_line(int line);
static void draw_sgx_overscan_line(int line);
static void pce_refresh_line(int which, int line, int external_input, UINT8 *drawn, UINT16 *line_buffer);
static void pce_refresh_sprites(running_machine *machine, int which, int line, UINT8 *drawn, UINT16 *line_buffer);
static void vdc_do_dma(running_machine *machine, int which);
static void vpc_init( running_machine *machine );

INTERRUPT_GEN( pce_interrupt )
{
	/* Draw the last scanline */
	if ( vce.current_bitmap_line >= 14 && vce.current_bitmap_line < 14 + 242 )
	{
		/* We are in the active display area */
		/* First fill the line with the overscan color */
		draw_overscan_line(vce.current_bitmap_line );

		/* Check if we need to draw more just the overscan color */
		if ( vdc[0].current_segment == STATE_VDW )
		{
			/* 0 - no sprite and background pixels drawn
               1 - background pixel drawn
               otherwise is 2 + sprite# */
			UINT8 drawn[VDC_WPF];
			/* our line buffer */
			UINT16 *line_buffer = BITMAP_ADDR16( vce.bmp, vce.current_bitmap_line, 86 );

			/* clear our priority/sprite collision detection buffer. */
			memset(drawn, 0, VDC_WPF);

			vdc[0].y_scroll = ( vdc[0].current_segment_line == 0 ) ? vdc[0].vdc_data[BYR].w : ( vdc[0].y_scroll + 1 );

			/* Draw VDC #0 background layer */
			pce_refresh_line( 0, vdc[0].current_segment_line, 0, drawn, line_buffer);

			/* Draw VDC #0 sprite layer */
			if(vdc[0].vdc_data[CR].w & CR_SB)
			{
				pce_refresh_sprites(device->machine, 0, vdc[0].current_segment_line, drawn, line_buffer);
			}
		}
	}
	else
	{
		/* We are in one of the blanking areas */
		draw_black_line(device->machine, vce.current_bitmap_line );
	}

	/* bump current scanline */
	vce.current_bitmap_line = ( vce.current_bitmap_line + 1 ) % VDC_LPF;
	vdc_advance_line(device->machine, 0 );
}

INTERRUPT_GEN( sgx_interrupt )
{
	/* Draw the last scanline */
	if ( vce.current_bitmap_line >= 14 && vce.current_bitmap_line < 14 + 242 )
	{
		/* We are in the active display area */
		/* First fill the line with the overscan color */
		draw_sgx_overscan_line(vce.current_bitmap_line );

		/* Check if we need to draw more just the overscan color */
		if ( vdc[0].current_segment == STATE_VDW )
		{
			/* 0 - no sprite and background pixels drawn
               1 - background pixel drawn
               otherwise is 2 + sprite# */
			UINT8 drawn[2][512];
			UINT16 *line_buffer;
			UINT16 temp_buffer[2][512];
			int i;

			/* clear our priority/sprite collision detection buffer. */
			memset( drawn, 0, sizeof(drawn) );

			vdc[0].y_scroll = ( vdc[0].current_segment_line == 0 ) ? vdc[0].vdc_data[BYR].w : ( vdc[0].y_scroll + 1 );
			vdc[1].y_scroll = ( vdc[1].current_segment_line == 0 ) ? vdc[1].vdc_data[BYR].w : ( vdc[1].y_scroll + 1 );

			/* Draw VDC #0 background layer */
			pce_refresh_line( 0, vdc[0].current_segment_line, 0, drawn[0], temp_buffer[0]);

			/* Draw VDC #0 sprite layer */
			if(vdc[0].vdc_data[CR].w & CR_SB)
			{
				pce_refresh_sprites(device->machine, 0, vdc[0].current_segment_line, drawn[0], temp_buffer[0]);
			}

			/* Draw VDC #1 background layer */
			pce_refresh_line( 1, vdc[1].current_segment_line, 1, drawn[1], temp_buffer[1]);

			/* Draw VDC #1 sprite layer */
			if ( vdc[1].vdc_data[CR].w & CR_SB )
			{
				pce_refresh_sprites(device->machine, 1, vdc[1].current_segment_line, drawn[1], temp_buffer[1]);
			}

			line_buffer = BITMAP_ADDR16( vce.bmp, vce.current_bitmap_line, 86 );
			/* Combine the output of both VDCs */
			for( i = 0; i < 512; i++ )
			{
				int cur_prio = vpc.prio_map[i];

				if ( vpc.vpc_prio[cur_prio].vdc0_enabled )
				{
					if ( vpc.vpc_prio[cur_prio].vdc1_enabled )
					{
						switch( vpc.vpc_prio[cur_prio].prio )
						{
						case 0:	/* BG1 SP1 BG0 SP0 */
							if ( drawn[0][i] )
							{
								line_buffer[i] = temp_buffer[0][i];
							}
							else if ( drawn[1][i] )
							{
								line_buffer[i] = temp_buffer[1][i];
							}
							break;
						case 1:	/* BG1 BG0 SP1 SP0 */
							if ( drawn[0][i] )
							{
								if ( drawn[0][i] > 1 )
								{
									line_buffer[i] = temp_buffer[0][i];
								}
								else
								{
									if ( drawn[1][i] > 1 )
									{
										line_buffer[i] = temp_buffer[1][i];
									}
									else
									{
										line_buffer[i] = temp_buffer[0][i];
									}
								}
							}
							else if ( drawn[1][i] )
							{
								line_buffer[i] = temp_buffer[1][i];
							}
							break;
						case 2: /* BG1 + SP1 => SP1
                                   BG0 + SP1 => BG0
                                   BG0 + BG1 => BG0
                                   BG0 + SP0 => SP0
                                   BG1 + SP0 => BG1
                                   SP0 + SP1 => SP0 */
							if ( drawn[0][i] )
							{
								if ( drawn[0][i] > 1 )
								{
									if ( drawn[1][i] == 1 )
									{
										line_buffer[i] = temp_buffer[1][i];
									}
									else
									{
										line_buffer[i] = temp_buffer[0][i];
									}
								}
								else
								{
									line_buffer[i] = temp_buffer[0][i];
								}
							}
							else if ( drawn[1][i] )
							{
								line_buffer[i] = temp_buffer[1][i];
							}
							break;
						}
					}
					else
					{
						if ( drawn[0][i] )
						{
							line_buffer[i] = temp_buffer[0][i];
						}
					}
				}
				else
				{
					if ( vpc.vpc_prio[cur_prio].vdc1_enabled )
					{
						if ( drawn[1][i] )
						{
							line_buffer[i] = temp_buffer[1][i];
						}
					}
				}
			}
		}
	}
	else
	{
		/* We are in one of the blanking areas */
		draw_black_line(device->machine, vce.current_bitmap_line );
	}

	/* bump current scanline */
	vce.current_bitmap_line = ( vce.current_bitmap_line + 1 ) % VDC_LPF;
	vdc_advance_line(device->machine, 0 );
	vdc_advance_line(device->machine, 1 );
}

static void vdc_advance_line(running_machine *machine, int which)
{
	int ret = 0;

	vdc[which].curline += 1;
	vdc[which].current_segment_line += 1;
	vdc[which].raster_count += 1;

	if ( vdc[which].satb_countdown )
	{
		vdc[which].satb_countdown -= 1;
		if ( vdc[which].satb_countdown == 0 )
		{
			if ( vdc[which].vdc_data[DCR].w & DCR_DSC )
			{
				vdc[which].status |= VDC_DS;	/* set satb done flag */
				ret = 1;
			}
		}
	}

	if ( vce.current_bitmap_line == 0 )
	{
		vdc[which].current_segment = STATE_VSW;
		vdc[which].current_segment_line = 0;
		vdc[which].vblank_triggered = 0;
		vdc[which].curline = 0;
	}

	if ( STATE_VSW == vdc[which].current_segment && vdc[which].current_segment_line >= ( vdc[which].vdc_data[VPR].b.l & 0x1F ) )
	{
		vdc[which].current_segment = STATE_VDS;
		vdc[which].current_segment_line = 0;
	}

	if ( STATE_VDS == vdc[which].current_segment && vdc[which].current_segment_line >= vdc[which].vdc_data[VPR].b.h )
	{
		vdc[which].current_segment = STATE_VDW;
		vdc[which].current_segment_line = 0;
		vdc[which].raster_count = 0x40;
	}

	if ( STATE_VDW == vdc[which].current_segment && vdc[which].current_segment_line > ( vdc[which].vdc_data[VDW].w & 0x01FF ) )
	{
		vdc[which].current_segment = STATE_VCR;
		vdc[which].current_segment_line = 0;

		/* Generate VBlank interrupt, sprite DMA */
		vdc[which].vblank_triggered = 1;
		if ( vdc[which].vdc_data[CR].w & CR_VR )
		{
			vdc[which].status |= VDC_VD;
			ret = 1;
		}

		/* do VRAM > SATB DMA if the enable bit is set or the DVSSR reg. was written to */
		if( ( vdc[which].vdc_data[DCR].w & DCR_DSR ) || vdc[which].dvssr_write )
		{
			int i;

			vdc[which].dvssr_write = 0;

			for( i = 0; i < 256; i++ )
			{
				vdc[which].sprite_ram[i] = ( vdc[which].vram[ ( vdc[which].vdc_data[DVSSR].w << 1 ) + i * 2 + 1 ] << 8 ) | vdc[which].vram[ ( vdc[which].vdc_data[DVSSR].w << 1 ) + i * 2 ];
			}

			/* generate interrupt if needed */
			if ( vdc[which].vdc_data[DCR].w & DCR_DSC )
			{
				vdc[which].satb_countdown = 4;
			}
		}
	}

	if ( STATE_VCR == vdc[which].current_segment )
	{
		if ( vdc[which].current_segment_line >= 3 && vdc[which].current_segment_line >= vdc[which].vdc_data[VCR].b.l )
		{
			vdc[which].current_segment = STATE_VSW;
			vdc[which].current_segment_line = 0;
			vdc[which].curline = 0;
		}
	}

	/* generate interrupt on line compare if necessary */
	if ( vdc[which].raster_count == vdc[which].vdc_data[RCR].w && vdc[which].vdc_data[CR].w & CR_RC )
	{
		vdc[which].status |= VDC_RR;
		ret = 1;
	}

	/* handle frame events */
	if(vdc[which].curline == 261 && ! vdc[which].vblank_triggered )
	{

		vdc[which].vblank_triggered = 1;
		if(vdc[which].vdc_data[CR].w & CR_VR)
		{	/* generate IRQ1 if enabled */
			vdc[which].status |= VDC_VD;	/* set vblank flag */
			ret = 1;
		}

		/* do VRAM > SATB DMA if the enable bit is set or the DVSSR reg. was written to */
		if ( ( vdc[which].vdc_data[DCR].w & DCR_DSR ) || vdc[which].dvssr_write )
		{
			int i;

			vdc[which].dvssr_write = 0;
#ifdef MAME_DEBUG
			assert(((vdc[which].vdc_data[DVSSR].w<<1) + 512) <= 0x10000);
#endif
			for( i = 0; i < 256; i++ )
			{
				vdc[which].sprite_ram[i] = ( vdc[which].vram[ ( vdc[which].vdc_data[DVSSR].w << 1 ) + i * 2 + 1 ] << 8 ) | vdc[which].vram[ ( vdc[which].vdc_data[DVSSR].w << 1 ) + i * 2 ];
			}

			/* generate interrupt if needed */
			if(vdc[which].vdc_data[DCR].w & DCR_DSC)
			{
				vdc[which].satb_countdown = 4;
			}
		}
	}

	if (ret)
		cputag_set_input_line(machine, "maincpu", 0, HOLD_LINE);
}

VIDEO_START( pce )
{
	logerror("*** pce_vh_start\n");

	/* clear context */
	memset(&vdc, 0, sizeof(vdc));
	memset(&vce, 0, sizeof(vce));
	memset(&vpc, 0, sizeof(vpc));

	/* allocate VRAM */
	vdc[0].vram = auto_alloc_array(machine, UINT8, 0x10000);
	vdc[1].vram = auto_alloc_array(machine, UINT8, 0x10000);
	memset(vdc[0].vram, 0, 0x10000);
	memset(vdc[1].vram, 0, 0x10000);

	/* create display bitmap */
	vce.bmp = video_screen_auto_bitmap_alloc(machine->primary_screen);

	vdc[0].inc = 1;
	vdc[1].inc = 1;

	vpc_init(machine);
}


VIDEO_UPDATE( pce )
{
	/* copy our rendering buffer to the display */
	copybitmap (bitmap,vce.bmp,0,0,0,0,cliprect);
	return 0;
}

static void draw_black_line(running_machine *machine, int line)
{
	int i;

	/* our line buffer */
	UINT16 *line_buffer = BITMAP_ADDR16( vce.bmp, line, 0 );

	for( i=0; i< VDC_WPF; i++ )
		line_buffer[i] = get_black_pen( machine );
}

static void draw_overscan_line(int line)
{
	int i;

	/* Are we in greyscale mode or in color mode? */
	int color_base = vce.vce_control & 0x80 ? 512 : 0;

	/* our line buffer */
	UINT16 *line_buffer = BITMAP_ADDR16( vce.bmp, line, 0 );

	for ( i = 0; i < VDC_WPF; i++ )
		line_buffer[i] = color_base + vce.vce_data[0x100].w;
}

static void draw_sgx_overscan_line(int line)
{
	int i;

	/* Are we in greyscale mode or in color mode? */
	int color_base = vce.vce_control & 0x80 ? 512 : 0;

	/* our line buffer */
	UINT16 *line_buffer = BITMAP_ADDR16( vce.bmp, line, 0 );

	for ( i = 0; i < VDC_WPF; i++ )
		line_buffer[i] = color_base + vce.vce_data[0].w;
}

static void vram_write(int which, offs_t offset, UINT8 data)
{
	if(offset & 0x10000)
	{
		logerror("VDC #%d: Write to VRAM offset %05X\n", which, offset);
		return;
	}
	else
	{
		vdc[which].vram[offset] = data;
	}
}

static UINT8 vram_read(int which, offs_t offset)
{
	UINT8 temp;

	if(offset & 0x10000)
	{
		temp = vdc[which].vram[offset & 0xFFFF];
	}
	else
	{
		temp = vdc[which].vram[offset];
	}

	return temp;
}


static void vdc_w( running_machine *machine, int which, offs_t offset, UINT8 data )
{
	switch(offset&3)
	{
		case 0x00:	/* VDC register select */
			vdc[which].vdc_register = (data & 0x1F);
			break;

		case 0x02:	/* VDC data (LSB) */
			vdc[which].vdc_data[vdc[which].vdc_register].b.l = data;
			switch(vdc[which].vdc_register)
			{
				case VxR:	/* LSB of data to write to VRAM */
					vdc[which].vdc_latch = data;
					break;

			    case BYR:
					vdc[which].y_scroll=vdc[which].vdc_data[BYR].w;
					break;

				case HDR:
					vdc[which].physical_width = ((data & 0x003F) + 1) << 3;
					break;

				case VDW:
					vdc[which].physical_height &= 0xFF00;
					vdc[which].physical_height |= (data & 0xFF);
					vdc[which].physical_height &= 0x01FF;
					break;

				case LENR:
//                  logerror("LENR LSB = %02X\n", data);
					break;
				case SOUR:
//                  logerror("SOUR LSB = %02X\n", data);
					break;
				case DESR:
//                  logerror("DESR LSB = %02X\n", data);
					break;
			}
			break;

		case 0x03:	/* VDC data (MSB) */
			vdc[which].vdc_data[vdc[which].vdc_register].b.h = data;
			switch(vdc[which].vdc_register)
			{
				case VxR:	/* MSB of data to write to VRAM */
					vram_write(which, vdc[which].vdc_data[MAWR].w*2+0, vdc[which].vdc_latch);
					vram_write(which, vdc[which].vdc_data[MAWR].w*2+1, data);
					vdc[which].vdc_data[MAWR].w += vdc[which].inc;
					break;

				case CR:
					{
						static const unsigned char inctab[] = {1, 32, 64, 128};
						vdc[which].inc = inctab[(data >> 3) & 3];
					}
					break;

				case VDW:
					vdc[which].physical_height &= 0x00FF;
					vdc[which].physical_height |= (data << 8);
					vdc[which].physical_height &= 0x01FF;
					break;

				case DVSSR:
					/* Force VRAM <> SATB DMA for this frame */
					vdc[which].dvssr_write = 1;
					break;

				case BYR:
					vdc[which].y_scroll=vdc[which].vdc_data[BYR].w;
					break;

				case LENR:
					vdc_do_dma( machine, which );
					break;
				case SOUR:
//                  logerror("SOUR MSB = %02X\n", data);
					break;
				case DESR:
//                  logerror("DESR MSB = %02X\n", data);
					break;
			}
			break;
	}
}

static UINT8 vdc_r( running_machine *machine, int which, offs_t offset )
{
	int temp = 0;
	switch(offset & 3)
	{
		case 0x00:
			temp = vdc[which].status;
			vdc[which].status &= ~(VDC_VD | VDC_RR | VDC_CR | VDC_OR | VDC_DS);
			cputag_set_input_line(machine, "maincpu", 0, CLEAR_LINE);
			break;

		case 0x02:
			temp = vram_read(which, vdc[which].vdc_data[MARR].w * 2 + 0);
			break;

		case 0x03:
			temp = vram_read(which, vdc[which].vdc_data[MARR].w * 2 + 1);
			if ( vdc[which].vdc_register == VxR )
			{
				vdc[which].vdc_data[MARR].w += vdc[which].inc;
			}
			break;
	}
	return (temp);
}

WRITE8_HANDLER( vdc_0_w ) {	vdc_w( space->machine, 0, offset, data ); }
WRITE8_HANDLER( vdc_1_w ) {	vdc_w( space->machine, 1, offset, data ); }
READ8_HANDLER( vdc_0_r ) {	return vdc_r( space->machine, 0, offset ); }
READ8_HANDLER( vdc_1_r ) {	return vdc_r( space->machine, 1, offset ); }

PALETTE_INIT( vce )
{
	int i;

	for( i = 0; i < 512; i++ )
	{
		int r = (( i >> 3) & 7) << 5;
		int g = (( i >> 6) & 7) << 5;
		int b = (( i     ) & 7) << 5;
		int y = ( (  66 * r + 129 * g +  25 * b + 128) >> 8) +  16;
		palette_set_color_rgb(machine, i, r, g, b);
		palette_set_color_rgb(machine, 512+i, y, y, y);
	}
}

 READ8_HANDLER ( vce_r )
{
	int temp = 0xFF;
	switch(offset & 7)
	{
		case 0x04:	/* color table data (LSB) */
			temp = vce.vce_data[vce.vce_address.w].b.l;
			break;

		case 0x05:	/* color table data (MSB) */
			temp = vce.vce_data[vce.vce_address.w].b.h;
			temp |= 0xFE;
			vce.vce_address.w = (vce.vce_address.w + 1) & 0x01FF;
			break;
	}
	return (temp);
}


WRITE8_HANDLER ( vce_w )
{
	switch(offset & 7)
	{
		case 0x00:	/* control reg. */
			vce.vce_control = data;
			break;

		case 0x02:	/* color table address (LSB) */
			vce.vce_address.b.l = data;
			vce.vce_address.w &= 0x1FF;
			break;

		case 0x03:	/* color table address (MSB) */
			vce.vce_address.b.h = data;
			vce.vce_address.w &= 0x1FF;
			break;

		case 0x04:	/* color table data (LSB) */
			vce.vce_data[vce.vce_address.w].b.l = data;
			break;

		case 0x05:	/* color table data (MSB) */
			vce.vce_data[vce.vce_address.w].b.h = data & 0x01;

			/* bump internal address */
			vce.vce_address.w = (vce.vce_address.w + 1) & 0x01FF;
			break;
	}
}


static void pce_refresh_line(int which, int line, int external_input, UINT8 *drawn, UINT16 *line_buffer)
{
    static const int width_table[4] = {5, 6, 7, 7};

    int scroll_y = ( vdc[which].y_scroll & 0x01FF);
    int scroll_x = (vdc[which].vdc_data[BXR].w & 0x03FF);
    int nt_index;

    /* is virtual map 32 or 64 characters tall ? (256 or 512 pixels) */
    int v_line = (scroll_y) & (vdc[which].vdc_data[MWR].w & 0x0040 ? 0x1FF : 0x0FF);

    /* row within character */
    int v_row = (v_line & 7);

    /* row of characters in BAT */
    int nt_row = (v_line >> 3);

    /* virtual X size (# bits to shift) */
    int v_width =        width_table[(vdc[which].vdc_data[MWR].w >> 4) & 3];

    /* pointer to the name table (Background Attribute Table) in VRAM */
    UINT8 *bat = &(vdc[which].vram[nt_row << (v_width+1)]);

	/* Are we in greyscale mode or in color mode? */
	int color_base = vce.vce_control & 0x80 ? 512 : 0;

    int b0, b1, b2, b3;
    int i0, i1, i2, i3;
    int cell_pattern_index;
    int cell_palette;
    int x, c, i;

    /* character blanking bit */
    if(!(vdc[which].vdc_data[CR].w & CR_BB))
    {
		return;
    }
	else
	{
		int	pixel = 0;
		int phys_x = - ( scroll_x & 0x07 );

		for(i=0;i<(vdc[which].physical_width >> 3) + 1;i++)
		{
			nt_index = (i + (scroll_x >> 3)) & ((2 << (v_width-1))-1);
			nt_index *= 2;

			/* get name table data: */

			/* palette # = index from 0-15 */
			cell_palette = ( bat[nt_index + 1] >> 4 ) & 0x0F;

			/* This is the 'character number', from 0-0x0FFF         */
			/* then it is shifted left 4 bits to form a VRAM address */
			/* and one more bit to convert VRAM word offset to a     */
			/* byte-offset within the VRAM space                     */
			cell_pattern_index = ( ( ( bat[nt_index + 1] << 8 ) | bat[nt_index] ) & 0x0FFF) << 5;

			b0 = vram_read(which, (cell_pattern_index) + (v_row << 1) + 0x00);
			b1 = vram_read(which, (cell_pattern_index) + (v_row << 1) + 0x01);
			b2 = vram_read(which, (cell_pattern_index) + (v_row << 1) + 0x10);
			b3 = vram_read(which, (cell_pattern_index) + (v_row << 1) + 0x11);

			for(x=0;x<8;x++)
			{
				i0 = (b0 >> (7-x)) & 1;
				i1 = (b1 >> (7-x)) & 1;
				i2 = (b2 >> (7-x)) & 1;
				i3 = (b3 >> (7-x)) & 1;
				c = (cell_palette << 4 | i3 << 3 | i2 << 2 | i1 << 1 | i0);

				/* colour #0 always comes from palette #0 */
				if ( ! ( c & 0x0F ) )
					c &= 0x0F;

				if ( phys_x >= 0 && phys_x < vdc[which].physical_width )
				{
					drawn[ pixel ] = c ? 1 : 0;
					if ( c || ! external_input )
						line_buffer[ pixel ] = color_base + vce.vce_data[c].w;
					pixel++;
					if ( vdc[which].physical_width != 512 )
					{
						while ( pixel < ( ( ( phys_x + 1 ) * 512 ) / vdc[which].physical_width ) )
						{
							drawn[ pixel ] = c ? 1 : 0;
							if ( c || ! external_input )
								line_buffer[ pixel ] = color_base + vce.vce_data[c].w;
							pixel++;
						}
					}
				}
				phys_x += 1;
			}
		}
	}
}



static void conv_obj(int which, int i, int l, int hf, int vf, char *buf)
{
	int b0, b1, b2, b3, i0, i1, i2, i3, x;
	int xi;
	int tmp;

	l &= 0x0F;
	if(vf) l = (15 - l);

	tmp = l + ( i << 5);

	b0 = vram_read(which, (tmp + 0x00)<<1);
	b0 |= vram_read(which, ((tmp + 0x00)<<1)+1)<<8;
	b1 = vram_read(which, (tmp + 0x10)<<1);
	b1 |= vram_read(which, ((tmp + 0x10)<<1)+1)<<8;
	b2 = vram_read(which, (tmp + 0x20)<<1);
	b2 |= vram_read(which, ((tmp + 0x20)<<1)+1)<<8;
	b3 = vram_read(which, (tmp + 0x30)<<1);
	b3 |= vram_read(which, ((tmp + 0x30)<<1)+1)<<8;

	for(x=0;x<16;x++)
	{
		if(hf) xi = x; else xi = (15 - x);
		i0 = (b0 >> xi) & 1;
		i1 = (b1 >> xi) & 1;
		i2 = (b2 >> xi) & 1;
		i3 = (b3 >> xi) & 1;
		buf[x] = (i3 << 3 | i2 << 2 | i1 << 1 | i0);
	}
}

static void pce_refresh_sprites(running_machine *machine, int which, int line, UINT8 *drawn, UINT16 *line_buffer)
{
    int i;
	UINT8 sprites_drawn = 0;

	/* Are we in greyscale mode or in color mode? */
	int color_base = vce.vce_control & 0x80 ? 512 : 0;

	/* count up: Highest priority is Sprite 0 */
	for(i = 0; i < 64; i++)
	{
		static const int cgy_table[] = {16, 32, 64, 64};

		int obj_y = (vdc[which].sprite_ram[(i << 2) + 0] & 0x03FF) - 64;
		int obj_x = (vdc[which].sprite_ram[(i << 2) + 1] & 0x03FF) - 32;
		int obj_i = (vdc[which].sprite_ram[(i << 2) + 2] & 0x07FE);
		int obj_a = (vdc[which].sprite_ram[(i << 2) + 3]);
		int cgx   = (obj_a >> 8) & 1;   /* sprite width */
		int cgy   = (obj_a >> 12) & 3;  /* sprite height */
		int hf    = (obj_a >> 11) & 1;  /* horizontal flip */
		int vf    = (obj_a >> 15) & 1;  /* vertical flip */
		int palette = (obj_a & 0x000F);
		int priority = (obj_a >> 7) & 1;
		int obj_h = cgy_table[cgy];
		int obj_l = (line - obj_y);
		int cgypos;
		char buf[16];

		if ((obj_y == -64) || (obj_y > line)) continue;
		if ((obj_x == -32) || (obj_x >= vdc[which].physical_width)) continue;

		/* no need to draw an object that's ABOVE where we are. */
		if((obj_y + obj_h) < line) continue;

		/* If CGX is set, bit 0 of sprite pattern index is forced to 0 */
		if ( cgx )
			obj_i &= ~2;

		/* If CGY is set to 1, bit 1 of the sprite pattern index is forced to 0. */
		if ( cgy & 1 )
			obj_i &= ~4;

		/* If CGY is set to 2 or 3, bit 1 and 2 of the sprite pattern index are forced to 0. */
		if ( cgy & 2 )
			obj_i &= ~12;

		if (obj_l < obj_h)
		{

			sprites_drawn++;
			if(sprites_drawn > 16)
			{
				vdc[which].status |= VDC_OR;
				if(vdc[which].vdc_data[CR].w & CR_OV)
					cputag_set_input_line(machine, "maincpu", 0, ASSERT_LINE);
				continue;  /* Should cause an interrupt */
			}

			cgypos = (obj_l >> 4);
			if(vf) cgypos = ((obj_h - 1) >> 4) - cgypos;

			if(cgx == 0)
			{
				int x;
				int pixel_x = ( ( obj_x * 512 ) / vdc[which].physical_width );

				conv_obj(which, obj_i + (cgypos << 2), obj_l, hf, vf, buf);

				for(x = 0; x < 16; x++)
				{
					if(((obj_x + x) < (vdc[which].physical_width)) && ((obj_x + x) >= 0))
					{
						if ( buf[x] )
						 {
							if( drawn[pixel_x] < 2 )
							{
								if( priority || drawn[pixel_x] == 0 )
								{
									line_buffer[pixel_x] = color_base + vce.vce_data[0x100 + (palette << 4) + buf[x]].w;
									if ( vdc[which].physical_width != 512 )
									{
										int dp = 1;
										while ( pixel_x + dp < ( ( ( obj_x + x + 1 ) * 512 ) / vdc[which].physical_width ) )
										{
											drawn[pixel_x + dp] = i + 2;
											line_buffer[pixel_x + dp] = color_base + vce.vce_data[0x100 + (palette << 4) + buf[x]].w;
											dp++;
										}
									}
								}
								drawn[pixel_x] = i + 2;
							}
							/* Check for sprite #0 collision */
							else if (drawn[pixel_x] == 2)
							{
								if(vdc[which].vdc_data[CR].w & CR_CC)
									cputag_set_input_line(machine, "maincpu", 0, ASSERT_LINE);
								vdc[which].status |= VDC_CR;
							}
						}
					}
					if ( vdc[which].physical_width != 512 )
					{
						pixel_x = ( ( obj_x + x + 1 ) * 512 ) / vdc[which].physical_width;
					}
					else
					{
						pixel_x += 1;
					}
				}
			}
			else
			{
				int x;
				int pixel_x = ( ( obj_x * 512 ) / vdc[which].physical_width );

				conv_obj(which, obj_i + (cgypos << 2) + (hf ? 2 : 0), obj_l, hf, vf, buf);

				for(x = 0; x < 16; x++)
				{
					if(((obj_x + x) < (vdc[which].physical_width)) && ((obj_x + x) >= 0))
					{
						if ( buf[x] )
						{
							if( drawn[pixel_x] < 2 )
							{
								if ( priority || drawn[pixel_x] == 0 )
								{
									line_buffer[pixel_x] = color_base + vce.vce_data[0x100 + (palette << 4) + buf[x]].w;
									if ( vdc[which].physical_width != 512 )
									{
										int dp = 1;
										while ( pixel_x + dp < ( ( ( obj_x + x + 1 ) * 512 ) / vdc[which].physical_width ) )
										{
											drawn[pixel_x + dp] = i + 2;
											line_buffer[pixel_x + dp] = color_base + vce.vce_data[0x100 + (palette << 4) + buf[x]].w;
											dp++;
										}
									}
								}
								drawn[pixel_x] = i + 2;
							}
							/* Check for sprite #0 collision */
							else if ( drawn[pixel_x] == 2 )
							{
								if(vdc[which].vdc_data[CR].w & CR_CC)
									cputag_set_input_line(machine, "maincpu", 0, ASSERT_LINE);
								vdc[which].status |= VDC_CR;
							}
						}
					}
					if ( vdc[which].physical_width != 512 )
					{
						pixel_x = ( ( obj_x + x + 1 ) * 512 ) / vdc[which].physical_width;
					}
					else
					{
						pixel_x += 1;
					}
				}

				/* 32 pixel wide sprites are counted as 2 sprites and the right half
                   is only drawn if there are 2 open slots.
                */
				sprites_drawn++;
				if( sprites_drawn > 16 )
				{
					vdc[which].status |= VDC_OR;
					if(vdc[which].vdc_data[CR].w&CR_OV)
						cputag_set_input_line(machine, "maincpu", 0, ASSERT_LINE);
				}
				else
				{
					conv_obj(which, obj_i + (cgypos << 2) + (hf ? 0 : 2), obj_l, hf, vf, buf);
					for(x = 0; x < 16; x++)
					{
						if(((obj_x + 0x10 + x) < (vdc[which].physical_width)) && ((obj_x + 0x10 + x) >= 0))
						{
							if ( buf[x] )
							{
								if( drawn[pixel_x] < 2 )
								{
									if( priority || drawn[pixel_x] == 0 )
									{
										line_buffer[pixel_x] = color_base + vce.vce_data[0x100 + (palette << 4) + buf[x]].w;
										if ( vdc[which].physical_width != 512 )
										{
											int dp = 1;
											while ( pixel_x + dp < ( ( ( obj_x + x + 17 ) * 512 ) / vdc[which].physical_width ) )
											{
												drawn[pixel_x + dp] = i + 2;
												line_buffer[pixel_x + dp] = color_base + vce.vce_data[0x100 + (palette << 4) + buf[x]].w;
												dp++;
											}
										}
									}
									drawn[pixel_x] = i + 2;
								}
								/* Check for sprite #0 collision */
								else if ( drawn[pixel_x] == 2 )
								 {
									if(vdc[which].vdc_data[CR].w & CR_CC)
										cputag_set_input_line(machine, "maincpu", 0, ASSERT_LINE);
									vdc[which].status |= VDC_CR;
								}
							}
						}
						if ( vdc[which].physical_width != 512 )
						{
							pixel_x = ( ( obj_x + x + 17 ) * 512 ) / vdc[which].physical_width;
						}
						else
						{
							pixel_x += 1;
						}
					}
				}
			}
		}
	}
}

static void vdc_do_dma(running_machine *machine, int which)
{
	int src = vdc[which].vdc_data[SOUR].w;
	int dst = vdc[which].vdc_data[DESR].w;
	int len = vdc[which].vdc_data[LENR].w;

	int did = (vdc[which].vdc_data[DCR].w >> 3) & 1;
	int sid = (vdc[which].vdc_data[DCR].w >> 2) & 1;
	int dvc = (vdc[which].vdc_data[DCR].w >> 1) & 1;

	do {
		UINT8 l, h;

		l = vram_read(which, src<<1);
		h = vram_read(which, (src<<1) + 1);

		vram_write(which, dst<<1,l);
		vram_write(which, 1+(dst<<1),h);

		if(sid) src = (src - 1) & 0xFFFF;
		else	src = (src + 1) & 0xFFFF;

		if(did) dst = (dst - 1) & 0xFFFF;
		else	dst = (dst + 1) & 0xFFFF;

		len = (len - 1) & 0xFFFF;

	} while (len != 0xFFFF);

	vdc[which].status |= VDC_DV;
	vdc[which].vdc_data[SOUR].w = src;
	vdc[which].vdc_data[DESR].w = dst;
	vdc[which].vdc_data[LENR].w = len;
	if(dvc)
	{
		cputag_set_input_line(machine, "maincpu", 0, ASSERT_LINE);
	}

}

static void vpc_update_prio_map( void )
{
	int i;

	for( i = 0; i < 512; i++ )
	{
		vpc.prio_map[i] = 0;
		if ( vpc.window1.w < 0x40 || i > vpc.window1.w )
		{
			vpc.prio_map[i] |= 1;
		}
		if ( vpc.window2.w < 0x40 || i > vpc.window2.w )
		{
			vpc.prio_map[i] |= 2;
		}
	}
}

WRITE8_HANDLER( vpc_w )
{
//if ( offset < 2 )
//printf("VPC write offset %02X, data %02X\n", offset, data );
	switch( offset & 0x07 )
	{
	case 0x00:	/* Priority register #0 */
		vpc.priority.b.l = data;
		vpc.vpc_prio[0].prio = ( data >> 2 ) & 3;
		vpc.vpc_prio[0].vdc0_enabled = data & 1;
		vpc.vpc_prio[0].vdc1_enabled = data & 2;
		vpc.vpc_prio[1].prio = ( data >> 6 ) & 3;
		vpc.vpc_prio[1].vdc0_enabled = data & 0x10;
		vpc.vpc_prio[1].vdc1_enabled = data & 0x20;
		break;
	case 0x01:	/* Priority register #1 */
		vpc.priority.b.h = data;
		vpc.vpc_prio[2].prio = ( data >> 2 ) & 3;
		vpc.vpc_prio[2].vdc0_enabled = data & 1;
		vpc.vpc_prio[2].vdc1_enabled = data & 2;
		vpc.vpc_prio[3].prio = ( data >> 6 ) & 3;
		vpc.vpc_prio[3].vdc0_enabled = data & 0x10;
		vpc.vpc_prio[3].vdc1_enabled = data & 0x20;
		break;
	case 0x02:	/* Window 1 LSB */
		vpc.window1.b.l = data;
		vpc_update_prio_map();
		break;
	case 0x03:	/* Window 1 MSB */
		vpc.window1.b.h = data & 3;
		vpc_update_prio_map();
		break;
	case 0x04:	/* Window 2 LSB */
		vpc.window2.b.l = data;
		vpc_update_prio_map();
		break;
	case 0x05:	/* Window 2 MSB */
		vpc.window2.b.h = data & 3;
		vpc_update_prio_map();
		break;
	case 0x06:	/* VDC I/O select */
		vpc.vdc_select = data & 1;
		break;
	}
}

READ8_HANDLER( vpc_r )
{
	UINT8 data = 0;
	switch( offset & 0x07 )
	{
	case 0x00:  /* Priority register #0 */
		data = vpc.priority.b.l;
		break;
	case 0x01:  /* Priority register #1 */
		data = vpc.priority.b.h;
		break;
	case 0x02:  /* Window 1 LSB */
		data = vpc.window1.b.l;
		break;
	case 0x03:  /* Window 1 MSB; high bits are 0 or 1? */
		data = vpc.window1.b.h;
		break;
	case 0x04:  /* Window 2 LSB */
		data = vpc.window2.b.l;
		break;
	case 0x05:  /* Window 2 MSB; high bits are 0 or 1? */
		data = vpc.window2.b.h;
		break;
	}
	return data;
}

static void vpc_init( running_machine *machine )
{
	const address_space *space = cputag_get_address_space(machine, "maincpu", ADDRESS_SPACE_PROGRAM);
	vpc_w( space, 0, 0x11 );
	vpc_w( space, 1, 0x11 );
	vpc.window1.w = 0;
	vpc.window2.w = 0;
	vpc.vdc_select = 0;
}

WRITE8_HANDLER( sgx_vdc_w )
{
	if ( vpc.vdc_select )
	{
		vdc_1_w( space, offset, data );
	}
	else
	{
		vdc_0_w( space, offset, data );
	}
}

READ8_HANDLER( sgx_vdc_r )
{
	return ( vpc.vdc_select ) ? vdc_1_r( space, offset ) : vdc_0_r( space, offset );
}

