summaryrefslogtreecommitdiffstats
path: root/docs/release/src/emu
diff options
context:
space:
mode:
Diffstat (limited to 'docs/release/src/emu')
-rw-r--r--docs/release/src/emu/digfx.h206
-rw-r--r--docs/release/src/emu/driver.h205
-rw-r--r--docs/release/src/emu/emuopts.cpp1285
-rw-r--r--docs/release/src/emu/emuopts.h541
-rw-r--r--docs/release/src/emu/gamedrv.h285
-rw-r--r--docs/release/src/emu/validity.cpp2250
-rw-r--r--docs/release/src/emu/video.cpp1571
-rw-r--r--docs/release/src/emu/video.h226
8 files changed, 6569 insertions, 0 deletions
diff --git a/docs/release/src/emu/digfx.h b/docs/release/src/emu/digfx.h
new file mode 100644
index 00000000000..b991ff15f12
--- /dev/null
+++ b/docs/release/src/emu/digfx.h
@@ -0,0 +1,206 @@
+// license:BSD-3-Clause
+// copyright-holders:Nicola Salmoria, Aaron Giles, Alex W. Jackson
+/***************************************************************************
+
+ digfx.h
+
+ Device graphics interfaces.
+
+***************************************************************************/
+
+#pragma once
+
+#ifndef __EMU_H__
+#error Dont include this file directly; include emu.h instead.
+#endif
+
+#ifndef MAME_EMU_DIGFX_H
+#define MAME_EMU_DIGFX_H
+
+
+
+//**************************************************************************
+// CONSTANTS
+//**************************************************************************
+
+constexpr u8 MAX_GFX_ELEMENTS = 32;
+constexpr u16 MAX_GFX_PLANES = 8;
+// HBMAME - needed by monaco
+constexpr u16 MAX_GFX_SIZE = 64;
+
+
+
+//**************************************************************************
+// GRAPHICS LAYOUT MACROS
+//**************************************************************************
+
+#define EXTENDED_XOFFS { 0 }
+#define EXTENDED_YOFFS { 0 }
+
+#define GFX_RAW 0x12345678
+#define GFXLAYOUT_RAW( name, width, height, linemod, charmod ) \
+const gfx_layout name = { width, height, RGN_FRAC(1,1), 8, { GFX_RAW }, { 0 }, { linemod }, charmod };
+// When planeoffset[0] is set to GFX_RAW, the gfx data is left as-is, with no conversion.
+// No buffer is allocated for the decoded data, and gfxdata is set to point to the source
+// data.
+// yoffset[0] is the line modulo (*8) and charincrement the char modulo (*8). They are *8
+// for consistency with the usual behaviour, but the bottom 3 bits are not used.
+//
+// This special mode can be used for graphics that are already in 8bpp linear format,
+// or for unusual formats that don't fit our generic model and need to be decoded using
+// custom code. See blend_gfx() in atarigen.c for an example of the latter usage.
+
+
+// these macros describe gfx_layouts in terms of fractions of a region
+// they can be used for total, planeoffset, xoffset, yoffset
+#define RGN_FRAC(num,den) (0x80000000 | (((num) & 0x0f) << 27) | (((den) & 0x0f) << 23))
+#define IS_FRAC(offset) ((offset) & 0x80000000)
+#define FRAC_NUM(offset) (((offset) >> 27) & 0x0f)
+#define FRAC_DEN(offset) (((offset) >> 23) & 0x0f)
+#define FRAC_OFFSET(offset) ((offset) & 0x007fffff)
+
+// these macros are useful in gfx_layouts
+#define STEP2(START,STEP) (START),(START)+(STEP)
+#define STEP4(START,STEP) STEP2(START,STEP),STEP2((START)+2*(STEP),STEP)
+#define STEP8(START,STEP) STEP4(START,STEP),STEP4((START)+4*(STEP),STEP)
+#define STEP16(START,STEP) STEP8(START,STEP),STEP8((START)+8*(STEP),STEP)
+#define STEP32(START,STEP) STEP16(START,STEP),STEP16((START)+16*(STEP),STEP)
+#define STEP64(START,STEP) STEP32(START,STEP),STEP32((START)+32*(STEP),STEP)
+#define STEP128(START,STEP) STEP64(START,STEP),STEP64((START)+64*(STEP),STEP)
+#define STEP256(START,STEP) STEP128(START,STEP),STEP128((START)+128*(STEP),STEP)
+#define STEP512(START,STEP) STEP256(START,STEP),STEP256((START)+256*(STEP),STEP)
+#define STEP1024(START,STEP) STEP512(START,STEP),STEP512((START)+512*(STEP),STEP)
+#define STEP2048(START,STEP) STEP1024(START,STEP),STEP1024((START)+1024*(STEP),STEP)
+
+#define STEP2_INV(START,STEP) (START)+(STEP),(START)
+#define STEP4_INV(START,STEP) STEP2_INV(START+2*STEP,STEP),STEP2_INV(START,STEP)
+
+//**************************************************************************
+// GRAPHICS INFO MACROS
+//**************************************************************************
+
+// optional horizontal and vertical scaling factors
+#define GFXENTRY_XSCALEMASK 0x000000ff
+#define GFXENTRY_YSCALEMASK 0x0000ff00
+#define GFXENTRY_XSCALE(x) ((((x)-1) << 0) & GFXENTRY_XSCALEMASK)
+#define GFXENTRY_YSCALE(x) ((((x)-1) << 8) & GFXENTRY_YSCALEMASK)
+#define GFXENTRY_GETXSCALE(x) ((((x) & GFXENTRY_XSCALEMASK) >> 0) + 1)
+#define GFXENTRY_GETYSCALE(x) ((((x) & GFXENTRY_YSCALEMASK) >> 8) + 1)
+
+// GFXENTRY_RAM means region tag refers to a RAM share instead of a ROM region
+#define GFXENTRY_ROM 0x00000000
+#define GFXENTRY_RAM 0x00010000
+#define GFXENTRY_ISROM(x) (((x) & GFXENTRY_RAM) == 0)
+#define GFXENTRY_ISRAM(x) (((x) & GFXENTRY_RAM) != 0)
+
+// GFXENTRY_DEVICE means region tag is relative to this device instead of its owner
+#define GFXENTRY_DEVICE 0x00020000
+#define GFXENTRY_ISDEVICE(x) (((x) & GFXENTRY_DEVICE) != 0)
+
+// GFXENTRY_REVERSE reverses the bit order in the layout (0-7 = LSB-MSB instead of MSB-LSB)
+#define GFXENTRY_REVERSE 0x00040000
+#define GFXENTRY_ISREVERSE(x) (((x) & GFXENTRY_REVERSE) != 0)
+
+
+// these macros are used for declaring gfx_decode_entry info arrays
+#define GFXDECODE_START( name ) const gfx_decode_entry name[] = {
+#define GFXDECODE_END { 0 } };
+
+// use these to declare a gfx_decode_entry array as a member of a device class
+#define DECLARE_GFXDECODE_MEMBER( name ) static const gfx_decode_entry name[]
+#define GFXDECODE_MEMBER( name ) const gfx_decode_entry name[] = {
+// common gfx_decode_entry macros
+#define GFXDECODE_ENTRYX(region,offset,layout,start,colors,flags) { region, offset, &layout, start, colors, flags },
+#define GFXDECODE_ENTRY(region,offset,layout,start,colors) { region, offset, &layout, start, colors, 0 },
+
+// specialized gfx_decode_entry macros
+#define GFXDECODE_RAM(region,offset,layout,start,colors) { region, offset, &layout, start, colors, GFXENTRY_RAM },
+#define GFXDECODE_DEVICE(region,offset,layout,start,colors) { region, offset, &layout, start, colors, GFXENTRY_DEVICE },
+#define GFXDECODE_DEVICE_RAM(region,offset,layout,start,colors) { region, offset, &layout, start, colors, GFXENTRY_DEVICE | GFXENTRY_RAM },
+#define GFXDECODE_SCALE(region,offset,layout,start,colors,x,y) { region, offset, &layout, start, colors, GFXENTRY_XSCALE(x) | GFXENTRY_YSCALE(y) },
+#define GFXDECODE_REVERSEBITS(region,offset,layout,start,colors) { region, offset, &layout, start, colors, GFXENTRY_REVERSE },
+
+
+
+//**************************************************************************
+// TYPE DEFINITIONS
+//**************************************************************************
+
+struct gfx_layout
+{
+ u32 xoffs(int x) const { return (extxoffs != nullptr) ? extxoffs[x] : xoffset[x]; }
+ u32 yoffs(int y) const { return (extyoffs != nullptr) ? extyoffs[y] : yoffset[y]; }
+
+ u16 width; // pixel width of each element
+ u16 height; // pixel height of each element
+ u32 total; // total number of elements, or RGN_FRAC()
+ u16 planes; // number of bitplanes
+ u32 planeoffset[MAX_GFX_PLANES]; // bit offset of each bitplane
+ u32 xoffset[MAX_GFX_SIZE]; // bit offset of each horizontal pixel
+ u32 yoffset[MAX_GFX_SIZE]; // bit offset of each vertical pixel
+ u32 charincrement; // distance between two consecutive elements (in bits)
+ const u32 * extxoffs; // extended X offset array for really big layouts
+ const u32 * extyoffs; // extended Y offset array for really big layouts
+};
+
+struct gfx_decode_entry
+{
+ const char * memory_region; // memory region where the data resides
+ u32 start; // offset of beginning of data to decode
+ const gfx_layout *gfxlayout; // pointer to gfx_layout describing the layout; nullptr marks the end of the array
+ u16 color_codes_start; // offset in the color lookup table where color codes start
+ u16 total_color_codes; // total number of color codes
+ u32 flags; // flags and optional scaling factors
+};
+
+// ======================> device_gfx_interface
+
+class device_gfx_interface : public device_interface
+{
+public:
+ static const gfx_decode_entry empty[];
+
+ // construction/destruction
+ device_gfx_interface(const machine_config &mconfig, device_t &device,
+ const gfx_decode_entry *gfxinfo = nullptr, const char *palette_tag = finder_base::DUMMY_TAG);
+ virtual ~device_gfx_interface();
+
+ // configuration
+ void set_info(const gfx_decode_entry *gfxinfo) { m_gfxdecodeinfo = gfxinfo; }
+ template <typename T> void set_palette(T &&tag) { m_palette.set_tag(std::forward<T>(tag)); }
+
+ void set_palette_disable(bool disable);
+
+ // getters
+ device_palette_interface &palette() const { assert(m_palette); return *m_palette; }
+ gfx_element *gfx(u8 index) const { assert(index < MAX_GFX_ELEMENTS); return m_gfx[index].get(); }
+
+ // decoding
+ void decode_gfx(const gfx_decode_entry *gfxdecodeinfo);
+ void decode_gfx() { decode_gfx(m_gfxdecodeinfo); }
+
+ void set_gfx(u8 index, std::unique_ptr<gfx_element> &&element) { assert(index < MAX_GFX_ELEMENTS); m_gfx[index] = std::move(element); }
+
+protected:
+ // interface-level overrides
+ virtual void interface_validity_check(validity_checker &valid) const override;
+ virtual void interface_pre_start() override;
+ virtual void interface_post_start() override;
+
+private:
+ optional_device<device_palette_interface> m_palette; // configured tag for palette device
+ std::unique_ptr<gfx_element> m_gfx[MAX_GFX_ELEMENTS]; // array of pointers to graphic sets
+
+ // configuration
+ const gfx_decode_entry * m_gfxdecodeinfo; // pointer to array of gfx decode information
+ bool m_palette_is_disabled; // no palette associated with this gfx decode
+
+ // internal state
+ bool m_decoded; // have we processed our decode info yet?
+};
+
+// iterator
+typedef device_interface_iterator<device_gfx_interface> gfx_interface_iterator;
+
+
+#endif /* MAME_EMU_DIGFX_H */
diff --git a/docs/release/src/emu/driver.h b/docs/release/src/emu/driver.h
new file mode 100644
index 00000000000..b3d102342ce
--- /dev/null
+++ b/docs/release/src/emu/driver.h
@@ -0,0 +1,205 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+/***************************************************************************
+
+ driver.h
+
+ Core driver device base class.
+
+***************************************************************************/
+
+#pragma once
+
+#ifndef __EMU_H__
+#error Dont include this file directly; include emu.h instead.
+#endif
+
+#ifndef MAME_EMU_DRIVER_H
+#define MAME_EMU_DRIVER_H
+
+
+//**************************************************************************
+// CONFIGURATION MACROS
+//**************************************************************************
+
+// core machine callbacks
+#define MCFG_MACHINE_START_OVERRIDE(_class, _func) \
+ driver_device::static_set_callback(config.root_device(), driver_device::CB_MACHINE_START, driver_callback_delegate(&_class::MACHINE_START_NAME(_func), this));
+
+#define MCFG_MACHINE_RESET_OVERRIDE(_class, _func) \
+ driver_device::static_set_callback(config.root_device(), driver_device::CB_MACHINE_RESET, driver_callback_delegate(&_class::MACHINE_RESET_NAME(_func), this));
+
+#define MCFG_MACHINE_RESET_REMOVE() \
+ driver_device::static_set_callback(config.root_device(), driver_device::CB_MACHINE_RESET, driver_callback_delegate());
+
+// core sound callbacks
+#define MCFG_SOUND_START_OVERRIDE(_class, _func) \
+ driver_device::static_set_callback(config.root_device(), driver_device::CB_SOUND_START, driver_callback_delegate(&_class::SOUND_START_NAME(_func), this));
+
+#define MCFG_SOUND_RESET_OVERRIDE(_class, _func) \
+ driver_device::static_set_callback(config.root_device(), driver_device::CB_SOUND_RESET, driver_callback_delegate(&_class::SOUND_RESET_NAME(_func), this));
+
+
+// core video callbacks
+#define MCFG_VIDEO_START_OVERRIDE(_class, _func) \
+ driver_device::static_set_callback(config.root_device(), driver_device::CB_VIDEO_START, driver_callback_delegate(&_class::VIDEO_START_NAME(_func), this));
+
+#define MCFG_VIDEO_RESET_OVERRIDE(_class, _func) \
+ driver_device::static_set_callback(config.root_device(), driver_device::CB_VIDEO_RESET, driver_callback_delegate(&_class::VIDEO_RESET_NAME(_func), this));
+
+
+
+//**************************************************************************
+// OTHER MACROS
+//**************************************************************************
+
+#define MACHINE_START_NAME(name) machine_start_##name
+#define MACHINE_START_CALL_MEMBER(name) MACHINE_START_NAME(name)()
+#define DECLARE_MACHINE_START(name) void MACHINE_START_NAME(name)() ATTR_COLD
+#define MACHINE_START_MEMBER(cls,name) void cls::MACHINE_START_NAME(name)()
+
+#define MACHINE_RESET_NAME(name) machine_reset_##name
+#define MACHINE_RESET_CALL_MEMBER(name) MACHINE_RESET_NAME(name)()
+#define DECLARE_MACHINE_RESET(name) void MACHINE_RESET_NAME(name)()
+#define MACHINE_RESET_MEMBER(cls,name) void cls::MACHINE_RESET_NAME(name)()
+
+#define SOUND_START_NAME(name) sound_start_##name
+#define DECLARE_SOUND_START(name) void SOUND_START_NAME(name)() ATTR_COLD
+#define SOUND_START_MEMBER(cls,name) void cls::SOUND_START_NAME(name)()
+
+#define SOUND_RESET_NAME(name) sound_reset_##name
+#define SOUND_RESET_CALL_MEMBER(name) SOUND_RESET_NAME(name)()
+#define DECLARE_SOUND_RESET(name) void SOUND_RESET_NAME(name)()
+#define SOUND_RESET_MEMBER(cls,name) void cls::SOUND_RESET_NAME(name)()
+
+#define VIDEO_START_NAME(name) video_start_##name
+#define VIDEO_START_CALL_MEMBER(name) VIDEO_START_NAME(name)()
+#define DECLARE_VIDEO_START(name) void VIDEO_START_NAME(name)() ATTR_COLD
+#define VIDEO_START_MEMBER(cls,name) void cls::VIDEO_START_NAME(name)()
+
+#define VIDEO_RESET_NAME(name) video_reset_##name
+#define VIDEO_RESET_CALL_MEMBER(name) VIDEO_RESET_NAME(name)()
+#define DECLARE_VIDEO_RESET(name) void VIDEO_RESET_NAME(name)()
+#define VIDEO_RESET_MEMBER(cls,name) void cls::VIDEO_RESET_NAME(name)()
+
+
+
+//**************************************************************************
+// TYPE DEFINITIONS
+//**************************************************************************
+
+// forward declarations
+typedef delegate<void ()> driver_callback_delegate;
+
+
+// ======================> driver_device
+
+// base class for machine driver-specific devices
+class driver_device : public device_t
+{
+public:
+ // construction/destruction
+ driver_device(const machine_config &mconfig, device_type type, const char *tag);
+ virtual ~driver_device();
+
+ // getters
+ const game_driver &system() const { assert(m_system != nullptr); return *m_system; }
+
+ // indexes into our generic callbacks
+ enum callback_type
+ {
+ CB_MACHINE_START,
+ CB_MACHINE_RESET,
+ CB_SOUND_START,
+ CB_SOUND_RESET,
+ CB_VIDEO_START,
+ CB_VIDEO_RESET,
+ CB_COUNT
+ };
+
+ // inline configuration helpers
+ void set_game_driver(const game_driver &game);
+ static void static_set_callback(device_t &device, callback_type type, driver_callback_delegate callback);
+
+ // dummy driver_init callback
+ void empty_init();
+ void init_0() { } // HBMAME
+
+ // memory helpers
+ address_space &generic_space() const { return machine().dummy_space(); }
+
+ // output heler
+ output_manager &output() const { return machine().output(); }
+
+ void nmi_line_pulse(device_t &device);
+ void nmi_line_assert(device_t &device);
+
+ void irq0_line_hold(device_t &device);
+ void irq0_line_assert(device_t &device);
+
+ void irq1_line_hold(device_t &device);
+ void irq1_line_assert(device_t &device);
+
+ void irq2_line_hold(device_t &device);
+ void irq2_line_assert(device_t &device);
+
+ void irq3_line_hold(device_t &device);
+ void irq3_line_assert(device_t &device);
+
+ void irq4_line_hold(device_t &device);
+ void irq4_line_assert(device_t &device);
+
+ void irq5_line_hold(device_t &device);
+ void irq5_line_assert(device_t &device);
+
+ void irq6_line_hold(device_t &device);
+ void irq6_line_assert(device_t &device);
+
+ void irq7_line_hold(device_t &device);
+ void irq7_line_assert(device_t &device);
+
+ virtual void driver_init();
+
+protected:
+ // helpers called at startup
+ virtual void driver_start();
+ virtual void machine_start();
+ virtual void sound_start();
+ virtual void video_start();
+
+ // helpers called at reset
+ virtual void driver_reset();
+ virtual void machine_reset();
+ virtual void sound_reset();
+ virtual void video_reset();
+
+ // device-level overrides
+ virtual const tiny_rom_entry *device_rom_region() const override;
+ virtual void device_add_mconfig(machine_config &config) override;
+ virtual ioport_constructor device_input_ports() const override;
+ virtual void device_start() override;
+ virtual void device_reset_after_children() override;
+
+ // generic video
+ void flip_screen_set(u32 on);
+ void flip_screen_x_set(u32 on);
+ void flip_screen_y_set(u32 on);
+ u32 flip_screen() const { return m_flip_screen_x; }
+ u32 flip_screen_x() const { return m_flip_screen_x; }
+ u32 flip_screen_y() const { return m_flip_screen_y; }
+
+private:
+ // helpers
+ void updateflip();
+
+ // internal state
+ const game_driver *m_system; // pointer to the game driver
+ driver_callback_delegate m_callbacks[CB_COUNT]; // start/reset callbacks
+
+ // generic video
+ u8 m_flip_screen_x;
+ u8 m_flip_screen_y;
+};
+
+
+#endif /* MAME_EMU_DRIVER_H */
diff --git a/docs/release/src/emu/emuopts.cpp b/docs/release/src/emu/emuopts.cpp
new file mode 100644
index 00000000000..77e9f645f2f
--- /dev/null
+++ b/docs/release/src/emu/emuopts.cpp
@@ -0,0 +1,1285 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+/***************************************************************************
+
+ emuopts.cpp
+
+ Options file and command line management.
+
+***************************************************************************/
+
+#include "emu.h"
+#include "emuopts.h"
+#include "drivenum.h"
+#include "softlist_dev.h"
+#include "hashfile.h"
+
+#include <stack>
+
+
+//**************************************************************************
+// CORE EMULATOR OPTIONS
+//**************************************************************************
+
+const options_entry emu_options::s_option_entries[] =
+{
+ // unadorned options - only a single one supported at the moment
+ { OPTION_SYSTEMNAME, nullptr, OPTION_STRING, nullptr },
+ { OPTION_SOFTWARENAME, nullptr, OPTION_STRING, nullptr },
+
+ // config options
+ { nullptr, nullptr, OPTION_HEADER, "CORE CONFIGURATION OPTIONS" },
+ { OPTION_READCONFIG ";rc", "1", OPTION_BOOLEAN, "enable loading of configuration files" },
+ { OPTION_WRITECONFIG ";wc", "0", OPTION_BOOLEAN, "write configuration to (driver).ini on exit" },
+
+ // search path options
+ { nullptr, nullptr, OPTION_HEADER, "CORE SEARCH PATH OPTIONS" },
+ { OPTION_HOMEPATH, ".", OPTION_STRING, "path to base folder for plugin data (read/write)" },
+ { OPTION_MEDIAPATH ";rp;biospath;bp", "roms", OPTION_STRING, "path to ROM sets and hard disk images" },
+ { OPTION_HASHPATH ";hash_directory;hash", "hash", OPTION_STRING, "path to software definition files" },
+ { OPTION_SAMPLEPATH ";sp", "samples", OPTION_STRING, "path to audio sample sets" },
+ { OPTION_ARTPATH, "artwork", OPTION_STRING, "path to artwork files" },
+ { OPTION_CTRLRPATH, "ctrlr", OPTION_STRING, "path to controller definitions" },
+ { OPTION_INIPATH, ".", OPTION_STRING, "path to ini files" }, // MESSUI
+ { OPTION_FONTPATH, ".", OPTION_STRING, "path to font files" },
+ { OPTION_CHEATPATH, "cheat", OPTION_STRING, "path to cheat files" },
+ { OPTION_CROSSHAIRPATH, "crosshair", OPTION_STRING, "path to crosshair files" },
+ { OPTION_PLUGINSPATH, "plugins", OPTION_STRING, "path to plugin files" },
+ { OPTION_LANGUAGEPATH, "language", OPTION_STRING, "path to UI translation files" },
+ { OPTION_SWPATH, "software", OPTION_STRING, "path to loose software" },
+
+ // output directory options
+ { nullptr, nullptr, OPTION_HEADER, "CORE OUTPUT DIRECTORY OPTIONS" },
+ { OPTION_CFG_DIRECTORY, "cfg", OPTION_STRING, "directory to save configurations" },
+ { OPTION_NVRAM_DIRECTORY, "nvram", OPTION_STRING, "directory to save NVRAM contents" },
+ { OPTION_INPUT_DIRECTORY, "inp", OPTION_STRING, "directory to save input device logs" },
+ { OPTION_STATE_DIRECTORY, "sta", OPTION_STRING, "directory to save states" },
+ { OPTION_SNAPSHOT_DIRECTORY, "snap", OPTION_STRING, "directory to save/load screenshots" },
+ { OPTION_DIFF_DIRECTORY, "diff", OPTION_STRING, "directory to save hard drive image difference files" },
+ { OPTION_COMMENT_DIRECTORY, "comments", OPTION_STRING, "directory to save debugger comments" },
+
+ // state/playback options
+ { nullptr, nullptr, OPTION_HEADER, "CORE STATE/PLAYBACK OPTIONS" },
+ { OPTION_STATE, nullptr, OPTION_STRING, "saved state to load" },
+ { OPTION_AUTOSAVE, "0", OPTION_BOOLEAN, "automatically restore state on start and save on exit for supported systems" },
+ { OPTION_REWIND, "0", OPTION_BOOLEAN, "enable rewind savestates" },
+ { OPTION_REWIND_CAPACITY "(1-2048)", "100", OPTION_INTEGER, "rewind buffer size in megabytes" },
+ { OPTION_PLAYBACK ";pb", nullptr, OPTION_STRING, "playback an input file" },
+ { OPTION_RECORD ";rec", nullptr, OPTION_STRING, "record an input file" },
+ { OPTION_RECORD_TIMECODE, "0", OPTION_BOOLEAN, "record an input timecode file (requires -record option)" },
+ { OPTION_EXIT_AFTER_PLAYBACK, "0", OPTION_BOOLEAN, "close the program at the end of playback" },
+
+ { OPTION_MNGWRITE, nullptr, OPTION_STRING, "optional filename to write a MNG movie of the current session" },
+ { OPTION_AVIWRITE, nullptr, OPTION_STRING, "optional filename to write an AVI movie of the current session" },
+ { OPTION_WAVWRITE, nullptr, OPTION_STRING, "optional filename to write a WAV file of the current session" },
+ { OPTION_SNAPNAME, "%g/%i", OPTION_STRING, "override of the default snapshot/movie naming; %g == gamename, %i == index" },
+ { OPTION_SNAPSIZE, "auto", OPTION_STRING, "specify snapshot/movie resolution (<width>x<height>) or 'auto' to use minimal size " },
+ { OPTION_SNAPVIEW, "internal", OPTION_STRING, "specify snapshot/movie view or 'internal' to use internal pixel-aspect views" },
+ { OPTION_SNAPBILINEAR, "1", OPTION_BOOLEAN, "specify if the snapshot/movie should have bilinear filtering applied" },
+ { OPTION_STATENAME, "%g", OPTION_STRING, "override of the default state subfolder naming; %g == gamename" },
+ { OPTION_BURNIN, "0", OPTION_BOOLEAN, "create burn-in snapshots for each screen" },
+
+ // performance options
+ { nullptr, nullptr, OPTION_HEADER, "CORE PERFORMANCE OPTIONS" },
+ { OPTION_AUTOFRAMESKIP ";afs", "0", OPTION_BOOLEAN, "enable automatic frameskip adjustment to maintain emulation speed" },
+ { OPTION_FRAMESKIP ";fs(0-10)", "0", OPTION_INTEGER, "set frameskip to fixed value, 0-10 (autoframeskip must be disabled)" },
+ { OPTION_SECONDS_TO_RUN ";str", "0", OPTION_INTEGER, "number of emulated seconds to run before automatically exiting" },
+ { OPTION_THROTTLE, "1", OPTION_BOOLEAN, "throttle emulation to keep system running in sync with real time" },
+ { OPTION_SLEEP, "1", OPTION_BOOLEAN, "enable sleeping, which gives time back to other applications when idle" },
+ { OPTION_SPEED "(0.01-100)", "1.0", OPTION_FLOAT, "controls the speed of gameplay, relative to realtime; smaller numbers are slower" },
+ { OPTION_REFRESHSPEED ";rs", "0", OPTION_BOOLEAN, "automatically adjust emulation speed to keep the emulated refresh rate slower than the host screen" },
+
+ // render options
+ { nullptr, nullptr, OPTION_HEADER, "CORE RENDER OPTIONS" },
+ { OPTION_KEEPASPECT ";ka", "1", OPTION_BOOLEAN, "maintain aspect ratio when scaling to fill output screen/window" },
+ { OPTION_UNEVENSTRETCH ";ues", "1", OPTION_BOOLEAN, "allow non-integer ratios when scaling to fill output screen/window horizontally or vertically" },
+ { OPTION_UNEVENSTRETCHX ";uesx", "0", OPTION_BOOLEAN, "allow non-integer ratios when scaling to fill output screen/window horizontally"},
+ { OPTION_UNEVENSTRETCHY ";uesy", "0", OPTION_BOOLEAN, "allow non-integer ratios when scaling to fill otuput screen/window vertially"},
+ { OPTION_AUTOSTRETCHXY ";asxy", "0", OPTION_BOOLEAN, "automatically apply -unevenstretchx/y based on source native orientation"},
+ { OPTION_INTOVERSCAN ";ios", "0", OPTION_BOOLEAN, "allow overscan on integer scaled targets"},
+ { OPTION_INTSCALEX ";sx", "0", OPTION_INTEGER, "set horizontal integer scale factor"},
+ { OPTION_INTSCALEY ";sy", "0", OPTION_INTEGER, "set vertical integer scale factor"},
+
+ // rotation options
+ { nullptr, nullptr, OPTION_HEADER, "CORE ROTATION OPTIONS" },
+ { OPTION_ROTATE, "1", OPTION_BOOLEAN, "rotate the game screen according to the game's orientation when needed" },
+ { OPTION_ROR, "0", OPTION_BOOLEAN, "rotate screen clockwise 90 degrees" },
+ { OPTION_ROL, "0", OPTION_BOOLEAN, "rotate screen counterclockwise 90 degrees" },
+ { OPTION_AUTOROR, "0", OPTION_BOOLEAN, "automatically rotate screen clockwise 90 degrees if vertical" },
+ { OPTION_AUTOROL, "0", OPTION_BOOLEAN, "automatically rotate screen counterclockwise 90 degrees if vertical" },
+ { OPTION_FLIPX, "0", OPTION_BOOLEAN, "flip screen left-right" },
+ { OPTION_FLIPY, "0", OPTION_BOOLEAN, "flip screen upside-down" },
+
+ // artwork options
+ { nullptr, nullptr, OPTION_HEADER, "CORE ARTWORK OPTIONS" },
+ { OPTION_ARTWORK_CROP ";artcrop", "0", OPTION_BOOLEAN, "crop artwork so emulated screen image fills output screen/window in one axis" },
+ { OPTION_USE_BACKDROPS ";backdrop", "1", OPTION_BOOLEAN, "enable backdrops if artwork is enabled and available" },
+ { OPTION_USE_OVERLAYS ";overlay", "1", OPTION_BOOLEAN, "enable overlays if artwork is enabled and available" },
+ { OPTION_USE_BEZELS ";bezel", "1", OPTION_BOOLEAN, "enable bezels if artwork is enabled and available" },
+ { OPTION_USE_CPANELS ";cpanel", "1", OPTION_BOOLEAN, "enable cpanels if artwork is enabled and available" },
+ { OPTION_USE_MARQUEES ";marquee", "1", OPTION_BOOLEAN, "enable marquees if artwork is enabled and available" },
+ { OPTION_FALLBACK_ARTWORK, nullptr, OPTION_STRING, "fallback artwork if no external artwork or internal driver layout defined" },
+ { OPTION_OVERRIDE_ARTWORK, nullptr, OPTION_STRING, "override artwork for external artwork and internal driver layout" },
+
+ // screen options
+ { nullptr, nullptr, OPTION_HEADER, "CORE SCREEN OPTIONS" },
+ { OPTION_BRIGHTNESS "(0.1-2.0)", "1.0", OPTION_FLOAT, "default game screen brightness correction" },
+ { OPTION_CONTRAST "(0.1-2.0)", "1.0", OPTION_FLOAT, "default game screen contrast correction" },
+ { OPTION_GAMMA "(0.1-3.0)", "1.0", OPTION_FLOAT, "default game screen gamma correction" },
+ { OPTION_PAUSE_BRIGHTNESS "(0.0-1.0)", "0.65", OPTION_FLOAT, "amount to scale the screen brightness when paused" },
+ { OPTION_EFFECT, "none", OPTION_STRING, "name of a PNG file to use for visual effects, or 'none'" },
+
+ // vector options
+ { nullptr, nullptr, OPTION_HEADER, "CORE VECTOR OPTIONS" },
+ { OPTION_BEAM_WIDTH_MIN, "1.0", OPTION_FLOAT, "set vector beam width minimum" },
+ { OPTION_BEAM_WIDTH_MAX, "1.0", OPTION_FLOAT, "set vector beam width maximum" },
+ { OPTION_BEAM_INTENSITY_WEIGHT, "0", OPTION_FLOAT, "set vector beam intensity weight " },
+ { OPTION_FLICKER, "0", OPTION_FLOAT, "set vector flicker effect" },
+
+ // sound options
+ { nullptr, nullptr, OPTION_HEADER, "CORE SOUND OPTIONS" },
+ { OPTION_SAMPLERATE ";sr(1000-1000000)", "48000", OPTION_INTEGER, "set sound output sample rate" },
+ { OPTION_SAMPLES, "1", OPTION_BOOLEAN, "enable the use of external samples if available" },
+ { OPTION_VOLUME ";vol", "0", OPTION_INTEGER, "sound volume in decibels (-32 min, 0 max)" },
+
+ // input options
+ { nullptr, nullptr, OPTION_HEADER, "CORE INPUT OPTIONS" },
+ { OPTION_COIN_LOCKOUT ";coinlock", "1", OPTION_BOOLEAN, "ignore coin inputs if coin lockout ouput is active" },
+ { OPTION_CTRLR, nullptr, OPTION_STRING, "preconfigure for specified controller" },
+ { OPTION_MOUSE, "0", OPTION_BOOLEAN, "enable mouse input" },
+ { OPTION_JOYSTICK ";joy", "1", OPTION_BOOLEAN, "enable joystick input" },
+ { OPTION_LIGHTGUN ";gun", "0", OPTION_BOOLEAN, "enable lightgun input" },
+ { OPTION_MULTIKEYBOARD ";multikey", "0", OPTION_BOOLEAN, "enable separate input from each keyboard device (if present)" },
+ { OPTION_MULTIMOUSE, "0", OPTION_BOOLEAN, "enable separate input from each mouse device (if present)" },
+ { OPTION_STEADYKEY ";steady", "0", OPTION_BOOLEAN, "enable steadykey support" },
+ { OPTION_UI_ACTIVE, "0", OPTION_BOOLEAN, "enable user interface on top of emulated keyboard (if present)" },
+ { OPTION_OFFSCREEN_RELOAD ";reload", "0", OPTION_BOOLEAN, "convert lightgun button 2 into offscreen reload" },
+ { OPTION_JOYSTICK_MAP ";joymap", "auto", OPTION_STRING, "explicit joystick map, or auto to auto-select" },
+ { OPTION_JOYSTICK_DEADZONE ";joy_deadzone;jdz(0.00-1)", "0.3", OPTION_FLOAT, "center deadzone range for joystick where change is ignored (0.0 center, 1.0 end)" },
+ { OPTION_JOYSTICK_SATURATION ";joy_saturation;jsat(0.00-1)", "0.85", OPTION_FLOAT, "end of axis saturation range for joystick where change is ignored (0.0 center, 1.0 end)" },
+ { OPTION_NATURAL_KEYBOARD ";nat", "0", OPTION_BOOLEAN, "specifies whether to use a natural keyboard or not" },
+ { OPTION_JOYSTICK_CONTRADICTORY ";joy_contradictory","0", OPTION_BOOLEAN, "enable contradictory direction digital joystick input at the same time" },
+ { OPTION_COIN_IMPULSE, "0", OPTION_INTEGER, "set coin impulse time (n<0 disable impulse, n==0 obey driver, 0<n set time n)" },
+
+ // input autoenable options
+ { nullptr, nullptr, OPTION_HEADER, "CORE INPUT AUTOMATIC ENABLE OPTIONS" },
+ { OPTION_PADDLE_DEVICE ";paddle", "keyboard", OPTION_STRING, "enable (none|keyboard|mouse|lightgun|joystick) if a paddle control is present" },
+ { OPTION_ADSTICK_DEVICE ";adstick", "keyboard", OPTION_STRING, "enable (none|keyboard|mouse|lightgun|joystick) if an analog joystick control is present" },
+ { OPTION_PEDAL_DEVICE ";pedal", "keyboard", OPTION_STRING, "enable (none|keyboard|mouse|lightgun|joystick) if a pedal control is present" },
+ { OPTION_DIAL_DEVICE ";dial", "keyboard", OPTION_STRING, "enable (none|keyboard|mouse|lightgun|joystick) if a dial control is present" },
+ { OPTION_TRACKBALL_DEVICE ";trackball", "keyboard", OPTION_STRING, "enable (none|keyboard|mouse|lightgun|joystick) if a trackball control is present" },
+ { OPTION_LIGHTGUN_DEVICE, "keyboard", OPTION_STRING, "enable (none|keyboard|mouse|lightgun|joystick) if a lightgun control is present" },
+ { OPTION_POSITIONAL_DEVICE, "keyboard", OPTION_STRING, "enable (none|keyboard|mouse|lightgun|joystick) if a positional control is present" },
+ { OPTION_MOUSE_DEVICE, "mouse", OPTION_STRING, "enable (none|keyboard|mouse|lightgun|joystick) if a mouse control is present" },
+
+ // debugging options
+ { nullptr, nullptr, OPTION_HEADER, "CORE DEBUGGING OPTIONS" },
+ { OPTION_VERBOSE ";v", "0", OPTION_BOOLEAN, "display additional diagnostic information" },
+ { OPTION_LOG, "0", OPTION_BOOLEAN, "generate an error.log file" },
+ { OPTION_OSLOG, "0", OPTION_BOOLEAN, "output error.log data to system diagnostic output (debugger or standard error)" },
+ { OPTION_DEBUG ";d", "0", OPTION_BOOLEAN, "enable/disable debugger" },
+ { OPTION_UPDATEINPAUSE, "0", OPTION_BOOLEAN, "keep calling video updates while in pause" },
+ { OPTION_DEBUGSCRIPT, nullptr, OPTION_STRING, "script for debugger" },
+
+ // comm options
+ { nullptr, nullptr, OPTION_HEADER, "CORE COMM OPTIONS" },
+ { OPTION_COMM_LOCAL_HOST, "0.0.0.0", OPTION_STRING, "local address to bind to" },
+ { OPTION_COMM_LOCAL_PORT, "15112", OPTION_STRING, "local port to bind to" },
+ { OPTION_COMM_REMOTE_HOST, "127.0.0.1", OPTION_STRING, "remote address to connect to" },
+ { OPTION_COMM_REMOTE_PORT, "15112", OPTION_STRING, "remote port to connect to" },
+ { OPTION_COMM_FRAME_SYNC, "0", OPTION_BOOLEAN, "sync frames" },
+
+ // misc options
+ { nullptr, nullptr, OPTION_HEADER, "CORE MISC OPTIONS" },
+ { OPTION_DRC, "1", OPTION_BOOLEAN, "enable DRC CPU core if available" },
+ { OPTION_DRC_USE_C, "0", OPTION_BOOLEAN, "force DRC to use C backend" },
+ { OPTION_DRC_LOG_UML, "0", OPTION_BOOLEAN, "write DRC UML disassembly log" },
+ { OPTION_DRC_LOG_NATIVE, "0", OPTION_BOOLEAN, "write DRC native disassembly log" },
+ { OPTION_BIOS, nullptr, OPTION_STRING, "select the system BIOS to use" },
+ { OPTION_CHEAT ";c", "0", OPTION_BOOLEAN, "enable cheat subsystem" },
+ { OPTION_SKIP_GAMEINFO, "0", OPTION_BOOLEAN, "skip displaying the system information screen at startup" },
+ { OPTION_UI_FONT, "default", OPTION_STRING, "specify a font to use" },
+ { OPTION_UI, "cabinet", OPTION_STRING, "type of UI (simple|cabinet)" },
+ { OPTION_RAMSIZE ";ram", nullptr, OPTION_STRING, "size of RAM (if supported by driver)" },
+ { OPTION_CONFIRM_QUIT, "0", OPTION_BOOLEAN, "ask for confirmation before exiting" },
+ { OPTION_UI_MOUSE, "1", OPTION_BOOLEAN, "display UI mouse cursor" },
+ { OPTION_LANGUAGE ";lang", "English", OPTION_STRING, "set UI display language" },
+ { OPTION_NVRAM_SAVE ";nvwrite", "1", OPTION_BOOLEAN, "save NVRAM data on exit" },
+
+ { nullptr, nullptr, OPTION_HEADER, "SCRIPTING OPTIONS" },
+ { OPTION_AUTOBOOT_COMMAND ";ab", nullptr, OPTION_STRING, "command to execute after machine boot" },
+ { OPTION_AUTOBOOT_DELAY, "0", OPTION_INTEGER, "delay before executing autoboot command (seconds)" },
+ { OPTION_AUTOBOOT_SCRIPT ";script", nullptr, OPTION_STRING, "Lua script to execute after machine boot" },
+ { OPTION_CONSOLE, "0", OPTION_BOOLEAN, "enable emulator Lua console" },
+ { OPTION_PLUGINS, "1", OPTION_BOOLEAN, "enable Lua plugin support" },
+ { OPTION_PLUGIN, nullptr, OPTION_STRING, "list of plugins to enable" },
+ { OPTION_NO_PLUGIN, nullptr, OPTION_STRING, "list of plugins to disable" },
+
+ { nullptr, nullptr, OPTION_HEADER, "HTTP SERVER OPTIONS" },
+ { OPTION_HTTP, "0", OPTION_BOOLEAN, "enable HTTP server" },
+ { OPTION_HTTP_PORT, "8080", OPTION_INTEGER, "HTTP server port" },
+ { OPTION_HTTP_ROOT, "web", OPTION_STRING, "HTTP server document root" },
+
+ { nullptr }
+};
+
+
+
+//**************************************************************************
+// CUSTOM OPTION ENTRIES AND SUPPORT CLASSES
+//**************************************************************************
+
+namespace
+{
+ // custom option entry for the system name
+ class system_name_option_entry : public core_options::entry
+ {
+ public:
+ system_name_option_entry(emu_options &host)
+ : entry(OPTION_SYSTEMNAME)
+ , m_host(host)
+ {
+ }
+
+ virtual const char *value() const override
+ {
+ // This is returning an empty string instead of nullptr to signify that
+ // specifying the value is a meaningful operation. The option types that
+ // return nullptr are option types that cannot have a value (e.g. - commands)
+ //
+ // See comments in core_options::entry::value() and core_options::simple_entry::value()
+ return m_host.system() ? m_host.system()->name : "";
+ }
+
+ protected:
+ virtual void internal_set_value(std::string &&newvalue) override
+ {
+ m_host.set_system_name(std::move(newvalue));
+ }
+
+ private:
+ emu_options &m_host;
+ };
+
+ // custom option entry for the software name
+ class software_name_option_entry : public core_options::entry
+ {
+ public:
+ software_name_option_entry(emu_options &host)
+ : entry(OPTION_SOFTWARENAME)
+ , m_host(host)
+ {
+ }
+
+ protected:
+ virtual void internal_set_value(std::string &&newvalue) override
+ {
+ m_host.set_software(std::move(newvalue));
+ }
+
+ private:
+ emu_options &m_host;
+ };
+
+ // custom option entry for slots
+ class slot_option_entry : public core_options::entry
+ {
+ public:
+ slot_option_entry(const char *name, slot_option &host)
+ : entry(name)
+ , m_host(host)
+ {
+ }
+
+ virtual const char *value() const override
+ {
+ const char *result = nullptr;
+ if (m_host.specified())
+ {
+ // m_temp is a temporary variable used to keep the specified value
+ // so the result can be returned as 'const char *'. Obviously, this
+ // value will be trampled upon if value() is called again. This doesn't
+ // happen in practice
+ //
+ // In reality, I want to really return std::optional<std::string> here
+ m_temp = m_host.specified_value();
+ result = m_temp.c_str();
+ }
+ return result;
+ }
+
+ protected:
+ virtual void internal_set_value(std::string &&newvalue) override
+ {
+ m_host.specify(std::move(newvalue), false);
+ }
+
+ private:
+ slot_option & m_host;
+ mutable std::string m_temp;
+ };
+
+ // custom option entry for images
+ class image_option_entry : public core_options::entry
+ {
+ public:
+ image_option_entry(std::vector<std::string> &&names, image_option &host)
+ : entry(std::move(names))
+ , m_host(host)
+ {
+ }
+
+ virtual const char *value() const override
+ {
+ return m_host.value().c_str();
+ }
+
+ protected:
+ virtual void internal_set_value(std::string &&newvalue) override
+ {
+ m_host.specify(std::move(newvalue), false);
+ }
+
+ private:
+ image_option &m_host;
+ };
+
+ // existing option tracker class; used by slot/image calculus to identify existing
+ // options for later purging
+ template<typename T>
+ class existing_option_tracker
+ {
+ public:
+ existing_option_tracker(const std::unordered_map<std::string, T> &map)
+ {
+ m_vec.reserve(map.size());
+ for (const auto &entry : map)
+ m_vec.push_back(&entry.first);
+ }
+
+ template<typename TStr>
+ void remove(const TStr &str)
+ {
+ auto iter = std::find_if(
+ m_vec.begin(),
+ m_vec.end(),
+ [&str](const auto &x) { return *x == str; });
+ if (iter != m_vec.end())
+ m_vec.erase(iter);
+ }
+
+ std::vector<const std::string *>::iterator begin() { return m_vec.begin(); }
+ std::vector<const std::string *>::iterator end() { return m_vec.end(); }
+
+ private:
+ std::vector<const std::string *> m_vec;
+ };
+
+
+ //-------------------------------------------------
+ // get_full_option_names
+ //-------------------------------------------------
+
+ std::vector<std::string> get_full_option_names(const device_image_interface &image)
+ {
+ std::vector<std::string> result;
+ bool same_name = image.instance_name() == image.brief_instance_name();
+
+ result.push_back(image.instance_name());
+ if (!same_name)
+ result.push_back(image.brief_instance_name());
+
+ if (image.instance_name() != image.cannonical_instance_name())
+ {
+ result.push_back(image.cannonical_instance_name());
+ if (!same_name)
+ result.push_back(image.brief_instance_name() + "1");
+ }
+ return result;
+ }
+
+
+ //-------------------------------------------------
+ // conditionally_peg_priority
+ //-------------------------------------------------
+
+ void conditionally_peg_priority(core_options::entry::weak_ptr &entry, bool peg_priority)
+ {
+ // if the [image|slot] entry was specified outside of the context of the options sytem, we need
+ // to peg the priority of any associated core_options::entry at the maximum priority
+ if (peg_priority && !entry.expired())
+ entry.lock()->set_priority(OPTION_PRIORITY_MAXIMUM);
+ }
+}
+
+
+//**************************************************************************
+// EMU OPTIONS
+//**************************************************************************
+
+//-------------------------------------------------
+// emu_options - constructor
+//-------------------------------------------------
+
+emu_options::emu_options(option_support support)
+ : m_support(support)
+ , m_system(nullptr)
+ , m_coin_impulse(0)
+ , m_joystick_contradictory(false)
+ , m_sleep(true)
+ , m_refresh_speed(false)
+ , m_ui(UI_CABINET)
+{
+ // add entries
+ if (support == option_support::FULL || support == option_support::GENERAL_AND_SYSTEM)
+ add_entry(std::make_shared<system_name_option_entry>(*this));
+ if (support == option_support::FULL)
+ add_entry(std::make_shared<software_name_option_entry>(*this));
+ add_entries(emu_options::s_option_entries);
+
+ // adding handlers to keep copies of frequently requested options in member variables
+ set_value_changed_handler(OPTION_COIN_IMPULSE, [this](const char *value) { m_coin_impulse = int_value(OPTION_COIN_IMPULSE); });
+ set_value_changed_handler(OPTION_JOYSTICK_CONTRADICTORY, [this](const char *value) { m_joystick_contradictory = bool_value(OPTION_JOYSTICK_CONTRADICTORY); });
+ set_value_changed_handler(OPTION_SLEEP, [this](const char *value) { m_sleep = bool_value(OPTION_SLEEP); });
+ set_value_changed_handler(OPTION_REFRESHSPEED, [this](const char *value) { m_refresh_speed = bool_value(OPTION_REFRESHSPEED); });
+ set_value_changed_handler(OPTION_UI, [this](const std::string &value)
+ {
+ if (value == "simple")
+ m_ui = UI_SIMPLE;
+ else
+ m_ui = UI_CABINET;
+ });
+}
+
+
+//-------------------------------------------------
+// emu_options - destructor
+//-------------------------------------------------
+
+emu_options::~emu_options()
+{
+}
+
+
+//-------------------------------------------------
+// system_name
+//-------------------------------------------------
+
+const char *emu_options::system_name() const
+{
+ return m_system ? m_system->name : "";
+}
+
+
+//-------------------------------------------------
+// set_system_name - called to set the system
+// name; will adjust slot/image options as appropriate
+//-------------------------------------------------
+
+void emu_options::set_system_name(std::string &&new_system_name)
+{
+ const game_driver *new_system = nullptr;
+
+ // we are making an attempt - record what we're attempting
+ m_attempted_system_name = std::move(new_system_name);
+
+ // was a system name specified?
+ if (!m_attempted_system_name.empty())
+ {
+ // if so, first extract the base name (the reason for this is drag-and-drop on Windows; a side
+ // effect is a command line like 'mame pacman.foo' will work correctly, but so be it)
+ std::string new_system_base_name = core_filename_extract_base(m_attempted_system_name, true);
+
+ // perform the lookup (and error if it cannot be found)
+ int index = driver_list::find(new_system_base_name.c_str());
+ if (index < 0)
+ throw options_error_exception("Unknown system '%s'", m_attempted_system_name);
+ new_system = &driver_list::driver(index);
+ }
+
+ // did we change anything?
+ if (new_system != m_system)
+ {
+ // if so, specify the new system and update (if we're fully supporting slot/image options)
+ m_system = new_system;
+ m_software_name.clear();
+ if (m_support == option_support::FULL)
+ update_slot_and_image_options();
+ }
+}
+
+
+//-------------------------------------------------
+// set_system_name - called to set the system
+// name; will adjust slot/image options as appropriate
+//-------------------------------------------------
+
+void emu_options::set_system_name(const std::string &new_system_name)
+{
+ set_system_name(std::string(new_system_name));
+}
+
+
+//-------------------------------------------------
+// update_slot_and_image_options
+//-------------------------------------------------
+
+void emu_options::update_slot_and_image_options()
+{
+ bool changed;
+ do
+ {
+ changed = false;
+
+ // first we add and remove slot options depending on what has been configured in the
+ // device, bringing m_slot_options up to a state where it matches machine_config
+ if (add_and_remove_slot_options())
+ changed = true;
+
+ // second, we perform an analgous operation with m_image_options
+ if (add_and_remove_image_options())
+ changed = true;
+
+ // if we changed anything, we should reevaluate existing options
+ if (changed)
+ reevaluate_default_card_software();
+ } while (changed);
+}
+
+
+//-------------------------------------------------
+// add_and_remove_slot_options - add any missing
+// and/or purge extraneous slot options
+//-------------------------------------------------
+
+bool emu_options::add_and_remove_slot_options()
+{
+ bool changed = false;
+
+ // first, create a list of existing slot options; this is so we can purge
+ // any stray slot options that are no longer pertinent when we're done
+ existing_option_tracker<::slot_option> existing(m_slot_options);
+
+ // it is perfectly legal for this to be called without a system; we
+ // need to check for that condition!
+ if (m_system)
+ {
+ // create the configuration
+ machine_config config(*m_system, *this);
+
+ for (const device_slot_interface &slot : slot_interface_iterator(config.root_device()))
+ {
+ // come up with the cannonical name of the slot
+ const char *slot_option_name = slot.slot_name();
+
+ // erase this option from existing (so we don't purge it later)
+ existing.remove(slot_option_name);
+
+ // do we need to add this option?
+ if (!has_slot_option(slot_option_name))
+ {
+ // we do - add it to m_slot_options
+ auto pair = std::make_pair(slot_option_name, ::slot_option(*this, slot.default_option()));
+ ::slot_option &new_option(m_slot_options.emplace(std::move(pair)).first->second);
+ changed = true;
+
+ // for non-fixed slots, this slot needs representation in the options collection
+ if (!slot.fixed())
+ {
+ // first device? add the header as to be pretty
+ const char *header = "SLOT DEVICES";
+ if (!header_exists(header))
+ add_header(header);
+
+ // create a new entry in the options
+ auto new_entry = new_option.setup_option_entry(slot_option_name);
+
+ // and add it
+ add_entry(std::move(new_entry), header);
+ }
+ }
+
+ }
+ }
+
+ // at this point we need to purge stray slot options that may no longer be pertinent
+ for (auto &opt_name : existing)
+ {
+ auto iter = m_slot_options.find(*opt_name);
+ assert(iter != m_slot_options.end());
+
+ // if this is represented in core_options, remove it
+ if (iter->second.option_entry())
+ remove_entry(*iter->second.option_entry());
+
+ // remove this option
+ m_slot_options.erase(iter);
+ changed = true;
+ }
+
+ return changed;
+}
+
+
+//-------------------------------------------------
+// add_and_remove_slot_options - add any missing
+// and/or purge extraneous slot options
+//-------------------------------------------------
+
+bool emu_options::add_and_remove_image_options()
+{
+ // The logic for image options is superficially similar to the logic for slot options, but
+ // there is one larger piece of complexity. The image instance names (returned by the
+ // image_instance() call and surfaced in the UI) may change simply because we've added more
+ // devices. This is because the instance_name() for a singular cartridge device might be
+ // "cartridge" starting out, but become "cartridge1" when another cartridge device is added.
+ //
+ // To get around this behavior, our internal data structures work in terms of what is
+ // returned by cannonical_instance_name(), which will be something like "cartridge1" both
+ // for a singular cartridge device and the first cartridge in a multi cartridge system.
+ //
+ // The need for this behavior was identified by Tafoid when the following command line
+ // regressed:
+ //
+ // mame snes bsxsore -cart2 bszelda
+ //
+ // Before we were accounting for this behavior, 'bsxsore' got stored in "cartridge" and
+ // the association got lost when the second cartridge was added.
+
+ bool changed = false;
+
+ // first, create a list of existing image options; this is so we can purge
+ // any stray slot options that are no longer pertinent when we're done; we
+ // have to do this for both "flavors" of name
+ existing_option_tracker<::image_option> existing(m_image_options_cannonical);
+
+ // wipe the non-cannonical image options; we're going to rebuild it
+ m_image_options.clear();
+
+ // it is perfectly legal for this to be called without a system; we
+ // need to check for that condition!
+ if (m_system)
+ {
+ // create the configuration
+ machine_config config(*m_system, *this);
+
+ // iterate through all image devices
+ for (device_image_interface &image : image_interface_iterator(config.root_device()))
+ {
+ const std::string &cannonical_name(image.cannonical_instance_name());
+
+ // erase this option from existing (so we don't purge it later)
+ existing.remove(cannonical_name);
+
+ // do we need to add this option?
+ auto iter = m_image_options_cannonical.find(cannonical_name);
+ ::image_option *this_option = iter != m_image_options_cannonical.end() ? &iter->second : nullptr;
+ if (!this_option)
+ {
+ // we do - add it to both m_image_options_cannonical and m_image_options
+ auto pair = std::make_pair(cannonical_name, ::image_option(*this, image.cannonical_instance_name()));
+ this_option = &m_image_options_cannonical.emplace(std::move(pair)).first->second;
+ changed = true;
+
+ // if this image is user loadable, we have to surface it in the core_options
+ if (image.user_loadable())
+ {
+ // first device? add the header as to be pretty
+ const char *header = "IMAGE DEVICES";
+ if (!header_exists(header))
+ add_header(header);
+
+ // name this options
+ auto names = get_full_option_names(image);
+
+ // create a new entry in the options
+ auto new_entry = this_option->setup_option_entry(std::move(names));
+
+ // and add it
+ add_entry(std::move(new_entry), header);
+ }
+ }
+
+ // whether we added it or we didn't, we have to add it to the m_image_option map
+ m_image_options[image.instance_name()] = this_option;
+ }
+ }
+
+ // at this point we need to purge stray image options that may no longer be pertinent
+ for (auto &opt_name : existing)
+ {
+ auto iter = m_image_options_cannonical.find(*opt_name);
+ assert(iter != m_image_options_cannonical.end());
+
+ // if this is represented in core_options, remove it
+ if (iter->second.option_entry())
+ remove_entry(*iter->second.option_entry());
+
+ // remove this option
+ m_image_options_cannonical.erase(iter);
+ changed = true;
+ }
+
+ return changed;
+}
+
+
+//-------------------------------------------------
+// reevaluate_default_card_software - based on recent
+// changes in what images are mounted, give drivers
+// a chance to specify new default slot options
+//-------------------------------------------------
+
+void emu_options::reevaluate_default_card_software()
+{
+ // if we don't have a system specified, this is
+ // a meaningless operation
+ if (!m_system)
+ return;
+
+ bool found;
+ do
+ {
+ // set up the machine_config
+ machine_config config(*m_system, *this);
+ found = false;
+
+ // iterate through all slot devices
+ for (device_slot_interface &slot : slot_interface_iterator(config.root_device()))
+ {
+ // retrieve info about the device instance
+ auto &slot_opt(slot_option(slot.slot_name()));
+
+ // device_slot_interface::get_default_card_software() allows a device that
+ // implements both device_slot_interface and device_image_interface to
+ // probe an image and specify the card device that should be loaded
+ //
+ // In the repeated cycle of adding slots and slot devices, this gives a chance
+ // for devices to "plug in" default software list items. Of course, the fact
+ // that this is all shuffling options is brittle and roundabout, but such is
+ // the nature of software lists.
+ //
+ // In reality, having some sort of hook into the pipeline of slot/device evaluation
+ // makes sense, but the fact that it is joined at the hip to device_image_interface
+ // and device_slot_interface is unfortunate
+ std::string default_card_software = get_default_card_software(slot);
+ if (slot_opt.default_card_software() != default_card_software)
+ {
+ slot_opt.set_default_card_software(std::move(default_card_software));
+
+ // calling set_default_card_software() can cause a cascade of slot/image
+ // evaluations; we need to bail out of this loop because the iterator
+ // may be bad
+ found = true;
+ break;
+ }
+ }
+ } while (found);
+}
+
+
+//-------------------------------------------------
+// get_default_card_software
+//-------------------------------------------------
+
+std::string emu_options::get_default_card_software(device_slot_interface &slot)
+{
+ std::string image_path;
+ std::function<bool(util::core_file &, std::string&)> get_hashfile_extrainfo;
+
+ // figure out if an image option has been specified, and if so, get the image path out of the options
+ device_image_interface *image = dynamic_cast<device_image_interface *>(&slot);
+ if (image)
+ {
+ image_path = image_option(image->instance_name()).value();
+
+ get_hashfile_extrainfo = [image, this](util::core_file &file, std::string &extrainfo)
+ {
+ util::hash_collection hashes = image->calculate_hash_on_file(file);
+
+ return hashfile_extrainfo(
+ hash_path(),
+ image->device().mconfig().gamedrv(),
+ hashes,
+ extrainfo);
+ };
+ }
+
+ // create the hook
+ get_default_card_software_hook hook(image_path, std::move(get_hashfile_extrainfo));
+
+ // and invoke the slot's implementation of get_default_card_software()
+ return slot.get_default_card_software(hook);
+}
+
+
+//-------------------------------------------------
+// set_software - called to load "unqualified"
+// software out of a software list (e.g. - "mame nes 'zelda'")
+//-------------------------------------------------
+
+void emu_options::set_software(std::string &&new_software)
+{
+ // identify any options as a result of softlists
+ software_options softlist_opts = evaluate_initial_softlist_options(new_software);
+
+ while (!softlist_opts.slot.empty() || !softlist_opts.image.empty())
+ {
+ // track how many options we have
+ size_t before_size = softlist_opts.slot.size() + softlist_opts.image.size();
+
+ // keep a list of deferred options, in case anything is applied
+ // out of order
+ software_options deferred_opts;
+
+ // distribute slot options
+ for (auto &slot_opt : softlist_opts.slot)
+ {
+ auto iter = m_slot_options.find(slot_opt.first);
+ if (iter != m_slot_options.end())
+ iter->second.specify(std::move(slot_opt.second));
+ else
+ deferred_opts.slot[slot_opt.first] = std::move(slot_opt.second);
+ }
+
+ // distribute image options
+ for (auto &image_opt : softlist_opts.image)
+ {
+ auto iter = m_image_options.find(image_opt.first);
+ if (iter != m_image_options.end())
+ iter->second->specify(std::move(image_opt.second));
+ else
+ deferred_opts.image[image_opt.first] = std::move(image_opt.second);
+ }
+
+ // keep any deferred options for the next round
+ softlist_opts = std::move(deferred_opts);
+
+ // do we have any pending options after failing to distribute any?
+ size_t after_size = softlist_opts.slot.size() + softlist_opts.image.size();
+ if ((after_size > 0) && after_size >= before_size)
+ throw options_error_exception("Could not assign software option");
+ }
+
+ // we've succeeded; update the set name
+ m_software_name = std::move(new_software);
+}
+
+
+//-------------------------------------------------
+// evaluate_initial_softlist_options
+//-------------------------------------------------
+
+emu_options::software_options emu_options::evaluate_initial_softlist_options(const std::string &software_identifier)
+{
+ software_options results;
+
+ // load software specified at the command line (if any of course)
+ if (!software_identifier.empty())
+ {
+ // we have software; first identify the proper game_driver
+ if (!m_system)
+ throw options_error_exception("Cannot specify software without specifying system");
+
+ // and set up a configuration
+ machine_config config(*m_system, *this);
+ software_list_device_iterator iter(config.root_device());
+ if (iter.count() == 0)
+ throw emu_fatalerror(EMU_ERR_FATALERROR, "Error: unknown option: %s\n", software_identifier.c_str());
+
+ // and finally set up the stack
+ std::stack<std::string> software_identifier_stack;
+ software_identifier_stack.push(software_identifier);
+
+ // we need to keep evaluating softlist identifiers until the stack is empty
+ while (!software_identifier_stack.empty())
+ {
+ // pop the identifier
+ std::string current_software_identifier = std::move(software_identifier_stack.top());
+ software_identifier_stack.pop();
+
+ // and parse it
+ std::string list_name, software_name;
+ auto colon_pos = current_software_identifier.find_first_of(':');
+ if (colon_pos != std::string::npos)
+ {
+ list_name = current_software_identifier.substr(0, colon_pos);
+ software_name = current_software_identifier.substr(colon_pos + 1);
+ }
+ else
+ {
+ software_name = current_software_identifier;
+ }
+
+ // loop through all softlist devices, and try to find one capable of handling the requested software
+ bool found = false;
+ bool compatible = false;
+ for (software_list_device &swlistdev : iter)
+ {
+ if (list_name.empty() || (list_name == swlistdev.list_name()))
+ {
+ const software_info *swinfo = swlistdev.find(software_name);
+ if (swinfo != nullptr)
+ {
+ // loop through all parts
+ for (const software_part &swpart : swinfo->parts())
+ {
+ // only load compatible software this way
+ if (swlistdev.is_compatible(swpart) == SOFTWARE_IS_COMPATIBLE)
+ {
+ // we need to find a mountable image slot, but we need to ensure it is a slot
+ // for which we have not already distributed a part to
+ device_image_interface *image = software_list_device::find_mountable_image(
+ config,
+ swpart,
+ [&results](const device_image_interface &candidate) { return results.image.count(candidate.instance_name()) == 0; });
+
+ // did we find a slot to put this part into?
+ if (image != nullptr)
+ {
+ // we've resolved this software
+ results.image[image->instance_name()] = string_format("%s:%s:%s", swlistdev.list_name(), software_name, swpart.name());
+
+ // does this software part have a requirement on another part?
+ const char *requirement = swpart.feature("requirement");
+ if (requirement)
+ software_identifier_stack.push(requirement);
+ }
+ compatible = true;
+ }
+ found = true;
+ }
+
+ // identify other shared features specified as '<<slot name>>_default'
+ //
+ // example from SMS:
+ //
+ // <software name = "alexbmx">
+ // ...
+ // <sharedfeat name = "ctrl1_default" value = "paddle" />
+ // </software>
+ for (const feature_list_item &fi : swinfo->shared_info())
+ {
+ const std::string default_suffix = "_default";
+ if (fi.name().size() > default_suffix.size()
+ && fi.name().compare(fi.name().size() - default_suffix.size(), default_suffix.size(), default_suffix) == 0)
+ {
+ std::string slot_name = fi.name().substr(0, fi.name().size() - default_suffix.size());
+ results.slot[slot_name] = fi.value();
+ }
+ }
+ }
+ }
+ if (compatible)
+ break;
+ }
+
+ if (!compatible)
+ {
+ software_list_device::display_matches(config, nullptr, software_name);
+
+ // The text of this options_error_exception() is then passed to osd_printf_error() in cli_frontend::execute(). Therefore, it needs
+ // to be human readable text. We want to snake through a message about software incompatibility while being silent if that is not
+ // the case.
+ //
+ // Arguably, anything related to user-visible text should really be done within src/frontend. The invocation of
+ // software_list_device::display_matches() should really be done there as well
+ if (!found)
+ throw options_error_exception("");
+ else
+ throw options_error_exception("Software '%s' is incompatible with system '%s'\n", software_name, m_system->name);
+ }
+ }
+ }
+ return results;
+}
+
+
+//-------------------------------------------------
+// find_slot_option
+//-------------------------------------------------
+
+const slot_option *emu_options::find_slot_option(const std::string &device_name) const
+{
+ auto iter = m_slot_options.find(device_name);
+ return iter != m_slot_options.end() ? &iter->second : nullptr;
+}
+
+slot_option *emu_options::find_slot_option(const std::string &device_name)
+{
+ auto iter = m_slot_options.find(device_name);
+ return iter != m_slot_options.end() ? &iter->second : nullptr;
+}
+
+
+
+//-------------------------------------------------
+// slot_option
+//-------------------------------------------------
+
+const slot_option &emu_options::slot_option(const std::string &device_name) const
+{
+ const ::slot_option *opt = find_slot_option(device_name);
+ assert(opt && "Attempt to access non-existent slot option");
+ return *opt;
+}
+
+slot_option &emu_options::slot_option(const std::string &device_name)
+{
+ ::slot_option *opt = find_slot_option(device_name);
+ assert(opt && "Attempt to access non-existent slot option");
+ return *opt;
+}
+
+
+//-------------------------------------------------
+// image_option
+//-------------------------------------------------
+
+const image_option &emu_options::image_option(const std::string &device_name) const
+{
+ auto iter = m_image_options.find(device_name);
+ assert(iter != m_image_options.end() && "Attempt to access non-existent image option");
+ return *iter->second;
+}
+
+image_option &emu_options::image_option(const std::string &device_name)
+{
+ auto iter = m_image_options.find(device_name);
+ assert(iter != m_image_options.end() && "Attempt to access non-existent image option");
+ return *iter->second;
+}
+
+
+//-------------------------------------------------
+// command_argument_processed
+//-------------------------------------------------
+
+void emu_options::command_argument_processed()
+{
+ // some command line arguments require that the system name be set, so we can get slot options
+ if (command_arguments().size() == 1 && !core_iswildstr(command_arguments()[0].c_str()) &&
+ (command() == "listdevices" || (command() == "listslots") || (command() == "listmedia")))
+ {
+ set_system_name(command_arguments()[0]);
+ }
+}
+
+
+//**************************************************************************
+// SLOT OPTIONS
+//**************************************************************************
+
+//-------------------------------------------------
+// slot_option ctor
+//-------------------------------------------------
+
+slot_option::slot_option(emu_options &host, const char *default_value)
+ : m_host(host)
+ , m_specified(false)
+ , m_default_value(default_value ? default_value : "")
+{
+}
+
+
+//-------------------------------------------------
+// slot_option::value
+//-------------------------------------------------
+
+const std::string &slot_option::value() const
+{
+ // There are a number of ways that the value can be determined; there
+ // is a specific order of precedence:
+ //
+ // 1. Highest priority is whatever may have been specified by the user (whether it
+ // was specified at the command line, an INI file, or in the UI). We keep track
+ // of whether these values were specified this way
+ //
+ // Take note that slots have a notion of being "selectable". Slots that are not
+ // marked as selectable cannot be specified with this technique
+ //
+ // 2. Next highest is what is returned from get_default_card_software()
+ //
+ // 3. Last in priority is what was specified as the slot default. This comes from
+ // device setup
+ if (m_specified)
+ return m_specified_value;
+ else if (!m_default_card_software.empty())
+ return m_default_card_software;
+ else
+ return m_default_value;
+}
+
+
+//-------------------------------------------------
+// slot_option::specified_value
+//-------------------------------------------------
+
+std::string slot_option::specified_value() const
+{
+ std::string result;
+ if (m_specified)
+ {
+ result = m_specified_bios.empty()
+ ? m_specified_value
+ : util::string_format("%s,bios=%s", m_specified_value, m_specified_bios);
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// slot_option::specify
+//-------------------------------------------------
+
+void slot_option::specify(std::string &&text, bool peg_priority)
+{
+ // record the old value; we may need to trigger an update
+ const std::string old_value = value();
+
+ // we need to do some elementary parsing here
+ const char *bios_arg = ",bios=";
+ const size_t pos = text.find(bios_arg);
+ if (pos != std::string::npos)
+ {
+ m_specified = true;
+ m_specified_value = text.substr(0, pos);
+ m_specified_bios = text.substr(pos + strlen(bios_arg));
+ }
+ else
+ {
+ m_specified = true;
+ m_specified_value = std::move(text);
+ m_specified_bios = "";
+ }
+
+ conditionally_peg_priority(m_entry, peg_priority);
+
+ // we may have changed
+ possibly_changed(old_value);
+}
+
+
+//-------------------------------------------------
+// slot_option::specify
+//-------------------------------------------------
+
+void slot_option::specify(const std::string &text, bool peg_priority)
+{
+ specify(std::string(text), peg_priority);
+}
+
+
+//-------------------------------------------------
+// slot_option::set_default_card_software
+//-------------------------------------------------
+
+void slot_option::set_default_card_software(std::string &&s)
+{
+ // record the old value; we may need to trigger an update
+ const std::string old_value = value();
+
+ // update the default card software
+ m_default_card_software = std::move(s);
+
+ // we may have changed
+ possibly_changed(old_value);
+}
+
+
+//-------------------------------------------------
+// slot_option::possibly_changed
+//-------------------------------------------------
+
+void slot_option::possibly_changed(const std::string &old_value)
+{
+ if (value() != old_value)
+ m_host.update_slot_and_image_options();
+}
+
+
+//-------------------------------------------------
+// slot_option::set_bios
+//-------------------------------------------------
+
+void slot_option::set_bios(std::string &&text)
+{
+ if (!m_specified)
+ {
+ m_specified = true;
+ m_specified_value = value();
+ }
+ m_specified_bios = std::move(text);
+}
+
+
+//-------------------------------------------------
+// slot_option::setup_option_entry
+//-------------------------------------------------
+
+core_options::entry::shared_ptr slot_option::setup_option_entry(const char *name)
+{
+ // this should only be called once
+ assert(m_entry.expired());
+
+ // create the entry and return it
+ core_options::entry::shared_ptr entry = std::make_shared<slot_option_entry>(name, *this);
+ m_entry = entry;
+ return entry;
+}
+
+
+//**************************************************************************
+// IMAGE OPTIONS
+//**************************************************************************
+
+//-------------------------------------------------
+// image_option ctor
+//-------------------------------------------------
+
+image_option::image_option(emu_options &host, const std::string &cannonical_instance_name)
+ : m_host(host)
+ , m_canonical_instance_name(cannonical_instance_name)
+{
+}
+
+
+//-------------------------------------------------
+// image_option::specify
+//-------------------------------------------------
+
+void image_option::specify(const std::string &value, bool peg_priority)
+{
+ if (value != m_value)
+ {
+ m_value = value;
+ m_host.reevaluate_default_card_software();
+ }
+ conditionally_peg_priority(m_entry, peg_priority);
+}
+
+void image_option::specify(std::string &&value, bool peg_priority)
+{
+ if (value != m_value)
+ {
+ m_value = std::move(value);
+ m_host.reevaluate_default_card_software();
+ }
+ conditionally_peg_priority(m_entry, peg_priority);
+}
+
+
+//-------------------------------------------------
+// image_option::setup_option_entry
+//-------------------------------------------------
+
+core_options::entry::shared_ptr image_option::setup_option_entry(std::vector<std::string> &&names)
+{
+ // this should only be called once
+ assert(m_entry.expired());
+
+ // create the entry and return it
+ core_options::entry::shared_ptr entry = std::make_shared<image_option_entry>(std::move(names), *this);
+ m_entry = entry;
+ return entry;
+}
diff --git a/docs/release/src/emu/emuopts.h b/docs/release/src/emu/emuopts.h
new file mode 100644
index 00000000000..b1869c82c16
--- /dev/null
+++ b/docs/release/src/emu/emuopts.h
@@ -0,0 +1,541 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+/***************************************************************************
+
+ emuopts.h
+
+ Options file and command line management.
+
+***************************************************************************/
+
+#ifndef MAME_EMU_EMUOPTS_H
+#define MAME_EMU_EMUOPTS_H
+
+#pragma once
+
+#include "options.h"
+
+#define OPTION_PRIORITY_CMDLINE OPTION_PRIORITY_HIGH + 1
+// core options
+#define OPTION_SYSTEMNAME core_options::unadorned(0)
+#define OPTION_SOFTWARENAME core_options::unadorned(1)
+
+// core configuration options
+#define OPTION_READCONFIG "readconfig"
+#define OPTION_WRITECONFIG "writeconfig"
+
+// core search path options
+#define OPTION_HOMEPATH "homepath"
+#define OPTION_MEDIAPATH "rompath"
+#define OPTION_HASHPATH "hashpath"
+#define OPTION_SAMPLEPATH "samplepath"
+#define OPTION_ARTPATH "artpath"
+#define OPTION_CTRLRPATH "ctrlrpath"
+#define OPTION_INIPATH "inipath"
+#define OPTION_FONTPATH "fontpath"
+#define OPTION_CHEATPATH "cheatpath"
+#define OPTION_CROSSHAIRPATH "crosshairpath"
+#define OPTION_PLUGINSPATH "pluginspath"
+#define OPTION_LANGUAGEPATH "languagepath"
+#define OPTION_SWPATH "swpath"
+
+// core directory options
+#define OPTION_CFG_DIRECTORY "cfg_directory"
+#define OPTION_NVRAM_DIRECTORY "nvram_directory"
+#define OPTION_INPUT_DIRECTORY "input_directory"
+#define OPTION_STATE_DIRECTORY "state_directory"
+#define OPTION_SNAPSHOT_DIRECTORY "snapshot_directory"
+#define OPTION_DIFF_DIRECTORY "diff_directory"
+#define OPTION_COMMENT_DIRECTORY "comment_directory"
+
+// core state/playback options
+#define OPTION_STATE "state"
+#define OPTION_AUTOSAVE "autosave"
+#define OPTION_REWIND "rewind"
+#define OPTION_REWIND_CAPACITY "rewind_capacity"
+#define OPTION_PLAYBACK "playback"
+#define OPTION_RECORD "record"
+#define OPTION_RECORD_TIMECODE "record_timecode"
+#define OPTION_EXIT_AFTER_PLAYBACK "exit_after_playback"
+#define OPTION_MNGWRITE "mngwrite"
+#define OPTION_AVIWRITE "aviwrite"
+#define OPTION_WAVWRITE "wavwrite"
+#define OPTION_SNAPNAME "snapname"
+#define OPTION_SNAPSIZE "snapsize"
+#define OPTION_SNAPVIEW "snapview"
+#define OPTION_SNAPBILINEAR "snapbilinear"
+#define OPTION_STATENAME "statename"
+#define OPTION_BURNIN "burnin"
+
+// core performance options
+#define OPTION_AUTOFRAMESKIP "autoframeskip"
+#define OPTION_FRAMESKIP "frameskip"
+#define OPTION_SECONDS_TO_RUN "seconds_to_run"
+#define OPTION_THROTTLE "throttle"
+#define OPTION_SLEEP "sleep"
+#define OPTION_SPEED "speed"
+#define OPTION_REFRESHSPEED "refreshspeed"
+
+// core render options
+#define OPTION_KEEPASPECT "keepaspect"
+#define OPTION_UNEVENSTRETCH "unevenstretch"
+#define OPTION_UNEVENSTRETCHX "unevenstretchx"
+#define OPTION_UNEVENSTRETCHY "unevenstretchy"
+#define OPTION_AUTOSTRETCHXY "autostretchxy"
+#define OPTION_INTOVERSCAN "intoverscan"
+#define OPTION_INTSCALEX "intscalex"
+#define OPTION_INTSCALEY "intscaley"
+
+// core rotation options
+#define OPTION_ROTATE "rotate"
+#define OPTION_ROR "ror"
+#define OPTION_ROL "rol"
+#define OPTION_AUTOROR "autoror"
+#define OPTION_AUTOROL "autorol"
+#define OPTION_FLIPX "flipx"
+#define OPTION_FLIPY "flipy"
+
+// core artwork options
+#define OPTION_ARTWORK_CROP "artwork_crop"
+#define OPTION_USE_BACKDROPS "use_backdrops"
+#define OPTION_USE_OVERLAYS "use_overlays"
+#define OPTION_USE_BEZELS "use_bezels"
+#define OPTION_USE_CPANELS "use_cpanels"
+#define OPTION_USE_MARQUEES "use_marquees"
+#define OPTION_FALLBACK_ARTWORK "fallback_artwork"
+#define OPTION_OVERRIDE_ARTWORK "override_artwork"
+
+// core screen options
+#define OPTION_BRIGHTNESS "brightness"
+#define OPTION_CONTRAST "contrast"
+#define OPTION_GAMMA "gamma"
+#define OPTION_PAUSE_BRIGHTNESS "pause_brightness"
+#define OPTION_EFFECT "effect"
+
+// core vector options
+#define OPTION_BEAM_WIDTH_MIN "beam_width_min"
+#define OPTION_BEAM_WIDTH_MAX "beam_width_max"
+#define OPTION_BEAM_INTENSITY_WEIGHT "beam_intensity_weight"
+#define OPTION_FLICKER "flicker"
+
+// core sound options
+#define OPTION_SAMPLERATE "samplerate"
+#define OPTION_SAMPLES "samples"
+#define OPTION_VOLUME "volume"
+
+// core input options
+#define OPTION_COIN_LOCKOUT "coin_lockout"
+#define OPTION_CTRLR "ctrlr"
+#define OPTION_MOUSE "mouse"
+#define OPTION_JOYSTICK "joystick"
+#define OPTION_LIGHTGUN "lightgun"
+#define OPTION_MULTIKEYBOARD "multikeyboard"
+#define OPTION_MULTIMOUSE "multimouse"
+#define OPTION_STEADYKEY "steadykey"
+#define OPTION_UI_ACTIVE "ui_active"
+#define OPTION_OFFSCREEN_RELOAD "offscreen_reload"
+#define OPTION_JOYSTICK_MAP "joystick_map"
+#define OPTION_JOYSTICK_DEADZONE "joystick_deadzone"
+#define OPTION_JOYSTICK_SATURATION "joystick_saturation"
+#define OPTION_NATURAL_KEYBOARD "natural"
+#define OPTION_JOYSTICK_CONTRADICTORY "joystick_contradictory"
+#define OPTION_COIN_IMPULSE "coin_impulse"
+
+// input autoenable options
+#define OPTION_PADDLE_DEVICE "paddle_device"
+#define OPTION_ADSTICK_DEVICE "adstick_device"
+#define OPTION_PEDAL_DEVICE "pedal_device"
+#define OPTION_DIAL_DEVICE "dial_device"
+#define OPTION_TRACKBALL_DEVICE "trackball_device"
+#define OPTION_LIGHTGUN_DEVICE "lightgun_device"
+#define OPTION_POSITIONAL_DEVICE "positional_device"
+#define OPTION_MOUSE_DEVICE "mouse_device"
+
+// core debugging options
+#define OPTION_LOG "log"
+#define OPTION_DEBUG "debug"
+#define OPTION_VERBOSE "verbose"
+#define OPTION_OSLOG "oslog"
+#define OPTION_UPDATEINPAUSE "update_in_pause"
+#define OPTION_DEBUGSCRIPT "debugscript"
+
+// core misc options
+#define OPTION_DRC "drc"
+#define OPTION_DRC_USE_C "drc_use_c"
+#define OPTION_DRC_LOG_UML "drc_log_uml"
+#define OPTION_DRC_LOG_NATIVE "drc_log_native"
+#define OPTION_BIOS "bios"
+#define OPTION_CHEAT "cheat"
+#define OPTION_SKIP_GAMEINFO "skip_gameinfo"
+#define OPTION_UI_FONT "uifont"
+#define OPTION_UI "ui"
+#define OPTION_RAMSIZE "ramsize"
+#define OPTION_NVRAM_SAVE "nvram_save"
+
+// core comm options
+#define OPTION_COMM_LOCAL_HOST "comm_localhost"
+#define OPTION_COMM_LOCAL_PORT "comm_localport"
+#define OPTION_COMM_REMOTE_HOST "comm_remotehost"
+#define OPTION_COMM_REMOTE_PORT "comm_remoteport"
+#define OPTION_COMM_FRAME_SYNC "comm_framesync"
+
+#define OPTION_CONFIRM_QUIT "confirm_quit"
+#define OPTION_UI_MOUSE "ui_mouse"
+
+#define OPTION_AUTOBOOT_COMMAND "autoboot_command"
+#define OPTION_AUTOBOOT_DELAY "autoboot_delay"
+#define OPTION_AUTOBOOT_SCRIPT "autoboot_script"
+
+#define OPTION_CONSOLE "console"
+#define OPTION_PLUGINS "plugins"
+#define OPTION_PLUGIN "plugin"
+#define OPTION_NO_PLUGIN "noplugin"
+
+#define OPTION_LANGUAGE "language"
+
+#define OPTION_HTTP "http"
+#define OPTION_HTTP_PORT "http_port"
+#define OPTION_HTTP_ROOT "http_root"
+
+//**************************************************************************
+// TYPE DEFINITIONS
+//**************************************************************************
+
+class game_driver;
+class device_slot_interface;
+class emu_options;
+
+class slot_option
+{
+public:
+ slot_option(emu_options &host, const char *default_value);
+ slot_option(const slot_option &that) = delete;
+ slot_option(slot_option &&that) = default;
+
+ // accessors
+ const std::string &value() const;
+ std::string specified_value() const;
+ const std::string &bios() const { return m_specified_bios; }
+ const std::string &default_card_software() const { return m_default_card_software; }
+ bool specified() const { return m_specified; }
+ core_options::entry::shared_ptr option_entry() const { return m_entry.lock(); }
+
+ // seters
+ void specify(const std::string &text, bool peg_priority = true);
+ void specify(std::string &&text, bool peg_priority = true);
+ void set_bios(std::string &&text);
+ void set_default_card_software(std::string &&s);
+
+ // instantiates an option entry (don't call outside of emuopts.cpp)
+ core_options::entry::shared_ptr setup_option_entry(const char *name);
+
+private:
+ void possibly_changed(const std::string &old_value);
+
+ emu_options & m_host;
+ bool m_specified;
+ std::string m_specified_value;
+ std::string m_specified_bios;
+ std::string m_default_card_software;
+ std::string m_default_value;
+ core_options::entry::weak_ptr m_entry;
+};
+
+
+class image_option
+{
+public:
+ image_option(emu_options &host, const std::string &canonical_instance_name);
+ image_option(const image_option &that) = delete;
+ image_option(image_option &&that) = default;
+
+ // accessors
+ const std::string &canonical_instance_name() const { return m_canonical_instance_name; }
+ const std::string &value() const { return m_value; }
+ core_options::entry::shared_ptr option_entry() const { return m_entry.lock(); }
+
+ // mutators
+ void specify(const std::string &value, bool peg_priority = true);
+ void specify(std::string &&value, bool peg_priority = true);
+
+ // instantiates an option entry (don't call outside of emuopts.cpp)
+ core_options::entry::shared_ptr setup_option_entry(std::vector<std::string> &&names);
+
+private:
+ emu_options & m_host;
+ std::string m_canonical_instance_name;
+ std::string m_value;
+ core_options::entry::weak_ptr m_entry;
+};
+
+
+class emu_options : public core_options
+{
+ friend class slot_option;
+ friend class image_option;
+public:
+ enum ui_option
+ {
+ UI_CABINET,
+ UI_SIMPLE
+ };
+
+ enum class option_support
+ {
+ FULL, // full option support
+ GENERAL_AND_SYSTEM, // support for general options and system (no softlist)
+ GENERAL_ONLY // only support for general options
+ };
+
+ // construction/destruction
+ emu_options(option_support support = option_support::FULL);
+ ~emu_options();
+
+ // mutation
+ void set_system_name(const std::string &new_system_name);
+ void set_system_name(std::string &&new_system_name);
+ void set_software(std::string &&new_software);
+
+ // core options
+ const game_driver *system() const { return m_system; }
+ const char *system_name() const;
+ const std::string &attempted_system_name() const { return m_attempted_system_name; }
+ const std::string &software_name() const { return m_software_name; }
+
+ // core configuration options
+ bool read_config() const { return bool_value(OPTION_READCONFIG); }
+ bool write_config() const { return bool_value(OPTION_WRITECONFIG); }
+
+ // core search path options
+ const char *home_path() const { return value(OPTION_HOMEPATH); }
+ const char *media_path() const { return value(OPTION_MEDIAPATH); }
+ const char *hash_path() const { return value(OPTION_HASHPATH); }
+ const char *sample_path() const { return value(OPTION_SAMPLEPATH); }
+ const char *art_path() const { return value(OPTION_ARTPATH); }
+ const char *ctrlr_path() const { return value(OPTION_CTRLRPATH); }
+ const char *ini_path() const { return value(OPTION_INIPATH); }
+ const char *font_path() const { return value(OPTION_FONTPATH); }
+ const char *cheat_path() const { return value(OPTION_CHEATPATH); }
+ const char *crosshair_path() const { return value(OPTION_CROSSHAIRPATH); }
+ const char *plugins_path() const { return value(OPTION_PLUGINSPATH); }
+ const char *language_path() const { return value(OPTION_LANGUAGEPATH); }
+ const char *sw_path() const { return value(OPTION_SWPATH); }
+
+ // core directory options
+ const char *cfg_directory() const { return value(OPTION_CFG_DIRECTORY); }
+ const char *nvram_directory() const { return value(OPTION_NVRAM_DIRECTORY); }
+ const char *input_directory() const { return value(OPTION_INPUT_DIRECTORY); }
+ const char *state_directory() const { return value(OPTION_STATE_DIRECTORY); }
+ const char *snapshot_directory() const { return value(OPTION_SNAPSHOT_DIRECTORY); }
+ const char *diff_directory() const { return value(OPTION_DIFF_DIRECTORY); }
+ const char *comment_directory() const { return value(OPTION_COMMENT_DIRECTORY); }
+
+ // core state/playback options
+ const char *state() const { return value(OPTION_STATE); }
+ bool autosave() const { return bool_value(OPTION_AUTOSAVE); }
+ int rewind() const { return bool_value(OPTION_REWIND); }
+ int rewind_capacity() const { return int_value(OPTION_REWIND_CAPACITY); }
+ const char *playback() const { return value(OPTION_PLAYBACK); }
+ const char *record() const { return value(OPTION_RECORD); }
+ bool record_timecode() const { return bool_value(OPTION_RECORD_TIMECODE); }
+ bool exit_after_playback() const { return bool_value(OPTION_EXIT_AFTER_PLAYBACK); }
+ const char *mng_write() const { return value(OPTION_MNGWRITE); }
+ const char *avi_write() const { return value(OPTION_AVIWRITE); }
+ const char *wav_write() const { return value(OPTION_WAVWRITE); }
+ const char *snap_name() const { return value(OPTION_SNAPNAME); }
+ const char *snap_size() const { return value(OPTION_SNAPSIZE); }
+ const char *snap_view() const { return value(OPTION_SNAPVIEW); }
+ bool snap_bilinear() const { return bool_value(OPTION_SNAPBILINEAR); }
+ const char *state_name() const { return value(OPTION_STATENAME); }
+ bool burnin() const { return bool_value(OPTION_BURNIN); }
+
+ // core performance options
+ bool auto_frameskip() const { return bool_value(OPTION_AUTOFRAMESKIP); }
+ int frameskip() const { return int_value(OPTION_FRAMESKIP); }
+ int seconds_to_run() const { return int_value(OPTION_SECONDS_TO_RUN); }
+ bool throttle() const { return bool_value(OPTION_THROTTLE); }
+ bool sleep() const { return m_sleep; }
+ float speed() const { return float_value(OPTION_SPEED); }
+ bool refresh_speed() const { return m_refresh_speed; }
+
+ // core render options
+ bool keep_aspect() const { return bool_value(OPTION_KEEPASPECT); }
+ bool uneven_stretch() const { return bool_value(OPTION_UNEVENSTRETCH); }
+ bool uneven_stretch_x() const { return bool_value(OPTION_UNEVENSTRETCHX); }
+ bool uneven_stretch_y() const { return bool_value(OPTION_UNEVENSTRETCHY); }
+ bool auto_stretch_xy() const { return bool_value(OPTION_AUTOSTRETCHXY); }
+ bool int_overscan() const { return bool_value(OPTION_INTOVERSCAN); }
+ int int_scale_x() const { return int_value(OPTION_INTSCALEX); }
+ int int_scale_y() const { return int_value(OPTION_INTSCALEY); }
+
+ // core rotation options
+ bool rotate() const { return bool_value(OPTION_ROTATE); }
+ bool ror() const { return bool_value(OPTION_ROR); }
+ bool rol() const { return bool_value(OPTION_ROL); }
+ bool auto_ror() const { return bool_value(OPTION_AUTOROR); }
+ bool auto_rol() const { return bool_value(OPTION_AUTOROL); }
+ bool flipx() const { return bool_value(OPTION_FLIPX); }
+ bool flipy() const { return bool_value(OPTION_FLIPY); }
+
+ // core artwork options
+ bool artwork_crop() const { return bool_value(OPTION_ARTWORK_CROP); }
+ bool use_backdrops() const { return bool_value(OPTION_USE_BACKDROPS); }
+ bool use_overlays() const { return bool_value(OPTION_USE_OVERLAYS); }
+ bool use_bezels() const { return bool_value(OPTION_USE_BEZELS); }
+ bool use_cpanels() const { return bool_value(OPTION_USE_CPANELS); }
+ bool use_marquees() const { return bool_value(OPTION_USE_MARQUEES); }
+ const char *fallback_artwork() const { return value(OPTION_FALLBACK_ARTWORK); }
+ const char *override_artwork() const { return value(OPTION_OVERRIDE_ARTWORK); }
+
+ // core screen options
+ float brightness() const { return float_value(OPTION_BRIGHTNESS); }
+ float contrast() const { return float_value(OPTION_CONTRAST); }
+ float gamma() const { return float_value(OPTION_GAMMA); }
+ float pause_brightness() const { return float_value(OPTION_PAUSE_BRIGHTNESS); }
+ const char *effect() const { return value(OPTION_EFFECT); }
+
+ // core vector options
+ float beam_width_min() const { return float_value(OPTION_BEAM_WIDTH_MIN); }
+ float beam_width_max() const { return float_value(OPTION_BEAM_WIDTH_MAX); }
+ float beam_intensity_weight() const { return float_value(OPTION_BEAM_INTENSITY_WEIGHT); }
+ float flicker() const { return float_value(OPTION_FLICKER); }
+
+ // core sound options
+ int sample_rate() const { return int_value(OPTION_SAMPLERATE); }
+ bool samples() const { return bool_value(OPTION_SAMPLES); }
+ int volume() const { return int_value(OPTION_VOLUME); }
+
+ // core input options
+ bool coin_lockout() const { return bool_value(OPTION_COIN_LOCKOUT); }
+ const char *ctrlr() const { return value(OPTION_CTRLR); }
+ bool mouse() const { return bool_value(OPTION_MOUSE); }
+ bool joystick() const { return bool_value(OPTION_JOYSTICK); }
+ bool lightgun() const { return bool_value(OPTION_LIGHTGUN); }
+ bool multi_keyboard() const { return bool_value(OPTION_MULTIKEYBOARD); }
+ bool multi_mouse() const { return bool_value(OPTION_MULTIMOUSE); }
+ const char *paddle_device() const { return value(OPTION_PADDLE_DEVICE); }
+ const char *adstick_device() const { return value(OPTION_ADSTICK_DEVICE); }
+ const char *pedal_device() const { return value(OPTION_PEDAL_DEVICE); }
+ const char *dial_device() const { return value(OPTION_DIAL_DEVICE); }
+ const char *trackball_device() const { return value(OPTION_TRACKBALL_DEVICE); }
+ const char *lightgun_device() const { return value(OPTION_LIGHTGUN_DEVICE); }
+ const char *positional_device() const { return value(OPTION_POSITIONAL_DEVICE); }
+ const char *mouse_device() const { return value(OPTION_MOUSE_DEVICE); }
+ const char *joystick_map() const { return value(OPTION_JOYSTICK_MAP); }
+ float joystick_deadzone() const { return float_value(OPTION_JOYSTICK_DEADZONE); }
+ float joystick_saturation() const { return float_value(OPTION_JOYSTICK_SATURATION); }
+ bool steadykey() const { return bool_value(OPTION_STEADYKEY); }
+ bool ui_active() const { return bool_value(OPTION_UI_ACTIVE); }
+ bool offscreen_reload() const { return bool_value(OPTION_OFFSCREEN_RELOAD); }
+ bool natural_keyboard() const { return bool_value(OPTION_NATURAL_KEYBOARD); }
+ bool joystick_contradictory() const { return m_joystick_contradictory; }
+ int coin_impulse() const { return m_coin_impulse; }
+
+ // core debugging options
+ bool log() const { return bool_value(OPTION_LOG); }
+ bool debug() const { return bool_value(OPTION_DEBUG); }
+ bool verbose() const { return bool_value(OPTION_VERBOSE); }
+ bool oslog() const { return bool_value(OPTION_OSLOG); }
+ const char *debug_script() const { return value(OPTION_DEBUGSCRIPT); }
+ bool update_in_pause() const { return bool_value(OPTION_UPDATEINPAUSE); }
+
+ // core misc options
+ bool drc() const { return bool_value(OPTION_DRC); }
+ bool drc_use_c() const { return bool_value(OPTION_DRC_USE_C); }
+ bool drc_log_uml() const { return bool_value(OPTION_DRC_LOG_UML); }
+ bool drc_log_native() const { return bool_value(OPTION_DRC_LOG_NATIVE); }
+ const char *bios() const { return value(OPTION_BIOS); }
+ bool cheat() const { return bool_value(OPTION_CHEAT); }
+ bool skip_gameinfo() const { return bool_value(OPTION_SKIP_GAMEINFO); }
+ const char *ui_font() const { return value(OPTION_UI_FONT); }
+ ui_option ui() const { return m_ui; }
+ const char *ram_size() const { return value(OPTION_RAMSIZE); }
+ bool nvram_save() const { return bool_value(OPTION_NVRAM_SAVE); }
+
+ // core comm options
+ const char *comm_localhost() const { return value(OPTION_COMM_LOCAL_HOST); }
+ const char *comm_localport() const { return value(OPTION_COMM_LOCAL_PORT); }
+ const char *comm_remotehost() const { return value(OPTION_COMM_REMOTE_HOST); }
+ const char *comm_remoteport() const { return value(OPTION_COMM_REMOTE_PORT); }
+ bool comm_framesync() const { return bool_value(OPTION_COMM_FRAME_SYNC); }
+
+
+ bool confirm_quit() const { return bool_value(OPTION_CONFIRM_QUIT); }
+ bool ui_mouse() const { return bool_value(OPTION_UI_MOUSE); }
+
+ const char *autoboot_command() const { return value(OPTION_AUTOBOOT_COMMAND); }
+ int autoboot_delay() const { return int_value(OPTION_AUTOBOOT_DELAY); }
+ const char *autoboot_script() const { return value(OPTION_AUTOBOOT_SCRIPT); }
+
+ bool console() const { return bool_value(OPTION_CONSOLE); }
+
+ bool plugins() const { return bool_value(OPTION_PLUGINS); }
+
+ const char *plugin() const { return value(OPTION_PLUGIN); }
+ const char *no_plugin() const { return value(OPTION_NO_PLUGIN); }
+
+ const char *language() const { return value(OPTION_LANGUAGE); }
+
+ // Web server specific options
+ bool http() const { return bool_value(OPTION_HTTP); }
+ short http_port() const { return int_value(OPTION_HTTP_PORT); }
+ const char *http_root() const { return value(OPTION_HTTP_ROOT); }
+
+ // slots and devices - the values for these are stored outside of the core_options
+ // structure
+ const ::slot_option &slot_option(const std::string &device_name) const;
+ ::slot_option &slot_option(const std::string &device_name);
+ const ::slot_option *find_slot_option(const std::string &device_name) const;
+ ::slot_option *find_slot_option(const std::string &device_name);
+ bool has_slot_option(const std::string &device_name) const { return find_slot_option(device_name) ? true : false; }
+ const ::image_option &image_option(const std::string &device_name) const;
+ ::image_option &image_option(const std::string &device_name);
+
+protected:
+ virtual void command_argument_processed() override;
+
+private:
+ struct software_options
+ {
+ std::unordered_map<std::string, std::string> slot;
+ std::unordered_map<std::string, std::string> image;
+ };
+
+ // slot/image/softlist calculus
+ software_options evaluate_initial_softlist_options(const std::string &software_identifier);
+ void update_slot_and_image_options();
+ bool add_and_remove_slot_options();
+ bool add_and_remove_image_options();
+ void reevaluate_default_card_software();
+ std::string get_default_card_software(device_slot_interface &slot);
+
+ // static list of options entries
+ static const options_entry s_option_entries[];
+
+ // the basics
+ option_support m_support;
+ const game_driver * m_system;
+
+ // slots and devices
+ std::unordered_map<std::string, ::slot_option> m_slot_options;
+ std::unordered_map<std::string, ::image_option> m_image_options_cannonical;
+ std::unordered_map<std::string, ::image_option *> m_image_options;
+
+ // cached options, for scenarios where parsing core_options is too slow
+ int m_coin_impulse;
+ bool m_joystick_contradictory;
+ bool m_sleep;
+ bool m_refresh_speed;
+ ui_option m_ui;
+
+ // special option; the system name we tried to specify
+ std::string m_attempted_system_name;
+
+ // special option; the software set name that we did specify
+ std::string m_software_name;
+};
+
+// takes an existing emu_options and adds system specific options
+void osd_setup_osd_specific_emu_options(emu_options &opts);
+
+#endif // MAME_EMU_EMUOPTS_H
diff --git a/docs/release/src/emu/gamedrv.h b/docs/release/src/emu/gamedrv.h
new file mode 100644
index 00000000000..c5ad4bc94f9
--- /dev/null
+++ b/docs/release/src/emu/gamedrv.h
@@ -0,0 +1,285 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+/***************************************************************************
+
+ gamedrv.h
+
+ Definitions for game drivers.
+
+***************************************************************************/
+
+#ifndef MAME_EMU_GAMEDRV_H
+#define MAME_EMU_GAMEDRV_H
+
+#pragma once
+
+#include <type_traits>
+
+
+//**************************************************************************
+// CONSTANTS
+//**************************************************************************
+
+// maxima
+constexpr int MAX_DRIVER_NAME_CHARS = 16;
+
+struct machine_flags
+{
+ enum type : u32
+ {
+ MASK_ORIENTATION = 0x00000007,
+ MASK_TYPE = 0x00000038,
+
+ FLIP_X = 0x00000001,
+ FLIP_Y = 0x00000002,
+ SWAP_XY = 0x00000004,
+ ROT0 = 0x00000000,
+ ROT90 = FLIP_X | SWAP_XY,
+ ROT180 = FLIP_X | FLIP_Y,
+ ROT270 = FLIP_Y | SWAP_XY,
+
+ TYPE_ARCADE = 0x00000008, // coin-operated machine for public use
+ TYPE_CONSOLE = 0x00000010, // console system
+ TYPE_COMPUTER = 0x00000018, // any kind of computer including home computers, minis, calculators, ...
+ TYPE_OTHER = 0x00000038, // any other emulated system (e.g. clock, satellite receiver, ...)
+
+ NOT_WORKING = 0x00000040,
+ SUPPORTS_SAVE = 0x00000080, // system supports save states
+ NO_COCKTAIL = 0x00000100, // screen flip support is missing
+ IS_BIOS_ROOT = 0x00000200, // this driver entry is a BIOS root
+ REQUIRES_ARTWORK = 0x00000400, // requires external artwork for key game elements
+ CLICKABLE_ARTWORK = 0x00000800, // artwork is clickable and requires mouse cursor
+ UNOFFICIAL = 0x00001000, // unofficial hardware modification
+ NO_SOUND_HW = 0x00002000, // system has no sound output
+ MECHANICAL = 0x00004000, // contains mechanical parts (pinball, redemption games, ...)
+ IS_INCOMPLETE = 0x00008000 // official system with blatantly incomplete hardware/software
+ };
+};
+
+DECLARE_ENUM_BITWISE_OPERATORS(machine_flags::type);
+
+
+// flags for machine drivers
+constexpr u64 MACHINE_TYPE_ARCADE = machine_flags::TYPE_ARCADE;
+constexpr u64 MACHINE_TYPE_CONSOLE = machine_flags::TYPE_CONSOLE;
+constexpr u64 MACHINE_TYPE_COMPUTER = machine_flags::TYPE_COMPUTER;
+constexpr u64 MACHINE_TYPE_OTHER = machine_flags::TYPE_OTHER;
+constexpr u64 MACHINE_NOT_WORKING = machine_flags::NOT_WORKING;
+constexpr u64 MACHINE_SUPPORTS_SAVE = machine_flags::SUPPORTS_SAVE;
+constexpr u64 MACHINE_NO_COCKTAIL = machine_flags::NO_COCKTAIL;
+constexpr u64 MACHINE_IS_BIOS_ROOT = machine_flags::IS_BIOS_ROOT;
+constexpr u64 MACHINE_REQUIRES_ARTWORK = machine_flags::REQUIRES_ARTWORK;
+constexpr u64 MACHINE_CLICKABLE_ARTWORK = machine_flags::CLICKABLE_ARTWORK;
+constexpr u64 MACHINE_UNOFFICIAL = machine_flags::UNOFFICIAL;
+constexpr u64 MACHINE_NO_SOUND_HW = machine_flags::NO_SOUND_HW;
+constexpr u64 MACHINE_MECHANICAL = machine_flags::MECHANICAL;
+constexpr u64 MACHINE_IS_INCOMPLETE = machine_flags::IS_INCOMPLETE;
+
+// flags taht map to device feature flags
+constexpr u64 MACHINE_UNEMULATED_PROTECTION = 0x00000001'00000000; // game's protection not fully emulated
+constexpr u64 MACHINE_WRONG_COLORS = 0x00000002'00000000; // colors are totally wrong
+constexpr u64 MACHINE_IMPERFECT_COLORS = 0x00000004'00000000; // colors are not 100% accurate, but close
+constexpr u64 MACHINE_IMPERFECT_GRAPHICS = 0x00000008'00000000; // graphics are wrong/incomplete
+constexpr u64 MACHINE_NO_SOUND = 0x00000010'00000000; // sound is missing
+constexpr u64 MACHINE_IMPERFECT_SOUND = 0x00000020'00000000; // sound is known to be wrong
+constexpr u64 MACHINE_IMPERFECT_CONTROLS = 0x00000040'00000000; // controls are known to be imperfectly emulated
+constexpr u64 MACHINE_NODEVICE_MICROPHONE = 0x00000080'00000000; // any game/system that has unemulated audio capture device
+constexpr u64 MACHINE_NODEVICE_PRINTER = 0x00000100'00000000; // any game/system that has unemulated hardcopy output device
+constexpr u64 MACHINE_NODEVICE_LAN = 0x00000200'00000000; // any game/system that has unemulated local networking
+constexpr u64 MACHINE_IMPERFECT_TIMING = 0x00000400'00000000; // timing is known to be imperfectly emulated
+
+// useful combinations of flags
+constexpr u64 MACHINE_IS_SKELETON = MACHINE_NO_SOUND | MACHINE_NOT_WORKING; // flag combination for skeleton drivers
+constexpr u64 MACHINE_IS_SKELETON_MECHANICAL = MACHINE_IS_SKELETON | MACHINE_MECHANICAL | MACHINE_REQUIRES_ARTWORK; // flag combination for skeleton mechanical machines
+
+
+//**************************************************************************
+// TYPE DEFINITIONS
+//**************************************************************************
+
+// static POD structure describing each game driver entry
+class game_driver
+{
+public:
+ typedef void (*machine_creator_wrapper)(machine_config &, device_t &);
+ typedef void (*driver_init_wrapper)(device_t &);
+
+ static constexpr device_t::feature_type unemulated_features(u64 flags)
+ {
+ return
+ ((flags & MACHINE_WRONG_COLORS) ? device_t::feature::PALETTE : device_t::feature::NONE) |
+ ((flags & MACHINE_NO_SOUND) ? device_t::feature::SOUND : device_t::feature::NONE) |
+ ((flags & MACHINE_NODEVICE_MICROPHONE) ? device_t::feature::MICROPHONE : device_t::feature::NONE) |
+ ((flags & MACHINE_NODEVICE_PRINTER) ? device_t::feature::PRINTER : device_t::feature::NONE) |
+ ((flags & MACHINE_NODEVICE_LAN) ? device_t::feature::LAN : device_t::feature::NONE);
+ }
+
+ static constexpr device_t::feature_type imperfect_features(u64 flags)
+ {
+ return
+ ((flags & MACHINE_UNEMULATED_PROTECTION) ? device_t::feature::PROTECTION : device_t::feature::NONE) |
+ ((flags & MACHINE_IMPERFECT_COLORS) ? device_t::feature::PALETTE : device_t::feature::NONE) |
+ ((flags & MACHINE_IMPERFECT_GRAPHICS) ? device_t::feature::GRAPHICS : device_t::feature::NONE) |
+ ((flags & MACHINE_IMPERFECT_SOUND) ? device_t::feature::SOUND : device_t::feature::NONE) |
+ ((flags & MACHINE_IMPERFECT_CONTROLS) ? device_t::feature::CONTROLS : device_t::feature::NONE) |
+ ((flags & MACHINE_IMPERFECT_TIMING) ? device_t::feature::TIMING : device_t::feature::NONE);
+ }
+
+ device_type type; // static type info for driver class
+ const char * parent; // if this is a clone, the name of the parent
+ const char * year; // year the game was released
+ const char * manufacturer; // manufacturer of the game
+ machine_creator_wrapper machine_creator; // machine driver tokens
+ ioport_constructor ipt; // pointer to constructor for input ports
+ driver_init_wrapper driver_init; // DRIVER_INIT callback
+ const tiny_rom_entry * rom; // pointer to list of ROMs for the game
+ const char * compatible_with;
+ const internal_layout * default_layout; // default internally defined layout
+ machine_flags::type flags; // orientation and other flags; see defines above
+ char name[MAX_DRIVER_NAME_CHARS + 1]; // short name of the game
+};
+
+
+//**************************************************************************
+// MACROS
+//**************************************************************************
+
+// wrappers for declaring and defining game drivers
+#define GAME_NAME(name) driver_##name
+#define GAME_TRAITS_NAME(name) driver_##name##traits
+#define GAME_EXTERN(name) extern game_driver const GAME_NAME(name)
+
+// static game traits
+#define GAME_DRIVER_TRAITS(NAME, FULLNAME) \
+namespace { \
+ struct GAME_TRAITS_NAME(NAME) { static constexpr char const shortname[] = #NAME, fullname[] = FULLNAME, source[] = __FILE__; }; \
+ constexpr char const GAME_TRAITS_NAME(NAME)::shortname[], GAME_TRAITS_NAME(NAME)::fullname[], GAME_TRAITS_NAME(NAME)::source[]; \
+}
+#define GAME_DRIVER_TYPE(NAME, CLASS, FLAGS) \
+driver_device_creator< \
+ CLASS, \
+ (GAME_TRAITS_NAME(NAME)::shortname), \
+ (GAME_TRAITS_NAME(NAME)::fullname), \
+ (GAME_TRAITS_NAME(NAME)::source), \
+ game_driver::unemulated_features(FLAGS), \
+ game_driver::imperfect_features(FLAGS)>
+
+// HBMAME start
+// standard GAME() macro
+#define HACK(YEAR,NAME,PARENT,MACHINE,INPUT,CLASS,INIT,MONITOR,COMPANY,FULLNAME,FLAGS) \
+GAME_DRIVER_TRAITS(NAME,FULLNAME) \
+extern game_driver const GAME_NAME(NAME) \
+{ \
+ GAME_DRIVER_TYPE(NAME, CLASS, FLAGS), \
+ #PARENT, \
+ #YEAR, \
+ COMPANY, \
+ [] (machine_config &config, device_t &owner) { downcast<CLASS &>(owner).MACHINE(config); }, \
+ INPUT_PORTS_NAME(INPUT), \
+ [] (device_t &owner) { downcast<CLASS &>(owner).init_##INIT(); }, \
+ ROM_NAME(NAME), \
+ nullptr, \
+ nullptr, \
+ machine_flags::type(u32((MONITOR) | (FLAGS) | MACHINE_TYPE_ARCADE)),\
+ #NAME \
+};
+// HBMAME end
+
+// standard GAME() macro
+#define GAME(YEAR,NAME,PARENT,MACHINE,INPUT,CLASS,INIT,MONITOR,COMPANY,FULLNAME,FLAGS) \
+GAME_DRIVER_TRAITS(NAME,FULLNAME) \
+extern game_driver const GAME_NAME(NAME) \
+{ \
+ GAME_DRIVER_TYPE(NAME, CLASS, FLAGS), \
+ #PARENT, \
+ #YEAR, \
+ COMPANY, \
+ [] (machine_config &config, device_t &owner) { downcast<CLASS &>(owner).MACHINE(config); }, \
+ INPUT_PORTS_NAME(INPUT), \
+ [] (device_t &owner) { downcast<CLASS &>(owner).INIT(); }, \
+ ROM_NAME(NAME), \
+ nullptr, \
+ nullptr, \
+ machine_flags::type(u32((MONITOR) | (FLAGS) | MACHINE_TYPE_ARCADE)),\
+ #NAME \
+};
+
+// standard macro with additional layout
+#define GAMEL(YEAR,NAME,PARENT,MACHINE,INPUT,CLASS,INIT,MONITOR,COMPANY,FULLNAME,FLAGS,LAYOUT) \
+GAME_DRIVER_TRAITS(NAME,FULLNAME) \
+extern game_driver const GAME_NAME(NAME) \
+{ \
+ GAME_DRIVER_TYPE(NAME, CLASS, FLAGS), \
+ #PARENT, \
+ #YEAR, \
+ COMPANY, \
+ [] (machine_config &config, device_t &owner) { downcast<CLASS &>(owner).MACHINE(config); }, \
+ INPUT_PORTS_NAME(INPUT), \
+ [] (device_t &owner) { downcast<CLASS &>(owner).INIT(); }, \
+ ROM_NAME(NAME), \
+ nullptr, \
+ &LAYOUT, \
+ machine_flags::type(u32((MONITOR) | (FLAGS) | MACHINE_TYPE_ARCADE)),\
+ #NAME \
+};
+
+
+// standard console definition macro
+#define CONS(YEAR,NAME,PARENT,COMPAT,MACHINE,INPUT,CLASS,INIT,COMPANY,FULLNAME,FLAGS) \
+GAME_DRIVER_TRAITS(NAME,FULLNAME) \
+extern game_driver const GAME_NAME(NAME) \
+{ \
+ GAME_DRIVER_TYPE(NAME, CLASS, FLAGS), \
+ #PARENT, \
+ #YEAR, \
+ COMPANY, \
+ [] (machine_config &config, device_t &owner) { downcast<CLASS &>(owner).MACHINE(config); }, \
+ INPUT_PORTS_NAME(INPUT), \
+ [] (device_t &owner) { downcast<CLASS &>(owner).INIT(); }, \
+ ROM_NAME(NAME), \
+ #COMPAT, \
+ nullptr, \
+ machine_flags::type(u32(ROT0 | (FLAGS) | MACHINE_TYPE_CONSOLE)), \
+ #NAME \
+};
+
+// standard computer definition macro
+#define COMP(YEAR,NAME,PARENT,COMPAT,MACHINE,INPUT,CLASS,INIT,COMPANY,FULLNAME,FLAGS) \
+GAME_DRIVER_TRAITS(NAME,FULLNAME) \
+extern game_driver const GAME_NAME(NAME) \
+{ \
+ GAME_DRIVER_TYPE(NAME, CLASS, FLAGS), \
+ #PARENT, \
+ #YEAR, \
+ COMPANY, \
+ [] (machine_config &config, device_t &owner) { downcast<CLASS &>(owner).MACHINE(config); }, \
+ INPUT_PORTS_NAME(INPUT), \
+ [] (device_t &owner) { downcast<CLASS &>(owner).INIT(); }, \
+ ROM_NAME(NAME), \
+ #COMPAT, \
+ nullptr, \
+ machine_flags::type(u32(ROT0 | (FLAGS) | MACHINE_TYPE_COMPUTER)), \
+ #NAME \
+};
+
+// standard system definition macro
+#define SYST(YEAR,NAME,PARENT,COMPAT,MACHINE,INPUT,CLASS,INIT,COMPANY,FULLNAME,FLAGS) \
+GAME_DRIVER_TRAITS(NAME,FULLNAME) \
+extern game_driver const GAME_NAME(NAME) \
+{ \
+ GAME_DRIVER_TYPE(NAME, CLASS, FLAGS), \
+ #PARENT, \
+ #YEAR, \
+ COMPANY, \
+ [] (machine_config &config, device_t &owner) { downcast<CLASS &>(owner).MACHINE(config); }, \
+ INPUT_PORTS_NAME(INPUT), \
+ [] (device_t &owner) { downcast<CLASS &>(owner).INIT(); }, \
+ ROM_NAME(NAME), \
+ #COMPAT, \
+ nullptr, \
+ machine_flags::type(u32(ROT0 | (FLAGS) | MACHINE_TYPE_OTHER)), \
+ #NAME \
+};
+
+
+#endif // MAME_EMU_GAMEDRV_H
diff --git a/docs/release/src/emu/validity.cpp b/docs/release/src/emu/validity.cpp
new file mode 100644
index 00000000000..2c87ec0081a
--- /dev/null
+++ b/docs/release/src/emu/validity.cpp
@@ -0,0 +1,2250 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles, Paul Priest
+/***************************************************************************
+
+ validity.cpp
+
+ Validity checks on internal data structures.
+
+***************************************************************************/
+
+#include "emu.h"
+#include "validity.h"
+
+#include "emuopts.h"
+#include "romload.h"
+#include "video/rgbutil.h"
+
+#include <ctype.h>
+#include <type_traits>
+#include <typeinfo>
+
+
+//**************************************************************************
+// TYPE DEFINITIONS
+//**************************************************************************
+
+//**************************************************************************
+// INLINE FUNCTIONS
+//**************************************************************************
+
+//-------------------------------------------------
+// ioport_string_from_index - return an indexed
+// string from the I/O port system
+//-------------------------------------------------
+
+inline const char *validity_checker::ioport_string_from_index(u32 index)
+{
+ return ioport_configurer::string_from_token((const char *)(uintptr_t)index);
+}
+
+
+//-------------------------------------------------
+// get_defstr_index - return the index of the
+// string assuming it is one of the default
+// strings
+//-------------------------------------------------
+
+inline int validity_checker::get_defstr_index(const char *string, bool suppress_error)
+{
+ // check for strings that should be DEF_STR
+ auto strindex = m_defstr_map.find(string);
+ if (!suppress_error && strindex != m_defstr_map.end() && string != ioport_string_from_index(strindex->second))
+ osd_printf_error("Must use DEF_STR( %s )\n", string);
+ return (strindex != m_defstr_map.end()) ? strindex->second : 0;
+}
+
+
+//-------------------------------------------------
+// random_u64
+// random_s64
+// random_u32
+// random_s32
+//-------------------------------------------------
+#undef rand
+inline s32 validity_checker::random_i32() { return s32(random_u32()); }
+inline u32 validity_checker::random_u32() { return rand() ^ (rand() << 15); }
+inline s64 validity_checker::random_i64() { return s64(random_u64()); }
+inline u64 validity_checker::random_u64() { return u64(random_u32()) ^ (u64(random_u32()) << 30); }
+
+
+
+//-------------------------------------------------
+// validate_tag - ensure that the given tag
+// meets the general requirements
+//-------------------------------------------------
+
+void validity_checker::validate_tag(const char *tag)
+{
+ // some common names that are now deprecated
+ if (strcmp(tag, "main") == 0 || strcmp(tag, "audio") == 0 || strcmp(tag, "sound") == 0 || strcmp(tag, "left") == 0 || strcmp(tag, "right") == 0)
+ osd_printf_error("Invalid generic tag '%s' used\n", tag);
+
+ // scan for invalid characters
+ static char const *const validchars = "abcdefghijklmnopqrstuvwxyz0123456789_.:^$";
+ for (const char *p = tag; *p != 0; p++)
+ {
+ // only lower-case permitted
+ if (*p != tolower(u8(*p)))
+ {
+ osd_printf_error("Tag '%s' contains upper-case characters\n", tag);
+ break;
+ }
+ if (*p == ' ')
+ {
+ osd_printf_error("Tag '%s' contains spaces\n", tag);
+ break;
+ }
+ if (strchr(validchars, *p) == nullptr)
+ {
+ osd_printf_error("Tag '%s' contains invalid character '%c'\n", tag, *p);
+ break;
+ }
+ }
+
+ // find the start of the final tag
+ const char *begin = strrchr(tag, ':');
+ if (begin == nullptr)
+ begin = tag;
+ else
+ begin += 1;
+
+ // 0-length = bad
+ if (*begin == 0)
+ osd_printf_error("Found 0-length tag\n");
+
+ // too short/too long = bad
+ if (strlen(begin) < MIN_TAG_LENGTH)
+ osd_printf_error("Tag '%s' is too short (must be at least %d characters)\n", tag, MIN_TAG_LENGTH);
+}
+
+
+
+//**************************************************************************
+// VALIDATION FUNCTIONS
+//**************************************************************************
+
+//-------------------------------------------------
+// validity_checker - constructor
+//-------------------------------------------------
+
+validity_checker::validity_checker(emu_options &options)
+ : m_drivlist(options)
+ , m_errors(0)
+ , m_warnings(0)
+ , m_print_verbose(options.verbose())
+ , m_current_driver(nullptr)
+ , m_current_config(nullptr)
+ , m_current_device(nullptr)
+ , m_current_ioport(nullptr)
+ , m_validate_all(false)
+{
+ // pre-populate the defstr map with all the default strings
+ for (int strnum = 1; strnum < INPUT_STRING_COUNT; strnum++)
+ {
+ const char *string = ioport_string_from_index(strnum);
+ if (string != nullptr)
+ m_defstr_map.insert(std::make_pair(string, strnum));
+ }
+}
+
+//-------------------------------------------------
+// validity_checker - destructor
+//-------------------------------------------------
+
+validity_checker::~validity_checker()
+{
+ validate_end();
+}
+
+//-------------------------------------------------
+// check_driver - check a single driver
+//-------------------------------------------------
+
+void validity_checker::check_driver(const game_driver &driver)
+{
+ // simply validate the one driver
+ validate_begin();
+ validate_one(driver);
+ validate_end();
+}
+
+
+//-------------------------------------------------
+// check_shared_source - check all drivers that
+// share the same source file as the given driver
+//-------------------------------------------------
+
+void validity_checker::check_shared_source(const game_driver &driver)
+{
+ // initialize
+ validate_begin();
+
+ // then iterate over all drivers and check the ones that share the same source file
+ m_drivlist.reset();
+ while (m_drivlist.next())
+ if (strcmp(driver.type.source(), m_drivlist.driver().type.source()) == 0)
+ validate_one(m_drivlist.driver());
+
+ // cleanup
+ validate_end();
+}
+
+
+//-------------------------------------------------
+// check_all_matching - check all drivers whose
+// names match the given string
+//-------------------------------------------------
+
+bool validity_checker::check_all_matching(const char *string)
+{
+ // start by checking core stuff
+ validate_begin();
+ validate_core();
+ validate_inlines();
+ validate_rgb();
+
+ // if we had warnings or errors, output
+ if (m_errors > 0 || m_warnings > 0 || !m_verbose_text.empty())
+ {
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Core: %d errors, %d warnings\n", m_errors, m_warnings);
+ if (m_errors > 0)
+ output_indented_errors(m_error_text, "Errors");
+ if (m_warnings > 0)
+ output_indented_errors(m_warning_text, "Warnings");
+ if (!m_verbose_text.empty())
+ output_indented_errors(m_verbose_text, "Messages");
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "\n");
+ }
+
+ // then iterate over all drivers and check them
+ m_drivlist.reset();
+ bool validated_any = false;
+ while (m_drivlist.next())
+ {
+ if (m_drivlist.matches(string, m_drivlist.driver().name))
+ {
+ validate_one(m_drivlist.driver());
+ validated_any = true;
+ }
+ }
+
+ // validate devices
+ if (!string)
+ validate_device_types();
+
+ // cleanup
+ validate_end();
+
+ // if we failed to match anything, it
+ if (string && !validated_any)
+ throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching systems found for '%s'", string);
+
+ return !(m_errors > 0 || m_warnings > 0);
+}
+
+
+//-------------------------------------------------
+// validate_begin - prepare for validation by
+// taking over the output callbacks and resetting
+// our internal state
+//-------------------------------------------------
+
+void validity_checker::validate_begin()
+{
+ // take over error and warning outputs
+ osd_output::push(this);
+
+ // reset all our maps
+ m_names_map.clear();
+ m_descriptions_map.clear();
+ m_roms_map.clear();
+ m_defstr_map.clear();
+ m_region_map.clear();
+
+ // reset internal state
+ m_errors = 0;
+ m_warnings = 0;
+ m_already_checked.clear();
+}
+
+
+//-------------------------------------------------
+// validate_end - restore output callbacks and
+// clean up
+//-------------------------------------------------
+
+void validity_checker::validate_end()
+{
+ // restore the original output callbacks
+ osd_output::pop(this);
+}
+
+
+//-------------------------------------------------
+// validate_drivers - master validity checker
+//-------------------------------------------------
+
+void validity_checker::validate_one(const game_driver &driver)
+{
+ // help verbose validation detect configuration-related crashes
+ if (m_print_verbose)
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Validating driver %s (%s)...\n", driver.name, core_filename_extract_base(driver.type.source()).c_str());
+
+ // set the current driver
+ m_current_driver = &driver;
+ m_current_config = nullptr;
+ m_current_device = nullptr;
+ m_current_ioport = nullptr;
+ m_region_map.clear();
+
+ // reset error/warning state
+ int start_errors = m_errors;
+ int start_warnings = m_warnings;
+ m_error_text.clear();
+ m_warning_text.clear();
+ m_verbose_text.clear();
+
+ // wrap in try/except to catch fatalerrors
+ try
+ {
+ machine_config config(driver, m_blank_options);
+ m_current_config = &config;
+ validate_driver();
+ validate_roms(m_current_config->root_device());
+ validate_inputs();
+ validate_devices();
+ m_current_config = nullptr;
+ }
+ catch (emu_fatalerror &err)
+ {
+ osd_printf_error("Fatal error %s", err.string());
+ }
+
+ // if we had warnings or errors, output
+ if (m_errors > start_errors || m_warnings > start_warnings || !m_verbose_text.empty())
+ {
+ if (!m_print_verbose)
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Driver %s (file %s): ", driver.name, core_filename_extract_base(driver.type.source()).c_str());
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "%d errors, %d warnings\n", m_errors - start_errors, m_warnings - start_warnings);
+ if (m_errors > start_errors)
+ output_indented_errors(m_error_text, "Errors");
+ if (m_warnings > start_warnings)
+ output_indented_errors(m_warning_text, "Warnings");
+ if (!m_verbose_text.empty())
+ output_indented_errors(m_verbose_text, "Messages");
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "\n");
+ }
+
+ // reset the driver/device
+ m_current_driver = nullptr;
+ m_current_config = nullptr;
+ m_current_device = nullptr;
+ m_current_ioport = nullptr;
+}
+
+
+//-------------------------------------------------
+// validate_core - validate core internal systems
+//-------------------------------------------------
+
+void validity_checker::validate_core()
+{
+ // basic system checks
+ if (~0 != -1) osd_printf_error("Machine must be two's complement\n");
+
+ u8 a = 0xff;
+ u8 b = a + 1;
+ if (b > a) osd_printf_error("u8 must be 8 bits\n");
+
+ // check size of core integer types
+ if (sizeof(s8) != 1) osd_printf_error("s8 must be 8 bits\n");
+ if (sizeof(u8) != 1) osd_printf_error("u8 must be 8 bits\n");
+ if (sizeof(s16) != 2) osd_printf_error("s16 must be 16 bits\n");
+ if (sizeof(u16) != 2) osd_printf_error("u16 must be 16 bits\n");
+ if (sizeof(s32) != 4) osd_printf_error("s32 must be 32 bits\n");
+ if (sizeof(u32) != 4) osd_printf_error("u32 must be 32 bits\n");
+ if (sizeof(s64) != 8) osd_printf_error("s64 must be 64 bits\n");
+ if (sizeof(u64) != 8) osd_printf_error("u64 must be 64 bits\n");
+
+ // check signed right shift
+ s8 a8 = -3;
+ s16 a16 = -3;
+ s32 a32 = -3;
+ s64 a64 = -3;
+ if (a8 >> 1 != -2) osd_printf_error("s8 right shift must be arithmetic\n");
+ if (a16 >> 1 != -2) osd_printf_error("s16 right shift must be arithmetic\n");
+ if (a32 >> 1 != -2) osd_printf_error("s32 right shift must be arithmetic\n");
+ if (a64 >> 1 != -2) osd_printf_error("s64 right shift must be arithmetic\n");
+
+ // check pointer size
+#ifdef PTR64
+ static_assert(sizeof(void *) == 8, "PTR64 flag enabled, but was compiled for 32-bit target\n");
+#else
+ static_assert(sizeof(void *) == 4, "PTR64 flag not enabled, but was compiled for 64-bit target\n");
+#endif
+
+ // TODO: check if this is actually working
+ // check endianness definition
+ u16 lsbtest = 0;
+ *(u8 *)&lsbtest = 0xff;
+#ifdef LSB_FIRST
+ if (lsbtest == 0xff00) osd_printf_error("LSB_FIRST specified, but running on a big-endian machine\n");
+#else
+ if (lsbtest == 0x00ff) osd_printf_error("LSB_FIRST not specified, but running on a little-endian machine\n");
+#endif
+}
+
+
+//-------------------------------------------------
+// validate_inlines - validate inline function
+// behaviors
+//-------------------------------------------------
+
+void validity_checker::validate_inlines()
+{
+ volatile u64 testu64a = random_u64();
+ volatile s64 testi64a = random_i64();
+ volatile u32 testu32a = random_u32();
+ volatile u32 testu32b = random_u32();
+ volatile s32 testi32a = random_i32();
+ volatile s32 testi32b = random_i32();
+ s32 resulti32, expectedi32;
+ u32 resultu32, expectedu32;
+ s64 resulti64, expectedi64;
+ u64 resultu64, expectedu64;
+ s32 remainder, expremainder;
+ u32 uremainder, expuremainder, bigu32 = 0xffffffff;
+
+ // use only non-zero, positive numbers
+ if (testu64a == 0) testu64a++;
+ if (testi64a == 0) testi64a++;
+ else if (testi64a < 0) testi64a = -testi64a;
+ if (testu32a == 0) testu32a++;
+ if (testu32b == 0) testu32b++;
+ if (testi32a == 0) testi32a++;
+ else if (testi32a < 0) testi32a = -testi32a;
+ if (testi32b == 0) testi32b++;
+ else if (testi32b < 0) testi32b = -testi32b;
+
+ resulti64 = mul_32x32(testi32a, testi32b);
+ expectedi64 = s64(testi32a) * s64(testi32b);
+ if (resulti64 != expectedi64)
+ osd_printf_error("Error testing mul_32x32 (%08X x %08X) = %08X%08X (expected %08X%08X)\n", testi32a, testi32b, u32(resulti64 >> 32), u32(resulti64), u32(expectedi64 >> 32), u32(expectedi64));
+
+ resultu64 = mulu_32x32(testu32a, testu32b);
+ expectedu64 = u64(testu32a) * u64(testu32b);
+ if (resultu64 != expectedu64)
+ osd_printf_error("Error testing mulu_32x32 (%08X x %08X) = %08X%08X (expected %08X%08X)\n", testu32a, testu32b, u32(resultu64 >> 32), u32(resultu64), u32(expectedu64 >> 32), u32(expectedu64));
+
+ resulti32 = mul_32x32_hi(testi32a, testi32b);
+ expectedi32 = (s64(testi32a) * s64(testi32b)) >> 32;
+ if (resulti32 != expectedi32)
+ osd_printf_error("Error testing mul_32x32_hi (%08X x %08X) = %08X (expected %08X)\n", testi32a, testi32b, resulti32, expectedi32);
+
+ resultu32 = mulu_32x32_hi(testu32a, testu32b);
+ expectedu32 = (s64(testu32a) * s64(testu32b)) >> 32;
+ if (resultu32 != expectedu32)
+ osd_printf_error("Error testing mulu_32x32_hi (%08X x %08X) = %08X (expected %08X)\n", testu32a, testu32b, resultu32, expectedu32);
+
+ resulti32 = mul_32x32_shift(testi32a, testi32b, 7);
+ expectedi32 = (s64(testi32a) * s64(testi32b)) >> 7;
+ if (resulti32 != expectedi32)
+ osd_printf_error("Error testing mul_32x32_shift (%08X x %08X) >> 7 = %08X (expected %08X)\n", testi32a, testi32b, resulti32, expectedi32);
+
+ resultu32 = mulu_32x32_shift(testu32a, testu32b, 7);
+ expectedu32 = (s64(testu32a) * s64(testu32b)) >> 7;
+ if (resultu32 != expectedu32)
+ osd_printf_error("Error testing mulu_32x32_shift (%08X x %08X) >> 7 = %08X (expected %08X)\n", testu32a, testu32b, resultu32, expectedu32);
+
+ while (s64(testi32a) * s64(0x7fffffff) < testi64a)
+ testi64a /= 2;
+ while (u64(testu32a) * u64(bigu32) < testu64a)
+ testu64a /= 2;
+
+ resulti32 = div_64x32(testi64a, testi32a);
+ expectedi32 = testi64a / s64(testi32a);
+ if (resulti32 != expectedi32)
+ osd_printf_error("Error testing div_64x32 (%08X%08X / %08X) = %08X (expected %08X)\n", u32(testi64a >> 32), u32(testi64a), testi32a, resulti32, expectedi32);
+
+ resultu32 = divu_64x32(testu64a, testu32a);
+ expectedu32 = testu64a / u64(testu32a);
+ if (resultu32 != expectedu32)
+ osd_printf_error("Error testing divu_64x32 (%08X%08X / %08X) = %08X (expected %08X)\n", u32(testu64a >> 32), u32(testu64a), testu32a, resultu32, expectedu32);
+
+ resulti32 = div_64x32_rem(testi64a, testi32a, &remainder);
+ expectedi32 = testi64a / s64(testi32a);
+ expremainder = testi64a % s64(testi32a);
+ if (resulti32 != expectedi32 || remainder != expremainder)
+ osd_printf_error("Error testing div_64x32_rem (%08X%08X / %08X) = %08X,%08X (expected %08X,%08X)\n", u32(testi64a >> 32), u32(testi64a), testi32a, resulti32, remainder, expectedi32, expremainder);
+
+ resultu32 = divu_64x32_rem(testu64a, testu32a, &uremainder);
+ expectedu32 = testu64a / u64(testu32a);
+ expuremainder = testu64a % u64(testu32a);
+ if (resultu32 != expectedu32 || uremainder != expuremainder)
+ osd_printf_error("Error testing divu_64x32_rem (%08X%08X / %08X) = %08X,%08X (expected %08X,%08X)\n", u32(testu64a >> 32), u32(testu64a), testu32a, resultu32, uremainder, expectedu32, expuremainder);
+
+ resulti32 = mod_64x32(testi64a, testi32a);
+ expectedi32 = testi64a % s64(testi32a);
+ if (resulti32 != expectedi32)
+ osd_printf_error("Error testing mod_64x32 (%08X%08X / %08X) = %08X (expected %08X)\n", u32(testi64a >> 32), u32(testi64a), testi32a, resulti32, expectedi32);
+
+ resultu32 = modu_64x32(testu64a, testu32a);
+ expectedu32 = testu64a % u64(testu32a);
+ if (resultu32 != expectedu32)
+ osd_printf_error("Error testing modu_64x32 (%08X%08X / %08X) = %08X (expected %08X)\n", u32(testu64a >> 32), u32(testu64a), testu32a, resultu32, expectedu32);
+
+ while (s64(testi32a) * s64(0x7fffffff) < (s32(testi64a) << 3))
+ testi64a /= 2;
+ while (u64(testu32a) * u64(0xffffffff) < (u32(testu64a) << 3))
+ testu64a /= 2;
+
+ resulti32 = div_32x32_shift(s32(testi64a), testi32a, 3);
+ expectedi32 = (s64(s32(testi64a)) << 3) / s64(testi32a);
+ if (resulti32 != expectedi32)
+ osd_printf_error("Error testing div_32x32_shift (%08X << 3) / %08X = %08X (expected %08X)\n", s32(testi64a), testi32a, resulti32, expectedi32);
+
+ resultu32 = divu_32x32_shift(u32(testu64a), testu32a, 3);
+ expectedu32 = (u64(u32(testu64a)) << 3) / u64(testu32a);
+ if (resultu32 != expectedu32)
+ osd_printf_error("Error testing divu_32x32_shift (%08X << 3) / %08X = %08X (expected %08X)\n", u32(testu64a), testu32a, resultu32, expectedu32);
+
+ if (fabsf(recip_approx(100.0f) - 0.01f) > 0.0001f)
+ osd_printf_error("Error testing recip_approx\n");
+
+ for (int i = 0; i <= 32; i++)
+ {
+ u32 t = i < 32 ? (1 << (31 - i) | testu32a >> i) : 0;
+ u8 resultu8 = count_leading_zeros(t);
+ if (resultu8 != i)
+ osd_printf_error("Error testing count_leading_zeros %08x=%02x (expected %02x)\n", t, resultu8, i);
+
+ t ^= 0xffffffff;
+ resultu8 = count_leading_ones(t);
+ if (resultu8 != i)
+ osd_printf_error("Error testing count_leading_ones %08x=%02x (expected %02x)\n", t, resultu8, i);
+ }
+}
+
+
+//-------------------------------------------------
+// validate_rgb - validate optimised RGB utility
+// class
+//-------------------------------------------------
+
+void validity_checker::validate_rgb()
+{
+ /*
+ This performs cursory tests of most of the vector-optimised RGB
+ utilities, concentrating on the low-level maths. It uses random
+ values most of the time for a quick go/no-go indication rather
+ than trying to exercise edge cases. It doesn't matter too much
+ if the compiler optimises out some of the operations since it's
+ really intended to check for logic bugs in the vector code. If
+ the compiler can work out that the code produces the expected
+ result, that's good enough.
+
+ The tests for bitwise logical operations are ordered to minimise
+ the chance of all-zero or all-one patterns producing a
+ misleading good result.
+
+ The following functions are not tested yet:
+ rgbaint_t()
+ clamp_and_clear(const u32)
+ sign_extend(const u32, const u32)
+ min(const s32)
+ max(const s32)
+ blend(const rgbaint_t&, u8)
+ scale_and_clamp(const rgbaint_t&)
+ scale_imm_and_clamp(const s32)
+ scale2_add_and_clamp(const rgbaint_t&, const rgbaint_t&, const rgbaint_t&)
+ scale_add_and_clamp(const rgbaint_t&, const rgbaint_t&);
+ scale_imm_add_and_clamp(const s32, const rgbaint_t&);
+ static bilinear_filter(u32, u32, u32, u32, u8, u8)
+ bilinear_filter_rgbaint(u32, u32, u32, u32, u8, u8)
+ */
+
+ auto random_i32_nolimit = [this]
+ {
+ s32 result;
+ do { result = random_i32(); } while ((result == std::numeric_limits<s32>::min()) || (result == std::numeric_limits<s32>::max()));
+ return result;
+ };
+
+ volatile s32 expected_a, expected_r, expected_g, expected_b;
+ volatile s32 actual_a, actual_r, actual_g, actual_b;
+ volatile s32 imm;
+ rgbaint_t rgb, other;
+ rgb_t packed;
+ auto check_expected = [&] (const char *desc)
+ {
+ const volatile s32 a = rgb.get_a32();
+ const volatile s32 r = rgb.get_r32();
+ const volatile s32 g = rgb.get_g32();
+ const volatile s32 b = rgb.get_b32();
+ if (a != expected_a) osd_printf_error("Error testing %s get_a32() = %d (expected %d)\n", desc, a, expected_a);
+ if (r != expected_r) osd_printf_error("Error testing %s get_r32() = %d (expected %d)\n", desc, r, expected_r);
+ if (g != expected_g) osd_printf_error("Error testing %s get_g32() = %d (expected %d)\n", desc, g, expected_g);
+ if (b != expected_b) osd_printf_error("Error testing %s get_b32() = %d (expected %d)\n", desc, b, expected_b);
+ };
+
+ // check set/get
+ expected_a = random_i32();
+ expected_r = random_i32();
+ expected_g = random_i32();
+ expected_b = random_i32();
+ rgb.set(expected_a, expected_r, expected_g, expected_b);
+ check_expected("rgbaint_t::set(a, r, g, b)");
+
+ // check construct/set
+ expected_a = random_i32();
+ expected_r = random_i32();
+ expected_g = random_i32();
+ expected_b = random_i32();
+ rgb.set(rgbaint_t(expected_a, expected_r, expected_g, expected_b));
+ check_expected("rgbaint_t::set(rgbaint_t)");
+
+ packed = random_i32();
+ expected_a = packed.a();
+ expected_r = packed.r();
+ expected_g = packed.g();
+ expected_b = packed.b();
+ rgb.set(packed);
+ check_expected("rgbaint_t::set(const rgb_t& rgb)");
+
+ // check construct/assign
+ expected_a = random_i32();
+ expected_r = random_i32();
+ expected_g = random_i32();
+ expected_b = random_i32();
+ rgb = rgbaint_t(expected_a, expected_r, expected_g, expected_b);
+ check_expected("rgbaint_t assignment");
+
+ // check piecewise set
+ rgb.set_a(expected_a = random_i32());
+ check_expected("rgbaint_t::set_a");
+ rgb.set_r(expected_r = random_i32());
+ check_expected("rgbaint_t::set_r");
+ rgb.set_g(expected_g = random_i32());
+ check_expected("rgbaint_t::set_g");
+ rgb.set_b(expected_b = random_i32());
+ check_expected("rgbaint_t::set_b");
+
+ // test merge_alpha
+ expected_a = rand();
+ rgb.merge_alpha(rgbaint_t(expected_a, rand(), rand(), rand()));
+ check_expected("rgbaint_t::merge_alpha");
+
+ // test RGB addition (method)
+ expected_a += actual_a = random_i32();
+ expected_r += actual_r = random_i32();
+ expected_g += actual_g = random_i32();
+ expected_b += actual_b = random_i32();
+ rgb.add(rgbaint_t(actual_a, actual_r, actual_g, actual_b));
+ check_expected("rgbaint_t::add");
+
+ // test RGB addition (operator)
+ expected_a += actual_a = random_i32();
+ expected_r += actual_r = random_i32();
+ expected_g += actual_g = random_i32();
+ expected_b += actual_b = random_i32();
+ rgb += rgbaint_t(actual_a, actual_r, actual_g, actual_b);
+ check_expected("rgbaint_t::operator+=");
+
+ // test offset addition (method)
+ imm = random_i32();
+ expected_a += imm;
+ expected_r += imm;
+ expected_g += imm;
+ expected_b += imm;
+ rgb.add_imm(imm);
+ check_expected("rgbaint_t::add_imm");
+
+ // test offset addition (operator)
+ imm = random_i32();
+ expected_a += imm;
+ expected_r += imm;
+ expected_g += imm;
+ expected_b += imm;
+ rgb += imm;
+ check_expected("rgbaint_t::operator+=");
+
+ // test immediate RGB addition
+ expected_a += actual_a = random_i32();
+ expected_r += actual_r = random_i32();
+ expected_g += actual_g = random_i32();
+ expected_b += actual_b = random_i32();
+ rgb.add_imm_rgba(actual_a, actual_r, actual_g, actual_b);
+ check_expected("rgbaint_t::add_imm_rgba");
+
+ // test RGB subtraction (method)
+ expected_a -= actual_a = random_i32();
+ expected_r -= actual_r = random_i32();
+ expected_g -= actual_g = random_i32();
+ expected_b -= actual_b = random_i32();
+ rgb.sub(rgbaint_t(actual_a, actual_r, actual_g, actual_b));
+ check_expected("rgbaint_t::sub");
+
+ // test RGB subtraction (operator)
+ expected_a -= actual_a = random_i32();
+ expected_r -= actual_r = random_i32();
+ expected_g -= actual_g = random_i32();
+ expected_b -= actual_b = random_i32();
+ rgb -= rgbaint_t(actual_a, actual_r, actual_g, actual_b);
+ check_expected("rgbaint_t::operator-=");
+
+ // test offset subtraction
+ imm = random_i32();
+ expected_a -= imm;
+ expected_r -= imm;
+ expected_g -= imm;
+ expected_b -= imm;
+ rgb.sub_imm(imm);
+ check_expected("rgbaint_t::sub_imm");
+
+ // test immediate RGB subtraction
+ expected_a -= actual_a = random_i32();
+ expected_r -= actual_r = random_i32();
+ expected_g -= actual_g = random_i32();
+ expected_b -= actual_b = random_i32();
+ rgb.sub_imm_rgba(actual_a, actual_r, actual_g, actual_b);
+ check_expected("rgbaint_t::sub_imm_rgba");
+
+ // test reversed RGB subtraction
+ expected_a = (actual_a = random_i32()) - expected_a;
+ expected_r = (actual_r = random_i32()) - expected_r;
+ expected_g = (actual_g = random_i32()) - expected_g;
+ expected_b = (actual_b = random_i32()) - expected_b;
+ rgb.subr(rgbaint_t(actual_a, actual_r, actual_g, actual_b));
+ check_expected("rgbaint_t::subr");
+
+ // test reversed offset subtraction
+ imm = random_i32();
+ expected_a = imm - expected_a;
+ expected_r = imm - expected_r;
+ expected_g = imm - expected_g;
+ expected_b = imm - expected_b;
+ rgb.subr_imm(imm);
+ check_expected("rgbaint_t::subr_imm");
+
+ // test reversed immediate RGB subtraction
+ expected_a = (actual_a = random_i32()) - expected_a;
+ expected_r = (actual_r = random_i32()) - expected_r;
+ expected_g = (actual_g = random_i32()) - expected_g;
+ expected_b = (actual_b = random_i32()) - expected_b;
+ rgb.subr_imm_rgba(actual_a, actual_r, actual_g, actual_b);
+ check_expected("rgbaint_t::subr_imm_rgba");
+
+ // test RGB multiplication (method)
+ expected_a *= actual_a = random_i32();
+ expected_r *= actual_r = random_i32();
+ expected_g *= actual_g = random_i32();
+ expected_b *= actual_b = random_i32();
+ rgb.mul(rgbaint_t(actual_a, actual_r, actual_g, actual_b));
+ check_expected("rgbaint_t::mul");
+
+ // test RGB multiplication (operator)
+ expected_a *= actual_a = random_i32();
+ expected_r *= actual_r = random_i32();
+ expected_g *= actual_g = random_i32();
+ expected_b *= actual_b = random_i32();
+ rgb *= rgbaint_t(actual_a, actual_r, actual_g, actual_b);
+ check_expected("rgbaint_t::operator*=");
+
+ // test factor multiplication (method)
+ imm = random_i32();
+ expected_a *= imm;
+ expected_r *= imm;
+ expected_g *= imm;
+ expected_b *= imm;
+ rgb.mul_imm(imm);
+ check_expected("rgbaint_t::mul_imm");
+
+ // test factor multiplication (operator)
+ imm = random_i32();
+ expected_a *= imm;
+ expected_r *= imm;
+ expected_g *= imm;
+ expected_b *= imm;
+ rgb *= imm;
+ check_expected("rgbaint_t::operator*=");
+
+ // test immediate RGB multiplication
+ expected_a *= actual_a = random_i32();
+ expected_r *= actual_r = random_i32();
+ expected_g *= actual_g = random_i32();
+ expected_b *= actual_b = random_i32();
+ rgb.mul_imm_rgba(actual_a, actual_r, actual_g, actual_b);
+ check_expected("rgbaint_t::mul_imm_rgba");
+
+ // test select alpha element multiplication
+ expected_a *= actual_a = random_i32();
+ expected_r *= actual_a;
+ expected_g *= actual_a;
+ expected_b *= actual_a;
+ rgb.mul(rgbaint_t(actual_a, actual_r, actual_g, actual_b).select_alpha32());
+ check_expected("rgbaint_t::mul(select_alpha32)");
+
+ // test select red element multiplication
+ expected_a *= actual_r = random_i32();
+ expected_r *= actual_r;
+ expected_g *= actual_r;
+ expected_b *= actual_r;
+ rgb.mul(rgbaint_t(actual_a, actual_r, actual_g, actual_b).select_red32());
+ check_expected("rgbaint_t::mul(select_red32)");
+
+ // test select green element multiplication
+ expected_a *= actual_g = random_i32();
+ expected_r *= actual_g;
+ expected_g *= actual_g;
+ expected_b *= actual_g;
+ rgb.mul(rgbaint_t(actual_a, actual_r, actual_g, actual_b).select_green32());
+ check_expected("rgbaint_t::mul(select_green32)");
+
+ // test select blue element multiplication
+ expected_a *= actual_b = random_i32();
+ expected_r *= actual_b;
+ expected_g *= actual_b;
+ expected_b *= actual_b;
+ rgb.mul(rgbaint_t(actual_a, actual_r, actual_g, actual_b).select_blue32());
+ check_expected("rgbaint_t::mul(select_blue32)");
+
+ // test RGB and not
+ expected_a &= ~(actual_a = random_i32());
+ expected_r &= ~(actual_r = random_i32());
+ expected_g &= ~(actual_g = random_i32());
+ expected_b &= ~(actual_b = random_i32());
+ rgb.andnot_reg(rgbaint_t(actual_a, actual_r, actual_g, actual_b));
+ check_expected("rgbaint_t::andnot_reg");
+
+ // test RGB or
+ expected_a |= actual_a = random_i32();
+ expected_r |= actual_r = random_i32();
+ expected_g |= actual_g = random_i32();
+ expected_b |= actual_b = random_i32();
+ rgb.or_reg(rgbaint_t(actual_a, actual_r, actual_g, actual_b));
+ check_expected("rgbaint_t::or_reg");
+
+ // test RGB and
+ expected_a &= actual_a = random_i32();
+ expected_r &= actual_r = random_i32();
+ expected_g &= actual_g = random_i32();
+ expected_b &= actual_b = random_i32();
+ rgb.and_reg(rgbaint_t(actual_a, actual_r, actual_g, actual_b));
+ check_expected("rgbaint_t::and_reg");
+
+ // test RGB xor
+ expected_a ^= actual_a = random_i32();
+ expected_r ^= actual_r = random_i32();
+ expected_g ^= actual_g = random_i32();
+ expected_b ^= actual_b = random_i32();
+ rgb.xor_reg(rgbaint_t(actual_a, actual_r, actual_g, actual_b));
+ check_expected("rgbaint_t::xor_reg");
+
+ // test uniform or
+ imm = random_i32();
+ expected_a |= imm;
+ expected_r |= imm;
+ expected_g |= imm;
+ expected_b |= imm;
+ rgb.or_imm(imm);
+ check_expected("rgbaint_t::or_imm");
+
+ // test uniform and
+ imm = random_i32();
+ expected_a &= imm;
+ expected_r &= imm;
+ expected_g &= imm;
+ expected_b &= imm;
+ rgb.and_imm(imm);
+ check_expected("rgbaint_t::and_imm");
+
+ // test uniform xor
+ imm = random_i32();
+ expected_a ^= imm;
+ expected_r ^= imm;
+ expected_g ^= imm;
+ expected_b ^= imm;
+ rgb.xor_imm(imm);
+ check_expected("rgbaint_t::xor_imm");
+
+ // test immediate RGB or
+ expected_a |= actual_a = random_i32();
+ expected_r |= actual_r = random_i32();
+ expected_g |= actual_g = random_i32();
+ expected_b |= actual_b = random_i32();
+ rgb.or_imm_rgba(actual_a, actual_r, actual_g, actual_b);
+ check_expected("rgbaint_t::or_imm_rgba");
+
+ // test immediate RGB and
+ expected_a &= actual_a = random_i32();
+ expected_r &= actual_r = random_i32();
+ expected_g &= actual_g = random_i32();
+ expected_b &= actual_b = random_i32();
+ rgb.and_imm_rgba(actual_a, actual_r, actual_g, actual_b);
+ check_expected("rgbaint_t::and_imm_rgba");
+
+ // test immediate RGB xor
+ expected_a ^= actual_a = random_i32();
+ expected_r ^= actual_r = random_i32();
+ expected_g ^= actual_g = random_i32();
+ expected_b ^= actual_b = random_i32();
+ rgb.xor_imm_rgba(actual_a, actual_r, actual_g, actual_b);
+ check_expected("rgbaint_t::xor_imm_rgba");
+
+ // test 8-bit get
+ expected_a = s32(u32(expected_a) & 0x00ff);
+ expected_r = s32(u32(expected_r) & 0x00ff);
+ expected_g = s32(u32(expected_g) & 0x00ff);
+ expected_b = s32(u32(expected_b) & 0x00ff);
+ actual_a = s32(u32(rgb.get_a()));
+ actual_r = s32(u32(rgb.get_r()));
+ actual_g = s32(u32(rgb.get_g()));
+ actual_b = s32(u32(rgb.get_b()));
+ if (actual_a != expected_a) osd_printf_error("Error testing rgbaint_t::get_a() = %d (expected %d)\n", actual_a, expected_a);
+ if (actual_r != expected_r) osd_printf_error("Error testing rgbaint_t::get_r() = %d (expected %d)\n", actual_r, expected_r);
+ if (actual_g != expected_g) osd_printf_error("Error testing rgbaint_t::get_g() = %d (expected %d)\n", actual_g, expected_g);
+ if (actual_b != expected_b) osd_printf_error("Error testing rgbaint_t::get_b() = %d (expected %d)\n", actual_b, expected_b);
+
+ // test set from packed RGBA
+ imm = random_i32();
+ expected_a = s32((u32(imm) >> 24) & 0x00ff);
+ expected_r = s32((u32(imm) >> 16) & 0x00ff);
+ expected_g = s32((u32(imm) >> 8) & 0x00ff);
+ expected_b = s32((u32(imm) >> 0) & 0x00ff);
+ rgb.set(u32(imm));
+ check_expected("rgbaint_t::set(u32)");
+
+ // while we have a value loaded that we know doesn't exceed 8-bit range, check the non-clamping convert-to-rgba
+ packed = rgb.to_rgba();
+ if (u32(imm) != u32(packed))
+ osd_printf_error("Error testing rgbaint_t::to_rgba() = %08x (expected %08x)\n", u32(packed), u32(imm));
+
+ // test construct from packed RGBA and assign
+ imm = random_i32();
+ expected_a = s32((u32(imm) >> 24) & 0x00ff);
+ expected_r = s32((u32(imm) >> 16) & 0x00ff);
+ expected_g = s32((u32(imm) >> 8) & 0x00ff);
+ expected_b = s32((u32(imm) >> 0) & 0x00ff);
+ rgb = rgbaint_t(u32(imm));
+ check_expected("rgbaint_t(u32)");
+
+ // while we have a value loaded that we know doesn't exceed 8-bit range, check the non-clamping convert-to-rgba
+ packed = rgb.to_rgba();
+ if (u32(imm) != u32(packed))
+ osd_printf_error("Error testing rgbaint_t::to_rgba() = %08x (expected %08x)\n", u32(packed), u32(imm));
+
+ // test set with rgb_t
+ packed = random_u32();
+ expected_a = s32(u32(packed.a()));
+ expected_r = s32(u32(packed.r()));
+ expected_g = s32(u32(packed.g()));
+ expected_b = s32(u32(packed.b()));
+ rgb.set(packed);
+ check_expected("rgbaint_t::set(rgba_t)");
+
+ // test construct with rgb_t
+ packed = random_u32();
+ expected_a = s32(u32(packed.a()));
+ expected_r = s32(u32(packed.r()));
+ expected_g = s32(u32(packed.g()));
+ expected_b = s32(u32(packed.b()));
+ rgb = rgbaint_t(packed);
+ check_expected("rgbaint_t::set(rgba_t)");
+
+ // test clamping convert-to-rgba with hand-crafted values to catch edge cases
+ rgb.set(std::numeric_limits<s32>::min(), -1, 0, 1);
+ packed = rgb.to_rgba_clamp();
+ if (u32(0x00000001) != u32(packed))
+ osd_printf_error("Error testing rgbaint_t::to_rgba_clamp() = %08x (expected 0x00000001)\n", u32(packed));
+ rgb.set(254, 255, 256, std::numeric_limits<s32>::max());
+ packed = rgb.to_rgba_clamp();
+ if (u32(0xfeffffff) != u32(packed))
+ osd_printf_error("Error testing rgbaint_t::to_rgba_clamp() = %08x (expected 0xfeffffff)\n", u32(packed));
+ rgb.set(std::numeric_limits<s32>::max(), std::numeric_limits<s32>::min(), 256, -1);
+ packed = rgb.to_rgba_clamp();
+ if (u32(0xff00ff00) != u32(packed))
+ osd_printf_error("Error testing rgbaint_t::to_rgba_clamp() = %08x (expected 0xff00ff00)\n", u32(packed));
+ rgb.set(0, 255, 1, 254);
+ packed = rgb.to_rgba_clamp();
+ if (u32(0x00ff01fe) != u32(packed))
+ osd_printf_error("Error testing rgbaint_t::to_rgba_clamp() = %08x (expected 0x00ff01fe)\n", u32(packed));
+
+ // test in-place clamping with hand-crafted values to catch edge cases
+ expected_a = 0;
+ expected_r = 0;
+ expected_g = 0;
+ expected_b = 1;
+ rgb.set(std::numeric_limits<s32>::min(), -1, 0, 1);
+ rgb.clamp_to_uint8();
+ check_expected("rgbaint_t::clamp_to_uint8");
+ expected_a = 254;
+ expected_r = 255;
+ expected_g = 255;
+ expected_b = 255;
+ rgb.set(254, 255, 256, std::numeric_limits<s32>::max());
+ rgb.clamp_to_uint8();
+ check_expected("rgbaint_t::clamp_to_uint8");
+ expected_a = 255;
+ expected_r = 0;
+ expected_g = 255;
+ expected_b = 0;
+ rgb.set(std::numeric_limits<s32>::max(), std::numeric_limits<s32>::min(), 256, -1);
+ rgb.clamp_to_uint8();
+ check_expected("rgbaint_t::clamp_to_uint8");
+ expected_a = 0;
+ expected_r = 255;
+ expected_g = 1;
+ expected_b = 254;
+ rgb.set(0, 255, 1, 254);
+ rgb.clamp_to_uint8();
+ check_expected("rgbaint_t::clamp_to_uint8");
+
+ // test shift left
+ expected_a = (actual_a = random_i32()) << 19;
+ expected_r = (actual_r = random_i32()) << 3;
+ expected_g = (actual_g = random_i32()) << 21;
+ expected_b = (actual_b = random_i32()) << 6;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.shl(rgbaint_t(19, 3, 21, 6));
+ check_expected("rgbaint_t::shl");
+
+ // test shift left immediate
+ expected_a = (actual_a = random_i32()) << 7;
+ expected_r = (actual_r = random_i32()) << 7;
+ expected_g = (actual_g = random_i32()) << 7;
+ expected_b = (actual_b = random_i32()) << 7;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.shl_imm(7);
+ check_expected("rgbaint_t::shl_imm");
+
+ // test logical shift right
+ expected_a = s32(u32(actual_a = random_i32()) >> 8);
+ expected_r = s32(u32(actual_r = random_i32()) >> 18);
+ expected_g = s32(u32(actual_g = random_i32()) >> 26);
+ expected_b = s32(u32(actual_b = random_i32()) >> 4);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.shr(rgbaint_t(8, 18, 26, 4));
+ check_expected("rgbaint_t::shr");
+
+ // test logical shift right with opposite signs
+ expected_a = s32(u32(actual_a = -actual_a) >> 21);
+ expected_r = s32(u32(actual_r = -actual_r) >> 13);
+ expected_g = s32(u32(actual_g = -actual_g) >> 11);
+ expected_b = s32(u32(actual_b = -actual_b) >> 17);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.shr(rgbaint_t(21, 13, 11, 17));
+ check_expected("rgbaint_t::shr");
+
+ // test logical shift right immediate
+ expected_a = s32(u32(actual_a = random_i32()) >> 5);
+ expected_r = s32(u32(actual_r = random_i32()) >> 5);
+ expected_g = s32(u32(actual_g = random_i32()) >> 5);
+ expected_b = s32(u32(actual_b = random_i32()) >> 5);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.shr_imm(5);
+ check_expected("rgbaint_t::shr_imm");
+
+ // test logical shift right immediate with opposite signs
+ expected_a = s32(u32(actual_a = -actual_a) >> 15);
+ expected_r = s32(u32(actual_r = -actual_r) >> 15);
+ expected_g = s32(u32(actual_g = -actual_g) >> 15);
+ expected_b = s32(u32(actual_b = -actual_b) >> 15);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.shr_imm(15);
+ check_expected("rgbaint_t::shr_imm");
+
+ // test arithmetic shift right
+ expected_a = (actual_a = random_i32()) >> 16;
+ expected_r = (actual_r = random_i32()) >> 20;
+ expected_g = (actual_g = random_i32()) >> 14;
+ expected_b = (actual_b = random_i32()) >> 2;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.sra(rgbaint_t(16, 20, 14, 2));
+ check_expected("rgbaint_t::sra");
+
+ // test arithmetic shift right with opposite signs
+ expected_a = (actual_a = -actual_a) >> 1;
+ expected_r = (actual_r = -actual_r) >> 29;
+ expected_g = (actual_g = -actual_g) >> 10;
+ expected_b = (actual_b = -actual_b) >> 22;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.sra(rgbaint_t(1, 29, 10, 22));
+ check_expected("rgbaint_t::sra");
+
+ // test arithmetic shift right immediate (method)
+ expected_a = (actual_a = random_i32()) >> 12;
+ expected_r = (actual_r = random_i32()) >> 12;
+ expected_g = (actual_g = random_i32()) >> 12;
+ expected_b = (actual_b = random_i32()) >> 12;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.sra_imm(12);
+ check_expected("rgbaint_t::sra_imm");
+
+ // test arithmetic shift right immediate with opposite signs (method)
+ expected_a = (actual_a = -actual_a) >> 9;
+ expected_r = (actual_r = -actual_r) >> 9;
+ expected_g = (actual_g = -actual_g) >> 9;
+ expected_b = (actual_b = -actual_b) >> 9;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.sra_imm(9);
+ check_expected("rgbaint_t::sra_imm");
+
+ // test arithmetic shift right immediate (operator)
+ expected_a = (actual_a = random_i32()) >> 7;
+ expected_r = (actual_r = random_i32()) >> 7;
+ expected_g = (actual_g = random_i32()) >> 7;
+ expected_b = (actual_b = random_i32()) >> 7;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb >>= 7;
+ check_expected("rgbaint_t::operator>>=");
+
+ // test arithmetic shift right immediate with opposite signs (operator)
+ expected_a = (actual_a = -actual_a) >> 11;
+ expected_r = (actual_r = -actual_r) >> 11;
+ expected_g = (actual_g = -actual_g) >> 11;
+ expected_b = (actual_b = -actual_b) >> 11;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb >>= 11;
+ check_expected("rgbaint_t::operator>>=");
+
+ // test RGB equality comparison
+ actual_a = random_i32_nolimit();
+ actual_r = random_i32_nolimit();
+ actual_g = random_i32_nolimit();
+ actual_b = random_i32_nolimit();
+ expected_a = ~s32(0);
+ expected_r = 0;
+ expected_g = 0;
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq(rgbaint_t(actual_a, actual_r - 1, actual_g + 1, std::numeric_limits<s32>::min()));
+ check_expected("rgbaint_t::cmpeq");
+ expected_a = 0;
+ expected_r = ~s32(0);
+ expected_g = 0;
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq(rgbaint_t(std::numeric_limits<s32>::max(), actual_r, actual_g - 1, actual_b + 1));
+ check_expected("rgbaint_t::cmpeq");
+
+ // test immediate equality comparison
+ actual_a = random_i32_nolimit();
+ actual_r = random_i32_nolimit();
+ actual_g = random_i32_nolimit();
+ actual_b = random_i32_nolimit();
+ expected_a = ~s32(0);
+ expected_r = (actual_r == actual_a) ? ~s32(0) : 0;
+ expected_g = (actual_g == actual_a) ? ~s32(0) : 0;
+ expected_b = (actual_b == actual_a) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq_imm(actual_a);
+ check_expected("rgbaint_t::cmpeq_imm");
+ expected_a = (actual_a == actual_r) ? ~s32(0) : 0;
+ expected_r = ~s32(0);
+ expected_g = (actual_g == actual_r) ? ~s32(0) : 0;
+ expected_b = (actual_b == actual_r) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq_imm(actual_r);
+ check_expected("rgbaint_t::cmpeq_imm");
+ expected_a = (actual_a == actual_g) ? ~s32(0) : 0;
+ expected_r = (actual_r == actual_g) ? ~s32(0) : 0;
+ expected_g = ~s32(0);
+ expected_b = (actual_b == actual_g) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq_imm(actual_g);
+ check_expected("rgbaint_t::cmpeq_imm");
+ expected_a = (actual_a == actual_b) ? ~s32(0) : 0;
+ expected_r = (actual_r == actual_b) ? ~s32(0) : 0;
+ expected_g = (actual_g == actual_b) ? ~s32(0) : 0;
+ expected_b = ~s32(0);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq_imm(actual_b);
+ check_expected("rgbaint_t::cmpeq_imm");
+ expected_a = 0;
+ expected_r = 0;
+ expected_g = 0;
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq_imm(std::numeric_limits<s32>::min());
+ check_expected("rgbaint_t::cmpeq_imm");
+ expected_a = !actual_a ? ~s32(0) : 0;
+ expected_r = !actual_r ? ~s32(0) : 0;
+ expected_g = !actual_g ? ~s32(0) : 0;
+ expected_b = !actual_b ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq_imm(0);
+ check_expected("rgbaint_t::cmpeq_imm");
+ expected_a = 0;
+ expected_r = 0;
+ expected_g = 0;
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq_imm(std::numeric_limits<s32>::max());
+ check_expected("rgbaint_t::cmpeq_imm");
+
+ // test immediate RGB equality comparison
+ actual_a = random_i32_nolimit();
+ actual_r = random_i32_nolimit();
+ actual_g = random_i32_nolimit();
+ actual_b = random_i32_nolimit();
+ expected_a = 0;
+ expected_r = 0;
+ expected_g = ~s32(0);
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq_imm_rgba(std::numeric_limits<s32>::min(), std::numeric_limits<s32>::max(), actual_g, actual_b - 1);
+ check_expected("rgbaint_t::cmpeq_imm_rgba");
+ expected_a = 0;
+ expected_r = 0;
+ expected_g = 0;
+ expected_b = ~s32(0);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq_imm_rgba(actual_a + 1, std::numeric_limits<s32>::min(), std::numeric_limits<s32>::max(), actual_b);
+ check_expected("rgbaint_t::cmpeq_imm_rgba");
+
+ // test RGB greater than comparison
+ actual_a = random_i32_nolimit();
+ actual_r = random_i32_nolimit();
+ actual_g = random_i32_nolimit();
+ actual_b = random_i32_nolimit();
+ expected_a = 0;
+ expected_r = ~s32(0);
+ expected_g = 0;
+ expected_b = ~s32(0);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt(rgbaint_t(actual_a, actual_r - 1, actual_g + 1, std::numeric_limits<s32>::min()));
+ check_expected("rgbaint_t::cmpgt");
+ expected_a = 0;
+ expected_r = 0;
+ expected_g = ~s32(0);
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt(rgbaint_t(std::numeric_limits<s32>::max(), actual_r, actual_g - 1, actual_b + 1));
+ check_expected("rgbaint_t::cmpgt");
+
+ // test immediate greater than comparison
+ actual_a = random_i32_nolimit();
+ actual_r = random_i32_nolimit();
+ actual_g = random_i32_nolimit();
+ actual_b = random_i32_nolimit();
+ expected_a = 0;
+ expected_r = (actual_r > actual_a) ? ~s32(0) : 0;
+ expected_g = (actual_g > actual_a) ? ~s32(0) : 0;
+ expected_b = (actual_b > actual_a) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt_imm(actual_a);
+ check_expected("rgbaint_t::cmpgt_imm");
+ expected_a = (actual_a > actual_r) ? ~s32(0) : 0;
+ expected_r = 0;
+ expected_g = (actual_g > actual_r) ? ~s32(0) : 0;
+ expected_b = (actual_b > actual_r) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt_imm(actual_r);
+ check_expected("rgbaint_t::cmpgt_imm");
+ expected_a = (actual_a > actual_g) ? ~s32(0) : 0;
+ expected_r = (actual_r > actual_g) ? ~s32(0) : 0;
+ expected_g =0;
+ expected_b = (actual_b > actual_g) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt_imm(actual_g);
+ check_expected("rgbaint_t::cmpgt_imm");
+ expected_a = (actual_a > actual_b) ? ~s32(0) : 0;
+ expected_r = (actual_r > actual_b) ? ~s32(0) : 0;
+ expected_g = (actual_g > actual_b) ? ~s32(0) : 0;
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt_imm(actual_b);
+ check_expected("rgbaint_t::cmpgt_imm");
+ expected_a = ~s32(0);
+ expected_r = ~s32(0);
+ expected_g = ~s32(0);
+ expected_b = ~s32(0);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt_imm(std::numeric_limits<s32>::min());
+ check_expected("rgbaint_t::cmpgt_imm");
+ expected_a = (actual_a > 0) ? ~s32(0) : 0;
+ expected_r = (actual_r > 0) ? ~s32(0) : 0;
+ expected_g = (actual_g > 0) ? ~s32(0) : 0;
+ expected_b = (actual_b > 0) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt_imm(0);
+ check_expected("rgbaint_t::cmpgt_imm");
+ expected_a = 0;
+ expected_r = 0;
+ expected_g = 0;
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt_imm(std::numeric_limits<s32>::max());
+ check_expected("rgbaint_t::cmpgt_imm");
+
+ // test immediate RGB greater than comparison
+ actual_a = random_i32_nolimit();
+ actual_r = random_i32_nolimit();
+ actual_g = random_i32_nolimit();
+ actual_b = random_i32_nolimit();
+ expected_a = ~s32(0);
+ expected_r = 0;
+ expected_g = 0;
+ expected_b = ~s32(0);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt_imm_rgba(std::numeric_limits<s32>::min(), std::numeric_limits<s32>::max(), actual_g, actual_b - 1);
+ check_expected("rgbaint_t::cmpgt_imm_rgba");
+ expected_a = 0;
+ expected_r = ~s32(0);
+ expected_g = 0;
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt_imm_rgba(actual_a + 1, std::numeric_limits<s32>::min(), std::numeric_limits<s32>::max(), actual_b);
+ check_expected("rgbaint_t::cmpgt_imm_rgba");
+
+ // test RGB less than comparison
+ actual_a = random_i32_nolimit();
+ actual_r = random_i32_nolimit();
+ actual_g = random_i32_nolimit();
+ actual_b = random_i32_nolimit();
+ expected_a = 0;
+ expected_r = 0;
+ expected_g = ~s32(0);
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt(rgbaint_t(actual_a, actual_r - 1, actual_g + 1, std::numeric_limits<s32>::min()));
+ check_expected("rgbaint_t::cmplt");
+ expected_a = ~s32(0);
+ expected_r = 0;
+ expected_g = 0;
+ expected_b = ~s32(0);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt(rgbaint_t(std::numeric_limits<s32>::max(), actual_r, actual_g - 1, actual_b + 1));
+ check_expected("rgbaint_t::cmplt");
+
+ // test immediate less than comparison
+ actual_a = random_i32_nolimit();
+ actual_r = random_i32_nolimit();
+ actual_g = random_i32_nolimit();
+ actual_b = random_i32_nolimit();
+ expected_a = 0;
+ expected_r = (actual_r < actual_a) ? ~s32(0) : 0;
+ expected_g = (actual_g < actual_a) ? ~s32(0) : 0;
+ expected_b = (actual_b < actual_a) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt_imm(actual_a);
+ check_expected("rgbaint_t::cmplt_imm");
+ expected_a = (actual_a < actual_r) ? ~s32(0) : 0;
+ expected_r = 0;
+ expected_g = (actual_g < actual_r) ? ~s32(0) : 0;
+ expected_b = (actual_b < actual_r) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt_imm(actual_r);
+ check_expected("rgbaint_t::cmplt_imm");
+ expected_a = (actual_a < actual_g) ? ~s32(0) : 0;
+ expected_r = (actual_r < actual_g) ? ~s32(0) : 0;
+ expected_g =0;
+ expected_b = (actual_b < actual_g) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt_imm(actual_g);
+ check_expected("rgbaint_t::cmplt_imm");
+ expected_a = (actual_a < actual_b) ? ~s32(0) : 0;
+ expected_r = (actual_r < actual_b) ? ~s32(0) : 0;
+ expected_g = (actual_g < actual_b) ? ~s32(0) : 0;
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt_imm(actual_b);
+ check_expected("rgbaint_t::cmplt_imm");
+ expected_a = 0;
+ expected_r = 0;
+ expected_g = 0;
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt_imm(std::numeric_limits<s32>::min());
+ check_expected("rgbaint_t::cmplt_imm");
+ expected_a = (actual_a < 0) ? ~s32(0) : 0;
+ expected_r = (actual_r < 0) ? ~s32(0) : 0;
+ expected_g = (actual_g < 0) ? ~s32(0) : 0;
+ expected_b = (actual_b < 0) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt_imm(0);
+ check_expected("rgbaint_t::cmplt_imm");
+ expected_a = ~s32(0);
+ expected_r = ~s32(0);
+ expected_g = ~s32(0);
+ expected_b = ~s32(0);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt_imm(std::numeric_limits<s32>::max());
+ check_expected("rgbaint_t::cmplt_imm");
+
+ // test immediate RGB less than comparison
+ actual_a = random_i32_nolimit();
+ actual_r = random_i32_nolimit();
+ actual_g = random_i32_nolimit();
+ actual_b = random_i32_nolimit();
+ expected_a = 0;
+ expected_r = ~s32(0);
+ expected_g = 0;
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt_imm_rgba(std::numeric_limits<s32>::min(), std::numeric_limits<s32>::max(), actual_g, actual_b - 1);
+ check_expected("rgbaint_t::cmplt_imm_rgba");
+ expected_a = ~s32(0);
+ expected_r = 0;
+ expected_g = ~s32(0);
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt_imm_rgba(actual_a + 1, std::numeric_limits<s32>::min(), std::numeric_limits<s32>::max(), actual_b);
+ check_expected("rgbaint_t::cmplt_imm_rgba");
+}
+
+
+//-------------------------------------------------
+// validate_driver - validate basic driver
+// information
+//-------------------------------------------------
+
+void validity_checker::validate_driver()
+{
+ // check for duplicate names
+ if (!m_names_map.insert(std::make_pair(m_current_driver->name, m_current_driver)).second)
+ {
+ const game_driver *match = m_names_map.find(m_current_driver->name)->second;
+ osd_printf_error("Driver name is a duplicate of %s(%s)\n", core_filename_extract_base(match->type.source()).c_str(), match->name);
+ }
+
+ // check for duplicate descriptions
+ if (!m_descriptions_map.insert(std::make_pair(m_current_driver->type.fullname(), m_current_driver)).second)
+ {
+ const game_driver *match = m_descriptions_map.find(m_current_driver->type.fullname())->second;
+ osd_printf_error("Driver description is a duplicate of %s(%s)\n", core_filename_extract_base(match->type.source()).c_str(), match->name);
+ }
+
+ // determine if we are a clone
+ bool is_clone = (strcmp(m_current_driver->parent, "0") != 0);
+ int clone_of = m_drivlist.clone(*m_current_driver);
+ if (clone_of != -1 && (m_drivlist.driver(clone_of).flags & machine_flags::IS_BIOS_ROOT))
+ is_clone = false;
+
+ // if we have at least 100 drivers, validate the clone
+ // (100 is arbitrary, but tries to avoid tiny.mak dependencies)
+ if (driver_list::total() > 100 && clone_of == -1 && is_clone)
+ osd_printf_error("Driver is a clone of nonexistent driver %s\n", m_current_driver->parent);
+
+ // look for recursive cloning
+ if (clone_of != -1 && &m_drivlist.driver(clone_of) == m_current_driver)
+ osd_printf_error("Driver is a clone of itself\n");
+
+ // look for clones that are too deep
+ if (clone_of != -1 && (clone_of = m_drivlist.non_bios_clone(clone_of)) != -1)
+ osd_printf_error("Driver is a clone of a clone\n");
+
+ // make sure the driver name is not too long
+ if (!is_clone && strlen(m_current_driver->name) > 16)
+ osd_printf_error("Parent driver name must be 16 characters or less\n");
+ if (is_clone && strlen(m_current_driver->name) > 16)
+ osd_printf_error("Clone driver name must be 16 characters or less\n");
+
+ // make sure the driver name doesn't contain invalid characters
+ for (const char *s = m_current_driver->name; *s != 0; s++)
+ if (((*s < '0') || (*s > '9')) && ((*s < 'a') || (*s > 'z')) && (*s != '_'))
+ {
+ osd_printf_error("Driver name contains invalid characters\n");
+ break;
+ }
+
+ // make sure the year is only digits, '?' or '+'
+ for (const char *s = m_current_driver->year; *s != 0; s++)
+ if (!isdigit(u8(*s)) && *s != '?' && *s != '+')
+ {
+ osd_printf_error("Driver has an invalid year '%s'\n", m_current_driver->year);
+ break;
+ }
+
+ // normalize driver->compatible_with
+ const char *compatible_with = m_current_driver->compatible_with;
+ if (compatible_with != nullptr && strcmp(compatible_with, "0") == 0)
+ compatible_with = nullptr;
+
+ // check for this driver being compatible with a nonexistent driver
+ if (compatible_with != nullptr && m_drivlist.find(m_current_driver->compatible_with) == -1)
+ osd_printf_error("Driver is listed as compatible with nonexistent driver %s\n", m_current_driver->compatible_with);
+
+ // check for clone_of and compatible_with being specified at the same time
+ if (m_drivlist.clone(*m_current_driver) != -1 && compatible_with != nullptr)
+ osd_printf_error("Driver cannot be both a clone and listed as compatible with another system\n");
+
+ // find any recursive dependencies on the current driver
+ for (int other_drv = m_drivlist.compatible_with(*m_current_driver); other_drv != -1; other_drv = m_drivlist.compatible_with(other_drv))
+ if (m_current_driver == &m_drivlist.driver(other_drv))
+ {
+ osd_printf_error("Driver is recursively compatible with itself\n");
+ break;
+ }
+
+ // make sure sound-less drivers are flagged
+ device_t::feature_type const unemulated(m_current_driver->type.unemulated_features());
+ device_t::feature_type const imperfect(m_current_driver->type.imperfect_features());
+ if (!(m_current_driver->flags & (machine_flags::IS_BIOS_ROOT | machine_flags::NO_SOUND_HW)) && !(unemulated & device_t::feature::SOUND))
+ {
+ sound_interface_iterator iter(m_current_config->root_device());
+ if (!iter.first())
+ osd_printf_error("Driver is missing MACHINE_NO_SOUND or MACHINE_NO_SOUND_HW flag\n");
+ }
+
+ // catch invalid flag combinations
+ if (unemulated & ~device_t::feature::ALL)
+ osd_printf_error("Driver has invalid unemulated feature flags (0x%08lX)\n", static_cast<unsigned long>(unemulated & ~device_t::feature::ALL));
+ if (imperfect & ~device_t::feature::ALL)
+ osd_printf_error("Driver has invalid imperfect feature flags (0x%08lX)\n", static_cast<unsigned long>(imperfect & ~device_t::feature::ALL));
+ if (unemulated & imperfect)
+ osd_printf_error("Driver cannot have features that are both unemulated and imperfect (0x%08lX)\n", static_cast<unsigned long>(unemulated & imperfect));
+ if ((m_current_driver->flags & machine_flags::NO_SOUND_HW) && ((unemulated | imperfect) & device_t::feature::SOUND))
+ osd_printf_error("Machine without sound hardware cannot have unemulated/imperfect sound\n");
+}
+
+
+//-------------------------------------------------
+// validate_roms - validate ROM definitions
+//-------------------------------------------------
+
+void validity_checker::validate_roms(device_t &root)
+{
+ // iterate, starting with the driver's ROMs and continuing with device ROMs
+ for (device_t &device : device_iterator(root))
+ {
+ // track the current device
+ m_current_device = &device;
+
+ // scan the ROM entries for this device
+ char const *last_region_name = "???";
+ char const *last_name = "???";
+ u32 current_length = 0;
+ int items_since_region = 1;
+ int last_bios = 0, max_bios = 0;
+ int total_files = 0;
+ std::unordered_map<std::string, int> bios_names;
+ std::unordered_map<std::string, std::string> bios_descs;
+ char const *defbios = nullptr;
+ for (tiny_rom_entry const *romp = device.rom_region(); romp && !ROMENTRY_ISEND(romp); ++romp)
+ {
+ if (ROMENTRY_ISREGION(romp)) // if this is a region, make sure it's valid, and record the length
+ {
+ // if we haven't seen any items since the last region, print a warning
+ if (items_since_region == 0)
+ osd_printf_warning("Empty ROM region '%s' (warning)\n", last_region_name);
+
+ // reset our region tracking states
+ char const *const basetag = romp->name;
+ items_since_region = (ROMREGION_ISERASE(romp) || ROMREGION_ISDISKDATA(romp)) ? 1 : 0;
+ last_region_name = basetag;
+
+ // check for a valid tag
+ if (!basetag)
+ {
+ osd_printf_error("ROM_REGION tag with nullptr name\n");
+ continue;
+ }
+
+ // validate the base tag
+ validate_tag(basetag);
+
+ // generate the full tag
+ std::string const fulltag = device.subtag(romp->name);
+
+ // attempt to add it to the map, reporting duplicates as errors
+ current_length = ROMREGION_GETLENGTH(romp);
+ if (!m_region_map.insert(std::make_pair(fulltag, current_length)).second)
+ osd_printf_error("Multiple ROM_REGIONs with the same tag '%s' defined\n", fulltag.c_str());
+ }
+ else if (ROMENTRY_ISSYSTEM_BIOS(romp)) // If this is a system bios, make sure it is using the next available bios number
+ {
+ int const bios_flags = ROM_GETBIOSFLAGS(romp);
+ char const *const biosname = romp->name;
+ if (bios_flags != last_bios + 1)
+ osd_printf_error("Non-sequential BIOS %s (specified as %d, expected to be %d)\n", biosname, bios_flags - 1, last_bios);
+ last_bios = bios_flags;
+
+ // validate the name
+ if (strlen(biosname) > 16)
+ osd_printf_error("BIOS name %s exceeds maximum 16 characters\n", biosname);
+ for (char const *s = biosname; *s; ++s)
+ {
+ if (((*s < '0') || (*s > '9')) && ((*s < 'a') || (*s > 'z')) && (*s != '.') && (*s != '_') && (*s != '-'))
+ {
+ osd_printf_error("BIOS name %s contains invalid characters\n", biosname);
+ break;
+ }
+ }
+
+ // check for duplicate names/descriptions
+ auto const nameins = bios_names.emplace(biosname, bios_flags);
+ if (!nameins.second)
+ osd_printf_error("Duplicate BIOS name %s specified (%d and %d)\n", biosname, nameins.first->second, bios_flags - 1);
+ auto const descins = bios_descs.emplace(romp->hashdata, biosname);
+ if (!descins.second)
+ osd_printf_error("BIOS %s has duplicate description '%s' (was %s)\n", biosname, romp->hashdata, descins.first->second.c_str());
+ }
+ else if (ROMENTRY_ISDEFAULT_BIOS(romp)) // if this is a default BIOS setting, remember it so it to check at the end
+ {
+ defbios = romp->name;
+ }
+ else if (ROMENTRY_ISFILE(romp)) // if this is a file, make sure it is properly formatted
+ {
+ // track the last filename we found
+ last_name = romp->name;
+ total_files++;
+ max_bios = std::max<int>(max_bios, ROM_GETBIOSFLAGS(romp));
+
+ // validate the name
+ if (strlen(last_name) > 127)
+ osd_printf_error("ROM label %s exceeds maximum 127 characters\n", last_name);
+ for (char const *s = last_name; *s; ++s)
+ {
+ if (((*s < '0') || (*s > '9')) && ((*s < 'a') || (*s > 'z')) && (*s != ' ') && (*s != '@') && (*s != '.') && (*s != ',') && (*s != '_') && (*s != '-') && (*s != '+') && (*s != '='))
+ {
+ osd_printf_error("ROM label %s contains invalid characters\n", last_name);
+ break;
+ }
+ }
+
+ // make sure the hash is valid
+ util::hash_collection hashes;
+ if (!hashes.from_internal_string(romp->hashdata))
+ osd_printf_error("ROM '%s' has an invalid hash string '%s'\n", last_name, romp->hashdata);
+ }
+
+ // for any non-region ending entries, make sure they don't extend past the end
+ if (!ROMENTRY_ISREGIONEND(romp) && current_length > 0 && !ROMENTRY_ISIGNORE(romp)) // HBMAME
+ {
+ items_since_region++;
+ if (!ROMENTRY_ISIGNORE(romp) && (ROM_GETOFFSET(romp) + ROM_GETLENGTH(romp) > current_length))
+ osd_printf_error("ROM '%s' extends past the defined memory region\n", last_name);
+ }
+ }
+
+ // check that default BIOS exists
+ if (defbios && (bios_names.find(defbios) == bios_names.end()))
+ osd_printf_error("Default BIOS '%s' not found\n", defbios);
+ if (!device.get_default_bios_tag().empty() && (bios_names.find(device.get_default_bios_tag()) == bios_names.end()))
+ osd_printf_error("Configured BIOS '%s' not found\n", device.get_default_bios_tag().c_str());
+
+ // check that there aren't ROMs for a non-existent BIOS option
+ if (max_bios > last_bios)
+ osd_printf_error("BIOS %d set on file is higher than maximum system BIOS number %d\n", max_bios - 1, last_bios - 1);
+
+ // final check for empty regions
+ if (items_since_region == 0)
+ osd_printf_warning("Empty ROM region '%s' (warning)\n", last_region_name);
+
+ // reset the current device
+ m_current_device = nullptr;
+ }
+}
+
+
+//-------------------------------------------------
+// validate_analog_input_field - validate an
+// analog input field
+//-------------------------------------------------
+
+void validity_checker::validate_analog_input_field(ioport_field &field)
+{
+ // analog ports must have a valid sensitivity
+ if (field.sensitivity() == 0)
+ osd_printf_error("Analog port with zero sensitivity\n");
+
+ // check that the default falls in the bitmask range
+ if (field.defvalue() & ~field.mask())
+ osd_printf_error("Analog port with a default value (%X) out of the bitmask range (%X)\n", field.defvalue(), field.mask());
+
+ // tests for positional devices
+ if (field.type() == IPT_POSITIONAL || field.type() == IPT_POSITIONAL_V)
+ {
+ int shift;
+ for (shift = 0; shift <= 31 && (~field.mask() & (1 << shift)) != 0; shift++) { }
+
+ // convert the positional max value to be in the bitmask for testing
+ //s32 analog_max = field.maxval();
+ //analog_max = (analog_max - 1) << shift;
+
+ // positional port size must fit in bits used
+ if ((field.mask() >> shift) + 1 < field.maxval())
+ osd_printf_error("Analog port with a positional port size bigger then the mask size\n");
+ }
+
+ // tests for absolute devices
+ else if (field.type() > IPT_ANALOG_ABSOLUTE_FIRST && field.type() < IPT_ANALOG_ABSOLUTE_LAST)
+ {
+ // adjust for signed values
+ s32 default_value = field.defvalue();
+ s32 analog_min = field.minval();
+ s32 analog_max = field.maxval();
+ if (analog_min > analog_max)
+ {
+ analog_min = -analog_min;
+ if (default_value > analog_max)
+ default_value = -default_value;
+ }
+
+ // check that the default falls in the MINMAX range
+ if (default_value < analog_min || default_value > analog_max)
+ osd_printf_error("Analog port with a default value (%X) out of PORT_MINMAX range (%X-%X)\n", field.defvalue(), field.minval(), field.maxval());
+
+ // check that the MINMAX falls in the bitmask range
+ // we use the unadjusted min for testing
+ if (field.minval() & ~field.mask() || analog_max & ~field.mask())
+ osd_printf_error("Analog port with a PORT_MINMAX (%X-%X) value out of the bitmask range (%X)\n", field.minval(), field.maxval(), field.mask());
+
+ // absolute analog ports do not use PORT_RESET
+ if (field.analog_reset())
+ osd_printf_error("Absolute analog port using PORT_RESET\n");
+
+ // absolute analog ports do not use PORT_WRAPS
+ if (field.analog_wraps())
+ osd_printf_error("Absolute analog port using PORT_WRAPS\n");
+ }
+
+ // tests for non IPT_POSITIONAL relative devices
+ else
+ {
+ // relative devices do not use PORT_MINMAX
+ if (field.minval() != 0 || field.maxval() != field.mask())
+ osd_printf_error("Relative port using PORT_MINMAX\n");
+
+ // relative devices do not use a default value
+ // the counter is at 0 on power up
+ if (field.defvalue() != 0)
+ osd_printf_error("Relative port using non-0 default value\n");
+
+ // relative analog ports do not use PORT_WRAPS
+ if (field.analog_wraps())
+ osd_printf_error("Absolute analog port using PORT_WRAPS\n");
+ }
+}
+
+
+//-------------------------------------------------
+// validate_dip_settings - validate a DIP switch
+// setting
+//-------------------------------------------------
+
+void validity_checker::validate_dip_settings(ioport_field &field)
+{
+ const char *demo_sounds = ioport_string_from_index(INPUT_STRING_Demo_Sounds);
+ const char *flipscreen = ioport_string_from_index(INPUT_STRING_Flip_Screen);
+ u8 coin_list[__input_string_coinage_end + 1 - __input_string_coinage_start] = { 0 };
+ bool coin_error = false;
+
+ // iterate through the settings
+ for (ioport_setting &setting : field.settings())
+ {
+ // note any coinage strings
+ int strindex = get_defstr_index(setting.name());
+ if (strindex >= __input_string_coinage_start && strindex <= __input_string_coinage_end)
+ coin_list[strindex - __input_string_coinage_start] = 1;
+
+ // make sure demo sounds default to on
+ if (field.name() == demo_sounds && strindex == INPUT_STRING_On && field.defvalue() != setting.value())
+ osd_printf_error("Demo Sounds must default to On\n");
+
+ // check for bad demo sounds options
+ if (field.name() == demo_sounds && (strindex == INPUT_STRING_Yes || strindex == INPUT_STRING_No))
+ osd_printf_error("Demo Sounds option must be Off/On, not %s\n", setting.name());
+
+ // check for bad flip screen options
+ if (field.name() == flipscreen && (strindex == INPUT_STRING_Yes || strindex == INPUT_STRING_No))
+ osd_printf_error("Flip Screen option must be Off/On, not %s\n", setting.name());
+
+ // if we have a neighbor, compare ourselves to him
+ if (setting.next() != nullptr)
+ {
+ // check for inverted off/on dispswitch order
+ int next_strindex = get_defstr_index(setting.next()->name(), true);
+ if (strindex == INPUT_STRING_On && next_strindex == INPUT_STRING_Off)
+ osd_printf_error("%s option must have Off/On options in the order: Off, On\n", field.name());
+
+ // check for inverted yes/no dispswitch order
+ else if (strindex == INPUT_STRING_Yes && next_strindex == INPUT_STRING_No)
+ osd_printf_error("%s option must have Yes/No options in the order: No, Yes\n", field.name());
+
+ // check for inverted upright/cocktail dispswitch order
+ else if (strindex == INPUT_STRING_Cocktail && next_strindex == INPUT_STRING_Upright)
+ osd_printf_error("%s option must have Upright/Cocktail options in the order: Upright, Cocktail\n", field.name());
+
+ // check for proper coin ordering
+ else if (strindex >= __input_string_coinage_start && strindex <= __input_string_coinage_end && next_strindex >= __input_string_coinage_start && next_strindex <= __input_string_coinage_end &&
+ strindex >= next_strindex && setting.condition() == setting.next()->condition())
+ {
+ osd_printf_error("%s option has unsorted coinage %s > %s\n", field.name(), setting.name(), setting.next()->name());
+ coin_error = true;
+ }
+ }
+ }
+
+ // if we have a coin error, demonstrate the correct way
+ if (coin_error)
+ {
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, " Note proper coin sort order should be:\n");
+ for (int entry = 0; entry < ARRAY_LENGTH(coin_list); entry++)
+ if (coin_list[entry])
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, " %s\n", ioport_string_from_index(__input_string_coinage_start + entry));
+ }
+}
+
+
+//-------------------------------------------------
+// validate_condition - validate a condition
+// stored within an ioport field or setting
+//-------------------------------------------------
+
+void validity_checker::validate_condition(ioport_condition &condition, device_t &device, std::unordered_set<std::string> &port_map)
+{
+ // resolve the tag
+ // then find a matching port
+ if (port_map.find(device.subtag(condition.tag())) == port_map.end())
+ osd_printf_error("Condition referencing non-existent ioport tag '%s'\n", condition.tag());
+}
+
+
+//-------------------------------------------------
+// validate_inputs - validate input configuration
+//-------------------------------------------------
+
+void validity_checker::validate_inputs()
+{
+ std::unordered_set<std::string> port_map;
+
+ // iterate over devices
+ for (device_t &device : device_iterator(m_current_config->root_device()))
+ {
+ // see if this device has ports; if not continue
+ if (device.input_ports() == nullptr)
+ continue;
+
+ // track the current device
+ m_current_device = &device;
+
+ // allocate the input ports
+ ioport_list portlist;
+ std::string errorbuf;
+ portlist.append(device, errorbuf);
+
+ // report any errors during construction
+ if (!errorbuf.empty())
+ osd_printf_error("I/O port error during construction:\n%s\n", errorbuf.c_str());
+
+ // do a first pass over ports to add their names and find duplicates
+ for (auto &port : portlist)
+ if (!port_map.insert(port.second->tag()).second)
+ osd_printf_error("Multiple I/O ports with the same tag '%s' defined\n", port.second->tag());
+
+ // iterate over ports
+ for (auto &port : portlist)
+ {
+ m_current_ioport = port.second->tag();
+
+ // iterate through the fields on this port
+ for (ioport_field &field : port.second->fields())
+ {
+ // verify analog inputs
+ if (field.is_analog())
+ validate_analog_input_field(field);
+
+ // look for invalid (0) types which should be mapped to IPT_OTHER
+ if (field.type() == IPT_INVALID)
+ osd_printf_error("Field has an invalid type (0); use IPT_OTHER instead\n");
+
+ if (field.type() == IPT_SPECIAL)
+ osd_printf_error("Field has an invalid type IPT_SPECIAL\n");
+
+ // verify dip switches
+ if (field.type() == IPT_DIPSWITCH)
+ {
+ // dip switch fields must have a specific name
+ if (field.specific_name() == nullptr)
+ osd_printf_error("DIP switch has no specific name\n");
+
+ // verify the settings list
+ validate_dip_settings(field);
+ }
+
+ // verify config settings
+ if (field.type() == IPT_CONFIG)
+ {
+ // config fields must have a specific name
+ if (field.specific_name() == nullptr)
+ osd_printf_error("Config switch has no specific name\n");
+ }
+
+ // verify names
+ const char *name = field.specific_name();
+ if (name != nullptr)
+ {
+ // check for empty string
+ if (name[0] == 0)
+ osd_printf_error("Field name is an empty string\n");
+
+ // check for trailing spaces
+ if (name[0] != 0 && name[strlen(name) - 1] == ' ')
+ osd_printf_error("Field '%s' has trailing spaces\n", name);
+
+ // check for invalid UTF-8
+ if (!utf8_is_valid_string(name))
+ osd_printf_error("Field '%s' has invalid characters\n", name);
+
+ // look up the string and print an error if default strings are not used
+ /*strindex =get_defstr_index(defstr_map, name, driver, &error);*/
+ }
+
+ // verify conditions on the field
+ if (!field.condition().none())
+ validate_condition(field.condition(), device, port_map);
+
+ // verify conditions on the settings
+ for (ioport_setting &setting : field.settings())
+ if (!setting.condition().none())
+ validate_condition(setting.condition(), device, port_map);
+
+ // verify natural keyboard codes
+ for (int which = 0; which < 1 << (UCHAR_SHIFT_END - UCHAR_SHIFT_BEGIN + 1); which++)
+ {
+ std::vector<char32_t> codes = field.keyboard_codes(which);
+ for (char32_t code : codes)
+ {
+ if (!uchar_isvalid(code))
+ {
+ osd_printf_error("Field '%s' has non-character U+%04X in PORT_CHAR(%d)\n",
+ name,
+ (unsigned)code,
+ (int)code);
+ }
+ }
+ }
+ }
+
+ // done with this port
+ m_current_ioport = nullptr;
+ }
+
+ // done with this device
+ m_current_device = nullptr;
+ }
+}
+
+
+//-------------------------------------------------
+// validate_devices - run per-device validity
+// checks
+//-------------------------------------------------
+
+void validity_checker::validate_devices()
+{
+ std::unordered_set<std::string> device_map;
+
+ for (device_t &device : device_iterator(m_current_config->root_device()))
+ {
+ // track the current device
+ m_current_device = &device;
+
+ // validate auto-finders
+ device.findit(true);
+
+ // validate the device tag
+ validate_tag(device.basetag());
+
+ // look for duplicates
+ bool duplicate = !device_map.insert(device.tag()).second;
+ if (duplicate)
+ osd_printf_error("Multiple devices with the same tag defined\n");
+
+ // check for device-specific validity check
+ device.validity_check(*this);
+
+ // done with this device
+ m_current_device = nullptr;
+
+ // if it's a slot, iterate over possible cards (don't recurse, or you'll stack infinite tee connectors)
+ device_slot_interface *const slot = dynamic_cast<device_slot_interface *>(&device);
+ if (slot && !slot->fixed() && !duplicate)
+ {
+ for (auto &option : slot->option_list())
+ {
+ // the default option is already instantiated here, so don't try adding it again
+ if (slot->default_option() != nullptr && option.first == slot->default_option())
+ continue;
+
+ device_t *card;
+ {
+ machine_config::token const tok(m_current_config->begin_configuration(slot->device()));
+ card = m_current_config->device_add(option.second->name(), option.second->devtype(), option.second->clock());
+
+ const char *const def_bios = option.second->default_bios();
+ if (def_bios)
+ card->set_default_bios_tag(def_bios);
+ auto additions = option.second->machine_config();
+ if (additions)
+ additions(card);
+ }
+
+ for (device_slot_interface &subslot : slot_interface_iterator(*card))
+ {
+ if (subslot.fixed())
+ {
+ // TODO: make this self-contained so it can apply itself
+ device_slot_interface::slot_option const *suboption = subslot.option(subslot.default_option());
+ if (suboption)
+ {
+ machine_config::token const tok(m_current_config->begin_configuration(subslot.device()));
+ device_t *const sub_card = m_current_config->device_add(suboption->name(), suboption->devtype(), suboption->clock());
+ const char *const sub_bios = suboption->default_bios();
+ if (sub_bios)
+ sub_card->set_default_bios_tag(sub_bios);
+ auto sub_additions = suboption->machine_config();
+ if (sub_additions)
+ sub_additions(sub_card);
+ }
+ }
+ }
+
+ for (device_t &card_dev : device_iterator(*card))
+ card_dev.config_complete();
+ validate_roms(*card);
+
+ for (device_t &card_dev : device_iterator(*card))
+ {
+ m_current_device = &card_dev;
+ card_dev.findit(true);
+ card_dev.validity_check(*this);
+ m_current_device = nullptr;
+ }
+
+ machine_config::token const tok(m_current_config->begin_configuration(slot->device()));
+ m_current_config->device_remove(option.second->name());
+ }
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// validate_devices_types - check validity of
+// registered device types
+//-------------------------------------------------
+
+void validity_checker::validate_device_types()
+{
+ // reset error/warning state
+ int start_errors = m_errors;
+ int start_warnings = m_warnings;
+ m_error_text.clear();
+ m_warning_text.clear();
+ m_verbose_text.clear();
+
+ std::unordered_map<std::string, std::add_pointer_t<device_type> > device_name_map, device_shortname_map;
+ machine_config config(GAME_NAME(___empty), m_drivlist.options());
+ machine_config::token const tok(config.begin_configuration(config.root_device()));
+ for (device_type type : registered_device_types)
+ {
+ device_t *const dev = config.device_add("_tmp", type, 0);
+
+ char const *name((dev->shortname() && *dev->shortname()) ? dev->shortname() : type.type().name());
+ std::string const description((dev->source() && *dev->source()) ? util::string_format("%s(%s)", core_filename_extract_base(dev->source()).c_str(), name) : name);
+
+ if (m_print_verbose)
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Validating device %s...\n", description.c_str());
+
+ // ensure shortname exists
+ if (!dev->shortname() || !*dev->shortname())
+ {
+ osd_printf_error("Device %s does not have short name defined\n", description.c_str());
+ }
+ else
+ {
+ // make sure the device name is not too long
+ if (strlen(dev->shortname()) > 32)
+ osd_printf_error("Device short name must be 32 characters or less\n");
+
+ // check for invalid characters in shortname
+ for (char const *s = dev->shortname(); *s; ++s)
+ {
+ if (((*s < '0') || (*s > '9')) && ((*s < 'a') || (*s > 'z')) && (*s != '_'))
+ {
+ osd_printf_error("Device %s short name contains invalid characters\n", description.c_str());
+ break;
+ }
+ }
+
+ // check for name conflicts
+ std::string tmpname(dev->shortname());
+ game_driver_map::const_iterator const drvname(m_names_map.find(tmpname));
+ auto const devname(device_shortname_map.emplace(std::move(tmpname), &type));
+ if (m_names_map.end() != drvname)
+ {
+ game_driver const &dup(*drvname->second);
+ osd_printf_error("Device %s short name is a duplicate of %s(%s)\n", description.c_str(), core_filename_extract_base(dup.type.source()).c_str(), dup.name);
+ }
+ else if (!devname.second)
+ {
+ device_t *const dup = config.device_add("_dup", *devname.first->second, 0);
+ osd_printf_error("Device %s short name is a duplicate of %s(%s)\n", description.c_str(), core_filename_extract_base(dup->source()).c_str(), dup->shortname());
+ config.device_remove("_dup");
+ }
+ }
+
+ // ensure name exists
+ if (!dev->name() || !*dev->name())
+ {
+ osd_printf_error("Device %s does not have name defined\n", description.c_str());
+ }
+ else
+ {
+ // check for description conflicts
+ std::string tmpdesc(dev->name());
+ game_driver_map::const_iterator const drvdesc(m_descriptions_map.find(tmpdesc));
+ auto const devdesc(device_name_map.emplace(std::move(tmpdesc), &type));
+ if (m_descriptions_map.end() != drvdesc)
+ {
+ game_driver const &dup(*drvdesc->second);
+ osd_printf_error("Device %s name '%s' is a duplicate of %s(%s)\n", description.c_str(), dev->name(), core_filename_extract_base(dup.type.source()).c_str(), dup.name);
+ }
+ else if (!devdesc.second)
+ {
+ device_t *const dup = config.device_add("_dup", *devdesc.first->second, 0);
+ osd_printf_error("Device %s name '%s' is a duplicate of %s(%s)\n", description.c_str(), dev->name(), core_filename_extract_base(dup->source()).c_str(), dup->shortname());
+ config.device_remove("_dup");
+ }
+ }
+
+ // ensure source exists
+ if (!dev->source() || !*dev->source())
+ osd_printf_error("Device %s does not have source defined\n", description.c_str());
+
+ // check that reported type matches supplied type
+ if (dev->type().type() != type.type())
+ osd_printf_error("Device %s reports type '%s' (created with '%s')\n", description.c_str(), dev->type().type().name(), type.type().name());
+
+ // catch invalid flag combinations
+ device_t::feature_type const unemulated(dev->type().unemulated_features());
+ device_t::feature_type const imperfect(dev->type().imperfect_features());
+ if (unemulated & ~device_t::feature::ALL)
+ osd_printf_error("Device has invalid unemulated feature flags (0x%08lX)\n", static_cast<unsigned long>(unemulated & ~device_t::feature::ALL));
+ if (imperfect & ~device_t::feature::ALL)
+ osd_printf_error("Device has invalid imperfect feature flags (0x%08lX)\n", static_cast<unsigned long>(imperfect & ~device_t::feature::ALL));
+ if (unemulated & imperfect)
+ osd_printf_error("Device cannot have features that are both unemulated and imperfect (0x%08lX)\n", static_cast<unsigned long>(unemulated & imperfect));
+
+ config.device_remove("_tmp");
+ }
+
+ // if we had warnings or errors, output
+ if (m_errors > start_errors || m_warnings > start_warnings || !m_verbose_text.empty())
+ {
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "%d errors, %d warnings\n", m_errors - start_errors, m_warnings - start_warnings);
+ if (m_errors > start_errors)
+ output_indented_errors(m_error_text, "Errors");
+ if (m_warnings > start_warnings)
+ output_indented_errors(m_warning_text, "Warnings");
+ if (!m_verbose_text.empty())
+ output_indented_errors(m_verbose_text, "Messages");
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "\n");
+ }
+}
+
+
+//-------------------------------------------------
+// build_output_prefix - create a prefix
+// indicating the current source file, driver,
+// and device
+//-------------------------------------------------
+
+void validity_checker::build_output_prefix(std::string &str)
+{
+ // start empty
+ str.clear();
+
+ // if we have a current (non-root) device, indicate that
+ if (m_current_device != nullptr && m_current_device->owner() != nullptr)
+ str.append(m_current_device->name()).append(" device '").append(m_current_device->tag() + 1).append("': ");
+
+ // if we have a current port, indicate that as well
+ if (m_current_ioport != nullptr)
+ str.append("ioport '").append(m_current_ioport).append("': ");
+}
+
+
+//-------------------------------------------------
+// error_output - error message output override
+//-------------------------------------------------
+
+void validity_checker::output_callback(osd_output_channel channel, const char *msg, va_list args)
+{
+ std::string output;
+ switch (channel)
+ {
+ case OSD_OUTPUT_CHANNEL_ERROR:
+ // count the error
+ m_errors++;
+
+ // output the source(driver) device 'tag'
+ build_output_prefix(output);
+
+ // generate the string
+ strcatvprintf(output, msg, args);
+ m_error_text.append(output);
+ break;
+
+ case OSD_OUTPUT_CHANNEL_WARNING:
+ // count the error
+ m_warnings++;
+
+ // output the source(driver) device 'tag'
+ build_output_prefix(output);
+
+ // generate the string and output to the original target
+ strcatvprintf(output, msg, args);
+ m_warning_text.append(output);
+ break;
+
+ case OSD_OUTPUT_CHANNEL_VERBOSE:
+ // if we're not verbose, skip it
+ if (!m_print_verbose) break;
+
+ // output the source(driver) device 'tag'
+ build_output_prefix(output);
+
+ // generate the string and output to the original target
+ strcatvprintf(output, msg, args);
+ m_verbose_text.append(output);
+ break;
+
+ default:
+ chain_output(channel, msg, args);
+ break;
+ }
+}
+
+//-------------------------------------------------
+// output_via_delegate - helper to output a
+// message via a varargs string, so the argptr
+// can be forwarded onto the given delegate
+//-------------------------------------------------
+
+void validity_checker::output_via_delegate(osd_output_channel channel, const char *format, ...)
+{
+ va_list argptr;
+
+ // call through to the delegate with the proper parameters
+ va_start(argptr, format);
+ chain_output(channel, format, argptr);
+ va_end(argptr);
+}
+
+//-------------------------------------------------
+// output_indented_errors - helper to output error
+// and warning messages with header and indents
+//-------------------------------------------------
+void validity_checker::output_indented_errors(std::string &text, const char *header)
+{
+ // remove trailing newline
+ if (text[text.size()-1] == '\n')
+ text.erase(text.size()-1, 1);
+ strreplace(text, "\n", "\n ");
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "%s:\n %s\n", header, text.c_str());
+}
diff --git a/docs/release/src/emu/video.cpp b/docs/release/src/emu/video.cpp
new file mode 100644
index 00000000000..f967ef78935
--- /dev/null
+++ b/docs/release/src/emu/video.cpp
@@ -0,0 +1,1571 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+/***************************************************************************
+
+ video.cpp
+
+ Core MAME video routines.
+
+***************************************************************************/
+
+#include "emu.h"
+#include "emuopts.h"
+#include "debugger.h"
+#include "ui/uimain.h"
+#include "crsshair.h"
+#include "rendersw.hxx"
+#include "output.h"
+
+#include "aviio.h"
+#include "png.h"
+#include "xmlfile.h"
+
+#include "osdepend.h"
+
+
+//**************************************************************************
+// DEBUGGING
+//**************************************************************************
+
+#define LOG_THROTTLE (0)
+
+
+
+//**************************************************************************
+// GLOBAL VARIABLES
+//**************************************************************************
+
+// frameskipping tables
+const bool video_manager::s_skiptable[FRAMESKIP_LEVELS][FRAMESKIP_LEVELS] =
+{
+ { false, false, false, false, false, false, false, false, false, false, false, false },
+ { false, false, false, false, false, false, false, false, false, false, false, true },
+ { false, false, false, false, false, true , false, false, false, false, false, true },
+ { false, false, false, true , false, false, false, true , false, false, false, true },
+ { false, false, true , false, false, true , false, false, true , false, false, true },
+ { false, true , false, false, true , false, true , false, false, true , false, true },
+ { false, true , false, true , false, true , false, true , false, true , false, true },
+ { false, true , false, true , true , false, true , false, true , true , false, true },
+ { false, true , true , false, true , true , false, true , true , false, true , true },
+ { false, true , true , true , false, true , true , true , false, true , true , true },
+ { false, true , true , true , true , true , false, true , true , true , true , true },
+ { false, true , true , true , true , true , true , true , true , true , true , true }
+};
+
+
+
+//**************************************************************************
+// VIDEO MANAGER
+//**************************************************************************
+
+static void video_notifier_callback(const char *outname, s32 value, void *param)
+{
+ video_manager *vm = (video_manager *)param;
+
+ vm->set_output_changed();
+}
+
+
+//-------------------------------------------------
+// video_manager - constructor
+//-------------------------------------------------
+
+video_manager::video_manager(running_machine &machine)
+ : m_machine(machine)
+ , m_screenless_frame_timer(nullptr)
+ , m_output_changed(false)
+ , m_throttle_last_ticks(0)
+ , m_throttle_realtime(attotime::zero)
+ , m_throttle_emutime(attotime::zero)
+ , m_throttle_history(0)
+ , m_speed_last_realtime(0)
+ , m_speed_last_emutime(attotime::zero)
+ , m_speed_percent(1.0)
+ , m_overall_real_seconds(0)
+ , m_overall_real_ticks(0)
+ , m_overall_emutime(attotime::zero)
+ , m_overall_valid_counter(0)
+ , m_throttled(machine.options().throttle())
+ , m_throttle_rate(1.0f)
+ , m_fastforward(false)
+ , m_seconds_to_run(machine.options().seconds_to_run())
+ , m_auto_frameskip(machine.options().auto_frameskip())
+ , m_speed(original_speed_setting())
+ , m_empty_skip_count(0)
+ , m_frameskip_level(machine.options().frameskip())
+ , m_frameskip_counter(0)
+ , m_frameskip_adjust(0)
+ , m_skipping_this_frame(false)
+ , m_average_oversleep(0)
+ , m_snap_target(nullptr)
+ , m_snap_native(true)
+ , m_snap_width(0)
+ , m_snap_height(0)
+ , m_timecode_enabled(false)
+ , m_timecode_write(false)
+ , m_timecode_text("")
+ , m_timecode_start(attotime::zero)
+ , m_timecode_total(attotime::zero)
+{
+ // request a callback upon exiting
+ machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&video_manager::exit, this));
+ machine.save().register_postload(save_prepost_delegate(FUNC(video_manager::postload), this));
+
+ // extract initial execution state from global configuration settings
+ update_refresh_speed();
+
+ const unsigned screen_count(screen_device_iterator(machine.root_device()).count());
+ const bool no_screens(!screen_count);
+
+ // create a render target for snapshots
+ const char *viewname = machine.options().snap_view();
+ m_snap_native = !no_screens && (viewname[0] == 0 || strcmp(viewname, "native") == 0);
+
+ if (m_snap_native)
+ {
+ // the native target is hard-coded to our internal layout and has all options disabled
+ util::xml::file::ptr const root(util::xml::file::create());
+ if (!root)
+ throw emu_fatalerror("Couldn't create XML document??");
+ util::xml::data_node *const layoutnode(root->add_child("mamelayout", nullptr));
+ if (!layoutnode)
+ throw emu_fatalerror("Couldn't create XML node??");
+ layoutnode->set_attribute_int("version", 2);
+
+ for (unsigned i = 0; screen_count > i; ++i)
+ {
+ util::xml::data_node *const viewnode(layoutnode->add_child("view", nullptr));
+ if (!viewnode)
+ throw emu_fatalerror("Couldn't create XML node??");
+ viewnode->set_attribute("name", util::xml::normalize_string(util::string_format("s%1$u", i).c_str()));
+ util::xml::data_node *const screennode(viewnode->add_child("screen", nullptr));
+ if (!screennode)
+ throw emu_fatalerror("Couldn't create XML node??");
+ screennode->set_attribute_int("index", i);
+ util::xml::data_node *const boundsnode(screennode->add_child("bounds", nullptr));
+ if (!boundsnode)
+ throw emu_fatalerror("Couldn't create XML node??");
+ boundsnode->set_attribute_int("left", 0);
+ boundsnode->set_attribute_int("top", 0);
+ boundsnode->set_attribute_int("right", 1);
+ boundsnode->set_attribute_int("bottom", 1);
+ }
+
+ m_snap_target = machine.render().target_alloc(*root, RENDER_CREATE_SINGLE_FILE | RENDER_CREATE_HIDDEN);
+ m_snap_target->set_backdrops_enabled(false);
+ m_snap_target->set_overlays_enabled(false);
+ m_snap_target->set_bezels_enabled(false);
+ m_snap_target->set_cpanels_enabled(false);
+ m_snap_target->set_marquees_enabled(false);
+ m_snap_target->set_screen_overlay_enabled(false);
+ m_snap_target->set_zoom_to_screen(false);
+ }
+ else
+ {
+ // otherwise, non-default targets select the specified view and turn off effects
+ m_snap_target = machine.render().target_alloc(nullptr, RENDER_CREATE_HIDDEN);
+ m_snap_target->set_view(m_snap_target->configured_view(viewname, 0, 1));
+ m_snap_target->set_screen_overlay_enabled(false);
+ }
+
+ // extract snap resolution if present
+ if (sscanf(machine.options().snap_size(), "%dx%d", &m_snap_width, &m_snap_height) != 2)
+ m_snap_width = m_snap_height = 0;
+
+ // start recording movie if specified
+ const char *filename = machine.options().mng_write();
+ if (filename[0] != 0)
+ begin_recording(filename, MF_MNG);
+
+ filename = machine.options().avi_write();
+ if (filename[0] != 0)
+ begin_recording(filename, MF_AVI);
+
+ // if no screens, create a periodic timer to drive updates
+ if (no_screens)
+ {
+ m_screenless_frame_timer = machine.scheduler().timer_alloc(timer_expired_delegate(FUNC(video_manager::screenless_update_callback), this));
+ m_screenless_frame_timer->adjust(screen_device::DEFAULT_FRAME_PERIOD, 0, screen_device::DEFAULT_FRAME_PERIOD);
+ machine.output().set_notifier(nullptr, video_notifier_callback, this);
+ }
+}
+
+
+//-------------------------------------------------
+// set_frameskip - set the current actual
+// frameskip (-1 means autoframeskip)
+//-------------------------------------------------
+
+void video_manager::set_frameskip(int frameskip)
+{
+ // -1 means autoframeskip
+ if (frameskip == -1)
+ {
+ m_auto_frameskip = true;
+ m_frameskip_level = 0;
+ }
+
+ // any other level is a direct control
+ else if (frameskip >= 0 && frameskip <= MAX_FRAMESKIP)
+ {
+ m_auto_frameskip = false;
+ m_frameskip_level = frameskip;
+ }
+}
+
+
+//-------------------------------------------------
+// frame_update - handle frameskipping and UI,
+// plus updating the screen during normal
+// operations
+//-------------------------------------------------
+
+void video_manager::frame_update(bool from_debugger)
+{
+ // only render sound and video if we're in the running phase
+ machine_phase const phase = machine().phase();
+ bool skipped_it = m_skipping_this_frame;
+ if (phase == machine_phase::RUNNING && (!machine().paused() || machine().options().update_in_pause()))
+ {
+ bool anything_changed = finish_screen_updates();
+
+ // if none of the screens changed and we haven't skipped too many frames in a row,
+ // mark this frame as skipped to prevent throttling; this helps for games that
+ // don't update their screen at the monitor refresh rate
+ if (!anything_changed && !m_auto_frameskip && m_frameskip_level == 0 && m_empty_skip_count++ < 3)
+ skipped_it = true;
+ else
+ m_empty_skip_count = 0;
+ }
+
+ // draw the user interface
+ emulator_info::draw_user_interface(machine());
+
+ // if we're throttling, synchronize before rendering
+ attotime current_time = machine().time();
+ if (!from_debugger && !skipped_it && effective_throttle())
+ update_throttle(current_time);
+
+ // ask the OSD to update
+ g_profiler.start(PROFILER_BLIT);
+ machine().osd().update(!from_debugger && skipped_it);
+ g_profiler.stop();
+
+ emulator_info::periodic_check();
+
+ // perform tasks for this frame
+ if (!from_debugger)
+ machine().call_notifiers(MACHINE_NOTIFY_FRAME);
+
+ // update frameskipping
+ if (!from_debugger)
+ update_frameskip();
+
+ // update speed computations
+ if (!from_debugger && !skipped_it)
+ recompute_speed(current_time);
+
+ // call the end-of-frame callback
+ if (phase == machine_phase::RUNNING)
+ {
+ // reset partial updates if we're paused or if the debugger is active
+ screen_device *const screen = screen_device_iterator(machine().root_device()).first();
+ bool const debugger_enabled = machine().debug_flags & DEBUG_FLAG_ENABLED;
+ bool const within_instruction_hook = debugger_enabled && machine().debugger().within_instruction_hook();
+ if (screen && (machine().paused() || from_debugger || within_instruction_hook))
+ screen->reset_partial_updates();
+ }
+}
+
+
+//-------------------------------------------------
+// speed_text - print the text to be displayed
+// into a string buffer
+//-------------------------------------------------
+
+std::string video_manager::speed_text()
+{
+ std::ostringstream str;
+
+ // if we're paused, just display Paused
+ bool paused = machine().paused();
+ if (paused)
+ str << "paused";
+
+ // if we're fast forwarding, just display Fast-forward
+ else if (m_fastforward)
+ str << "fast ";
+
+ // if we're auto frameskipping, display that plus the level
+ else if (effective_autoframeskip())
+ util::stream_format(str, "auto%2d/%d", effective_frameskip(), MAX_FRAMESKIP);
+
+ // otherwise, just display the frameskip plus the level
+ else
+ util::stream_format(str, "skip %d/%d", effective_frameskip(), MAX_FRAMESKIP);
+
+ // append the speed for all cases except paused
+ if (!paused)
+ util::stream_format(str, "%4d%%", (int)(100 * m_speed_percent + 0.5));
+
+ // display the number of partial updates as well
+ int partials = 0;
+ for (screen_device &screen : screen_device_iterator(machine().root_device()))
+ partials += screen.partial_updates();
+ if (partials > 1)
+ util::stream_format(str, "\n%d partial updates", partials);
+
+ return str.str();
+}
+
+
+//-------------------------------------------------
+// save_snapshot - save a snapshot to the given
+// file handle
+//-------------------------------------------------
+
+void video_manager::save_snapshot(screen_device *screen, emu_file &file)
+{
+ // validate
+ assert(!m_snap_native || screen != nullptr);
+
+ // create the bitmap to pass in
+ create_snapshot_bitmap(screen);
+
+ // add two text entries describing the image
+ std::string text1 = std::string(emulator_info::get_appname()).append(" ").append(emulator_info::get_build_version());
+ std::string text2 = std::string(machine().system().manufacturer).append(" ").append(machine().system().type.fullname());
+ png_info pnginfo;
+ pnginfo.add_text("Software", text1.c_str());
+ pnginfo.add_text("System", text2.c_str());
+
+ // now do the actual work
+ const rgb_t *palette = (screen != nullptr && screen->has_palette()) ? screen->palette().palette()->entry_list_adjusted() : nullptr;
+ int entries = (screen != nullptr && screen->has_palette()) ? screen->palette().entries() : 0;
+ png_error error = png_write_bitmap(file, &pnginfo, m_snap_bitmap, entries, palette);
+ if (error != PNGERR_NONE)
+ osd_printf_error("Error generating PNG for snapshot: png_error = %d\n", error);
+}
+
+
+//-------------------------------------------------
+// save_active_screen_snapshots - save a
+// snapshot of all active screens
+//-------------------------------------------------
+
+void video_manager::save_active_screen_snapshots()
+{
+ // if we're native, then write one snapshot per visible screen
+ if (m_snap_native)
+ {
+ // write one snapshot per visible screen
+ for (screen_device &screen : screen_device_iterator(machine().root_device()))
+ if (machine().render().is_live(screen))
+ {
+ emu_file file(machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS);
+ osd_file::error filerr = open_next(file, "png");
+ if (filerr == osd_file::error::NONE)
+ save_snapshot(&screen, file);
+ }
+ }
+
+ // otherwise, just write a single snapshot
+ else
+ {
+ emu_file file(machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS);
+ osd_file::error filerr = open_next(file, "png");
+ if (filerr == osd_file::error::NONE)
+ save_snapshot(nullptr, file);
+ }
+}
+
+
+//-------------------------------------------------
+// save_input_timecode - add a line of current
+// timestamp to inp.timecode file
+//-------------------------------------------------
+
+void video_manager::save_input_timecode()
+{
+ // if record timecode input is not active, do nothing
+ if (!m_timecode_enabled) {
+ return;
+ }
+ m_timecode_write = true;
+}
+
+std::string &video_manager::timecode_text(std::string &str)
+{
+ attotime elapsed_time = machine().time() - m_timecode_start;
+ str = string_format(" %s%s%02d:%02d %s",
+ m_timecode_text,
+ m_timecode_text.empty() ? "" : " ",
+ (elapsed_time.m_seconds / 60) % 60,
+ elapsed_time.m_seconds % 60,
+ machine().paused() ? "[paused] " : "");
+ return str;
+}
+
+std::string &video_manager::timecode_total_text(std::string &str)
+{
+ attotime elapsed_time = m_timecode_total;
+ if (machine().ui().show_timecode_counter()) {
+ elapsed_time += machine().time() - m_timecode_start;
+ }
+ str = string_format("TOTAL %02d:%02d ",
+ (elapsed_time.m_seconds / 60) % 60,
+ elapsed_time.m_seconds % 60);
+ return str;
+}
+
+//-------------------------------------------------
+// begin_recording_mng - begin recording a MNG
+//-------------------------------------------------
+
+void video_manager::begin_recording_mng(const char *name, uint32_t index, screen_device *screen)
+{
+ // stop any existing recording
+ end_recording_mng(index);
+
+ mng_info_t &info = m_mngs[index];
+
+ // reset the state
+ info.m_mng_frame = 0;
+ info.m_mng_next_frame_time = machine().time();
+
+ // create a new movie file and start recording
+ info.m_mng_file = std::make_unique<emu_file>(machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS);
+ osd_file::error filerr;
+ if (name != nullptr)
+ {
+ std::string full_name(name);
+
+ if (index > 0)
+ {
+ char name_buf[256] = { 0 };
+ snprintf(name_buf, 256, "%s%d", name, index);
+ full_name = name_buf;
+ }
+
+ filerr = info.m_mng_file->open(full_name.c_str());
+ }
+ else
+ {
+ filerr = open_next(*info.m_mng_file, "mng");
+ }
+
+ if (filerr == osd_file::error::NONE)
+ {
+ // start the capture
+ int rate = int(screen->frame_period().as_hz());
+ png_error pngerr = mng_capture_start(*info.m_mng_file, m_snap_bitmap, rate);
+ if (pngerr != PNGERR_NONE)
+ {
+ osd_printf_error("Error capturing MNG, png_error=%d\n", pngerr);
+ return end_recording_mng(index);
+ }
+
+ // compute the frame time
+ info.m_mng_frame_period = attotime::from_hz(rate);
+ }
+ else
+ {
+ osd_printf_error("Error creating MNG, osd_file::error=%d\n", int(filerr));
+ info.m_mng_file.reset();
+ }
+}
+
+//-------------------------------------------------
+// begin_recording_avi - begin recording an AVI
+//-------------------------------------------------
+
+void video_manager::begin_recording_avi(const char *name, uint32_t index, screen_device *screen)
+{
+ // stop any existing recording
+ end_recording_avi(index);
+
+ avi_info_t &avi_info = m_avis[index];
+
+ // reset the state
+ avi_info.m_avi_frame = 0;
+ avi_info.m_avi_next_frame_time = machine().time();
+
+ // build up information about this new movie
+ avi_file::movie_info info;
+ info.video_format = 0;
+ info.video_timescale = 1000 * screen->frame_period().as_hz();
+ info.video_sampletime = 1000;
+ info.video_numsamples = 0;
+ info.video_width = m_snap_bitmap.width();
+ info.video_height = m_snap_bitmap.height();
+ info.video_depth = 24;
+
+ info.audio_format = 0;
+ info.audio_timescale = machine().sample_rate();
+ info.audio_sampletime = 1;
+ info.audio_numsamples = 0;
+ info.audio_channels = 2;
+ info.audio_samplebits = 16;
+ info.audio_samplerate = machine().sample_rate();
+
+ // create a new temporary movie file
+ osd_file::error filerr;
+ std::string fullpath;
+ {
+ emu_file tempfile(machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS);
+ if (name != nullptr)
+ {
+ std::string full_name(name);
+
+ if (index > 0)
+ {
+ char name_buf[256] = { 0 };
+ snprintf(name_buf, 256, "%s%d", name, index);
+ full_name = name_buf;
+ }
+
+ filerr = tempfile.open(full_name.c_str());
+ }
+ else
+ {
+ filerr = open_next(tempfile, "avi");
+ }
+
+ // if we succeeded, make a copy of the name and create the real file over top
+ if (filerr == osd_file::error::NONE)
+ fullpath = tempfile.fullpath();
+ }
+
+ if (filerr == osd_file::error::NONE)
+ {
+ // compute the frame time
+ avi_info.m_avi_frame_period = attotime::from_seconds(1000) / info.video_timescale;
+
+ // create the file and free the string
+ avi_file::error avierr = avi_file::create(fullpath, info, avi_info.m_avi_file);
+ if (avierr != avi_file::error::NONE)
+ {
+ osd_printf_error("Error creating AVI: %s\n", avi_file::error_string(avierr));
+ return end_recording_avi(index);
+ }
+ }
+}
+
+//-------------------------------------------------
+// begin_recording - begin recording of a movie
+//-------------------------------------------------
+
+void video_manager::begin_recording(const char *name, movie_format format)
+{
+ // create a snapshot bitmap so we know what the target size is
+ screen_device_iterator iterator = screen_device_iterator(machine().root_device());
+ screen_device_iterator::auto_iterator iter = iterator.begin();
+ const uint32_t count = (uint32_t)iterator.count();
+
+ switch (format)
+ {
+ case MF_AVI:
+ if (m_avis.empty())
+ m_avis.resize(count);
+ if (m_snap_native)
+ {
+ for (uint32_t index = 0; index < count; index++, iter++)
+ {
+ create_snapshot_bitmap(iter.current());
+ begin_recording_avi(name, index, iter.current());
+ }
+ }
+ else
+ {
+ create_snapshot_bitmap(nullptr);
+ begin_recording_avi(name, 0, iter.current());
+ }
+ break;
+
+ case MF_MNG:
+ if (m_mngs.empty())
+ m_mngs.resize(count);
+ if (m_snap_native)
+ {
+ for (uint32_t index = 0; index < count; index++, iter++)
+ {
+ create_snapshot_bitmap(iter.current());
+ begin_recording_mng(name, index, iter.current());
+ }
+ }
+ else
+ {
+ create_snapshot_bitmap(nullptr);
+ begin_recording_mng(name, 0, iter.current());
+ }
+ break;
+
+ default:
+ osd_printf_error("Unknown movie format: %d\n", format);
+ break;
+ }
+}
+
+
+//--------------------------------------------------
+// end_recording_avi - stop recording an AVI movie
+//--------------------------------------------------
+
+void video_manager::end_recording_avi(uint32_t index)
+{
+ avi_info_t &info = m_avis[index];
+ if (info.m_avi_file)
+ {
+ info.m_avi_file.reset();
+
+ // reset the state
+ info.m_avi_frame = 0;
+ }
+}
+
+//--------------------------------------------------
+// end_recording_mng - stop recording a MNG movie
+//--------------------------------------------------
+
+void video_manager::end_recording_mng(uint32_t index)
+{
+ mng_info_t &info = m_mngs[index];
+ if (info.m_mng_file != nullptr)
+ {
+ mng_capture_stop(*info.m_mng_file);
+ info.m_mng_file.reset();
+
+ // reset the state
+ info.m_mng_frame = 0;
+ }
+}
+
+//-------------------------------------------------
+// add_sound_to_recording - add sound to a movie
+// recording
+//-------------------------------------------------
+
+void video_manager::add_sound_to_recording(const s16 *sound, int numsamples)
+{
+ for (uint32_t index = 0; index < m_avis.size(); index++)
+ {
+ add_sound_to_avi_recording(sound, numsamples, index);
+ if (!m_snap_native)
+ break;
+ }
+}
+
+//-------------------------------------------------
+// add_sound_to_avi_recording - add sound to an
+// AVI recording for a given screen
+//-------------------------------------------------
+
+void video_manager::add_sound_to_avi_recording(const s16 *sound, int numsamples, uint32_t index)
+{
+ avi_info_t &info = m_avis[index];
+ // only record if we have a file
+ if (info.m_avi_file != nullptr)
+ {
+ g_profiler.start(PROFILER_MOVIE_REC);
+
+ // write the next frame
+ avi_file::error avierr = info.m_avi_file->append_sound_samples(0, sound + 0, numsamples, 1);
+ if (avierr == avi_file::error::NONE)
+ avierr = info.m_avi_file->append_sound_samples(1, sound + 1, numsamples, 1);
+ if (avierr != avi_file::error::NONE)
+ end_recording_avi(index);
+
+ g_profiler.stop();
+ }
+}
+
+//-------------------------------------------------
+// video_exit - close down the video system
+//-------------------------------------------------
+
+void video_manager::exit()
+{
+ // stop recording any movie
+ for (uint32_t index = 0; index < (std::max)(m_mngs.size(), m_avis.size()); index++)
+ {
+ if (index < m_avis.size())
+ end_recording_avi(index);
+
+ if (index < m_mngs.size())
+ end_recording_mng(index);
+
+ if (!m_snap_native)
+ break;
+ }
+
+ // free the snapshot target
+ machine().render().target_free(m_snap_target);
+ m_snap_bitmap.reset();
+
+ // print a final result if we have at least 2 seconds' worth of data
+ if (!emulator_info::standalone() && m_overall_emutime.seconds() >= 1)
+ {
+ osd_ticks_t tps = osd_ticks_per_second();
+ double final_real_time = (double)m_overall_real_seconds + (double)m_overall_real_ticks / (double)tps;
+ double final_emu_time = m_overall_emutime.as_double();
+ osd_printf_info("Average speed: %.2f%% (%d seconds)\n", 100 * final_emu_time / final_real_time, (m_overall_emutime + attotime(0, ATTOSECONDS_PER_SECOND / 2)).seconds());
+ }
+}
+
+
+//-------------------------------------------------
+// screenless_update_callback - update generator
+// when there are no screens to drive it
+//-------------------------------------------------
+
+void video_manager::screenless_update_callback(void *ptr, int param)
+{
+ // force an update
+ frame_update(false);
+}
+
+
+//-------------------------------------------------
+// postload - callback for resetting things after
+// state has been loaded
+//-------------------------------------------------
+
+void video_manager::postload()
+{
+ for (uint32_t index = 0; index < (std::max)(m_mngs.size(), m_avis.size()); index++)
+ {
+ if (index < m_avis.size())
+ m_avis[index].m_avi_next_frame_time = machine().time();
+
+ if (index < m_mngs.size())
+ m_mngs[index].m_mng_next_frame_time = machine().time();
+
+ if (!m_snap_native)
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// is_recording - returns whether or not any
+// screen is currently recording
+//-------------------------------------------------
+
+bool video_manager::is_recording() const
+{
+ for (mng_info_t const &mng : m_mngs)
+ {
+ if (mng.m_mng_file)
+ return true;
+ else if (!m_snap_native)
+ break;
+ }
+ for (avi_info_t const &avi : m_avis)
+ {
+ if (avi.m_avi_file)
+ return true;
+ else if (!m_snap_native)
+ break;
+ }
+ return false;
+}
+
+//-------------------------------------------------
+// effective_autoframeskip - return the effective
+// autoframeskip value, accounting for fast
+// forward
+//-------------------------------------------------
+
+inline bool video_manager::effective_autoframeskip() const
+{
+ // if we're fast forwarding or paused, autoframeskip is disabled
+ if (m_fastforward || machine().paused())
+ return false;
+
+ // otherwise, it's up to the user
+ return m_auto_frameskip;
+}
+
+
+//-------------------------------------------------
+// effective_frameskip - return the effective
+// frameskip value, accounting for fast
+// forward
+//-------------------------------------------------
+
+inline int video_manager::effective_frameskip() const
+{
+ // if we're fast forwarding, use the maximum frameskip
+ if (m_fastforward)
+ return FRAMESKIP_LEVELS - 1;
+
+ // otherwise, it's up to the user
+ return m_frameskip_level;
+}
+
+
+//-------------------------------------------------
+// effective_throttle - return the effective
+// throttle value, accounting for fast
+// forward and user interface
+//-------------------------------------------------
+
+inline bool video_manager::effective_throttle() const
+{
+ // if we're paused, or if the UI is active, we always throttle
+ if (machine().paused()) //|| machine().ui().is_menu_active())
+ return true;
+
+ // if we're fast forwarding, we don't throttle
+ if (m_fastforward)
+ return false;
+
+ // otherwise, it's up to the user
+ return throttled();
+}
+
+
+//-------------------------------------------------
+// original_speed_setting - return the original
+// speed setting
+//-------------------------------------------------
+
+inline int video_manager::original_speed_setting() const
+{
+ return machine().options().speed() * 1000.0f + 0.5f;
+}
+
+
+//-------------------------------------------------
+// finish_screen_updates - finish updating all
+// the screens
+//-------------------------------------------------
+
+bool video_manager::finish_screen_updates()
+{
+ // finish updating the screens
+ screen_device_iterator iter(machine().root_device());
+
+ bool has_screen = false;
+ for (screen_device &screen : iter)
+ {
+ screen.update_partial(screen.visible_area().max_y);
+ has_screen = true;
+ }
+
+ bool anything_changed = !has_screen || m_output_changed;
+ m_output_changed = false;
+
+ // now add the quads for all the screens
+ for (screen_device &screen : iter)
+ if (screen.update_quads())
+ anything_changed = true;
+
+ // draw HUD from LUA callback (if any)
+ anything_changed |= emulator_info::frame_hook();
+
+ // update our movie recording and burn-in state
+ if (!machine().paused())
+ {
+ record_frame();
+
+ // iterate over screens and update the burnin for the ones that care
+ for (screen_device &screen : iter)
+ screen.update_burnin();
+ }
+
+ // draw any crosshairs
+ for (screen_device &screen : iter)
+ machine().crosshair().render(screen);
+
+ return anything_changed;
+}
+
+
+
+//-------------------------------------------------
+// update_throttle - throttle to the game's
+// natural speed
+//-------------------------------------------------
+
+void video_manager::update_throttle(attotime emutime)
+{
+/*
+
+ Throttling theory:
+
+ This routine is called periodically with an up-to-date emulated time.
+ The idea is to synchronize real time with emulated time. We do this
+ by "throttling", or waiting for real time to catch up with emulated
+ time.
+
+ In an ideal world, it will take less real time to emulate and render
+ each frame than the emulated time, so we need to slow things down to
+ get both times in sync.
+
+ There are many complications to this model:
+
+ * some games run too slow, so each frame we get further and
+ further behind real time; our only choice here is to not
+ throttle
+
+ * some games have very uneven frame rates; one frame will take
+ a long time to emulate, and the next frame may be very fast
+
+ * we run on top of multitasking OSes; sometimes execution time
+ is taken away from us, and this means we may not get enough
+ time to emulate one frame
+
+ * we may be paused, and emulated time may not be marching
+ forward
+
+ * emulated time could jump due to resetting the machine or
+ restoring from a saved state
+
+*/
+ static const u8 popcount[256] =
+ {
+ 0,1,1,2,1,2,2,3, 1,2,2,3,2,3,3,4, 1,2,2,3,2,3,3,4, 2,3,3,4,3,4,4,5,
+ 1,2,2,3,2,3,3,4, 2,3,3,4,3,4,4,5, 2,3,3,4,3,4,4,5, 3,4,4,5,4,5,5,6,
+ 1,2,2,3,2,3,3,4, 2,3,3,4,3,4,4,5, 2,3,3,4,3,4,4,5, 3,4,4,5,4,5,5,6,
+ 2,3,3,4,3,4,4,5, 3,4,4,5,4,5,5,6, 3,4,4,5,4,5,5,6, 4,5,5,6,5,6,6,7,
+ 1,2,2,3,2,3,3,4, 2,3,3,4,3,4,4,5, 2,3,3,4,3,4,4,5, 3,4,4,5,4,5,5,6,
+ 2,3,3,4,3,4,4,5, 3,4,4,5,4,5,5,6, 3,4,4,5,4,5,5,6, 4,5,5,6,5,6,6,7,
+ 2,3,3,4,3,4,4,5, 3,4,4,5,4,5,5,6, 3,4,4,5,4,5,5,6, 4,5,5,6,5,6,6,7,
+ 3,4,4,5,4,5,5,6, 4,5,5,6,5,6,6,7, 4,5,5,6,5,6,6,7, 5,6,6,7,6,7,7,8
+ };
+
+ // outer scope so we can break out in case of a resync
+ while (1)
+ {
+ // apply speed factor to emu time
+ if (m_speed != 0 && m_speed != 1000)
+ {
+ // multiply emutime by 1000, then divide by the global speed factor
+ emutime = (emutime * 1000) / m_speed;
+ }
+
+ // compute conversion factors up front
+ osd_ticks_t ticks_per_second = osd_ticks_per_second();
+ attoseconds_t attoseconds_per_tick = ATTOSECONDS_PER_SECOND / ticks_per_second * m_throttle_rate;
+
+ // if we're paused, emutime will not advance; instead, we subtract a fixed
+ // amount of time (1/60th of a second) from the emulated time that was passed in,
+ // and explicitly reset our tracked real and emulated timers to that value ...
+ // this means we pretend that the last update was exactly 1/60th of a second
+ // ago, and was in sync in both real and emulated time
+ if (machine().paused())
+ {
+ m_throttle_emutime = emutime - attotime(0, ATTOSECONDS_PER_SECOND / PAUSED_REFRESH_RATE);
+ m_throttle_realtime = m_throttle_emutime;
+ }
+
+ // attempt to detect anomalies in the emulated time by subtracting the previously
+ // reported value from our current value; this should be a small value somewhere
+ // between 0 and 1/10th of a second ... anything outside of this range is obviously
+ // wrong and requires a resync
+ attoseconds_t emu_delta_attoseconds = (emutime - m_throttle_emutime).as_attoseconds();
+ if (emu_delta_attoseconds < 0 || emu_delta_attoseconds > ATTOSECONDS_PER_SECOND / 10)
+ {
+ if (LOG_THROTTLE)
+ machine().logerror("Resync due to weird emutime delta: %s\n", attotime(0, emu_delta_attoseconds).as_string(18));
+ break;
+ }
+
+ // now determine the current real time in OSD-specified ticks; we have to be careful
+ // here because counters can wrap, so we only use the difference between the last
+ // read value and the current value in our computations
+ osd_ticks_t diff_ticks = osd_ticks() - m_throttle_last_ticks;
+ m_throttle_last_ticks += diff_ticks;
+
+ // if it has been more than a full second of real time since the last call to this
+ // function, we just need to resynchronize
+ if (diff_ticks >= ticks_per_second)
+ {
+ if (LOG_THROTTLE)
+ machine().logerror("Resync due to real time advancing by more than 1 second\n");
+ break;
+ }
+
+ // convert this value into attoseconds for easier comparison
+ attoseconds_t real_delta_attoseconds = diff_ticks * attoseconds_per_tick;
+
+ // now update our real and emulated timers with the current values
+ m_throttle_emutime = emutime;
+ m_throttle_realtime += attotime(0, real_delta_attoseconds);
+
+ // keep a history of whether or not emulated time beat real time over the last few
+ // updates; this can be used for future heuristics
+ m_throttle_history = (m_throttle_history << 1) | (emu_delta_attoseconds > real_delta_attoseconds);
+
+ // determine how far ahead real time is versus emulated time; note that we use the
+ // accumulated times for this instead of the deltas for the current update because
+ // we want to track time over a longer duration than a single update
+ attoseconds_t real_is_ahead_attoseconds = (m_throttle_emutime - m_throttle_realtime).as_attoseconds();
+
+ // if we're more than 1/10th of a second out, or if we are behind at all and emulation
+ // is taking longer than the real frame, we just need to resync
+ if (real_is_ahead_attoseconds < -ATTOSECONDS_PER_SECOND / 10 ||
+ (real_is_ahead_attoseconds < 0 && popcount[m_throttle_history & 0xff] < 6))
+ {
+ if (LOG_THROTTLE)
+ machine().logerror("Resync due to being behind: %s (history=%08X)\n", attotime(0, -real_is_ahead_attoseconds).as_string(18), m_throttle_history);
+ break;
+ }
+
+ // if we're behind, it's time to just get out
+ if (real_is_ahead_attoseconds < 0)
+ return;
+
+ // compute the target real time, in ticks, where we want to be
+ osd_ticks_t target_ticks = m_throttle_last_ticks + real_is_ahead_attoseconds / attoseconds_per_tick;
+
+ // throttle until we read the target, and update real time to match the final time
+ diff_ticks = throttle_until_ticks(target_ticks) - m_throttle_last_ticks;
+ m_throttle_last_ticks += diff_ticks;
+ m_throttle_realtime += attotime(0, diff_ticks * attoseconds_per_tick);
+ return;
+ }
+
+ // reset realtime and emutime to the same value
+ m_throttle_realtime = m_throttle_emutime = emutime;
+}
+
+
+//-------------------------------------------------
+// throttle_until_ticks - spin until the
+// specified target time, calling the OSD code
+// to sleep if possible
+//-------------------------------------------------
+
+osd_ticks_t video_manager::throttle_until_ticks(osd_ticks_t target_ticks)
+{
+ // we're allowed to sleep via the OSD code only if we're configured to do so
+ // and we're not frameskipping due to autoframeskip, or if we're paused
+ bool const allowed_to_sleep = (machine().options().sleep() && (!effective_autoframeskip() || effective_frameskip() == 0)) || machine().paused();
+
+ // loop until we reach our target
+ g_profiler.start(PROFILER_IDLE);
+ osd_ticks_t current_ticks = osd_ticks();
+ while (current_ticks < target_ticks)
+ {
+ // compute how much time to sleep for, taking into account the average oversleep
+ osd_ticks_t delta = target_ticks - current_ticks;
+ if (delta > m_average_oversleep / 1000)
+ delta -= m_average_oversleep / 1000;
+ else
+ delta = 0;
+
+ // see if we can sleep
+ bool const slept = allowed_to_sleep && delta;
+ if (slept)
+ osd_sleep(delta);
+
+ // read the new value
+ osd_ticks_t const new_ticks = osd_ticks();
+
+ // keep some metrics on the sleeping patterns of the OSD layer
+ if (slept)
+ {
+ // if we overslept, keep an average of the amount
+ osd_ticks_t const actual_ticks = new_ticks - current_ticks;
+ if (actual_ticks > delta)
+ {
+ // take 99% of the previous average plus 1% of the new value
+ osd_ticks_t const oversleep_milliticks = 1000 * (actual_ticks - delta);
+ m_average_oversleep = (m_average_oversleep * 99 + oversleep_milliticks) / 100;
+
+ if (LOG_THROTTLE)
+ machine().logerror("Slept for %d ticks, got %d ticks, avgover = %d\n", (int)delta, (int)actual_ticks, (int)m_average_oversleep);
+ }
+ }
+ current_ticks = new_ticks;
+ }
+ g_profiler.stop();
+
+ return current_ticks;
+}
+
+
+//-------------------------------------------------
+// update_frameskip - update frameskipping
+// counters and periodically update autoframeskip
+//-------------------------------------------------
+
+void video_manager::update_frameskip()
+{
+ // if we're throttling and autoframeskip is on, adjust
+ if (effective_throttle() && effective_autoframeskip() && m_frameskip_counter == 0)
+ {
+ // calibrate the "adjusted speed" based on the target
+ double adjusted_speed_percent = m_speed_percent / (double) m_throttle_rate;
+
+ // if we're too fast, attempt to increase the frameskip
+ double speed = m_speed * 0.001;
+ if (adjusted_speed_percent >= 0.995 * speed)
+ {
+ // but only after 3 consecutive frames where we are too fast
+ if (++m_frameskip_adjust >= 3)
+ {
+ m_frameskip_adjust = 0;
+ if (m_frameskip_level > 0)
+ m_frameskip_level--;
+ }
+ }
+
+ // if we're too slow, attempt to increase the frameskip
+ else
+ {
+ // if below 80% speed, be more aggressive
+ if (adjusted_speed_percent < 0.80 * speed)
+ m_frameskip_adjust -= (0.90 * speed - m_speed_percent) / 0.05;
+
+ // if we're close, only force it up to frameskip 8
+ else if (m_frameskip_level < 8)
+ m_frameskip_adjust--;
+
+ // perform the adjustment
+ while (m_frameskip_adjust <= -2)
+ {
+ m_frameskip_adjust += 2;
+ if (m_frameskip_level < MAX_FRAMESKIP)
+ m_frameskip_level++;
+ }
+ }
+ }
+
+ // increment the frameskip counter and determine if we will skip the next frame
+ m_frameskip_counter = (m_frameskip_counter + 1) % FRAMESKIP_LEVELS;
+ m_skipping_this_frame = s_skiptable[effective_frameskip()][m_frameskip_counter];
+}
+
+
+//-------------------------------------------------
+// update_refresh_speed - update the m_speed
+// based on the maximum refresh rate supported
+//-------------------------------------------------
+
+void video_manager::update_refresh_speed()
+{
+ // only do this if the refreshspeed option is used
+ if (machine().options().refresh_speed())
+ {
+ double minrefresh = machine().render().max_update_rate();
+ if (minrefresh != 0)
+ {
+ // find the screen with the shortest frame period (max refresh rate)
+ // note that we first check the token since this can get called before all screens are created
+ attoseconds_t min_frame_period = ATTOSECONDS_PER_SECOND;
+ for (screen_device &screen : screen_device_iterator(machine().root_device()))
+ {
+ attoseconds_t period = screen.frame_period().attoseconds();
+ if (period != 0)
+ min_frame_period = std::min(min_frame_period, period);
+ }
+
+ // compute a target speed as an integral percentage
+ // note that we lop 0.25Hz off of the minrefresh when doing the computation to allow for
+ // the fact that most refresh rates are not accurate to 10 digits...
+ u32 target_speed = floor((minrefresh - 0.25) * 1000.0 / ATTOSECONDS_TO_HZ(min_frame_period));
+ u32 original_speed = original_speed_setting();
+ target_speed = std::min(target_speed, original_speed);
+
+ // if we changed, log that verbosely
+ if (target_speed != m_speed)
+ {
+ osd_printf_verbose("Adjusting target speed to %.1f%% (hw=%.2fHz, game=%.2fHz, adjusted=%.2fHz)\n", target_speed / 10.0, minrefresh, ATTOSECONDS_TO_HZ(min_frame_period), ATTOSECONDS_TO_HZ(min_frame_period * 1000.0 / target_speed));
+ m_speed = target_speed;
+ }
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// recompute_speed - recompute the current
+// overall speed; we assume this is called only
+// if we did not skip a frame
+//-------------------------------------------------
+
+void video_manager::recompute_speed(const attotime &emutime)
+{
+ // if we don't have a starting time yet, or if we're paused, reset our starting point
+ if (m_speed_last_realtime == 0 || machine().paused())
+ {
+ m_speed_last_realtime = osd_ticks();
+ m_speed_last_emutime = emutime;
+ }
+
+ // if it has been more than the update interval, update the time
+ attotime delta_emutime = emutime - m_speed_last_emutime;
+ if (delta_emutime > attotime(0, ATTOSECONDS_PER_SPEED_UPDATE))
+ {
+ // convert from ticks to attoseconds
+ osd_ticks_t realtime = osd_ticks();
+ osd_ticks_t delta_realtime = realtime - m_speed_last_realtime;
+ osd_ticks_t tps = osd_ticks_per_second();
+ m_speed_percent = delta_emutime.as_double() * (double)tps / (double)delta_realtime;
+
+ // remember the last times
+ m_speed_last_realtime = realtime;
+ m_speed_last_emutime = emutime;
+
+ // if we're throttled, this time period counts for overall speed; otherwise, we reset the counter
+ if (!m_fastforward)
+ m_overall_valid_counter++;
+ else
+ m_overall_valid_counter = 0;
+
+ // if we've had at least 4 consecutive valid periods, accumulate stats
+ if (m_overall_valid_counter >= 4)
+ {
+ m_overall_real_ticks += delta_realtime;
+ while (m_overall_real_ticks >= tps)
+ {
+ m_overall_real_ticks -= tps;
+ m_overall_real_seconds++;
+ }
+ m_overall_emutime += delta_emutime;
+ }
+ }
+
+ // if we're past the "time-to-execute" requested, signal an exit
+ if (m_seconds_to_run != 0 && emutime.seconds() >= m_seconds_to_run)
+ {
+ // create a final screenshot
+ emu_file file(machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS);
+ osd_file::error filerr = file.open(machine().basename(), PATH_SEPARATOR "final.png");
+ if (filerr == osd_file::error::NONE)
+ save_snapshot(nullptr, file);
+
+ //printf("Scheduled exit at %f\n", emutime.as_double());
+ // schedule our demise
+ machine().schedule_exit();
+ }
+}
+
+
+//-------------------------------------------------
+// create_snapshot_bitmap - creates a
+// bitmap containing the screenshot for the
+// given screen
+//-------------------------------------------------
+
+typedef software_renderer<u32, 0,0,0, 16,8,0, false, true> snap_renderer_bilinear;
+typedef software_renderer<u32, 0,0,0, 16,8,0, false, false> snap_renderer;
+
+void video_manager::create_snapshot_bitmap(screen_device *screen)
+{
+ // select the appropriate view in our dummy target
+ if (m_snap_native && screen != nullptr)
+ {
+ screen_device_iterator iter(machine().root_device());
+ int view_index = iter.indexof(*screen);
+ assert(view_index != -1);
+ m_snap_target->set_view(view_index);
+ }
+
+ // get the minimum width/height and set it on the target
+ s32 width = m_snap_width;
+ s32 height = m_snap_height;
+ if (width == 0 || height == 0)
+ m_snap_target->compute_minimum_size(width, height);
+ m_snap_target->set_bounds(width, height);
+
+ // if we don't have a bitmap, or if it's not the right size, allocate a new one
+ if (!m_snap_bitmap.valid() || width != m_snap_bitmap.width() || height != m_snap_bitmap.height())
+ m_snap_bitmap.allocate(width, height);
+
+ // render the screen there
+ render_primitive_list &primlist = m_snap_target->get_primitives();
+ primlist.acquire_lock();
+ if (machine().options().snap_bilinear())
+ snap_renderer_bilinear::draw_primitives(primlist, &m_snap_bitmap.pix32(0), width, height, m_snap_bitmap.rowpixels());
+ else
+ snap_renderer::draw_primitives(primlist, &m_snap_bitmap.pix32(0), width, height, m_snap_bitmap.rowpixels());
+ primlist.release_lock();
+}
+
+
+//-------------------------------------------------
+// open_next - open the next non-existing file of
+// type filetype according to our numbering
+// scheme
+//-------------------------------------------------
+
+osd_file::error video_manager::open_next(emu_file &file, const char *extension, uint32_t added_index)
+{
+ u32 origflags = file.openflags();
+
+ // handle defaults
+ const char *snapname = machine().options().snap_name();
+
+ if (snapname == nullptr || snapname[0] == 0)
+ snapname = "%g/%i";
+ std::string snapstr(snapname);
+
+ // strip any extension in the provided name
+ int index = snapstr.find_last_of('.');
+ if (index != -1)
+ snapstr = snapstr.substr(0, index);
+
+ // handle %d in the template (for image devices)
+ std::string snapdev("%d_");
+ int pos = snapstr.find(snapdev);
+
+ if (pos != -1)
+ {
+ // if more %d are found, revert to default and ignore them all
+ if (snapstr.find(snapdev.c_str(), pos + 3) != -1)
+ snapstr.assign("%g/%i");
+ // else if there is a single %d, try to create the correct snapname
+ else
+ {
+ int name_found = 0;
+
+ // find length of the device name
+ int end1 = snapstr.find("/", pos + 3);
+ int end2 = snapstr.find("%", pos + 3);
+ int end;
+
+ if ((end1 != -1) && (end2 != -1))
+ end = std::min(end1, end2);
+ else if (end1 != -1)
+ end = end1;
+ else if (end2 != -1)
+ end = end2;
+ else
+ end = snapstr.length();
+
+ if (end - pos < 3)
+ fatalerror("Something very wrong is going on!!!\n");
+
+ // copy the device name to an std::string
+ std::string snapdevname;
+ snapdevname.assign(snapstr.substr(pos + 3, end - pos - 3));
+ //printf("check template: %s\n", snapdevname.c_str());
+
+ // verify that there is such a device for this system
+ for (device_image_interface &image : image_interface_iterator(machine().root_device()))
+ {
+ // get the device name
+ std::string tempdevname(image.brief_instance_name());
+ //printf("check device: %s\n", tempdevname.c_str());
+
+ if (snapdevname.compare(tempdevname) == 0)
+ {
+ // verify that such a device has an image mounted
+ if (image.basename() != nullptr)
+ {
+ std::string filename(image.basename());
+
+ // strip extension
+ filename = filename.substr(0, filename.find_last_of('.'));
+
+ // setup snapname and remove the %d_
+ strreplace(snapstr, snapdevname.c_str(), filename.c_str());
+ snapstr.erase(pos, 3);
+ //printf("check image: %s\n", filename.c_str());
+
+ name_found = 1;
+ }
+ }
+ }
+
+ // or fallback to default
+ if (name_found == 0)
+ snapstr.assign("%g/%i");
+ }
+ }
+
+ // add our own extension
+ snapstr.append(".").append(extension);
+
+ // substitute path and gamename up front
+ strreplace(snapstr, "/", PATH_SEPARATOR);
+ strreplace(snapstr, "%g", machine().basename());
+
+ // determine if the template has an index; if not, we always use the same name
+ std::string fname;
+ if (snapstr.find("%i") == -1)
+ fname.assign(snapstr);
+
+ // otherwise, we scan for the next available filename
+ else
+ {
+ // try until we succeed
+ file.set_openflags(OPEN_FLAG_WRITE);
+ for (int seq = 0; ; seq++)
+ {
+ // build up the filename
+ fname.assign(snapstr);
+ strreplace(fname, "%i", string_format("%04d", seq).c_str());
+
+ // try to open the file; stop when we fail
+ osd_file::error filerr = file.open(fname.c_str());
+ if (filerr == osd_file::error::NOT_FOUND)
+ {
+ break;
+ }
+ }
+ }
+
+ // create the final file
+ file.set_openflags(origflags);
+ return file.open(fname.c_str());
+}
+
+
+//-------------------------------------------------
+// record_frame - record a frame of a movie
+//-------------------------------------------------
+
+void video_manager::record_frame()
+{
+ // ignore if nothing to do
+ if (!is_recording())
+ return;
+
+ // start the profiler and get the current time
+ g_profiler.start(PROFILER_MOVIE_REC);
+ attotime curtime = machine().time();
+
+ screen_device_iterator device_iterator = screen_device_iterator(machine().root_device());
+ screen_device_iterator::auto_iterator iter = device_iterator.begin();
+
+ for (uint32_t index = 0; index < (std::max)(m_mngs.size(), m_avis.size()); index++, iter++)
+ {
+ // create the bitmap
+ create_snapshot_bitmap(iter.current());
+
+ // handle an AVI recording
+ if ((index < m_avis.size()) && m_avis[index].m_avi_file)
+ {
+ avi_info_t &avi_info = m_avis[index];
+
+ // loop until we hit the right time
+ while (avi_info.m_avi_next_frame_time <= curtime)
+ {
+ // write the next frame
+ avi_file::error avierr = avi_info.m_avi_file->append_video_frame(m_snap_bitmap);
+ if (avierr != avi_file::error::NONE)
+ {
+ g_profiler.stop(); // FIXME: double exit if this happens?
+ end_recording_avi(index);
+ break;
+ }
+
+ // advance time
+ avi_info.m_avi_next_frame_time += avi_info.m_avi_frame_period;
+ avi_info.m_avi_frame++;
+ }
+ }
+
+ // handle a MNG recording
+ if ((index < m_mngs.size()) && m_mngs[index].m_mng_file)
+ {
+ mng_info_t &mng_info = m_mngs[index];
+
+ // loop until we hit the right time
+ while (mng_info.m_mng_next_frame_time <= curtime)
+ {
+ // set up the text fields in the movie info
+ png_info pnginfo;
+ if (mng_info.m_mng_frame == 0)
+ {
+ std::string text1 = std::string(emulator_info::get_appname()).append(" ").append(emulator_info::get_build_version());
+ std::string text2 = std::string(machine().system().manufacturer).append(" ").append(machine().system().type.fullname());
+ pnginfo.add_text("Software", text1.c_str());
+ pnginfo.add_text("System", text2.c_str());
+ }
+
+ // write the next frame
+ screen_device *screen = iter.current();
+ const rgb_t *palette = (screen != nullptr && screen->has_palette()) ? screen->palette().palette()->entry_list_adjusted() : nullptr;
+ int entries = (screen != nullptr && screen->has_palette()) ? screen->palette().entries() : 0;
+ png_error error = mng_capture_frame(*mng_info.m_mng_file, pnginfo, m_snap_bitmap, entries, palette);
+ if (error != PNGERR_NONE)
+ {
+ g_profiler.stop(); // FIXME: double exit if this happens?
+ end_recording_mng(index);
+ break;
+ }
+
+ // advance time
+ mng_info.m_mng_next_frame_time += mng_info.m_mng_frame_period;
+ mng_info.m_mng_frame++;
+ }
+ }
+
+ if (!m_snap_native)
+ {
+ break;
+ }
+ }
+
+ g_profiler.stop();
+}
+
+//-------------------------------------------------
+// toggle_throttle
+//-------------------------------------------------
+
+void video_manager::toggle_throttle()
+{
+ set_throttled(!throttled());
+}
+
+
+//-------------------------------------------------
+// toggle_record_movie
+//-------------------------------------------------
+
+void video_manager::toggle_record_movie(movie_format format)
+{
+ if (!is_recording())
+ {
+ begin_recording(nullptr, format);
+ machine().popmessage("REC START (%s)", format == MF_MNG ? "MNG" : "AVI");
+ }
+ else
+ {
+ end_recording(format);
+ machine().popmessage("REC STOP (%s)", format == MF_MNG ? "MNG" : "AVI");
+ }
+}
+
+void video_manager::end_recording(movie_format format)
+{
+ screen_device_iterator device_iterator = screen_device_iterator(machine().root_device());
+ screen_device_iterator::auto_iterator iter = device_iterator.begin();
+ const uint32_t count = (uint32_t)device_iterator.count();
+
+ switch (format)
+ {
+ case MF_AVI:
+ for (uint32_t index = 0; index < count; index++, iter++)
+ {
+ end_recording_avi(index);
+ if (!m_snap_native)
+ {
+ break;
+ }
+ }
+ break;
+
+ case MF_MNG:
+ for (uint32_t index = 0; index < count; index++, iter++)
+ {
+ end_recording_mng(index);
+ if (!m_snap_native)
+ {
+ break;
+ }
+ }
+ break;
+
+ default:
+ osd_printf_error("Unknown movie format: %d\n", format);
+ break;
+ }
+}
diff --git a/docs/release/src/emu/video.h b/docs/release/src/emu/video.h
new file mode 100644
index 00000000000..725107e7db2
--- /dev/null
+++ b/docs/release/src/emu/video.h
@@ -0,0 +1,226 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+/***************************************************************************
+
+ video.h
+
+ Core MAME video routines.
+
+***************************************************************************/
+
+#pragma once
+
+#ifndef __EMU_H__
+#error Dont include this file directly; include emu.h instead.
+#endif
+
+#ifndef MAME_EMU_VIDEO_H
+#define MAME_EMU_VIDEO_H
+
+#include "aviio.h"
+
+
+//**************************************************************************
+// CONSTANTS
+//**************************************************************************
+
+// number of levels of frameskipping supported
+constexpr int FRAMESKIP_LEVELS = 12;
+constexpr int MAX_FRAMESKIP = FRAMESKIP_LEVELS - 2;
+
+
+//**************************************************************************
+// TYPE DEFINITIONS
+//**************************************************************************
+
+// ======================> video_manager
+
+class video_manager
+{
+ friend class screen_device;
+
+public:
+ // movie format options
+ enum movie_format
+ {
+ MF_MNG,
+ MF_AVI
+ };
+
+ // construction/destruction
+ video_manager(running_machine &machine);
+
+ // getters
+ running_machine &machine() const { return m_machine; }
+ bool skip_this_frame() const { return m_skipping_this_frame; }
+ int speed_factor() const { return m_speed; }
+ int frameskip() const { return m_auto_frameskip ? -1 : m_frameskip_level; }
+ bool throttled() const { return m_throttled; }
+ float throttle_rate() const { return m_throttle_rate; }
+ bool fastforward() const { return m_fastforward; }
+ bool is_recording() const;
+
+ // setters
+ void set_speed_factor(int speed) { m_speed = speed; } // MESSUI
+ void set_frameskip(int frameskip);
+ void set_throttled(bool throttled = true) { m_throttled = throttled; }
+ void set_throttle_rate(float throttle_rate) { m_throttle_rate = throttle_rate; }
+ void set_fastforward(bool ffwd = true) { m_fastforward = ffwd; }
+ void set_output_changed() { m_output_changed = true; }
+
+ // misc
+ void toggle_throttle();
+ void toggle_record_movie(movie_format format);
+ void toggle_record_mng() { toggle_record_movie(MF_MNG); }
+ void toggle_record_avi() { toggle_record_movie(MF_AVI); }
+ osd_file::error open_next(emu_file &file, const char *extension, uint32_t index = 0);
+
+ // render a frame
+ void frame_update(bool from_debugger = false);
+
+ // current speed helpers
+ std::string speed_text();
+ double speed_percent() const { return m_speed_percent; }
+
+ // snapshots
+ void save_snapshot(screen_device *screen, emu_file &file);
+ void save_active_screen_snapshots();
+ void save_input_timecode();
+
+ // movies
+ void begin_recording(const char *name, movie_format format);
+ void begin_recording_mng(const char *name, uint32_t index, screen_device *screen);
+ void begin_recording_avi(const char *name, uint32_t index, screen_device *screen);
+ void end_recording(movie_format format);
+ void end_recording_mng(uint32_t index);
+ void end_recording_avi(uint32_t index);
+ void add_sound_to_recording(const s16 *sound, int numsamples);
+ void add_sound_to_avi_recording(const s16 *sound, int numsamples, uint32_t index);
+
+ void set_timecode_enabled(bool value) { m_timecode_enabled = value; }
+ bool get_timecode_enabled() { return m_timecode_enabled; }
+ bool get_timecode_write() { return m_timecode_write; }
+ void set_timecode_write(bool value) { m_timecode_write = value; }
+ void set_timecode_text(std::string &str) { m_timecode_text = str; }
+ void set_timecode_start(attotime time) { m_timecode_start = time; }
+ void add_to_total_time(attotime time) { m_timecode_total += time; }
+ std::string &timecode_text(std::string &str);
+ std::string &timecode_total_text(std::string &str);
+
+private:
+ // internal helpers
+ void exit();
+ void screenless_update_callback(void *ptr, int param);
+ void postload();
+
+ // effective value helpers
+ bool effective_autoframeskip() const;
+ int effective_frameskip() const;
+ bool effective_throttle() const;
+
+ // speed and throttling helpers
+ int original_speed_setting() const;
+ bool finish_screen_updates();
+ void update_throttle(attotime emutime);
+ osd_ticks_t throttle_until_ticks(osd_ticks_t target_ticks);
+ void update_frameskip();
+ void update_refresh_speed();
+ void recompute_speed(const attotime &emutime);
+
+ // snapshot/movie helpers
+ void create_snapshot_bitmap(screen_device *screen);
+ void record_frame();
+
+ // internal state
+ running_machine & m_machine; // reference to our machine
+
+ // screenless systems
+ emu_timer * m_screenless_frame_timer; // timer to signal VBLANK start
+ bool m_output_changed; // did an output element change?
+
+ // throttling calculations
+ osd_ticks_t m_throttle_last_ticks; // osd_ticks the last call to throttle
+ attotime m_throttle_realtime; // real time the last call to throttle
+ attotime m_throttle_emutime; // emulated time the last call to throttle
+ u32 m_throttle_history; // history of frames where we were fast enough
+
+ // dynamic speed computation
+ osd_ticks_t m_speed_last_realtime; // real time at the last speed calculation
+ attotime m_speed_last_emutime; // emulated time at the last speed calculation
+ double m_speed_percent; // most recent speed percentage
+
+ // overall speed computation
+ u32 m_overall_real_seconds; // accumulated real seconds at normal speed
+ osd_ticks_t m_overall_real_ticks; // accumulated real ticks at normal speed
+ attotime m_overall_emutime; // accumulated emulated time at normal speed
+ u32 m_overall_valid_counter; // number of consecutive valid time periods
+
+ // configuration
+ bool m_throttled; // flag: true if we're currently throttled
+ float m_throttle_rate; // target rate for throttling
+ bool m_fastforward; // flag: true if we're currently fast-forwarding
+ u32 m_seconds_to_run; // number of seconds to run before quitting
+ bool m_auto_frameskip; // flag: true if we're automatically frameskipping
+ u32 m_speed; // overall speed (*1000)
+
+ // frameskipping
+ u8 m_empty_skip_count; // number of empty frames we have skipped
+ u8 m_frameskip_level; // current frameskip level
+ u8 m_frameskip_counter; // counter that counts through the frameskip steps
+ s8 m_frameskip_adjust;
+ bool m_skipping_this_frame; // flag: true if we are skipping the current frame
+ osd_ticks_t m_average_oversleep; // average number of ticks the OSD oversleeps
+
+ // snapshot stuff
+ render_target * m_snap_target; // screen shapshot target
+ bitmap_rgb32 m_snap_bitmap; // screen snapshot bitmap
+ bool m_snap_native; // are we using native per-screen layouts?
+ s32 m_snap_width; // width of snapshots (0 == auto)
+ s32 m_snap_height; // height of snapshots (0 == auto)
+
+ // movie recording - MNG
+ class mng_info_t
+ {
+ public:
+ mng_info_t()
+ : m_mng_frame_period(attotime::zero)
+ , m_mng_next_frame_time(attotime::zero)
+ , m_mng_frame(0) { }
+
+ std::unique_ptr<emu_file> m_mng_file; // handle to the open movie file
+ attotime m_mng_frame_period; // period of a single movie frame
+ attotime m_mng_next_frame_time; // time of next frame
+ u32 m_mng_frame; // current movie frame number
+ };
+ std::vector<mng_info_t> m_mngs;
+
+ // movie recording - AVI
+ class avi_info_t
+ {
+ public:
+ avi_info_t()
+ : m_avi_file(nullptr)
+ , m_avi_frame_period(attotime::zero)
+ , m_avi_next_frame_time(attotime::zero)
+ , m_avi_frame(0) { }
+
+ avi_file::ptr m_avi_file; // handle to the open movie file
+ attotime m_avi_frame_period; // period of a single movie frame
+ attotime m_avi_next_frame_time; // time of next frame
+ u32 m_avi_frame; // current movie frame number
+ };
+ std::vector<avi_info_t> m_avis;
+
+ static const bool s_skiptable[FRAMESKIP_LEVELS][FRAMESKIP_LEVELS];
+
+ static const attoseconds_t ATTOSECONDS_PER_SPEED_UPDATE = ATTOSECONDS_PER_SECOND / 4;
+ static const int PAUSED_REFRESH_RATE = 30;
+
+ bool m_timecode_enabled; // inp.timecode record enabled
+ bool m_timecode_write; // Show/hide timer at right (partial time)
+ std::string m_timecode_text; // Message for that video part (intro, gameplay, extra)
+ attotime m_timecode_start; // Starting timer for that video part (intro, gameplay, extra)
+ attotime m_timecode_total; // Show/hide timer at left (total elapsed on resulting video preview)
+};
+
+#endif // MAME_EMU_VIDEO_H