diff options
Diffstat (limited to 'src/emu/video.cpp')
-rw-r--r-- | src/emu/video.cpp | 810 |
1 files changed, 245 insertions, 565 deletions
diff --git a/src/emu/video.cpp b/src/emu/video.cpp index e253afcfc4a..06a16b76cda 100644 --- a/src/emu/video.cpp +++ b/src/emu/video.cpp @@ -9,19 +9,26 @@ ***************************************************************************/ #include "emu.h" -#include "emuopts.h" -#include "debugger.h" -#include "ui/uimain.h" + #include "crsshair.h" -#include "rendersw.hxx" +#include "debugger.h" +#include "emuopts.h" +#include "fileio.h" +#include "main.h" #include "output.h" +#include "screen.h" -#include "aviio.h" +#include "ui/uimain.h" + +#include "corestr.h" +#include "path.h" #include "png.h" #include "xmlfile.h" #include "osdepend.h" +#include "rendersw.hxx" + //************************************************************************** // DEBUGGING @@ -85,14 +92,16 @@ video_manager::video_manager(running_machine &machine) , m_overall_real_ticks(0) , m_overall_emutime(attotime::zero) , m_overall_valid_counter(0) - , m_throttled(machine.options().throttle()) + , m_throttled(true) , 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_low_latency(machine.options().low_latency()) , m_empty_skip_count(0) - , m_frameskip_level(machine.options().frameskip()) + , m_frameskip_max(m_auto_frameskip ? machine.options().frameskip() : 0) + , m_frameskip_level(m_auto_frameskip ? 0 : machine.options().frameskip()) , m_frameskip_counter(0) , m_frameskip_adjust(0) , m_skipping_this_frame(false) @@ -101,11 +110,6 @@ video_manager::video_manager(running_machine &machine) , 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)); @@ -114,12 +118,12 @@ video_manager::video_manager(running_machine &machine) // extract initial execution state from global configuration settings update_refresh_speed(); - const unsigned screen_count(screen_device_iterator(machine.root_device()).count()); + const unsigned screen_count(screen_device_enumerator(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); + m_snap_native = !no_screens && !strcmp(viewname, "native"); if (m_snap_native) { @@ -137,7 +141,7 @@ video_manager::video_manager(running_machine &machine) 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())); + viewnode->set_attribute("name", 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??"); @@ -167,21 +171,12 @@ video_manager::video_manager(running_machine &machine) 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); + machine.output().set_global_notifier(video_notifier_callback, this); } } @@ -193,18 +188,18 @@ video_manager::video_manager(running_machine &machine) void video_manager::set_frameskip(int frameskip) { - // -1 means autoframeskip - if (frameskip == -1) + if (0 > frameskip) { + // -1 means autoframeskip + if (!m_auto_frameskip) + m_frameskip_level = 0; m_auto_frameskip = true; - m_frameskip_level = 0; } - - // any other level is a direct control - else if (frameskip >= 0 && frameskip <= MAX_FRAMESKIP) + else { + // any other level is a direct control m_auto_frameskip = false; - m_frameskip_level = frameskip; + m_frameskip_level = std::min<int>(frameskip, MAX_FRAMESKIP); } } @@ -220,54 +215,64 @@ 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(); + bool const update_screens = (phase == machine_phase::RUNNING) && (!machine().paused() || machine().options().update_in_pause()); + bool anything_changed = update_screens && 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; - } + // update inputs and draw the user interface + machine().osd().input_update(true); + anything_changed = emulator_info::draw_user_interface(machine()) || anything_changed; - // draw the user interface - emulator_info::draw_user_interface(machine()); + // let plugins draw over the UI + anything_changed = emulator_info::frame_hook() || anything_changed; + + // 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; // if we're throttling, synchronize before rendering attotime current_time = machine().time(); - if (!from_debugger && !skipped_it && effective_throttle()) + if (!from_debugger && phase > machine_phase::INIT && !m_low_latency && 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(); + { + auto profile = g_profiler.start(PROFILER_BLIT); + machine().osd().update(!from_debugger && skipped_it); + } + + // we synchronize after rendering instead of before, if low latency mode is enabled + if (!from_debugger && phase > machine_phase::INIT && m_low_latency && effective_throttle()) + update_throttle(current_time); + machine().osd().input_update(false); emulator_info::periodic_check(); - // perform tasks for this frame if (!from_debugger) + { + // perform tasks for this frame machine().call_notifiers(MACHINE_NOTIFY_FRAME); - // update frameskipping - if (!from_debugger) - update_frameskip(); + // update frameskipping + if (phase > machine_phase::INIT) + update_frameskip(); - // update speed computations - if (!from_debugger && !skipped_it) - recompute_speed(current_time); + // update speed computations + if (!skipped_it && phase > machine_phase::INIT) + 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(); + screen_device *const screen = screen_device_enumerator(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)) + if (screen && ((machine().paused() && machine().options().update_in_pause()) || from_debugger || within_instruction_hook)) screen->reset_partial_updates(); } } @@ -293,7 +298,7 @@ std::string video_manager::speed_text() // 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); + util::stream_format(str, "auto%2d/%d", effective_frameskip(), m_frameskip_max ? m_frameskip_max : MAX_FRAMESKIP); // otherwise, just display the frameskip plus the level else @@ -301,11 +306,11 @@ std::string video_manager::speed_text() // append the speed for all cases except paused if (!paused) - util::stream_format(str, "%4d%%", (int)(100 * m_speed_percent + 0.5)); + util::stream_format(str, " %3d%%", 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())) + for (screen_device &screen : screen_device_enumerator(machine().root_device())) partials += screen.partial_updates(); if (partials > 1) util::stream_format(str, "\n%d partial updates", partials); @@ -319,7 +324,7 @@ std::string video_manager::speed_text() // file handle //------------------------------------------------- -void video_manager::save_snapshot(screen_device *screen, emu_file &file) +void video_manager::save_snapshot(screen_device *screen, util::core_file &file) { // validate assert(!m_snap_native || screen != nullptr); @@ -330,16 +335,16 @@ void video_manager::save_snapshot(screen_device *screen, emu_file &file) // 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()); + util::png_info pnginfo; + pnginfo.add_text("Software", text1); + pnginfo.add_text("System", text2); // 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); + std::error_condition const error = util::png_write_bitmap(file, &pnginfo, m_snap_bitmap, entries, palette); + if (error) + osd_printf_error("Error generating PNG for snapshot (%s:%d %s)\n", error.category().name(), error.value(), error.message()); } @@ -350,290 +355,127 @@ void video_manager::save_snapshot(screen_device *screen, emu_file &file) 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 we're native, then write one snapshot per visible screen + for (screen_device &screen : screen_device_enumerator(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) + std::error_condition const filerr = open_next(file, "png"); + if (!filerr) save_snapshot(&screen, file); } } - - // otherwise, just write a single snapshot else { + // otherwise, just write a single snapshot 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) + std::error_condition const filerr = open_next(file, "png"); + if (!filerr) save_snapshot(nullptr, file); } } //------------------------------------------------- -// save_input_timecode - add a line of current -// timestamp to inp.timecode file +// begin_recording_screen - begin recording a +// movie for a specific screen //------------------------------------------------- -void video_manager::save_input_timecode() +void video_manager::begin_recording_screen(const std::string &filename, uint32_t index, screen_device *screen, movie_recording::format format) { - // if record timecode input is not active, do nothing - if (!m_timecode_enabled) { + // create the emu_file + bool const is_absolute_path = !filename.empty() && osd_is_absolute_path(filename); + std::unique_ptr<emu_file> movie_file = std::make_unique<emu_file>( + is_absolute_path ? "" : machine().options().snapshot_directory(), + OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); + + // and open the actual file + std::error_condition filerr = filename.empty() + ? open_next(*movie_file, movie_recording::format_file_extension(format)) + : movie_file->open(filename); + if (filerr) + { + osd_printf_error("Error creating movie, %s:%d %s\n", filerr.category().name(), filerr.value(), filerr.message()); 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; -} + // we have a file; try to create the recording + std::unique_ptr<movie_recording> recording = movie_recording::create(machine(), screen, format, std::move(movie_file), m_snap_bitmap); -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; + // if successful push it onto the list + if (recording) + m_movie_recordings.push_back(std::move(recording)); } + //------------------------------------------------- -// begin_recording_mng - begin recording a MNG +// begin_recording - begin recording of a movie //------------------------------------------------- -void video_manager::begin_recording_mng(const char *name, uint32_t index, screen_device *screen) +void video_manager::begin_recording(const char *name, movie_recording::format format) { - // 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); - } + // create a snapshot bitmap so we know what the target size is + screen_device_enumerator iterator(machine().root_device()); + screen_device_enumerator::iterator iter(iterator.begin()); + uint32_t count = (uint32_t)iterator.count(); + const bool no_screens(!count); - // compute the frame time - info.m_mng_frame_period = attotime::from_hz(rate); - } - else + if (no_screens) { - osd_printf_error("Error creating MNG, osd_file::error=%d\n", int(filerr)); - info.m_mng_file.reset(); + assert(!m_snap_native); + count = 1; } -} -//------------------------------------------------- -// begin_recording_avi - begin recording an AVI -//------------------------------------------------- + // clear out existing recordings + m_movie_recordings.clear(); -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; + // check if the supplied name already has the desired extension + std::string_view basename; + std::string extension; + if (name) { - 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 + std::string_view const desired_ext = movie_recording::format_file_extension(format); + basename = name; + extension.reserve(1 + desired_ext.length()); + extension.assign(1, '.').append(desired_ext); + if (core_filename_ends_with(basename, extension)) { - filerr = open_next(tempfile, "avi"); + extension = basename.substr(basename.length() - extension.length()); + basename.remove_suffix(extension.length()); } - - // 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) + if (m_snap_native) { - // 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) + std::string tempname; + for (uint32_t index = 0; index < count; index++, iter++) { - 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; + create_snapshot_bitmap(iter.current()); // TODO: only do this when starting on-the-fly, and make name match AVI file name - case MF_MNG: - if (m_mngs.empty()) - m_mngs.resize(count); - if (m_snap_native) + if (name) { - for (uint32_t index = 0; index < count; index++, iter++) - { - create_snapshot_bitmap(iter.current()); - begin_recording_mng(name, index, iter.current()); - } + if (1 < count) + tempname = util::string_format("%s%d%s", basename, index, extension); + else + tempname.assign(name).append(extension); } - else - { - create_snapshot_bitmap(nullptr); - begin_recording_mng(name, 0, iter.current()); - } - break; - default: - osd_printf_error("Unknown movie format: %d\n", format); - break; + begin_recording_screen( + tempname, + index, + iter.current(), + format); + } } -} - - -//-------------------------------------------------- -// 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) + else { - info.m_avi_file.reset(); - - // reset the state - info.m_avi_frame = 0; + create_snapshot_bitmap(nullptr); + begin_recording_screen(std::string(basename) + extension, 0, iter.current(), format); } } -//-------------------------------------------------- -// 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 @@ -642,37 +484,10 @@ void video_manager::end_recording_mng(uint32_t index) 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; - } + for (auto &recording : m_movie_recordings) + recording->add_sound_to_recording(sound, numsamples); } -//------------------------------------------------- -// 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 @@ -681,17 +496,7 @@ void video_manager::add_sound_to_avi_recording(const s16 *sound, int numsamples, 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; - } + m_movie_recordings.clear(); // free the snapshot target machine().render().target_free(m_snap_target); @@ -713,7 +518,7 @@ void video_manager::exit() // when there are no screens to drive it //------------------------------------------------- -void video_manager::screenless_update_callback(void *ptr, int param) +void video_manager::screenless_update_callback(s32 param) { // force an update frame_update(false); @@ -727,45 +532,17 @@ void video_manager::screenless_update_callback(void *ptr, int param) 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(); + attotime const emutime = machine().time(); + for (const auto &x : m_movie_recordings) + x->set_next_frame_time(emutime); - if (!m_snap_native) - break; - } + // reset speed measurements + m_speed_last_realtime = osd_ticks(); + m_speed_last_emutime = emutime; } //------------------------------------------------- -// 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 @@ -839,12 +616,15 @@ inline int video_manager::original_speed_setting() const bool video_manager::finish_screen_updates() { // finish updating the screens - screen_device_iterator iter(machine().root_device()); + screen_device_enumerator iter(machine().root_device()); bool has_live_screen = false; for (screen_device &screen : iter) { + if (screen.partial_scan_hpos() > 0) // previous update ended mid-scanline + screen.update_now(); screen.update_partial(screen.visible_area().max_y); + if (machine().render().is_live(screen)) has_live_screen = true; } @@ -857,9 +637,6 @@ bool video_manager::finish_screen_updates() 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()) { @@ -919,17 +696,6 @@ void video_manager::update_throttle(attotime emutime) 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) @@ -1002,7 +768,7 @@ void video_manager::update_throttle(attotime emutime) // 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)) + (real_is_ahead_attoseconds < 0 && population_count_32(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); @@ -1041,7 +807,7 @@ osd_ticks_t video_manager::throttle_until_ticks(osd_ticks_t target_ticks) 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); + auto profile = g_profiler.start(PROFILER_IDLE); osd_ticks_t current_ticks = osd_ticks(); while (current_ticks < target_ticks) { @@ -1077,7 +843,6 @@ osd_ticks_t video_manager::throttle_until_ticks(osd_ticks_t target_ticks) } current_ticks = new_ticks; } - g_profiler.stop(); return current_ticks; } @@ -1094,12 +859,12 @@ void video_manager::update_frameskip() 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; + 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) { + // if we're too fast, attempt to decrease the frameskip // but only after 3 consecutive frames where we are too fast if (++m_frameskip_adjust >= 3) { @@ -1108,23 +873,19 @@ void video_manager::update_frameskip() 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) + // if we're too slow, attempt to increase the frameskip + if (adjusted_speed_percent < 0.80 * speed) // if below 80% speed, be more aggressive 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) + else if (m_frameskip_level < 8) // if we're close, only force it up to frameskip 8 m_frameskip_adjust--; // perform the adjustment while (m_frameskip_adjust <= -2) { m_frameskip_adjust += 2; - if (m_frameskip_level < MAX_FRAMESKIP) + if (m_frameskip_level < (m_frameskip_max ? m_frameskip_max : MAX_FRAMESKIP)) m_frameskip_level++; } } @@ -1152,7 +913,7 @@ void video_manager::update_refresh_speed() // 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())) + for (screen_device &screen : screen_device_enumerator(machine().root_device())) { attoseconds_t period = screen.frame_period().attoseconds(); if (period != 0) @@ -1229,10 +990,23 @@ void video_manager::recompute_speed(const attotime &emutime) 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); + if (m_snap_native) + { + for (screen_device &screen : screen_device_enumerator(machine().root_device())) + { + emu_file file(machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); + std::error_condition const filerr = open_next(file, "png"); + if (!filerr) + save_snapshot(&screen, file); + } + } + else + { + emu_file file(machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); + std::error_condition const filerr = open_next(file, "png"); + if (!filerr) + save_snapshot(nullptr, file); + } //printf("Scheduled exit at %f\n", emutime.as_double()); // schedule our demise @@ -1253,30 +1027,28 @@ 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) + if (m_snap_native && screen) { - screen_device_iterator iter(machine().root_device()); + screen_device_enumerator 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 + // get the minimum width/height and set it on the target and bitmap s32 width, height; compute_snapshot_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); + if (width != m_snap_bitmap.width() || height != m_snap_bitmap.height()) + m_snap_bitmap.resize(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()); + snap_renderer_bilinear::draw_primitives(primlist, &m_snap_bitmap.pix(0), width, height, m_snap_bitmap.rowpixels()); else - snap_renderer::draw_primitives(primlist, &m_snap_bitmap.pix32(0), width, height, m_snap_bitmap.rowpixels()); + snap_renderer::draw_primitives(primlist, &m_snap_bitmap.pix(0), width, height, m_snap_bitmap.rowpixels()); primlist.release_lock(); } @@ -1321,21 +1093,26 @@ void video_manager::pixels(u32 *buffer) // scheme //------------------------------------------------- -osd_file::error video_manager::open_next(emu_file &file, const char *extension, uint32_t added_index) +std::error_condition 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) + if (!snapname || !snapname[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); + // strip desired extension if already present + std::string extstr; + extstr.reserve(1 + std::strlen(extension)); + extstr.assign(1, '.').append(extension); + if (core_filename_ends_with(snapstr, extstr)) + { + extstr = snapstr.substr(snapstr.length() - extstr.length()); + snapstr.resize(snapstr.length() - extstr.length()); + } // handle %d in the template (for image devices) std::string snapdev("%d_"); @@ -1344,7 +1121,7 @@ osd_file::error video_manager::open_next(emu_file &file, const char *extension, if (pos != -1) { // if more %d are found, revert to default and ignore them all - if (snapstr.find(snapdev.c_str(), pos + 3) != -1) + if (snapstr.find(snapdev, pos + 3) != -1) snapstr.assign("%g/%i"); // else if there is a single %d, try to create the correct snapname else @@ -1352,29 +1129,17 @@ osd_file::error video_manager::open_next(emu_file &file, const char *extension, 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 + int end = snapstr.find_first_not_of("abcdefghijklmnopqrstuvwxyz1234567890", pos + 3); + if (end == -1) 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())) + for (device_image_interface &image : image_interface_enumerator(machine().root_device())) { // get the device name std::string tempdevname(image.brief_instance_name()); @@ -1391,7 +1156,7 @@ osd_file::error video_manager::open_next(emu_file &file, const char *extension, filename = filename.substr(0, filename.find_last_of('.')); // setup snapname and remove the %d_ - strreplace(snapstr, snapdevname.c_str(), filename.c_str()); + strreplace(snapstr, snapdevname, filename); snapstr.erase(pos, 3); //printf("check image: %s\n", filename.c_str()); @@ -1406,41 +1171,54 @@ osd_file::error video_manager::open_next(emu_file &file, const char *extension, } } - // add our own extension - snapstr.append(".").append(extension); + // handle %t in the template (for timestamp) + std::string snaptime("%t"); + int pos_time = snapstr.find(snaptime); + + if (pos_time != -1) + { + char t_str[16]; + const std::time_t cur_time = std::time(nullptr); + strftime(t_str, sizeof(t_str), "%Y%m%d_%H%M%S", std::localtime(&cur_time)); + strreplace(snapstr, "%t", t_str); + } + + // append extension + snapstr.append(extstr); // 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 + // determine if the template has an index std::string fname; if (snapstr.find("%i") == -1) + { + // if not, we always use the same name fname.assign(snapstr); - - // otherwise, we scan for the next available filename + } else { + // otherwise, we scan for the next available filename + // 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()); + strreplace(fname, "%i", string_format("%04d", seq)); // 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) - { + std::error_condition const filerr = file.open(fname); + if (std::errc::no_such_file_or_directory == filerr) break; - } } } // create the final file file.set_openflags(origflags); - return file.open(fname.c_str()); + return file.open(fname); } @@ -1455,92 +1233,25 @@ void video_manager::record_frame() return; // start the profiler and get the current time - g_profiler.start(PROFILER_MOVIE_REC); + auto profile = 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++) + bool error = false; + for (auto &recording : m_movie_recordings) { // 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; - } + create_snapshot_bitmap(recording->screen()); - // advance time - mng_info.m_mng_next_frame_time += mng_info.m_mng_frame_period; - mng_info.m_mng_frame++; - } - } - - if (!m_snap_native) + // and append the frame + if (!recording->append_video_frame(m_snap_bitmap, curtime)) { + error = true; break; } } - g_profiler.stop(); -} - -//------------------------------------------------- -// toggle_throttle -//------------------------------------------------- - -void video_manager::toggle_throttle() -{ - set_throttled(!throttled()); + if (error) + end_recording(); } @@ -1548,52 +1259,21 @@ void video_manager::toggle_throttle() // toggle_record_movie //------------------------------------------------- -void video_manager::toggle_record_movie(movie_format format) +void video_manager::toggle_record_movie(movie_recording::format format) { if (!is_recording()) { begin_recording(nullptr, format); - machine().popmessage("REC START (%s)", format == MF_MNG ? "MNG" : "AVI"); + machine().popmessage("REC START (%s)", format == movie_recording::format::MNG ? "MNG" : "AVI"); } else { - end_recording(format); - machine().popmessage("REC STOP (%s)", format == MF_MNG ? "MNG" : "AVI"); + end_recording(); + machine().popmessage("REC STOP"); } } -void video_manager::end_recording(movie_format format) +void video_manager::end_recording() { - 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; - } + m_movie_recordings.clear(); } |