sinclair/next/specnext_vtest.cpp: Factory Video test (joistick's XYZ to activate)

This commit is contained in:
Andrei I. Holub 2026-05-24 09:32:16 -04:00
parent fdce3c40fa
commit 6f78bdf018
3 changed files with 206 additions and 1 deletions

View File

@ -32,6 +32,7 @@
#include "specnext_sprites.h" #include "specnext_sprites.h"
#include "specnext_tiles.h" #include "specnext_tiles.h"
#include "specnext_uart.h" #include "specnext_uart.h"
#include "specnext_vtest.h"
#include "bus/midi/midi.h" #include "bus/midi/midi.h"
#include "bus/rs232/rs232.h" #include "bus/rs232/rs232.h"
@ -145,6 +146,7 @@ public:
, m_layer2(*this, "layer2") , m_layer2(*this, "layer2")
, m_lores(*this, "lores") , m_lores(*this, "lores")
, m_sprites(*this, "sprites") , m_sprites(*this, "sprites")
, m_vtest(*this, "vtest")
, m_io_video(*this, "VIDEO") , m_io_video(*this, "VIDEO")
, m_io_layers(*this, "LYRS") , m_io_layers(*this, "LYRS")
, m_io_mouse(*this, "mouse_input%u", 0U) , m_io_mouse(*this, "mouse_input%u", 0U)
@ -389,8 +391,10 @@ private:
required_device<specnext_layer2_device> m_layer2; required_device<specnext_layer2_device> m_layer2;
required_device<specnext_lores_device> m_lores; required_device<specnext_lores_device> m_lores;
required_device<specnext_sprites_device> m_sprites; required_device<specnext_sprites_device> m_sprites;
required_device<specnext_vtest_device> m_vtest;
optional_ioport m_io_video; optional_ioport m_io_video;
optional_ioport m_io_layers; optional_ioport m_io_layers;
bool m_video_test_pattern_active = false;
required_ioport_array<4> m_io_mouse; required_ioport_array<4> m_io_mouse;
required_ioport m_io_joy_left; required_ioport m_io_joy_left;
required_ioport m_io_joy_right; required_ioport m_io_joy_right;
@ -989,6 +993,7 @@ void specnext_state::update_video_mode()
m_tiles->set_raster_offset(left, top); m_tiles->set_raster_offset(left, top);
m_layer2->set_raster_offset(left, top); m_layer2->set_raster_offset(left, top);
m_sprites->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_03_machine_timing = m_nr_03_machine_timing;
m_eff_nr_05_5060 = m_nr_05_5060; 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) 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; rectangle clip256x192 = m_clip256x192;
clip256x192 &= cliprect; clip256x192 &= cliprect;
rectangle clip320x256 = m_clip320x256; rectangle clip320x256 = m_clip320x256;
@ -2770,6 +2781,7 @@ INTERRUPT_GEN_MEMBER(specnext_state::specnext_interrupt)
} }
line_irq_adjust(); 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()) 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)); 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(0x00, "360x288 (HDMI)" )
PORT_CONFSETTING(0x01, "320x256 (VGA)" ) PORT_CONFSETTING(0x01, "320x256 (VGA)" )
PORT_BIT(0xfe, IP_ACTIVE_HIGH, IPT_UNUSED) PORT_BIT(0xfe, IP_ACTIVE_HIGH, IPT_UNUSED)
PORT_START("mouse_input0") PORT_START("mouse_input0")
PORT_BIT(0x7ff, 0, IPT_MOUSE_X) PORT_SENSITIVITY(100) PORT_BIT(0x7ff, 0, IPT_MOUSE_X) PORT_SENSITIVITY(100)
@ -3373,6 +3384,7 @@ void specnext_state::machine_start()
// Save // Save
save_item(NAME(m_page_shadow)); save_item(NAME(m_page_shadow));
save_item(NAME(m_bootrom_en)); 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_ff_data));
save_item(NAME(m_port_1ffd_special_old)); save_item(NAME(m_port_1ffd_special_old));
save_item(NAME(m_port_1ffd_data)); save_item(NAME(m_port_1ffd_data));
@ -3618,6 +3630,7 @@ void specnext_state::reset_hard()
{ {
m_nr_02_hard_reset = 0; m_nr_02_hard_reset = 0;
m_bootrom_en = 1; m_bootrom_en = 1;
m_video_test_pattern_active = 0;
m_dma->dma_mode_w(0); m_dma->dma_mode_w(0);
// nmi_mf = 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. // 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. // 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_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); 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); }); m_copper->out_nextreg_cb().set([this](offs_t offset, u8 data) { m_next_regs.write_byte(offset, data); });

View File

@ -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));
}

View File

@ -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