mirror of
https://github.com/holub/mame
synced 2025-04-19 23:12:11 +03:00
advision: emulate the display at a lower level
This commit is contained in:
parent
b09ea40089
commit
fecf31406e
@ -62,7 +62,7 @@ license:CC0
|
||||
|
||||
<!-- Homebrew -->
|
||||
|
||||
<software name="codered" supported="partial">
|
||||
<software name="codered">
|
||||
<!-- ref: https://www.m-e-g-a.org/research-education/research/adventure-vision/ -->
|
||||
<description>Code Red</description>
|
||||
<year>2013</year>
|
||||
|
@ -1,5 +1,5 @@
|
||||
// license:BSD-3-Clause
|
||||
// copyright-holders:Dan Boris
|
||||
// copyright-holders:Dan Boris, hap
|
||||
/*******************************************************************************
|
||||
|
||||
Entex Adventure Vision, tabletop video game console
|
||||
@ -15,23 +15,28 @@ Hardware notes:
|
||||
right button panels are electronically the same)
|
||||
- expansion port (unused)
|
||||
|
||||
The mirror rotates at around 7.5Hz, the motor speed is not controlled by software.
|
||||
There's a mirror on both sides so the display refreshes at around 15Hz. A similar
|
||||
technology was later used in the Nintendo Virtual Boy.
|
||||
|
||||
The display is faked in MAME. On the real thing, the picture is not as stable.
|
||||
|
||||
A game cartridge is basically an EPROM chip wearing a jacket, there is no
|
||||
dedicated cartridge slot as is common on other consoles. Only 4 games were
|
||||
released in total.
|
||||
|
||||
The mirror rotates at around 7Hz, the motor speed is not controlled by software.
|
||||
There's a mirror on both sides so the display refreshes at around 14Hz. A similar
|
||||
technology was later used in the Nintendo Virtual Boy.
|
||||
|
||||
The display is faked in MAME. On the real thing, the picture is not as stable. The
|
||||
width of 150 is specified by the BIOS, but it's possible to update the leds at a
|
||||
different rate, hence MAME configures a larger screen. In fact, the homebrew demo
|
||||
Code Red doesn't use the BIOS for it, and runs at 50*40 to save some RAM.
|
||||
|
||||
TODO:
|
||||
- EA banking is ugly, it can be turd-polished but the real issue is in mcs48
|
||||
- display refresh is actually ~15Hz, but doing that will make MAME very sluggish
|
||||
- Do the spinning mirror simulation differently? Right now it relies on the BIOS
|
||||
specifying a width of 150, and the official games work fine. But it should be
|
||||
possible to update the leds at a different rate. In fact, the homebrew demo
|
||||
Code Red doesn't use the BIOS for it, and runs at 50*40.
|
||||
- display refresh is actually ~14Hz, but doing that will make MAME very sluggish
|
||||
|
||||
BTANB:
|
||||
- 2 thin vertical seams (do a hyperspace in defender to see them more clearly)
|
||||
- glitches at the right edge during gameplay in scobra, and also during some
|
||||
explosion sounds in defender
|
||||
- scobra screen is shifted to the right when the game scrolls
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
@ -62,6 +67,8 @@ public:
|
||||
, m_volume(*this, "volume")
|
||||
, m_screen(*this, "screen")
|
||||
, m_mirror_sync(*this, "mirror_sync")
|
||||
, m_led_update(*this, "led_update")
|
||||
, m_led_off(*this, "led_off")
|
||||
, m_cart(*this, "cartslot")
|
||||
, m_ea_bank(*this, "ea_bank")
|
||||
, m_joy(*this, "JOY")
|
||||
@ -80,19 +87,20 @@ private:
|
||||
required_device<filter_volume_device> m_volume;
|
||||
required_device<screen_device> m_screen;
|
||||
required_device<timer_device> m_mirror_sync;
|
||||
required_device<timer_device> m_led_update;
|
||||
required_device<timer_device> m_led_off;
|
||||
required_device<generic_slot_device> m_cart;
|
||||
required_memory_bank m_ea_bank;
|
||||
required_ioport m_joy;
|
||||
|
||||
u32 screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect);
|
||||
DECLARE_WRITE_LINE_MEMBER(vblank);
|
||||
void vh_update();
|
||||
void io_map(address_map &map);
|
||||
void program_map(address_map &map);
|
||||
|
||||
u8 ext_ram_r(offs_t offset);
|
||||
void ext_ram_w(offs_t offset, u8 data);
|
||||
u8 controller_r();
|
||||
void bankswitch_w(u8 data);
|
||||
u32 screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect);
|
||||
void av_control_w(u8 data);
|
||||
DECLARE_WRITE_LINE_MEMBER(vblank);
|
||||
TIMER_DEVICE_CALLBACK_MEMBER(led_update);
|
||||
TIMER_DEVICE_CALLBACK_MEMBER(led_off);
|
||||
DECLARE_READ_LINE_MEMBER(vsync_r);
|
||||
|
||||
TIMER_CALLBACK_MEMBER(sound_cmd_sync);
|
||||
@ -100,20 +108,25 @@ private:
|
||||
void sound_g_w(u8 data);
|
||||
void sound_d_w(u8 data);
|
||||
|
||||
memory_region *m_cart_rom = nullptr;
|
||||
std::vector<u8> m_ext_ram;
|
||||
u16 m_rambank = 0;
|
||||
void bankswitch_w(u8 data);
|
||||
u8 ext_ram_r(offs_t offset);
|
||||
void ext_ram_w(offs_t offset, u8 data);
|
||||
u8 controller_r();
|
||||
|
||||
u8 m_video_enable = 0;
|
||||
static constexpr u32 DISPLAY_WIDTH = 0x400;
|
||||
|
||||
bool m_video_strobe = false;
|
||||
bool m_video_enable = false;
|
||||
u8 m_video_bank = 0;
|
||||
u8 m_video_hpos = 0;
|
||||
u32 m_video_hpos = 0;
|
||||
u8 m_led_output[5] = { };
|
||||
u8 m_led_latch[5] = { };
|
||||
std::unique_ptr<u8 []> m_display;
|
||||
|
||||
memory_region *m_cart_rom = nullptr;
|
||||
std::vector<u8> m_ext_ram;
|
||||
u16 m_rambank = 0;
|
||||
u8 m_sound_cmd = 0;
|
||||
|
||||
void io_map(address_map &map);
|
||||
void program_map(address_map &map);
|
||||
};
|
||||
|
||||
|
||||
@ -126,45 +139,55 @@ u32 advision_state::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, c
|
||||
{
|
||||
for (int y = 0; y < 40; y++)
|
||||
{
|
||||
u8 *src = &m_display[y * 256];
|
||||
u8 *src = &m_display[y * DISPLAY_WIDTH];
|
||||
|
||||
for (int x = 0; x < 150; x++)
|
||||
for (int x = 0; x < DISPLAY_WIDTH; x++)
|
||||
{
|
||||
int dx = x + 1;
|
||||
int dx = x;
|
||||
int dy = y * 2 + 1;
|
||||
|
||||
if (cliprect.contains(dx, dy))
|
||||
bitmap.pix(dy, dx) = src[x] ? (0xff << 16) : 0;
|
||||
{
|
||||
u8 red = 0xff;
|
||||
|
||||
if (src[x] == 0)
|
||||
{
|
||||
// do some horizontal interpolation
|
||||
int prev = 0;
|
||||
for (int i = 1; i < 5; i++)
|
||||
prev += ((x - i) < 0) ? 0 : src[x - i];
|
||||
|
||||
red = (prev * 0xff) / 4;
|
||||
}
|
||||
|
||||
u8 green = red / 16;
|
||||
u8 blue = red / 12;
|
||||
|
||||
bitmap.pix(dy, dx) = red << 16 | green << 8 | blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void advision_state::vh_update()
|
||||
{
|
||||
u8 *dst = &m_display[m_video_hpos];
|
||||
|
||||
for (int y = 4; y >= 0; y--)
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
dst[i * 256] = BIT(m_led_latch[y], 7 - i) ? 0 : 1;
|
||||
|
||||
m_led_latch[y] = 0xff;
|
||||
dst += 8 * 256;
|
||||
}
|
||||
|
||||
if (++m_video_hpos == 0)
|
||||
logerror("HPOS OVERFLOW\n");
|
||||
}
|
||||
|
||||
void advision_state::av_control_w(u8 data)
|
||||
{
|
||||
if ((m_video_enable == 0x00) && (data & 0x10))
|
||||
vh_update();
|
||||
// P25-P27: led latch select
|
||||
m_video_bank = data >> 5 & 7;
|
||||
|
||||
m_video_enable = data & 0x10;
|
||||
m_video_bank = (data & 0xe0) >> 5;
|
||||
// disable led outputs (there is some delay before it takes effect)
|
||||
if (m_video_bank == 0)
|
||||
m_led_off->adjust(attotime::from_usec(50));
|
||||
|
||||
// P24 rising edge: transfer led latches to outputs
|
||||
if (!m_video_strobe && bool(data & 0x10))
|
||||
{
|
||||
std::copy_n(m_led_latch, std::size(m_led_output), m_led_output);
|
||||
m_led_off->adjust(attotime::never);
|
||||
m_video_enable = true;
|
||||
}
|
||||
m_video_strobe = bool(data & 0x10);
|
||||
|
||||
// sync soundlatch (using gen_latch device here would give lots of logerror)
|
||||
machine().scheduler().synchronize(timer_expired_delegate(FUNC(advision_state::sound_cmd_sync), this), data >> 4);
|
||||
@ -174,15 +197,45 @@ WRITE_LINE_MEMBER(advision_state::vblank)
|
||||
{
|
||||
if (!state && (m_screen->frame_number() & 3) == 0)
|
||||
{
|
||||
std::fill_n(m_display.get(), 256 * 40, 0);
|
||||
// starting a new frame
|
||||
std::fill_n(m_display.get(), DISPLAY_WIDTH * 40, 0);
|
||||
|
||||
m_mirror_sync->adjust(attotime::from_usec(100));
|
||||
|
||||
m_video_hpos = 0;
|
||||
m_led_update->adjust(attotime::zero);
|
||||
}
|
||||
}
|
||||
|
||||
TIMER_DEVICE_CALLBACK_MEMBER(advision_state::led_update)
|
||||
{
|
||||
// write current leds to display buffer
|
||||
for (int y = 0; y < 8; y++)
|
||||
{
|
||||
for (int b = 0; b < 5; b++)
|
||||
{
|
||||
int pixel = m_video_enable ? BIT(m_led_output[b], y ^ 7) : 0;
|
||||
m_display[(b * 8 + y) * DISPLAY_WIDTH + m_video_hpos] = pixel;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_video_hpos < DISPLAY_WIDTH - 1)
|
||||
{
|
||||
// for official games, 1 'pixel' is 60us, but there are two spots that have
|
||||
// a longer duration: at x=50 and x=100 (see BTANB note about seams)
|
||||
m_led_update->adjust(attotime::from_usec(10));
|
||||
m_video_hpos++;
|
||||
}
|
||||
}
|
||||
|
||||
TIMER_DEVICE_CALLBACK_MEMBER(advision_state::led_off)
|
||||
{
|
||||
m_video_enable = false;
|
||||
}
|
||||
|
||||
READ_LINE_MEMBER(advision_state::vsync_r)
|
||||
{
|
||||
// mirror sync pulse (half rotation)
|
||||
// T1: mirror sync pulse (half rotation)
|
||||
return (m_mirror_sync->enabled()) ? 0 : 1;
|
||||
}
|
||||
|
||||
@ -199,16 +252,19 @@ TIMER_CALLBACK_MEMBER(advision_state::sound_cmd_sync)
|
||||
|
||||
u8 advision_state::sound_cmd_r()
|
||||
{
|
||||
// L0-L3: sound command
|
||||
return m_sound_cmd;
|
||||
}
|
||||
|
||||
void advision_state::sound_g_w(u8 data)
|
||||
{
|
||||
// G0: speaker out
|
||||
m_dac->write(data & 1);
|
||||
}
|
||||
|
||||
void advision_state::sound_d_w(u8 data)
|
||||
{
|
||||
// D0: speaker volume
|
||||
m_volume->flt_volume_set_volume((data & 1) ? 0.5 : 1.0);
|
||||
}
|
||||
|
||||
@ -220,8 +276,10 @@ void advision_state::sound_d_w(u8 data)
|
||||
|
||||
void advision_state::bankswitch_w(u8 data)
|
||||
{
|
||||
m_rambank = (data & 0x03) << 8;
|
||||
// P10,P11: RAM bank
|
||||
m_rambank = data & 3;
|
||||
|
||||
// P12: 8048 EA pin
|
||||
m_maincpu->set_input_line(MCS48_INPUT_EA, BIT(data, 2) ? ASSERT_LINE : CLEAR_LINE);
|
||||
if (m_cart_rom)
|
||||
m_ea_bank->set_entry(BIT(data, 2));
|
||||
@ -229,23 +287,26 @@ void advision_state::bankswitch_w(u8 data)
|
||||
|
||||
u8 advision_state::ext_ram_r(offs_t offset)
|
||||
{
|
||||
u8 data = m_ext_ram[m_rambank + offset];
|
||||
// read from external RAM
|
||||
u8 data = m_ext_ram[m_rambank << 8 | offset];
|
||||
if (machine().side_effects_disabled())
|
||||
return data;
|
||||
|
||||
// the video hardware interprets reads as writes
|
||||
if (m_video_bank >= 1 && m_video_bank <= 5)
|
||||
m_led_latch[m_video_bank - 1] = data;
|
||||
// transfer data to led latch
|
||||
if (m_video_bank > 0 && m_video_bank < 6)
|
||||
m_led_latch[5 - m_video_bank] = data ^ 0xff;
|
||||
|
||||
// reset sound cpu
|
||||
else if (m_video_bank == 6)
|
||||
m_soundcpu->set_input_line(INPUT_LINE_RESET, (data & 0x01) ? CLEAR_LINE : ASSERT_LINE);
|
||||
m_soundcpu->set_input_line(INPUT_LINE_RESET, (data & 1) ? CLEAR_LINE : ASSERT_LINE);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void advision_state::ext_ram_w(offs_t offset, u8 data)
|
||||
{
|
||||
m_ext_ram[m_rambank + offset] = data;
|
||||
// write to external RAM
|
||||
m_ext_ram[m_rambank << 8 | offset] = data;
|
||||
}
|
||||
|
||||
void advision_state::program_map(address_map &map)
|
||||
@ -308,18 +369,19 @@ void advision_state::machine_start()
|
||||
m_ea_bank->set_entry(0);
|
||||
|
||||
// allocate display buffer
|
||||
m_display = std::make_unique<u8 []>(256 * 40);
|
||||
std::fill_n(m_display.get(), 256 * 40, 0);
|
||||
save_pointer(NAME(m_display), 256 * 40);
|
||||
m_display = std::make_unique<u8 []>(DISPLAY_WIDTH * 40);
|
||||
std::fill_n(m_display.get(), DISPLAY_WIDTH * 40, 0);
|
||||
save_pointer(NAME(m_display), DISPLAY_WIDTH * 40);
|
||||
|
||||
// allocate external RAM
|
||||
m_ext_ram.resize(0x400);
|
||||
save_item(NAME(m_ext_ram));
|
||||
|
||||
std::fill_n(m_led_latch, std::size(m_led_latch), 0xff);
|
||||
save_item(NAME(m_led_output));
|
||||
save_item(NAME(m_led_latch));
|
||||
|
||||
save_item(NAME(m_rambank));
|
||||
save_item(NAME(m_video_strobe));
|
||||
save_item(NAME(m_video_enable));
|
||||
save_item(NAME(m_video_bank));
|
||||
save_item(NAME(m_sound_cmd));
|
||||
@ -329,6 +391,7 @@ void advision_state::machine_start()
|
||||
void advision_state::machine_reset()
|
||||
{
|
||||
m_video_hpos = 0;
|
||||
m_led_update->adjust(attotime::never);
|
||||
|
||||
// reset sound CPU
|
||||
m_soundcpu->set_input_line(INPUT_LINE_RESET, ASSERT_LINE);
|
||||
@ -359,14 +422,16 @@ void advision_state::advision(machine_config &config)
|
||||
|
||||
// video hardware
|
||||
SCREEN(config, m_screen, SCREEN_TYPE_RASTER);
|
||||
m_screen->set_refresh_hz(4*15); // actually 15Hz
|
||||
m_screen->set_refresh_hz(4*14); // see notes
|
||||
m_screen->set_vblank_time(0);
|
||||
m_screen->set_size(150 + 2, 40 * 2 + 1);
|
||||
m_screen->set_size(960, 40 * 2 + 1);
|
||||
m_screen->set_visarea_full();
|
||||
m_screen->set_screen_update(FUNC(advision_state::screen_update));
|
||||
m_screen->screen_vblank().set(FUNC(advision_state::vblank));
|
||||
|
||||
TIMER(config, "mirror_sync").configure_generic(nullptr);
|
||||
TIMER(config, "led_update").configure_generic(FUNC(advision_state::led_update));
|
||||
TIMER(config, "led_off").configure_generic(FUNC(advision_state::led_off));
|
||||
|
||||
// sound hardware
|
||||
SPEAKER(config, "speaker").front_center();
|
||||
|
Loading…
Reference in New Issue
Block a user