summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/video/exidy440.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mame/video/exidy440.c')
-rw-r--r--src/mame/video/exidy440.c463
1 files changed, 463 insertions, 0 deletions
diff --git a/src/mame/video/exidy440.c b/src/mame/video/exidy440.c
new file mode 100644
index 00000000000..eceade70355
--- /dev/null
+++ b/src/mame/video/exidy440.c
@@ -0,0 +1,463 @@
+/***************************************************************************
+
+ Exidy 440 video system
+
+***************************************************************************/
+
+#include "driver.h"
+#include "exidy440.h"
+
+#define SPRITE_COUNT 40
+
+
+/* globals */
+UINT8 *exidy440_scanline;
+UINT8 *exidy440_imageram;
+UINT8 exidy440_firq_vblank;
+UINT8 exidy440_firq_beam;
+UINT8 topsecex_yscroll;
+
+/* local allocated storage */
+static UINT8 exidy440_latched_x;
+static UINT8 *local_videoram;
+static UINT8 *local_paletteram;
+
+/* local variables */
+static UINT8 firq_enable;
+static UINT8 firq_select;
+static UINT8 palettebank_io;
+static UINT8 palettebank_vis;
+
+/* function prototypes */
+static void exidy440_update_firq(void);
+
+
+
+/*************************************
+ *
+ * Initialize the video system
+ *
+ *************************************/
+
+VIDEO_START( exidy440 )
+{
+ /* reset the system */
+ firq_enable = 0;
+ firq_select = 0;
+ palettebank_io = 0;
+ palettebank_vis = 0;
+ exidy440_firq_vblank = 0;
+ exidy440_firq_beam = 0;
+
+ /* reset Top Secret scroll position */
+ topsecex_yscroll = 0;
+
+ /* allocate a buffer for VRAM */
+ local_videoram = auto_malloc(256 * 256 * 2);
+ memset(local_videoram, 0, 256 * 256 * 2);
+
+ /* allocate a buffer for palette RAM */
+ local_paletteram = auto_malloc(512 * 2);
+ memset(local_paletteram, 0, 512 * 2);
+}
+
+
+
+/*************************************
+ *
+ * Video RAM read/write
+ *
+ *************************************/
+
+READ8_HANDLER( exidy440_videoram_r )
+{
+ UINT8 *base = &local_videoram[(*exidy440_scanline * 256 + offset) * 2];
+
+ /* combine the two pixel values into one byte */
+ return (base[0] << 4) | base[1];
+}
+
+
+WRITE8_HANDLER( exidy440_videoram_w )
+{
+ UINT8 *base = &local_videoram[(*exidy440_scanline * 256 + offset) * 2];
+
+ /* expand the two pixel values into two bytes */
+ base[0] = (data >> 4) & 15;
+ base[1] = data & 15;
+}
+
+
+
+/*************************************
+ *
+ * Palette RAM read/write
+ *
+ *************************************/
+
+READ8_HANDLER( exidy440_paletteram_r )
+{
+ return local_paletteram[palettebank_io * 512 + offset];
+}
+
+
+WRITE8_HANDLER( exidy440_paletteram_w )
+{
+ /* update palette ram in the I/O bank */
+ local_paletteram[palettebank_io * 512 + offset] = data;
+
+ /* if we're modifying the active palette, change the color immediately */
+ if (palettebank_io == palettebank_vis)
+ {
+ int word;
+
+ /* combine two bytes into a word */
+ offset = palettebank_vis * 512 + (offset & 0x1fe);
+ word = (local_paletteram[offset] << 8) + local_paletteram[offset + 1];
+
+ /* extract the 5-5-5 RGB colors */
+ palette_set_color_rgb(Machine, offset / 2, pal5bit(word >> 10), pal5bit(word >> 5), pal5bit(word >> 0));
+ }
+}
+
+
+
+/*************************************
+ *
+ * Horizontal/vertical positions
+ *
+ *************************************/
+
+READ8_HANDLER( exidy440_horizontal_pos_r )
+{
+ /* clear the FIRQ on a read here */
+ exidy440_firq_beam = 0;
+ exidy440_update_firq();
+
+ /* according to the schems, this value is only latched on an FIRQ
+ * caused by collision or beam */
+ return exidy440_latched_x;
+}
+
+
+READ8_HANDLER( exidy440_vertical_pos_r )
+{
+ int result;
+
+ /* according to the schems, this value is latched on any FIRQ
+ * caused by collision or beam, ORed together with CHRCLK,
+ * which probably goes off once per scanline; for now, we just
+ * always return the current scanline */
+ result = video_screen_get_vpos(0);
+ return (result < 255) ? result : 255;
+}
+
+
+
+/*************************************
+ *
+ * Sprite RAM handler
+ *
+ *************************************/
+
+WRITE8_HANDLER( exidy440_spriteram_w )
+{
+ video_screen_update_partial(0, video_screen_get_vpos(0));
+ spriteram[offset] = data;
+}
+
+
+
+/*************************************
+ *
+ * Interrupt and I/O control regs
+ *
+ *************************************/
+
+WRITE8_HANDLER( exidy440_control_w )
+{
+ int oldvis = palettebank_vis;
+
+ /* extract the various bits */
+ exidy440_bank_select(data >> 4);
+ firq_enable = (data >> 3) & 1;
+ firq_select = (data >> 2) & 1;
+ palettebank_io = (data >> 1) & 1;
+ palettebank_vis = data & 1;
+
+ /* update the FIRQ in case we enabled something */
+ exidy440_update_firq();
+
+ /* if we're swapping palettes, change all the colors */
+ if (oldvis != palettebank_vis)
+ {
+ int i;
+
+ /* pick colors from the visible bank */
+ offset = palettebank_vis * 512;
+ for (i = 0; i < 256; i++, offset += 2)
+ {
+ /* extract a word and the 5-5-5 RGB components */
+ int word = (local_paletteram[offset] << 8) + local_paletteram[offset + 1];
+ palette_set_color_rgb(Machine, i, pal5bit(word >> 10), pal5bit(word >> 5), pal5bit(word >> 0));
+ }
+ }
+}
+
+
+WRITE8_HANDLER( exidy440_interrupt_clear_w )
+{
+ /* clear the VBLANK FIRQ on a write here */
+ exidy440_firq_vblank = 0;
+ exidy440_update_firq();
+}
+
+
+
+/*************************************
+ *
+ * Interrupt generators
+ *
+ *************************************/
+
+void exidy440_update_firq(void)
+{
+ if (exidy440_firq_vblank || (firq_enable && exidy440_firq_beam))
+ cpunum_set_input_line(0, 1, ASSERT_LINE);
+ else
+ cpunum_set_input_line(0, 1, CLEAR_LINE);
+}
+
+
+INTERRUPT_GEN( exidy440_vblank_interrupt )
+{
+ /* set the FIRQ line on a VBLANK */
+ exidy440_firq_vblank = 1;
+ exidy440_update_firq();
+}
+
+
+
+/*************************************
+ *
+ * IRQ callback handlers
+ *
+ *************************************/
+
+static TIMER_CALLBACK( beam_firq_callback )
+{
+ /* generate the interrupt, if we're selected */
+ if (firq_select && firq_enable)
+ {
+ exidy440_firq_beam = 1;
+ exidy440_update_firq();
+ }
+
+ /* round the x value to the nearest byte */
+ param = (param + 1) / 2;
+
+ /* latch the x value; this convolution comes from the read routine */
+ exidy440_latched_x = (param + 3) ^ 2;
+}
+
+
+static TIMER_CALLBACK( collide_firq_callback )
+{
+ /* generate the interrupt, if we're selected */
+ if (!firq_select && firq_enable)
+ {
+ exidy440_firq_beam = 1;
+ exidy440_update_firq();
+ }
+
+ /* round the x value to the nearest byte */
+ param = (param + 1) / 2;
+
+ /* latch the x value; this convolution comes from the read routine */
+ exidy440_latched_x = (param + 3) ^ 2;
+}
+
+
+
+/*************************************
+ *
+ * Sprite drawing
+ *
+ *************************************/
+
+static void draw_sprites(running_machine *machine, mame_bitmap *bitmap, const rectangle *cliprect, int scroll_offset)
+{
+ int i;
+
+ /* get a pointer to the palette to look for collision flags */
+ UINT8 *palette = &local_paletteram[palettebank_vis * 512];
+
+ /* start the count high for topsecret, which doesn't use collision flags */
+ int count = exidy440_topsecret ? 128 : 0;
+
+ /* draw the sprite images, checking for collisions along the way */
+ UINT8 *sprite = spriteram + (SPRITE_COUNT - 1) * 4;
+
+ for (i = 0; i < SPRITE_COUNT; i++, sprite -= 4)
+ {
+ int image = (~sprite[3] & 0x3f);
+ int xoffs = (~((sprite[1] << 8) | sprite[2]) & 0x1ff);
+ int yoffs = (~sprite[0] & 0xff) + 1;
+ int x, y, sy;
+ UINT8 *src;
+
+ /* skip if out of range */
+ if (yoffs < cliprect->min_y || yoffs >= cliprect->max_y + 16)
+ continue;
+
+ /* get a pointer to the source image */
+ src = &exidy440_imageram[image * 128];
+
+ /* account for large positive offsets meaning small negative values */
+ if (xoffs >= 0x1ff - 16)
+ xoffs -= 0x1ff;
+
+ /* loop over y */
+ sy = yoffs + scroll_offset;
+ for (y = 0; y < 16; y++, yoffs--, sy--)
+ {
+ /* wrap at the top and bottom of the screen */
+ if (sy >= EXIDY440_VBSTART)
+ sy -= (EXIDY440_VBSTART - EXIDY440_VBEND);
+ else if (sy < EXIDY440_VBEND)
+ sy += (EXIDY440_VBSTART - EXIDY440_VBEND);
+
+ /* stop if we get before the current scanline */
+ if (yoffs < cliprect->min_y)
+ break;
+
+ /* only draw scanlines that are in this cliprect */
+ if (yoffs <= cliprect->max_y)
+ {
+ UINT8 *old = &local_videoram[sy * 512 + xoffs];
+ int currx = xoffs;
+
+ /* loop over x */
+ for (x = 0; x < 8; x++, old += 2)
+ {
+ int ipixel = *src++;
+ int left = ipixel & 0xf0;
+ int right = (ipixel << 4) & 0xf0;
+ int pen;
+
+ /* left pixel */
+ if (left && EXIDY440_HBEND >= 0 && currx < EXIDY440_HBSTART)
+ {
+ /* combine with the background */
+ pen = left | old[0];
+ *BITMAP_ADDR16(bitmap, yoffs, currx) = machine->pens[pen];
+
+ /* check the collisions bit */
+ if ((palette[2 * pen] & 0x80) && count++ < 128)
+ timer_set(video_screen_get_time_until_pos(0, yoffs, currx), currx, collide_firq_callback);
+ }
+ currx++;
+
+ /* right pixel */
+ if (right && EXIDY440_HBEND >= 0 && currx < EXIDY440_HBSTART)
+ {
+ /* combine with the background */
+ pen = right | old[1];
+ *BITMAP_ADDR16(bitmap, yoffs, currx) = machine->pens[pen];
+
+ /* check the collisions bit */
+ if ((palette[2 * pen] & 0x80) && count++ < 128)
+ timer_set(video_screen_get_time_until_pos(0, yoffs, currx), currx, collide_firq_callback);
+ }
+ currx++;
+ }
+ }
+ else
+ src += 8;
+ }
+ }
+}
+
+
+
+/*************************************
+ *
+ * Core refresh routine
+ *
+ *************************************/
+
+static void update_screen(running_machine *machine, mame_bitmap *bitmap, const rectangle *cliprect, int scroll_offset)
+{
+ int y, sy;
+
+ /* draw any dirty scanlines from the VRAM directly */
+ sy = scroll_offset + cliprect->min_y;
+ for (y = cliprect->min_y; y <= cliprect->max_y; y++, sy++)
+ {
+ /* wrap at the bottom of the screen */
+ if (sy >= EXIDY440_VBSTART)
+ sy -= (EXIDY440_VBSTART - EXIDY440_VBEND);
+
+ /* draw line */
+ draw_scanline8(bitmap, 0, y, (EXIDY440_HBSTART - EXIDY440_HBEND), &local_videoram[sy * 512], machine->pens, -1);
+ }
+
+ /* draw the sprites */
+ draw_sprites(machine, bitmap, cliprect, scroll_offset);
+}
+
+
+
+/*************************************
+ *
+ * Standard screen refresh callback
+ *
+ *************************************/
+
+VIDEO_UPDATE( exidy440 )
+{
+ /* redraw the screen */
+ if (exidy440_topsecret)
+ update_screen(machine, bitmap, cliprect, topsecex_yscroll);
+ else
+ update_screen(machine, bitmap, cliprect, 0);
+
+ return 0;
+}
+
+
+
+/*************************************
+ *
+ * Standard screen refresh callback
+ *
+ *************************************/
+
+VIDEO_EOF( exidy440 )
+{
+ /* generate an interrupt once/frame for the beam */
+ if (!exidy440_topsecret)
+ {
+ attotime time;
+ attoseconds_t increment;
+ int beamx, beamy;
+ int i;
+
+ beamx = ((input_port_4_r(0) & 0xff) * (EXIDY440_HBSTART - EXIDY440_HBEND)) >> 8;
+ beamy = ((input_port_5_r(0) & 0xff) * (EXIDY440_VBSTART - EXIDY440_VBEND)) >> 8;
+
+ /* The timing of this FIRQ is very important. The games look for an FIRQ
+ and then wait about 650 cycles, clear the old FIRQ, and wait a
+ very short period of time (~130 cycles) for another one to come in.
+ From this, it appears that they are expecting to get beams over
+ a 12 scanline period, and trying to pick roughly the middle one.
+ This is how it is implemented. */
+ increment = attotime_to_attoseconds(video_screen_get_scan_period(0));
+ time = attotime_sub(video_screen_get_time_until_pos(0, beamy, beamx), attotime_make(0, increment * 6));
+ for (i = 0; i <= 12; i++)
+ {
+ timer_set(time, beamx, beam_firq_callback);
+ time = attotime_add(time, attotime_make(0, increment));
+ }
+ }
+}