diff options
Diffstat (limited to 'docs/release/src/emu')
-rw-r--r-- | docs/release/src/emu/digfx.h | 206 | ||||
-rw-r--r-- | docs/release/src/emu/driver.h | 205 | ||||
-rw-r--r-- | docs/release/src/emu/emuopts.cpp | 1285 | ||||
-rw-r--r-- | docs/release/src/emu/emuopts.h | 541 | ||||
-rw-r--r-- | docs/release/src/emu/gamedrv.h | 285 | ||||
-rw-r--r-- | docs/release/src/emu/validity.cpp | 2250 | ||||
-rw-r--r-- | docs/release/src/emu/video.cpp | 1571 | ||||
-rw-r--r-- | docs/release/src/emu/video.h | 226 |
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 |