summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/video
diff options
context:
space:
mode:
author David Haywood <28625134+DavidHaywood@users.noreply.github.com>2019-11-04 03:50:48 +0000
committer ajrhacker <ajrhacker@users.noreply.github.com>2019-11-03 22:50:48 -0500
commitd24fc57a95d5d817dc7d04e1df0f266157647199 (patch)
tree55369ca28ec56ea86bd6174a1609c653f6c055db /src/mame/video
parent36bfba32f7ca7320d949277b3ecf9b15a5f1ac18 (diff)
Plug & Play / Elan EU3A05 / EU3A14 splitting up, identifying of common features etc. (#5849)
* elan eu3a05 / eu3a14 - identify some common bits (nw) * some elan refactoring baed on current knowledge / standards (nw) * elan refactoring (nw) * elan splitting / refactoring (nw) * validate (nw) * note used areas (nw) * register observations (nw) * another observation (nw) * add the sprite double feature (nw) * rendering tweaks (nw) * rendering improvements for air blaster 3d stages (nw) * refactoring (nw)
Diffstat (limited to 'src/mame/video')
-rw-r--r--src/mame/video/elan_eu3a05commonvid.cpp87
-rw-r--r--src/mame/video/elan_eu3a05commonvid.h35
-rw-r--r--src/mame/video/elan_eu3a05vid.cpp596
-rw-r--r--src/mame/video/elan_eu3a05vid.h80
4 files changed, 798 insertions, 0 deletions
diff --git a/src/mame/video/elan_eu3a05commonvid.cpp b/src/mame/video/elan_eu3a05commonvid.cpp
new file mode 100644
index 00000000000..19221f3c463
--- /dev/null
+++ b/src/mame/video/elan_eu3a05commonvid.cpp
@@ -0,0 +1,87 @@
+// license:BSD-3-Clause
+// copyright-holders:David Haywood
+
+#include "emu.h"
+#include "elan_eu3a05commonvid.h"
+
+/*
+ Common video functions shared by Elan EU3A05 and EU3A14 CPU types
+*/
+
+DEFINE_DEVICE_TYPE(ELAN_EU3A05_COMMONVID, elan_eu3a05commonvid_device, "elan_eu3a05commonvid", "Elan EU3A05/EU3A14 Common Video")
+
+elan_eu3a05commonvid_device::elan_eu3a05commonvid_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : elan_eu3a05commonvid_device(mconfig, ELAN_EU3A05_COMMONVID, tag, owner, clock)
+{
+}
+
+elan_eu3a05commonvid_device::elan_eu3a05commonvid_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, type, tag, owner, clock)
+ , m_palette(*this, finder_base::DUMMY_TAG)
+{
+}
+
+
+void elan_eu3a05commonvid_device::device_start()
+{
+}
+
+void elan_eu3a05commonvid_device::device_reset()
+{
+}
+
+double elan_eu3a05commonvid_device::hue2rgb(double p, double q, double t)
+{
+ if (t < 0) t += 1;
+ if (t > 1) t -= 1;
+ if (t < 1 / 6.0f) return p + (q - p) * 6 * t;
+ if (t < 1 / 2.0f) return q;
+ if (t < 2 / 3.0f) return p + (q - p) * (2 / 3.0f - t) * 6;
+ return p;
+}
+
+
+void elan_eu3a05commonvid_device::update_pen(int pen)
+{
+ uint16_t dat = m_palram[(pen*2)] << 8;
+ dat |= m_palram[(pen*2)+1];
+
+ // llll lsss ---h hhhh
+ int l_raw = (dat & 0xf800) >> 11;
+ int sl_raw = (dat & 0x0700) >> 8;
+ int h_raw = (dat & 0x001f) >> 0;
+
+ double l = (double)l_raw / 31.0f;
+ double s = (double)sl_raw / 7.0f;
+ double h = (double)h_raw / 24.0f;
+
+ double r, g, b;
+
+ if (s == 0) {
+ r = g = b = l; // greyscale
+ } else {
+ double q = l < 0.5f ? l * (1 + s) : l + s - l * s;
+ double p = 2 * l - q;
+ r = hue2rgb(p, q, h + 1/3.0f);
+ g = hue2rgb(p, q, h);
+ b = hue2rgb(p, q, h - 1/3.0f);
+ }
+
+ int r_real = r * 255.0f;
+ int g_real = g * 255.0f;
+ int b_real = b * 255.0f;
+
+ m_palette->set_pen_color(pen, r_real, g_real, b_real);
+}
+
+
+READ8_MEMBER(elan_eu3a05commonvid_device::palette_r)
+{
+ return m_palram[offset];
+}
+
+WRITE8_MEMBER(elan_eu3a05commonvid_device::palette_w)
+{
+ m_palram[offset] = data;
+ update_pen(offset/2);
+}
diff --git a/src/mame/video/elan_eu3a05commonvid.h b/src/mame/video/elan_eu3a05commonvid.h
new file mode 100644
index 00000000000..beb7d5bb7d7
--- /dev/null
+++ b/src/mame/video/elan_eu3a05commonvid.h
@@ -0,0 +1,35 @@
+// license:BSD-3-Clause
+// copyright-holders:David Haywood
+
+#ifndef MAME_VIDEO_ELAN_EU3A05COMMONVID_H
+#define MAME_VIDEO_ELAN_EU3A05COMMONVID_H
+
+#include "emupal.h"
+
+class elan_eu3a05commonvid_device : public device_t
+{
+public:
+ elan_eu3a05commonvid_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+ elan_eu3a05commonvid_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
+
+ template <typename T> void set_palette(T &&tag) { m_palette.set_tag(std::forward<T>(tag)); }
+ void set_entries(int entries) { m_palram.resize(entries*2); }
+
+ DECLARE_READ8_MEMBER(palette_r);
+ DECLARE_WRITE8_MEMBER(palette_w);
+
+protected:
+ // device-level overrides
+ virtual void device_start() override;
+ virtual void device_reset() override;
+
+private:
+ required_device<palette_device> m_palette;
+ std::vector<uint8_t> m_palram;
+ void update_pen(int pen);
+ double hue2rgb(double p, double q, double t);
+};
+
+DECLARE_DEVICE_TYPE(ELAN_EU3A05_COMMONVID, elan_eu3a05commonvid_device)
+
+#endif // MAME_VIDEO_ELAN_EU3A05COMMONVID_H
diff --git a/src/mame/video/elan_eu3a05vid.cpp b/src/mame/video/elan_eu3a05vid.cpp
new file mode 100644
index 00000000000..d21d324ebb9
--- /dev/null
+++ b/src/mame/video/elan_eu3a05vid.cpp
@@ -0,0 +1,596 @@
+// license:BSD-3-Clause
+// copyright-holders:David Haywood
+
+#include "emu.h"
+#include "elan_eu3a05vid.h"
+
+DEFINE_DEVICE_TYPE(ELAN_EU3A05_VID, elan_eu3a05vid_device, "elan_eu3a05vid", "Elan EU3A05 Video")
+
+// map(0x0600, 0x3dff).ram().share("vram");
+// map(0x3e00, 0x3fff).ram().share("spriteram");
+
+elan_eu3a05vid_device::elan_eu3a05vid_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : elan_eu3a05commonvid_device(mconfig, ELAN_EU3A05_VID, tag, owner, clock),
+ m_cpu(*this, finder_base::DUMMY_TAG),
+ m_bank(*this, finder_base::DUMMY_TAG)
+{
+}
+
+void elan_eu3a05vid_device::device_start()
+{
+ elan_eu3a05commonvid_device::device_start();
+
+ save_item(NAME(m_vidctrl));
+ save_item(NAME(m_tile_gfxbase_lo_data));
+ save_item(NAME(m_tile_gfxbase_hi_data));
+ save_item(NAME(m_sprite_gfxbase_lo_data));
+ save_item(NAME(m_sprite_gfxbase_hi_data));
+ save_item(NAME(m_tile_scroll));
+ save_item(NAME(m_splitpos));
+}
+
+void elan_eu3a05vid_device::device_reset()
+{
+ elan_eu3a05commonvid_device::device_reset();
+
+ m_vidctrl = 0x00; // need to default to an 8x8 mode for Space Invaders test mode at least
+
+ for (int i=0;i<2;i++)
+ m_tile_scroll[i] = 0x00;
+
+ for (int i=0;i<2;i++)
+ m_tile_xscroll[i] = 0x00;
+
+ m_tile_gfxbase_lo_data = 0x00;
+ m_tile_gfxbase_hi_data = 0x00;
+
+ m_sprite_gfxbase_lo_data = 0x00;
+ m_sprite_gfxbase_hi_data = 0x00;
+
+ m_splitpos = 0x00;
+}
+
+uint8_t elan_eu3a05vid_device::read_spriteram(int offset)
+{
+ address_space& cpuspace = m_cpu->space(AS_PROGRAM);
+ int realoffset = offset+0x3e00;
+ if (realoffset < 0x4000)
+ {
+ return cpuspace.read_byte(realoffset);
+ }
+ else
+ return 0x00;
+}
+
+
+uint8_t elan_eu3a05vid_device::read_vram(int offset)
+{
+ address_space& cpuspace = m_cpu->space(AS_PROGRAM);
+ int realoffset = offset+0x0600;
+ if (realoffset < 0x4000)
+ {
+ return cpuspace.read_byte(realoffset);
+ }
+ else
+ return 0x00;
+}
+
+
+
+/* (m_tile_gfxbase_lo_data | (m_tile_gfxbase_hi_data << 8)) * 0x100
+ gives you the actual rom address, everything references the 3MByte - 4MByte region, like the banking so
+ the system can probably have up to a 4MByte rom, all games we have so far just use the upper 1MByte of
+ that space (Tetris seems to rely on mirroring? as it sets all addresses up for the lower 1MB instead)
+*/
+
+void elan_eu3a05vid_device::draw_sprites(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
+{
+ address_space& fullbankspace = m_bank->space(AS_PROGRAM);
+
+ /*
+ Sprites
+ FF yy xx AA XX YY aa bb
+
+ yy = y position
+ xx = x position
+ XX = texture x start
+ YY = texture y start
+ aa = same as unk2 on tiles
+ bb = sometimes set in invaders
+ AA = same as attr on tiles (colour / priority?)
+
+ FF = flags ( e-dD fFsS )
+ e = enable
+ D = ZoomX to double size (boss explosions on Air Blaster)
+ d = ZoomY to double size (boss explosions on Air Blaster)
+ S = SizeX
+ s = SizeY
+ F = FlipX
+ f = FlipY (assumed, not seen)
+
+ */
+
+ for (int i = 0; i < 512; i += 8)
+ {
+ uint8_t x = read_spriteram(i + 2);
+ uint8_t y = read_spriteram(i + 1);
+
+ /*
+ Space Invaders draws the player base with this specific y value before the start of each life
+ and expects it to NOT wrap around. There are no high priority tiles or anything else to hide
+ and it doesn't appear on real hardware.
+
+ it's possible sprites don't wrap around at all (but then you couldn't have smooth entry at the
+ top of the screen - there are no extra y co-ordinate bits. However there would have been easier
+ ways to hide this tho as there are a bunch of unseen lines at the bottom of the screen anyway!
+
+ Air Blaster Joystick seems to indicate there is no sprite wrapping - sprites abruptly enter
+ the screen in pieces on real hardware.
+
+ needs further investigation.
+ */
+ if (y==255)
+ continue;
+
+ uint8_t tex_x = read_spriteram(i + 4);
+ uint8_t tex_y = read_spriteram(i + 5);
+
+ uint8_t flags = read_spriteram(i + 0);
+ uint8_t attr = read_spriteram(i + 3);
+ uint8_t unk2 = read_spriteram(i + 6);
+
+ const int doubleX = (flags & 0x10)>>4;
+ const int doubleY = (flags & 0x20)>>5;
+
+ //int priority = attr & 0x0f;
+ int colour = attr & 0xf0;
+
+ // ? game select has this set to 0xff, but clearly doesn't want the palette to change!
+ // while in Space Invaders this has to be palette for the UFO to be red.
+ if (colour & 0x80)
+ colour = 0;
+
+ int transpen = 0;
+
+ /* HACK - how is this calculated
+ phoenix and the game select need it like this
+ it isn't a simple case of unk2 being transpen either because Qix has some elements set to 0x07
+ where the transpen needs to be 0x00 and Space Invaders has it set to 0x04
+ it could be a global register rather than something in the spritelist?
+ */
+ if ((attr == 0xff) && (unk2 == 0xff))
+ transpen = 0xff;
+
+
+ if (!(flags & 0x80))
+ continue;
+
+ int sizex = 8;
+ int sizey = 8;
+
+ if (flags & 0x01)
+ {
+ sizex = 16;
+ }
+
+ if (flags & 0x02)
+ {
+ sizey = 16;
+ }
+
+ int base = (m_sprite_gfxbase_lo_data | (m_sprite_gfxbase_hi_data << 8)) * 0x100;
+
+ if (doubleX)
+ sizex = sizex * 2;
+
+ if (doubleY)
+ sizey = sizey * 2;
+
+ for (int yy = 0; yy < sizey; yy++)
+ {
+ uint16_t* row;
+
+ if (flags & 0x08) // guess flipy
+ {
+ row = &bitmap.pix16((y + (sizey - 1 - yy)) & 0xff);
+ }
+ else
+ {
+ row = &bitmap.pix16((y + yy) & 0xff);
+ }
+
+ for (int xx = 0; xx < sizex; xx++)
+ {
+ int realaddr;
+
+ if (!doubleX)
+ realaddr = base + ((tex_x + xx) & 0xff);
+ else
+ realaddr = base + ((tex_x + (xx>>1)) & 0xff);
+
+ if (!doubleY)
+ realaddr += ((tex_y + yy) & 0xff) * 256;
+ else
+ realaddr += ((tex_y + (yy>>1)) & 0xff) * 256;
+
+ uint8_t pix = fullbankspace.read_byte(realaddr);
+
+ if (pix != transpen)
+ {
+ if (flags & 0x04) // flipx
+ {
+ row[(x + (sizex - 1 - xx)) & 0xff] = (pix + ((colour & 0x70) << 1)) & 0xff;
+ }
+ else
+ {
+ row[(x + xx) & 0xff] = (pix + ((colour & 0x70) << 1)) & 0xff;
+ }
+ }
+ }
+ }
+ }
+}
+
+
+// a hacky mess for now
+bool elan_eu3a05vid_device::get_tile_data(int base, int drawpri, int& tile, int &attr, int &unk2)
+{
+ tile = read_vram(base * 4) + (read_vram((base * 4) + 1) << 8);
+
+ // these seem to be the basically the same as attr/unk2 in the sprites, which also make
+ // very little sense.
+ attr = read_vram((base * 4) + 2);
+ unk2 = read_vram((base * 4) + 3);
+
+ /* hack for phoenix title screens.. the have attr set to 0x3f which change the colour bank in ways we don't want
+ and also by our interpretation of 0x0f bits sets the tiles to priority over sprites (although in this case
+ that might tbe ok, because it looks like the sprites also have that set */
+ if (unk2 == 0x07)
+ attr = 0;
+
+ int priority = attr & 0x0f;
+
+ // likely wrong
+ if ((drawpri == 0 && priority == 0x0f) || (drawpri == 1 && priority != 0x0f))
+ return false;
+
+ return true;
+}
+void elan_eu3a05vid_device::draw_tilemaps(screen_device& screen, bitmap_ind16& bitmap, const rectangle& cliprect, int drawpri)
+{
+ /*
+ this doesn't handle 8x8 4bpp (not used by anything yet)
+ */
+
+ int scroll = (m_tile_scroll[1] << 8) | m_tile_scroll[0];
+ address_space& fullbankspace = m_bank->space(AS_PROGRAM);
+
+ // Phoenix scrolling actually skips a pixel, jumping from 0x001 to 0x1bf, scroll 0x000 isn't used, maybe it has other meanings?
+
+ int totalyrow;
+ int totalxcol;
+ int mapyrowsbase;
+ int tileysize;
+ int tilexsize;
+ int startrow;
+
+ if (m_vidctrl & 0x40) // 16x16 tiles
+ {
+ totalyrow = 16;
+ totalxcol = 16;
+ mapyrowsbase = 14;
+ tileysize = 16;
+ tilexsize = 16;
+ startrow = (scroll >> 4) & 0x1f;
+ }
+ else
+ {
+ totalyrow = 32;
+ totalxcol = 32;
+ mapyrowsbase = 28;
+ tileysize = 8;
+ tilexsize = 8;
+ startrow = (scroll >> 3) & 0x3f;
+ }
+
+ for (int y = 0; y < totalyrow; y++)
+ {
+ for (int x = 0; x < totalxcol * 2; x++)
+ {
+ int realstartrow = (startrow + y);
+
+ int yrows;
+
+ if (m_vidctrl & 0x01)
+ yrows = mapyrowsbase;
+ else
+ yrows = mapyrowsbase * 2;
+
+ if (realstartrow >= yrows)
+ realstartrow -= yrows;
+
+ // in double width & double height mode the page addressing needs adjusting
+ if (!(m_vidctrl & 0x02))
+ {
+ if (!(m_vidctrl & 0x01))
+ {
+ if (realstartrow >= (yrows / 2))
+ {
+ realstartrow += yrows / 2;
+ }
+ }
+ }
+
+ for (int i = 0; i < tileysize; i++)
+ {
+ int drawline = (y * tileysize) + i;
+ drawline -= scroll & (tileysize - 1);
+
+ if ((drawline >= 0) && (drawline < 256))
+ {
+ int scrollx;
+
+ // split can be probably configured in more ways than this
+ if (drawline > m_splitpos)
+ scrollx = get_xscroll(1);
+ else
+ scrollx = get_xscroll(0);
+
+ int base;
+
+ if (m_vidctrl & 0x40) // 16x16 tiles
+ {
+ base = (((realstartrow + y) & 0x3f) * 8) + x;
+ }
+ else
+ {
+ base = (((realstartrow) & 0x7f) * totalxcol) + (x & (totalxcol - 1));
+ }
+
+ if (!(m_vidctrl & 0x02))
+ {
+ if (x & totalxcol)
+ {
+ base += totalxcol * mapyrowsbase;
+ }
+ }
+
+ int tile, attr, unk2;
+
+ if (!get_tile_data(base, drawpri, tile, attr, unk2))
+ continue;
+
+ int colour = attr & 0xf0;
+
+ if (m_vidctrl & 0x40) // 16x16 tiles
+ {
+ if (m_vidctrl & 0x20) // 4bpp mode
+ {
+ tile = (tile & 0xf) + ((tile & ~0xf) * 16);
+ tile += ((m_tile_gfxbase_lo_data | m_tile_gfxbase_hi_data << 8) << 5);
+ }
+ else
+ {
+ tile = (tile & 0xf) + ((tile & ~0xf) * 16);
+ tile <<= 1;
+
+ tile += ((m_tile_gfxbase_lo_data | m_tile_gfxbase_hi_data << 8) << 5);
+ }
+ }
+ else
+ {
+ if (m_vidctrl & 0x20) // 4bpp
+ {
+ // TODO
+ tile = 0x0000;//machine().rand() & 0x1ff;
+ }
+ else
+ {
+ tile = (tile & 0x1f) + ((tile & ~0x1f) * 8);
+ tile += ((m_tile_gfxbase_lo_data | m_tile_gfxbase_hi_data << 8) << 5);
+ }
+ }
+
+ uint16_t* row = &bitmap.pix16(drawline);
+
+ if (m_vidctrl & 0x40) // 16x16 tiles
+ {
+ if (m_vidctrl & 0x20) // 4bpp
+ {
+ for (int xx = 0; xx < tilexsize; xx += 2)
+ {
+ int realaddr = ((tile + i * 16) << 3) + (xx >> 1);
+ uint8_t pix = fullbankspace.read_byte(realaddr);
+
+ int drawxpos;
+
+ drawxpos = x * 16 + xx + 0;
+ drawxpos &= 0x1ff;
+ if ((drawxpos >= 0) && (drawxpos < 256))
+ row[drawxpos] = ((pix & 0xf0) >> 4) + colour;
+
+ drawxpos = x * 16 + xx + 1;
+ drawxpos &= 0x1ff;
+ if ((drawxpos >= 0) && (drawxpos < 256))
+ row[drawxpos] = ((pix & 0x0f) >> 0) + colour;
+ }
+ }
+ else // 8bpp
+ {
+ for (int xx = 0; xx < tilexsize; xx++)
+ {
+ int realaddr = ((tile + i * 32) << 3) + xx;
+ uint8_t pix = fullbankspace.read_byte(realaddr);
+
+ int drawxpos = x * 16 + xx;
+ drawxpos &= 0x1ff;
+ if ((drawxpos >= 0) && (drawxpos < 256))
+ row[drawxpos] = (pix + ((colour & 0x70) << 1)) & 0xff;
+ }
+ }
+ }
+ else // 8x8 tiles
+ {
+ if (m_vidctrl & 0x20) // 4bpp
+ {
+ // TODO
+ }
+ else
+ {
+ for (int xx = 0; xx < tilexsize; xx++)
+ {
+ const int realaddr = ((tile + i * 32) << 3) + xx;
+ const uint8_t pix = fullbankspace.read_byte(realaddr);
+
+ int drawxpos = x * tilexsize + xx - scrollx;
+ drawxpos &= 0x1ff;
+
+ if ((drawxpos >= 0) && (drawxpos < 256))
+ row[drawxpos] = (pix + ((colour & 0x70) << 1)) & 0xff;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+uint32_t elan_eu3a05vid_device::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
+{
+ bitmap.fill(0, cliprect);
+
+ draw_tilemaps(screen,bitmap,cliprect,0);
+ draw_sprites(screen,bitmap,cliprect);
+ draw_tilemaps(screen,bitmap,cliprect,1);
+
+ return 0;
+}
+
+// Tile bases
+
+WRITE8_MEMBER(elan_eu3a05vid_device::tile_gfxbase_lo_w)
+{
+ //logerror("%s: tile_gfxbase_lo_w (select GFX base lower) %02x\n", machine().describe_context(), data);
+ m_tile_gfxbase_lo_data = data;
+}
+
+WRITE8_MEMBER(elan_eu3a05vid_device::tile_gfxbase_hi_w)
+{
+ //logerror("%s: tile_gfxbase_hi_w (select GFX base upper) %02x\n", machine().describe_context(), data);
+ m_tile_gfxbase_hi_data = data;
+}
+
+READ8_MEMBER(elan_eu3a05vid_device::tile_gfxbase_lo_r)
+{
+ //logerror("%s: tile_gfxbase_lo_r (GFX base lower)\n", machine().describe_context());
+ return m_tile_gfxbase_lo_data;
+}
+
+READ8_MEMBER(elan_eu3a05vid_device::tile_gfxbase_hi_r)
+{
+ //logerror("%s: tile_gfxbase_hi_r (GFX base upper)\n", machine().describe_context());
+ return m_tile_gfxbase_hi_data;
+}
+
+
+
+// Sprite Tile bases
+
+WRITE8_MEMBER(elan_eu3a05vid_device::sprite_gfxbase_lo_w)
+{
+ //logerror("%s: sprite_gfxbase_lo_w (select Sprite GFX base lower) %02x\n", machine().describe_context(), data);
+ m_sprite_gfxbase_lo_data = data;
+}
+
+WRITE8_MEMBER(elan_eu3a05vid_device::sprite_gfxbase_hi_w)
+{
+ //logerror("%s: sprite_gfxbase_hi_w (select Sprite GFX base upper) %02x\n", machine().describe_context(), data);
+ m_sprite_gfxbase_hi_data = data;
+}
+
+READ8_MEMBER(elan_eu3a05vid_device::sprite_gfxbase_lo_r)
+{
+ //logerror("%s: sprite_gfxbase_lo_r (Sprite GFX base lower)\n", machine().describe_context());
+ return m_sprite_gfxbase_lo_data;
+}
+
+READ8_MEMBER(elan_eu3a05vid_device::sprite_gfxbase_hi_r)
+{
+ //logerror("%s: sprite_gfxbase_hi_r (Sprite GFX base upper)\n", machine().describe_context());
+ return m_sprite_gfxbase_hi_data;
+}
+
+
+
+READ8_MEMBER(elan_eu3a05vid_device::tile_scroll_r)
+{
+ return m_tile_scroll[offset];
+}
+
+WRITE8_MEMBER(elan_eu3a05vid_device::tile_scroll_w)
+{
+ m_tile_scroll[offset] = data;
+}
+
+READ8_MEMBER(elan_eu3a05vid_device::tile_xscroll_r)
+{
+ return m_tile_xscroll[offset];
+}
+
+WRITE8_MEMBER(elan_eu3a05vid_device::tile_xscroll_w)
+{
+ m_tile_xscroll[offset] = data;
+}
+
+READ8_MEMBER(elan_eu3a05vid_device::splitpos_r)
+{
+ return m_splitpos;
+}
+
+WRITE8_MEMBER(elan_eu3a05vid_device::splitpos_w)
+{
+ m_splitpos = data;
+}
+
+uint16_t elan_eu3a05vid_device::get_xscroll(int which)
+{
+ switch (which)
+ {
+ case 0x0: return (m_tile_xscroll[1] << 8) | (m_tile_xscroll[0]);
+ case 0x1: return (m_tile_xscroll[3] << 8) | (m_tile_xscroll[2]);
+ }
+
+ return 0x0000;
+}
+
+READ8_MEMBER(elan_eu3a05vid_device::elan_eu3a05_vidctrl_r)
+{
+ return m_vidctrl;
+}
+
+WRITE8_MEMBER(elan_eu3a05vid_device::elan_eu3a05_vidctrl_w)
+{
+ logerror("%s: elan_eu3a05_vidctrl_w %02x (video control?)\n", machine().describe_context(), data);
+ /*
+ c3 8bpp 16x16 1100 0011 abl logo
+ e3 4bpp 16x16 1110 0011
+ 83 8bpp 8x8 1000 0011 air blaster logo
+ 02 8bpp 8x8 (phoenix) 0000 0010 air blaster 2d normal
+ 03 8bpp 8x8 0000 0011 air blaster 2d bosses
+ 00 0000 0000 air blaster 3d stages
+
+ ?tb- --wh
+
+ ? = unknown
+ t = tile size (1 = 16x16, 0 = 8x8)
+ b = bpp (0 = 8bpp, 1 = 4bpp)
+ - = haven't seen used
+ h = tilemap height? (0 = double height)
+ w = tilemap width? (0 = double width)
+
+ space invaders test mode doesn't initialize this
+
+ */
+ m_vidctrl = data;
+} \ No newline at end of file
diff --git a/src/mame/video/elan_eu3a05vid.h b/src/mame/video/elan_eu3a05vid.h
new file mode 100644
index 00000000000..4f4f52ad2f9
--- /dev/null
+++ b/src/mame/video/elan_eu3a05vid.h
@@ -0,0 +1,80 @@
+// license:BSD-3-Clause
+// copyright-holders:David Haywood
+
+#ifndef MAME_VIDEO_ELAN_EU3A05VID_H
+#define MAME_VIDEO_ELAN_EU3A05VID_H
+
+#include "elan_eu3a05commonvid.h"
+#include "cpu/m6502/m6502.h"
+#include "machine/bankdev.h"
+
+class elan_eu3a05vid_device : public elan_eu3a05commonvid_device
+{
+public:
+ elan_eu3a05vid_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ template <typename T> void set_cpu(T &&tag) { m_cpu.set_tag(std::forward<T>(tag)); }
+ template <typename T> void set_addrbank(T &&tag) { m_bank.set_tag(std::forward<T>(tag)); }
+
+ // VIDEO
+ // tile bases
+ DECLARE_WRITE8_MEMBER(tile_gfxbase_lo_w);
+ DECLARE_WRITE8_MEMBER(tile_gfxbase_hi_w);
+ DECLARE_READ8_MEMBER(tile_gfxbase_lo_r);
+ DECLARE_READ8_MEMBER(tile_gfxbase_hi_r);
+ // sprite tile bases
+ DECLARE_WRITE8_MEMBER(sprite_gfxbase_lo_w);
+ DECLARE_WRITE8_MEMBER(sprite_gfxbase_hi_w);
+ DECLARE_READ8_MEMBER(sprite_gfxbase_lo_r);
+ DECLARE_READ8_MEMBER(sprite_gfxbase_hi_r);
+
+ DECLARE_READ8_MEMBER(elan_eu3a05_vidctrl_r);
+ DECLARE_WRITE8_MEMBER(elan_eu3a05_vidctrl_w);
+
+ DECLARE_READ8_MEMBER(tile_scroll_r);
+ DECLARE_WRITE8_MEMBER(tile_scroll_w);
+
+ DECLARE_READ8_MEMBER(tile_xscroll_r);
+ DECLARE_WRITE8_MEMBER(tile_xscroll_w);
+
+ DECLARE_READ8_MEMBER(splitpos_r);
+ DECLARE_WRITE8_MEMBER(splitpos_w);
+
+ uint16_t get_xscroll(int which);
+
+ uint32_t screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);
+
+protected:
+ // device-level overrides
+ virtual void device_start() override;
+ virtual void device_reset() override;
+
+private:
+ required_device<m6502_device> m_cpu;
+ required_device<address_map_bank_device> m_bank;
+
+ uint8_t m_vidctrl;
+
+ uint8_t m_tile_gfxbase_lo_data;
+ uint8_t m_tile_gfxbase_hi_data;
+
+ uint8_t m_sprite_gfxbase_lo_data;
+ uint8_t m_sprite_gfxbase_hi_data;
+
+ uint8_t m_tile_scroll[2];
+ uint8_t m_tile_xscroll[4];
+
+ uint8_t m_splitpos;
+
+ bool get_tile_data(int base, int drawpri, int& tile, int &attr, int &unk2);
+ void draw_tilemaps(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect, int drawpri);
+ void draw_sprites(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);
+
+ uint8_t read_spriteram(int offset);
+ uint8_t read_vram(int offset);
+
+};
+
+DECLARE_DEVICE_TYPE(ELAN_EU3A05_VID, elan_eu3a05vid_device)
+
+#endif // MAME_VIDEO_ELAN_EU3A05VID_H