diff --git a/src/mame/sinclair/next/specnext.cpp b/src/mame/sinclair/next/specnext.cpp index b3cf0d7fb..da0c12a07 100644 --- a/src/mame/sinclair/next/specnext.cpp +++ b/src/mame/sinclair/next/specnext.cpp @@ -32,6 +32,7 @@ #include "specnext_sprites.h" #include "specnext_tiles.h" #include "specnext_uart.h" +#include "specnext_vtest.h" #include "bus/midi/midi.h" #include "bus/rs232/rs232.h" @@ -145,6 +146,7 @@ public: , m_layer2(*this, "layer2") , m_lores(*this, "lores") , m_sprites(*this, "sprites") + , m_vtest(*this, "vtest") , m_io_video(*this, "VIDEO") , m_io_layers(*this, "LYRS") , m_io_mouse(*this, "mouse_input%u", 0U) @@ -389,8 +391,10 @@ private: required_device m_layer2; required_device m_lores; required_device m_sprites; + required_device m_vtest; optional_ioport m_io_video; optional_ioport m_io_layers; + bool m_video_test_pattern_active = false; required_ioport_array<4> m_io_mouse; required_ioport m_io_joy_left; required_ioport m_io_joy_right; @@ -989,6 +993,7 @@ void specnext_state::update_video_mode() m_tiles->set_raster_offset(left, top); m_layer2->set_raster_offset(left, top); m_sprites->set_raster_offset(left, top); + m_vtest->set_raster_offset(left, top); m_eff_nr_03_machine_timing = m_nr_03_machine_timing; m_eff_nr_05_5060 = m_nr_05_5060; @@ -997,6 +1002,12 @@ void specnext_state::update_video_mode() u32 specnext_state::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect) { + if (m_video_test_pattern_active) + { + m_vtest->draw(bitmap, cliprect); + return 0; + } + rectangle clip256x192 = m_clip256x192; clip256x192 &= cliprect; rectangle clip320x256 = m_clip320x256; @@ -2770,6 +2781,7 @@ INTERRUPT_GEN_MEMBER(specnext_state::specnext_interrupt) } line_irq_adjust(); + m_video_test_pattern_active = ((m_io_joy_left->read() & 0x700) == 0x700) || ((m_io_joy_right->read() & 0x700) == 0x700); if (!port_ff_interrupt_disable()) { m_irq_on_timer->adjust(m_screen->time_until_pos(m_video_timings.int_v, m_video_timings.int_h << 1)); @@ -3276,7 +3288,6 @@ INPUT_PORTS_START(specnext) PORT_CONFSETTING(0x00, "360x288 (HDMI)" ) PORT_CONFSETTING(0x01, "320x256 (VGA)" ) PORT_BIT(0xfe, IP_ACTIVE_HIGH, IPT_UNUSED) - PORT_START("mouse_input0") PORT_BIT(0x7ff, 0, IPT_MOUSE_X) PORT_SENSITIVITY(100) @@ -3373,6 +3384,7 @@ void specnext_state::machine_start() // Save save_item(NAME(m_page_shadow)); save_item(NAME(m_bootrom_en)); + save_item(NAME(m_video_test_pattern_active)); save_item(NAME(m_port_ff_data)); save_item(NAME(m_port_1ffd_special_old)); save_item(NAME(m_port_1ffd_data)); @@ -3618,6 +3630,7 @@ void specnext_state::reset_hard() { m_nr_02_hard_reset = 0; m_bootrom_en = 1; + m_video_test_pattern_active = 0; m_dma->dma_mode_w(0); // nmi_mf = 0; @@ -4164,6 +4177,7 @@ void specnext_state::tbblue(machine_config &config) // drawgfx doesn't allow to mask palette access and in case of 256-color sprite does use offset, the index overflow palette boundries. // We are duplicating palletes to imitate mask on palette index which required by sprites device. SPECNEXT_SPRITES(config, m_sprites, 0).set_palette(m_palette->device().tag(), 0x600, 0x800); + SPECNEXT_VTEST(config, m_vtest, 0); SPECNEXT_COPPER(config, m_copper, 28_MHz_XTAL); m_copper->out_nextreg_cb().set([this](offs_t offset, u8 data) { m_next_regs.write_byte(offset, data); }); diff --git a/src/mame/sinclair/next/specnext_vtest.cpp b/src/mame/sinclair/next/specnext_vtest.cpp new file mode 100644 index 000000000..96cb0d088 --- /dev/null +++ b/src/mame/sinclair/next/specnext_vtest.cpp @@ -0,0 +1,164 @@ +// license:BSD-3-Clause +// copyright-holders:Andrei I. Holub + +#include "emu.h" +#include "specnext_vtest.h" + + +DEFINE_DEVICE_TYPE(SPECNEXT_VTEST, specnext_vtest_device, "vtest", "Spectrum Next Video Test Pattern") + + +specnext_vtest_device::specnext_vtest_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) + : device_t(mconfig, SPECNEXT_VTEST, tag, owner, clock) +{ +} + +void specnext_vtest_device::draw(bitmap_rgb32 &bitmap, const rectangle &cliprect) +{ + static constexpr unsigned BORDER = 32; + static constexpr unsigned FULL_W = 640; + static constexpr unsigned FULL_H = 192 + (BORDER << 1); // 256 + + const int ox = m_offset_h - (BORDER << 1); // left - 64 + const int oy = m_offset_v - BORDER; // top - 32 + + // FPGA video_test_pattern.vhd vertical state machine (FPGA line numbers): + // + // VS_BLANK_0 : lines 0-47 black (48 lines) + // VS_RAINBOW : lines 48-111 rainbow (64 lines) + // VS_BLANK_1 : lines 112-127 black (16 lines) + // VS_RG : lines 128-191 RG ramp (64 lines) + // VS_BLANK_2 : lines 192-207 black (16 lines) + // VS_BGR : lines 208-271 BGR ramp(64 lines) + // remainder : black + + // MAME doesn't scandouble, so FPGA line counts are halved (>> 1) + static constexpr unsigned BLANK0_LINES = 48 >> 1; + static constexpr unsigned RAINBOW_LINES = 64 >> 1; + static constexpr unsigned BLANK1_LINES = 16 >> 1; + static constexpr unsigned RG_LINES = 64 >> 1; + static constexpr unsigned BLANK2_LINES = 16 >> 1; + static constexpr unsigned BGR_LINES = 64 >> 1; + + static constexpr unsigned RAINBOW_START = BLANK0_LINES; + static constexpr unsigned RAINBOW_END = RAINBOW_START + RAINBOW_LINES; + static constexpr unsigned RG_START = RAINBOW_END + BLANK1_LINES; + static constexpr unsigned RG_END = RG_START + RG_LINES; + static constexpr unsigned BGR_START = RG_END + BLANK2_LINES; + static constexpr unsigned BGR_END = BGR_START + BGR_LINES; + + for (int y = cliprect.top(); y <= cliprect.bottom(); y++) + { + const int vrow = y - oy; + if (vrow < 0 || vrow >= FULL_H) + { + u32 *pix = &bitmap.pix(y, cliprect.left()); + for (int x = cliprect.left(); x <= cliprect.right(); x++, pix++) + *pix = rgb_t::black(); + continue; + } + + // Determine vertical band + unsigned band_row = 0; + int band_id; // 0=rainbow, 1=RG, 2=BGR, -1=blank + + if (vrow >= BGR_START && vrow < BGR_END) + { + band_id = 2; + band_row = vrow - BGR_START; + } + else if (vrow >= RG_START && vrow < RG_END) + { + band_id = 1; + band_row = vrow - RG_START; + } + else if (vrow >= RAINBOW_START && vrow < RAINBOW_END) + { + band_id = 0; + band_row = vrow - RAINBOW_START; + } + else + band_id = -1; + + bool is_3bit = (band_id >= 0) && (band_row < 8); + bool is_4bit = (band_id >= 0) && (band_row >= 8 && band_row < 16); + + u32 *pix = &bitmap.pix(y, cliprect.left()); + for (int x = cliprect.left(); x <= cliprect.right(); x++, pix++) + { + const int hcol = x - ox; + + if (band_id < 0 || hcol < 0 || hcol >= FULL_W) + { + *pix = rgb_t::black(); + continue; + } + + u8 r8 = 0, g8 = 0, b8 = 0; + + if (band_id == 0) // Rainbow + { + const unsigned delta_top = hcol * 255 / FULL_W; + const unsigned phase = hcol * 512 / FULL_W; + + r8 = u8(~delta_top & 0xff); + b8 = u8(delta_top & 0xff); + g8 = (phase < 256) ? u8(phase & 0xff) : u8(~phase & 0xff); + } + else if (band_id == 1) // Red/Green ramp + { + const unsigned half = FULL_W / 2; + if (hcol < half) + { + r8 = hcol * 255 / half; + } + else + { + g8 = (hcol - half) * 255 / half; + } + } + else // Blue/Grey ramp + { + const unsigned half = FULL_W / 2; + if (hcol < half) + { + b8 = hcol * 255 / half; + } + else + { + const u8 grey = (hcol - half) * 255 / half; + r8 = grey; + g8 = grey; + b8 = grey; + } + } + + if (is_4bit) + { + r8 &= 0xf0; + g8 &= 0xf0; + b8 &= 0xf0; + } + else if (is_3bit) + { + r8 &= 0xe0; + g8 &= 0xe0; + b8 &= 0xe0; + } + + *pix = rgb_t(r8, g8, b8); + } + } +} + +void specnext_vtest_device::device_add_mconfig(machine_config &config) +{ + m_offset_h = 0; + m_offset_v = 0; +} + +void specnext_vtest_device::device_start() +{ + save_item(NAME(m_offset_h)); + save_item(NAME(m_offset_v)); +} diff --git a/src/mame/sinclair/next/specnext_vtest.h b/src/mame/sinclair/next/specnext_vtest.h new file mode 100644 index 000000000..1efb6e277 --- /dev/null +++ b/src/mame/sinclair/next/specnext_vtest.h @@ -0,0 +1,27 @@ +// license:BSD-3-Clause +// copyright-holders:Andrei I. Holub +#ifndef MAME_SINCLAIR_NEXT_SPECNEXT_VTEST_H +#define MAME_SINCLAIR_NEXT_SPECNEXT_VTEST_H + +#pragma once + +class specnext_vtest_device : public device_t +{ +public: + specnext_vtest_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock); + + void set_raster_offset(u16 offset_h, u16 offset_v) { m_offset_h = offset_h; m_offset_v = offset_v; } + + void draw(bitmap_rgb32 &bitmap, const rectangle &cliprect); + +protected: + virtual void device_add_mconfig(machine_config &config) override ATTR_COLD; + virtual void device_start() override ATTR_COLD; + +private: + u16 m_offset_h, m_offset_v; +}; + + +DECLARE_DEVICE_TYPE(SPECNEXT_VTEST, specnext_vtest_device) +#endif // MAME_SINCLAIR_NEXT_SPECNEXT_VTEST_H