summaryrefslogtreecommitdiffstats
path: root/docs/release/src/emu/video.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'docs/release/src/emu/video.cpp')
-rw-r--r--docs/release/src/emu/video.cpp1571
1 files changed, 1571 insertions, 0 deletions
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;
+ }
+}