/***************************************************************************

    Lock-On video hardware

***************************************************************************/

#include "emu.h"
#include "includes/lockon.h"
#include "cpu/nec/nec.h"
#include "video/resnet.h"

#define CURSOR_XPOS			168
#define CURSOR_YPOS			239
#define FRAMEBUFFER_MAX_X	431
#define FRAMEBUFFER_MAX_Y	(UINT32)((FRAMEBUFFER_CLOCK / (float)(FRAMEBUFFER_MAX_X-1)) / ((float)PIXEL_CLOCK/(HTOTAL*VTOTAL)))


/*************************************
 *
 *  HD46505S-2 CRT Controller
 *
 *************************************/

READ16_HANDLER( lockon_crtc_r )
{
	return 0xffff;
}

WRITE16_HANDLER( lockon_crtc_w )
{
#if 0
	data &= 0xff;

	if (offset == 0)
	{
		switch (data)
		{
			case 0x00: mame_printf_debug("Horizontal Total         "); break;
			case 0x01: mame_printf_debug("Horizontal displayed     "); break;
			case 0x02: mame_printf_debug("Horizontal sync position "); break;
			case 0x03: mame_printf_debug("Horizontal sync width    "); break;
			case 0x04: mame_printf_debug("Vertical total           "); break;
			case 0x05: mame_printf_debug("Vertical total adjust    "); break;
			case 0x06: mame_printf_debug("Vertical displayed       "); break;
			case 0x07: mame_printf_debug("Vertical sync position   "); break;
			case 0x08: mame_printf_debug("Interlace mode           "); break;
			case 0x09: mame_printf_debug("Max. scan line address   "); break;
			case 0x0a: mame_printf_debug("Cursror start            "); break;
			case 0x0b: mame_printf_debug("Cursor end               "); break;
			case 0x0c: mame_printf_debug("Start address (h)        "); break;
			case 0x0d: mame_printf_debug("Start address (l)        "); break;
			case 0x0e: mame_printf_debug("Cursor (h)               "); break;
			case 0x0f: mame_printf_debug("Cursor (l)               "); break;
			case 0x10: mame_printf_debug("Light pen (h))           "); break;
			case 0x11: mame_printf_debug("Light pen (l)            "); break;
		}
	}
	else if (offset == 1)
	{
		mame_printf_debug("0x%.2x, (%d)\n",data, data);
	}
#endif
}

static TIMER_CALLBACK( cursor_callback )
{
	lockon_state *state = machine.driver_data<lockon_state>();

	if (state->m_main_inten)
		device_set_input_line_and_vector(state->m_maincpu, 0, HOLD_LINE, 0xff);

	state->m_cursor_timer->adjust(machine.primary_screen->time_until_pos(CURSOR_YPOS, CURSOR_XPOS));
}

/*************************************
 *
 *  Palette decoding
 *
 *************************************/

static const res_net_info lockon_net_info =
{
	RES_NET_VCC_5V | RES_NET_VBIAS_5V | RES_NET_VIN_TTL_OUT,
	{
		{RES_NET_AMP_NONE, 560, 0, 5, {4700, 2200, 1000, 470, 220}},
		{RES_NET_AMP_NONE, 560, 0, 5, {4700, 2200, 1000, 470, 220}},
		{RES_NET_AMP_NONE, 560, 0, 5, {4700, 2200, 1000, 470, 220}}
	}
};

static const res_net_info lockon_pd_net_info =
{
	RES_NET_VCC_5V | RES_NET_VBIAS_5V | RES_NET_VIN_TTL_OUT,
	{
		{RES_NET_AMP_NONE, 560, 580, 5, {4700, 2200, 1000, 470, 220}},
		{RES_NET_AMP_NONE, 560, 580, 5, {4700, 2200, 1000, 470, 220}},
		{RES_NET_AMP_NONE, 560, 580, 5, {4700, 2200, 1000, 470, 220}}
	}
};

PALETTE_INIT( lockon )
{
	int i;

	for (i = 0; i < 1024; ++i)
	{
		UINT8 r, g, b;
		UINT8 p1 = color_prom[i];
		UINT8 p2 = color_prom[i + 0x400];

		if (p2 & 0x80)
		{
			r = compute_res_net((p2 >> 2) & 0x1f, 0, &lockon_net_info);
			g = compute_res_net(((p1 >> 5) & 0x7) | (p2 & 3) << 3, 1, &lockon_net_info);
			b = compute_res_net((p1 & 0x1f), 2, &lockon_net_info);
		}
		else
		{
			r = compute_res_net((p2 >> 2) & 0x1f, 0, &lockon_pd_net_info);
			g = compute_res_net(((p1 >> 5) & 0x7) | (p2 & 3) << 3, 1, &lockon_pd_net_info);
			b = compute_res_net((p1 & 0x1f), 2, &lockon_pd_net_info);
		}

		palette_set_color(machine, i, MAKE_RGB(r, g, b));
	}
}


/*************************************
 *
 *  Character tilemap handling
 *
 *************************************/

WRITE16_HANDLER( lockon_char_w )
{
	lockon_state *state = space->machine().driver_data<lockon_state>();
	state->m_char_ram[offset] = data;
	tilemap_mark_tile_dirty(state->m_tilemap, offset);
}

static TILE_GET_INFO( get_lockon_tile_info )
{
	lockon_state *state = machine.driver_data<lockon_state>();
	UINT32 tileno = state->m_char_ram[tile_index] & 0x03ff;
	UINT32 col = (state->m_char_ram[tile_index] >> 10) & 0x3f;

	col = (col & 0x1f) + (col & 0x20 ? 64 : 0);
	SET_TILE_INFO(0, tileno, col, 0);
}


/*******************************************************************************************

  Scene tilemap hardware

*******************************************************************************************/

WRITE16_HANDLER( lockon_scene_h_scr_w )
{
	lockon_state *state = space->machine().driver_data<lockon_state>();
	state->m_scroll_h = data & 0x1ff;
}

WRITE16_HANDLER( lockon_scene_v_scr_w )
{
	lockon_state *state = space->machine().driver_data<lockon_state>();
	state->m_scroll_v = data & 0x81ff;
}

static void scene_draw( running_machine &machine )
{
	lockon_state *state = machine.driver_data<lockon_state>();
	UINT32 y;

	/* 3bpp characters */
	const UINT8 *const gfx1 = machine.region("gfx2")->base();
	const UINT8 *const gfx2 = gfx1 + 0x10000;
	const UINT8 *const gfx3 = gfx1 + 0x20000;
	const UINT8 *const clut = gfx1 + 0x30000;

	for (y = 0; y < FRAMEBUFFER_MAX_Y; ++y)
	{
		UINT32 x;
		UINT32 d0 = 0, d1 = 0, d2 = 0;
		UINT32 colour = 0;
		UINT32 y_offs;
		UINT32 x_offs;
		UINT32 y_gran;
		UINT16 *bmpaddr;
		UINT32 ram_mask = 0x7ff;

		y_offs = (y + state->m_scroll_v) & 0x1ff;

		/* Clamp - stops tilemap wrapping when screen is rotated */
		if (BIT(state->m_scroll_v, 15) && y_offs & 0x100)
			ram_mask = 0x7;

		x_offs = (state->m_scroll_h - 8) & 0x1ff;
		y_gran = y_offs & 7;

		if (x_offs & 7)
		{
			UINT32 tileidx;
			UINT16 addr = ((y_offs & ~7) << 3) + ((x_offs >> 3) & 0x3f);
			UINT16 ram_val = state->m_scene_ram[addr & ram_mask];

			colour = (clut[ram_val & 0x7fff] & 0x3f) << 3;
			tileidx = ((ram_val & 0x0fff) << 3) + y_gran;

			d0 = *(gfx1 + tileidx);
			d1 = *(gfx2 + tileidx);
			d2 = *(gfx3 + tileidx);
		}

		bmpaddr = BITMAP_ADDR16(state->m_back_buffer, y, 0);

		for (x = 0; x < FRAMEBUFFER_MAX_X; ++x)
		{
			UINT32 x_gran = (x_offs & 7) ^ 7;
			UINT32 col;

			if (!(x_offs & 7))
			{
				UINT32 tileidx;
				UINT16 addr = ((y_offs & ~7) << 3) + ((x_offs >> 3) & 0x3f);
				UINT16 ram_val = state->m_scene_ram[addr & ram_mask];

				colour = (clut[ram_val & 0x7fff] & 0x3f) << 3;
				tileidx = ((ram_val & 0x0fff) << 3) + y_gran;

				d0 = *(gfx1 + tileidx);
				d1 = *(gfx2 + tileidx);
				d2 = *(gfx3 + tileidx);
			}

			col = colour
				 | (((d2 >> x_gran) & 1) << 2)
				 | (((d1 >> x_gran) & 1) << 1)
				 | ( (d0 >> x_gran) & 1);

			*bmpaddr++ = 0xa00 + col;

			x_offs = (x_offs + 1) & 0x1ff;
		}

	}
}

/*******************************************************************************************

    Ground Hardware

    Each framebuffer line corresponds to a three word entry in ground RAM,
    starting from offset 3:

       FEDCBA9876543210
    0 |.............xxx|  Tile line (A0-A2 GFX ROMs)
      |...........xx...|  Tile index (A6-A5 GFX ROMs)
      |........xxx.....|  ROM lut address (A6-A4)
      |.xxxxxxx........|  ROM lut address (A13-A7)
      |x...............|  /Line enable

    1 |........xxxxxxxx|  TZ2213 value
      |xxxxxxxx........|  X offset

    2 |........xxxxxxxx|  TZ2213 DX
      |.......x........|  Carry
      |x...............|  End of list marker

    An 8-bit ground control register controls the following:

       76543210
      |......xx|  LUT ROM A15-A14
      |....xx..|  LUT ROM select
      |..xx....|  CLUT ROM A13-A12
      |.x......|  GFX ROM A15, CLUT ROM A14
      |x.......|  GFX ROM bank select (always 0 - only 1 bank is present)

 *******************************************************************************************/

WRITE16_HANDLER( lockon_ground_ctrl_w )
{
	lockon_state *state = space->machine().driver_data<lockon_state>();
	state->m_ground_ctrl = data & 0xff;
}

static TIMER_CALLBACK( bufend_callback )
{
	lockon_state *state = machine.driver_data<lockon_state>();
	device_set_input_line_and_vector(state->m_ground, 0, HOLD_LINE, 0xff);
	device_set_input_line(state->m_object, NEC_INPUT_LINE_POLL, ASSERT_LINE);
}

/* Get data for a each 8x8x3 ground tile */
#define GET_GROUND_DATA()                                                                \
{                                                                                        \
	UINT32 gfx_a4_3  = (ls163 & 0xc) << 1;                                               \
	UINT32 lut_addr  = lut_address + ((ls163 >> 4) & 0xf);                               \
	UINT32 gfx_a14_7 = lut_rom[lut_addr] << 7;                                           \
	clut_addr = (lut_rom[lut_addr] << 4) | clut_a14_12 | clut_a4_3 | (ls163 & 0xc) >> 2; \
	gfx_addr  = gfx_a15 | gfx_a14_7 | gfx_a6_5 | gfx_a4_3 | gfx_a2_0;                    \
	pal = (clut_rom[clut_addr] << 3);                                                    \
	rom_data1 = gfx_rom[gfx_addr];                                                       \
	rom_data2 = gfx_rom[gfx_addr + 0x10000];                                             \
	rom_data3 = gfx_rom[gfx_addr + 0x20000];                                             \
}

static void ground_draw( running_machine &machine )
{
	lockon_state *state = machine.driver_data<lockon_state>();

	/* ROM pointers */
	const UINT8 *const gfx_rom  = machine.region("gfx4")->base();
	const UINT8 *const lut_rom  = gfx_rom + 0x30000 + ((state->m_ground_ctrl >> 2) & 0x3 ? 0x10000 : 0);
	const UINT8 *const clut_rom = gfx_rom + 0x50000;

	UINT32 lut_a15_14	= (state->m_ground_ctrl & 0x3) << 14;
	UINT32 clut_a14_12 = (state->m_ground_ctrl & 0x70) << 8;
	UINT32 gfx_a15 = (state->m_ground_ctrl & 0x40) << 9;
	UINT32 offs = 3;
	UINT32 y;

	/* TODO: Clean up and emulate CS of GFX ROMs? */
	for (y = 0; y < FRAMEBUFFER_MAX_Y; ++y)
	{
		UINT16 *bmpaddr = BITMAP_ADDR16(state->m_back_buffer, y, 0);
		UINT8 ls163;
		UINT32 clut_addr;
		UINT32 gfx_addr;
		UINT8 rom_data1 = 0;
		UINT8 rom_data2 = 0;
		UINT8 rom_data3 = 0;
		UINT32 pal = 0;
		UINT32 x;

		/* Draw this line? */
		if (!(state->m_ground_ram[offs] & 0x8000))
		{
			UINT32 gfx_a2_0  =  state->m_ground_ram[offs] & 0x0007;
			UINT32 gfx_a6_5  = (state->m_ground_ram[offs] & 0x0018) << 2;
			UINT32 clut_a4_3 = (state->m_ground_ram[offs] & 0x0018) >> 1;
			UINT8	tz2213_x  = state->m_ground_ram[offs + 1] & 0xff;
			UINT8	tz2213_dx = state->m_ground_ram[offs + 2] & 0xff;

			UINT32 lut_address = lut_a15_14 + ((state->m_ground_ram[offs] & 0x7fe0) >> 1);
			UINT32 cy = state->m_ground_ram[offs + 2] & 0x0100;
			UINT32 color;
			UINT32 gpbal2_0_prev;

			ls163 = state->m_ground_ram[offs + 1] >> 8;

			gpbal2_0_prev = ((ls163 & 3) << 1) | BIT(tz2213_x, 7);

			if (gpbal2_0_prev)
				GET_GROUND_DATA();

			for (x = 0; x < FRAMEBUFFER_MAX_X; x++)
			{
				UINT32 tz2213_cy;
				UINT32 gpbal2_0 = ((ls163 & 3) << 1) | BIT(tz2213_x, 7);

				/* Stepped into a new tile? */
				if (gpbal2_0 < gpbal2_0_prev)
					GET_GROUND_DATA();

				gpbal2_0_prev = gpbal2_0;

				color = pal;
				color += (rom_data1 >> gpbal2_0) & 0x1;
				color += ((rom_data2 >> gpbal2_0) & 0x1) << 1;
				color += ((rom_data3 >> gpbal2_0) & 0x1) << 2;

				*bmpaddr++ = 0x800 + color;

				/* Update the counters */
				tz2213_cy = (UINT8)tz2213_dx > (UINT8)~(tz2213_x);
				tz2213_x = (tz2213_x + tz2213_dx);

				/* Carry? */
				if (tz2213_cy || cy)
					++ls163;
			}
		}

		offs += 3;

		/* End of list marker */
		if (state->m_ground_ram[offs + 2] & 0x8000)
		{
			state->m_bufend_timer->adjust(attotime::from_hz(FRAMEBUFFER_CLOCK) * (FRAMEBUFFER_MAX_X * y));
		}
	}
}


/*******************************************************************************************

    Object hardware

    Customs (4 each, 1 per bitplane)
    TZA118 - Scaling
    TZ4203 - Objects with integrated line buffer.

       FEDCBA9876543210
    0 |......xxxxxxxxxx|  Y position
      |....xx..........|  Object Y size
      |..xx............|  Object X size
      |.x..............|  Y flip
      |x...............|  X flip
    1 |........xxxxxxxx|  X/Y scale
      |.xxxxxxx........|  Colour
      |x...............|  End of list marker
    2 |xxxxxxxxxxxxxxxx|  Chunk ROM address
    3 |.....xxxxxxxxxxx|  X position

*******************************************************************************************/

/*
   There's logic to prevent shadow pixels from being drawn against the scene tilemap,
   so that shadows don't appear against the sky.
*/
#define DRAW_OBJ_PIXEL(COLOR)                            \
do {                                                     \
	if (px < FRAMEBUFFER_MAX_X)							 \
	if (COLOR != 0xf)                                    \
	{                                                    \
		UINT8 clr = state->m_obj_pal_ram[(pal << 4) + COLOR];     \
		UINT16 *pix = (line + px);						 \
		if (!(clr == 0xff && ((*pix & 0xe00) == 0xa00))) \
			*pix = 0x400 + clr;			 \
	}                                                    \
	px = (px + 1) & 0x7ff;                               \
} while(0)

static void objects_draw( running_machine &machine )
{
	UINT32 offs;
	lockon_state *state = machine.driver_data<lockon_state>();

	const UINT8  *const romlut = machine.region("user1")->base();
	const UINT16 *const chklut = (UINT16*)machine.region("user2")->base();
	const UINT8  *const gfxrom = machine.region("gfx5")->base();
	const UINT8  *const sproms = machine.region("proms")->base() + 0x800;

	for (offs = 0; offs < state->m_objectram_size; offs += 4)
	{
		UINT32 y;
		UINT32 xpos;
		UINT32 ypos;
		UINT32 xsize;
		UINT32 ysize;
		UINT32 xflip;
		UINT32 yflip;
		UINT32 scale;
		UINT32 pal;
		UINT32 lines;
		UINT32 opsta;
		UINT32 opsta15_8;

		/* Retrieve the object attributes */
		ypos	= state->m_object_ram[offs] & 0x03ff;
		xpos	= state->m_object_ram[offs + 3] & 0x07ff;
		ysize	= (state->m_object_ram[offs] >> 10) & 0x3;
		xsize	= (state->m_object_ram[offs] >> 12) & 0x3;
		yflip	= BIT(state->m_object_ram[offs], 14);
		xflip	= BIT(state->m_object_ram[offs], 15);
		scale	= state->m_object_ram[offs + 1] & 0xff;
		pal = (state->m_object_ram[offs + 1] >> 8) & 0x7f;
		opsta	= state->m_object_ram[offs + 2];

		if (state->m_iden)
		{
			state->m_obj_pal_ram[(pal << 4) + state->m_obj_pal_addr] = state->m_obj_pal_latch;
			break;
		}

		/* How many lines will this sprite occupy? The PAL @ IC154 knows... */
		lines = scale >> (3 - ysize);

		opsta15_8 = opsta & 0xff00;

		/* Account for line buffering */
		ypos -=1;

		for (y = 0; y < FRAMEBUFFER_MAX_Y; y++)
		{
			UINT32 cy = (y + ypos) & 0x3ff;
			UINT32 optab;
			UINT32 lutaddr;
			UINT32 tile;
			UINT8	cnt;
			UINT32 yidx;
			UINT16 *line = BITMAP_ADDR16(state->m_back_buffer, y, 0);
			UINT32 px = xpos;

			/* Outside the limits? */
			if (cy & 0x300)
				continue;

			if ((cy  & 0xff) >= lines)
				break;

			lutaddr = (scale & 0x80 ? 0x8000 : 0) | ((scale & 0x7f) << 8) | (cy & 0xff);
			optab = romlut[lutaddr] & 0x7f;

			if (yflip)
				optab ^= 7;

			yidx = (optab & 7);

			/* Now calculate the lower 7-bits of the LUT ROM address. PAL @ IC157 does this */
			cnt = (optab >> 3) * (1 << xsize);

			if (xflip)
				cnt ^= 7 >> (3 - xsize);
			if (yflip)
				cnt ^= (0xf >> (3 - ysize)) * (1 << xsize);

			cnt = (cnt + (opsta & 0xff));

			/* Draw! */
			for (tile = 0; tile < (1 << xsize); ++tile)
			{
				UINT16 sc;
				UINT16 scl;
				UINT32 x;
				UINT32 tileaddr;
				UINT16 td0, td1, td2, td3;
				UINT32 j;
				UINT32 bank;

				scl = scale & 0x7f;
				tileaddr = (chklut[opsta15_8 + cnt] & 0x7fff);
				bank = ((tileaddr >> 12) & 3) * 0x40000;
				tileaddr = bank + ((tileaddr & ~0xf000) << 3);

				if (xflip)
					--cnt;
				else
					++cnt;

				/* Draw two 8x8 tiles */
				for (j = 0; j < 2; ++j)
				{
					/* Get tile data */
					UINT32 tileadd = tileaddr + (0x20000 * (j ^ xflip));

					/* Retrieve scale values from PROMs */
					sc = sproms[(scl << 4) + (tile * 2) + j];

					/* Data from ROMs is inverted */
					td3 = gfxrom[tileadd + yidx] ^ 0xff;
					td2 = gfxrom[tileadd + 0x8000 + yidx] ^ 0xff;
					td1 = gfxrom[tileadd + 0x10000 + yidx] ^ 0xff;
					td0 = gfxrom[tileadd + 0x18000 + yidx] ^ 0xff;

					if (scale & 0x80)
					{
						for (x = 0; x < 8; ++x)
						{
							UINT8 col;
							UINT8 pix = x;

							if (!xflip)
								pix ^= 0x7;

							col = BIT(td0, pix)
								| (BIT(td1, pix) << 1)
								| (BIT(td2, pix) << 2)
								| (BIT(td3, pix) << 3);

							DRAW_OBJ_PIXEL(col);

							if (BIT(sc, x))
								DRAW_OBJ_PIXEL(col);
						}
					}
					else
					{
						for (x = 0; x < 8; ++x)
						{
							UINT8 col;
							UINT8 pix = x;

							if (BIT(sc, x))
							{
								if (!xflip)
									pix ^= 0x7;

								col = BIT(td0, pix)
									| (BIT(td1, pix) << 1)
									| (BIT(td2, pix) << 2)
									| (BIT(td3, pix) << 3);

								DRAW_OBJ_PIXEL(col);
							}
						}
					}
				}
			}
		}

		/* Check for the end of list marker */
		if (state->m_object_ram[offs + 1] & 0x8000)
			return;
	}
}

/* The mechanism used by the object CPU to update the object ASICs palette RAM */
WRITE16_HANDLER( lockon_tza112_w )
{
	lockon_state *state = space->machine().driver_data<lockon_state>();

	if (state->m_iden)
	{
		state->m_obj_pal_latch = data & 0xff;
		state->m_obj_pal_addr = offset & 0xf;
		objects_draw(space->machine());
	}
}

READ16_HANDLER( lockon_obj_4000_r )
{
	lockon_state *state = space->machine().driver_data<lockon_state>();

	device_set_input_line(state->m_object, NEC_INPUT_LINE_POLL, CLEAR_LINE);
	return 0xffff;
}

WRITE16_HANDLER( lockon_obj_4000_w )
{
	lockon_state *state = space->machine().driver_data<lockon_state>();
	state->m_iden = data & 1;
}


/*******************************************************************************************

    Frame buffer rotation hardware

      FEDCBA9876543210
    0 |........xxxxxxxx|  X start address
      |.......x........|  Direction
    1 |........xxxxxxxx|  TZ2213 IC65 value
      |.......x........|  TZ2213 IC65 /enable
    2 |........xxxxxxxx|  TZ2213 IC65 delta
    3 |........xxxxxxxx|  TZ2213 IC106 delta
      |.......x........|  TZ2213 IC106 enable
    4 |.......xxxxxxxxx|  Y start address
    5 |........xxxxxxxx|  TZ2213 IC66 value
    6 |........xxxxxxxx|  TZ2213 IC66 delta
      |.......x........|  TZ2213 IC65 /enable
    7 |........xxxxxxxx|  TZ2213 IC107 delta
      |.......x........|  TZ2213 /enable
      |......x.........|  Direction

*******************************************************************************************/

WRITE16_HANDLER( lockon_fb_clut_w )
{
	rgb_t color;

	color = palette_get_color(space->machine(), 0x300 + (data & 0xff));
	palette_set_color(space->machine(), 0x400 + offset, color);
}

/* Rotation control register */
WRITE16_HANDLER( lockon_rotate_w )
{
	lockon_state *state = space->machine().driver_data<lockon_state>();

	switch (offset & 7)
	{
		case 0: state->m_xsal  = data & 0x1ff;	break;
		case 1: state->m_x0ll  = data & 0xff;	break;
		case 2: state->m_dx0ll = data & 0x1ff;	break;
		case 3: state->m_dxll  = data & 0x1ff;	break;

		case 4: state->m_ysal  = data & 0x1ff;	break;
		case 5: state->m_y0ll  = data & 0xff;	break;
		case 6: state->m_dy0ll =	data & 0x1ff;	break;
		case 7: state->m_dyll  = data & 0x3ff;	break;
	}
}

#define INCREMENT(ACC, CNT)              \
do {                                     \
	carry = (UINT8)d##ACC > (UINT8)~ACC; \
	ACC += d##ACC;                       \
	if (carry) ++CNT;                    \
} while(0)

#define DECREMENT(ACC, CNT)              \
do {                                     \
	carry = (UINT8)d##ACC > (UINT8)ACC;  \
	ACC -= d##ACC;                       \
	if (carry) --CNT;                    \
} while(0)

static void rotate_draw( running_machine &machine, bitmap_t *bitmap, const rectangle *cliprect )
{
	lockon_state *state = machine.driver_data<lockon_state>();
	UINT32 y;

	/* Counters */
	UINT32 cxy = state->m_xsal & 0xff;
	UINT32 cyy = state->m_ysal & 0x1ff;

	/* Accumulator values and deltas */
	UINT8 axy  = state->m_x0ll & 0xff;
	UINT8 daxy = state->m_dx0ll & 0xff;
	UINT8 ayy  = state->m_y0ll & 0xff;
	UINT8 dayy = state->m_dy0ll & 0xff;
	UINT8 dayx = state->m_dyll & 0xff;
	UINT8 daxx = state->m_dxll & 0xff;

	UINT32 xy_up = BIT(state->m_xsal, 8);
	UINT32 yx_up = BIT(state->m_dyll, 9);
	UINT32 axx_en  = !BIT(state->m_dxll, 8);
	UINT32 ayx_en  = !BIT(state->m_dyll, 8);
	UINT32 axy_en  = !BIT(state->m_dx0ll, 8);
	UINT32 ayy_en  = !BIT(state->m_dy0ll, 8);

	for (y = 0; y <= cliprect->max_y; ++y)
	{
		UINT32 carry;
		UINT16 *dst = BITMAP_ADDR16(bitmap, y, 0);
		UINT32 x;

		UINT32 cx = cxy;
		UINT32 cy = cyy;

		UINT8 axx = axy;
		UINT8 ayx = ayy;

		for (x = 0; x <= cliprect->max_x; ++x)
		{
			cx &= 0x1ff;
			cy &= 0x1ff;

			*dst++ = *BITMAP_ADDR16(state->m_front_buffer, cy, cx);

			if (axx_en)
				INCREMENT(axx, cx);
			else
				++cx;

			if (ayx_en)
			{
				if (yx_up)
					INCREMENT(ayx, cy);
				else
					DECREMENT(ayx, cy);
			}
			else
			{
				if (yx_up)
					++cy;
				else
					--cy;
			}
		}

		if (axy_en)
		{
			if (xy_up)
				INCREMENT(axy, cxy);
			else
				DECREMENT(axy, cxy);
		}
		else
		{
			if (xy_up)
				++cxy;
			else
				--cxy;
		}

		if (ayy_en)
			INCREMENT(ayy, cyy);
		else
			++cyy;

		cxy &= 0xff;
		cyy &= 0x1ff;
	}
}


/*******************************************************************************************

    HUD Drawing Hardware

    A sprite layer that uses 8x8x1bpp tiles to form bigger sprites

    0  |.......xxxxxxxxx|  Y Position
       |xxxxxxx.........|  Code
    1  |.......xxxxxxxxx|  X Position
       |....xxx.........|  Colour
       |.xxx............|  Sprite width (0=8, 1=16, 2=24, 3=32 pixels, etc.)
       |x...............|  End of list marker

*******************************************************************************************/

static void hud_draw( running_machine &machine, bitmap_t *bitmap, const rectangle *cliprect )
{
	lockon_state *state = machine.driver_data<lockon_state>();
	UINT8	*tile_rom = machine.region("gfx3")->base();
	UINT32 offs;

	for (offs = 0x0; offs <= state->m_hudram_size; offs += 2)
	{
		UINT32 y;
		UINT32 y_pos;
		UINT32 x_pos;
		UINT32 y_size;
		UINT32 x_size;
		UINT32 layout;
		UINT16 colour;
		UINT32 code;
		UINT32 rom_a12_7;

		/* End of sprite list marker */
		if (state->m_hud_ram[offs + 1] & 0x8000)
			break;

		y_pos	= state->m_hud_ram[offs] & 0x1ff;
		x_pos	= state->m_hud_ram[offs + 1] & 0x1ff;
		x_size = (state->m_hud_ram[offs + 1] >> 12) & 7;
		code	= (state->m_hud_ram[offs] >> 9) & 0x7f;
		colour = 0x200 + ((state->m_hud_ram[offs + 1] >> 9) & 7);
		layout = (code >> 5) & 3;

		rom_a12_7 = (code & 0xfe) << 6;

		/* Account for line buffering */
		y_pos -= 1;

		if (layout == 3)
			y_size = 32;
		else if (layout == 2)
			y_size = 16;
		else
			y_size = 8;

		for (y = cliprect->min_y; y <= cliprect->max_y; ++y)
		{
			UINT32 xt;
			UINT32 cy;

			cy = y_pos + y;

			if (cy < 0x200)
				continue;

			if ((cy & 0xff) == y_size)
				break;

			for (xt = 0; xt <= x_size; ++xt)
			{
				UINT32 rom_a6_3;
				UINT32 px;
				UINT8	gfx_strip;

				if (layout == 3)
					rom_a6_3 = (BIT(cy, 4) << 3) | (BIT(cy, 3) << 2) | (BIT(xt, 1) << 1) | BIT(xt, 0);
				else if (layout == 2)
					rom_a6_3 = ((BIT(code, 0) << 3) | (BIT(xt, 1) << 2) | (BIT(cy, 3) << 1) | (BIT(xt, 0)));
				else
					rom_a6_3 = (BIT(code, 0) << 3) | (xt & 7);

				rom_a6_3 <<= 3;

				/* Get tile data */
				gfx_strip = tile_rom[rom_a12_7 | rom_a6_3 | (cy & 7)];

				if (gfx_strip == 0)
					continue;

				/* Draw */
				for (px = 0; px < 8; ++px)
				{
					UINT32 x = x_pos + (xt << 3) + px;

					if (x <= cliprect->max_x)
					{
						UINT16 *dst = BITMAP_ADDR16(bitmap, y, x);

						if (BIT(gfx_strip, px ^ 7) && *dst > 255)
							*dst = colour;
					}
				}
			}
		}
	}
}


/*************************************
 *
 *  Driver video handlers
 *
 *************************************/

VIDEO_START( lockon )
{
	lockon_state *state = machine.driver_data<lockon_state>();

	state->m_tilemap = tilemap_create(machine, get_lockon_tile_info, tilemap_scan_rows, 8, 8, 64, 32);
	tilemap_set_transparent_pen(state->m_tilemap, 0);

	/* Allocate the two frame buffers for rotation */
	state->m_back_buffer = auto_bitmap_alloc(machine, 512, 512, BITMAP_FORMAT_INDEXED16);
	state->m_front_buffer = auto_bitmap_alloc(machine, 512, 512, BITMAP_FORMAT_INDEXED16);

	/* 2kB of object ASIC palette RAM */
	state->m_obj_pal_ram = auto_alloc_array(machine, UINT8, 2048);

	/* Timer for ground display list callback */
	state->m_bufend_timer = machine.scheduler().timer_alloc(FUNC(bufend_callback));

	/* Timer for the CRTC cursor pulse */
	state->m_cursor_timer = machine.scheduler().timer_alloc(FUNC(cursor_callback));
	state->m_cursor_timer->adjust(machine.primary_screen->time_until_pos(CURSOR_YPOS, CURSOR_XPOS));

	state->save_item(NAME(*state->m_back_buffer));
	state->save_item(NAME(*state->m_front_buffer));
	state->save_pointer(NAME(state->m_obj_pal_ram), 2048);
}

SCREEN_UPDATE( lockon )
{
	lockon_state *state = screen->machine().driver_data<lockon_state>();

	/* If screen output is disabled, fill with black */
	if (!BIT(state->m_ctrl_reg, 7))
	{
		bitmap_fill(bitmap, cliprect, get_black_pen(screen->machine()));
		return 0;
	}

	/* Scan out the frame buffer in rotated order */
	rotate_draw(screen->machine(), bitmap, cliprect);

	/* Draw the character tilemap */
	tilemap_draw(bitmap, cliprect, state->m_tilemap, 0, 0);

	/* Draw the HUD */
	hud_draw(screen->machine(), bitmap, cliprect);

	return 0;
}

SCREEN_EOF( lockon )
{
	lockon_state *state = machine.driver_data<lockon_state>();

	/* Swap the frame buffers */
	bitmap_t *tmp = state->m_front_buffer;
	state->m_front_buffer = state->m_back_buffer;
	state->m_back_buffer = tmp;

	/* Draw the frame buffer layers */
	scene_draw(machine);
	ground_draw(machine);
	objects_draw(machine);
}
