summaryrefslogtreecommitdiffstatshomepage
path: root/trunk/src/emu/video.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/emu/video.c')
-rw-r--r--trunk/src/emu/video.c1330
1 files changed, 1330 insertions, 0 deletions
diff --git a/trunk/src/emu/video.c b/trunk/src/emu/video.c
new file mode 100644
index 00000000000..b11add50c94
--- /dev/null
+++ b/trunk/src/emu/video.c
@@ -0,0 +1,1330 @@
+/***************************************************************************
+
+ video.c
+
+ Core MAME video routines.
+
+****************************************************************************
+
+ Copyright Aaron Giles
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name 'MAME' nor the names of its contributors may be
+ used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+***************************************************************************/
+
+#include "emu.h"
+#include "emuopts.h"
+#include "png.h"
+#include "debugger.h"
+#include "debugint/debugint.h"
+#include "ui.h"
+#include "aviio.h"
+#include "crsshair.h"
+
+#include "snap.lh"
+
+
+
+//**************************************************************************
+// DEBUGGING
+//**************************************************************************
+
+#define LOG_THROTTLE (0)
+
+
+
+//**************************************************************************
+// GLOBAL VARIABLES
+//**************************************************************************
+
+// frameskipping tables
+const UINT8 video_manager::s_skiptable[FRAMESKIP_LEVELS][FRAMESKIP_LEVELS] =
+{
+ { 0,0,0,0,0,0,0,0,0,0,0,0 },
+ { 0,0,0,0,0,0,0,0,0,0,0,1 },
+ { 0,0,0,0,0,1,0,0,0,0,0,1 },
+ { 0,0,0,1,0,0,0,1,0,0,0,1 },
+ { 0,0,1,0,0,1,0,0,1,0,0,1 },
+ { 0,1,0,0,1,0,1,0,0,1,0,1 },
+ { 0,1,0,1,0,1,0,1,0,1,0,1 },
+ { 0,1,0,1,1,0,1,0,1,1,0,1 },
+ { 0,1,1,0,1,1,0,1,1,0,1,1 },
+ { 0,1,1,1,0,1,1,1,0,1,1,1 },
+ { 0,1,1,1,1,1,0,1,1,1,1,1 },
+ { 0,1,1,1,1,1,1,1,1,1,1,1 }
+};
+
+
+
+//**************************************************************************
+// FUNCTION PROTOTYPES
+//**************************************************************************
+
+// software rendering
+static void rgb888_draw_primitives(const render_primitive_list &primlist, void *dstdata, UINT32 width, UINT32 height, UINT32 pitch);
+
+
+
+//**************************************************************************
+// VIDEO MANAGER
+//**************************************************************************
+
+//-------------------------------------------------
+// video_manager - constructor
+//-------------------------------------------------
+
+video_manager::video_manager(running_machine &machine)
+ : m_machine(machine),
+ m_screenless_frame_timer(NULL),
+ 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_throttle(machine.options().throttle()),
+ 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(NULL),
+ m_snap_bitmap(NULL),
+ m_snap_native(true),
+ m_snap_width(0),
+ m_snap_height(0),
+ m_mngfile(NULL),
+ m_avifile(NULL),
+ m_movie_frame_period(attotime::zero),
+ m_movie_next_frame_time(attotime::zero),
+ m_movie_frame(0)
+{
+ // request a callback upon exiting
+ machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(FUNC(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();
+
+ // create a render target for snapshots
+ const char *viewname = machine.options().snap_view();
+ m_snap_native = (machine.primary_screen != NULL && (viewname[0] == 0 || strcmp(viewname, "native") == 0));
+
+ // the native target is hard-coded to our internal layout and has all options disabled
+ if (m_snap_native)
+ {
+ m_snap_target = machine.render().target_alloc(layout_snap, 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);
+ }
+
+ // other targets select the specified view and turn off effects
+ else
+ {
+ m_snap_target = machine.render().target_alloc(NULL, 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 (machine.primary_screen == NULL)
+ {
+ 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);
+ }
+}
+
+
+//-------------------------------------------------
+// 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 debug)
+{
+ // only render sound and video if we're in the running phase
+ int 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
+ ui_update_and_render(machine(), &machine().render().ui_container());
+
+ // update the internal render debugger
+ debugint_update_during_game(machine());
+
+ // if we're throttling, synchronize before rendering
+ attotime current_time = machine().time();
+ if (!debug && !skipped_it && effective_throttle())
+ update_throttle(current_time);
+
+ // ask the OSD to update
+ g_profiler.start(PROFILER_BLIT);
+ machine().osd().update(!debug && skipped_it);
+ g_profiler.stop();
+
+ // perform tasks for this frame
+ if (!debug)
+ machine().call_notifiers(MACHINE_NOTIFY_FRAME);
+
+ // update frameskipping
+ if (!debug)
+ update_frameskip();
+
+ // update speed computations
+ if (!debug && !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
+ if (machine().primary_screen != NULL && (machine().paused() || debug || debugger_within_instruction_hook(machine())))
+ machine().primary_screen->scanline0_callback();
+
+ // otherwise, call the video EOF callback
+ else
+ {
+ g_profiler.start(PROFILER_VIDEO);
+ for (screen_device *screen = machine().first_screen(); screen != NULL; screen = screen->next_screen())
+ screen->screen_eof();
+ g_profiler.stop();
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// speed_text - print the text to be displayed
+// into a string buffer
+//-------------------------------------------------
+
+astring &video_manager::speed_text(astring &string)
+{
+ string.reset();
+
+ // if we're paused, just display Paused
+ bool paused = machine().paused();
+ if (paused)
+ string.cat("paused");
+
+ // if we're fast forwarding, just display Fast-forward
+ else if (m_fastforward)
+ string.cat("fast ");
+
+ // if we're auto frameskipping, display that plus the level
+ else if (effective_autoframeskip())
+ string.catprintf("auto%2d/%d", effective_frameskip(), MAX_FRAMESKIP);
+
+ // otherwise, just display the frameskip plus the level
+ else
+ string.catprintf("skip %d/%d", effective_frameskip(), MAX_FRAMESKIP);
+
+ // append the speed for all cases except paused
+ if (!paused)
+ string.catprintf("%4d%%", (int)(100 * m_speed_percent + 0.5));
+
+ // display the number of partial updates as well
+ int partials = 0;
+ for (screen_device *screen = machine().first_screen(); screen != NULL; screen = screen->next_screen())
+ partials += screen->partial_updates();
+ if (partials > 1)
+ string.catprintf("\n%d partial updates", partials);
+
+ return string;
+}
+
+
+//-------------------------------------------------
+// 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 != NULL);
+
+ // create the bitmap to pass in
+ create_snapshot_bitmap(screen);
+
+ // add two text entries describing the image
+ astring text1(APPNAME, " ", build_version);
+ astring text2(machine().system().manufacturer, " ", machine().system().description);
+ png_info pnginfo = { 0 };
+ png_add_text(&pnginfo, "Software", text1);
+ png_add_text(&pnginfo, "System", text2);
+
+ // now do the actual work
+ const rgb_t *palette = (machine().palette != NULL) ? palette_entry_list_adjusted(machine().palette) : NULL;
+ png_error error = png_write_bitmap(file, &pnginfo, m_snap_bitmap, machine().total_colors(), palette);
+ if (error != PNGERR_NONE)
+ mame_printf_error("Error generating PNG for snapshot: png_error = %d\n", error);
+
+ // free any data allocated
+ png_free(&pnginfo);
+}
+
+
+//-------------------------------------------------
+// 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 = machine().first_screen(); screen != NULL; screen = screen->next_screen())
+ if (machine().render().is_live(*screen))
+ {
+ emu_file file(machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS);
+ file_error filerr = open_next(file, "png");
+ if (filerr == FILERR_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);
+ file_error filerr = open_next(file, "png");
+ if (filerr == FILERR_NONE)
+ save_snapshot(NULL, file);
+ }
+}
+
+
+//-------------------------------------------------
+// begin_recording - begin recording of a movie
+//-------------------------------------------------
+
+void video_manager::begin_recording(const char *name, movie_format format)
+{
+ // stop any existign recording
+ end_recording();
+
+ // create a snapshot bitmap so we know what the target size is
+ create_snapshot_bitmap(NULL);
+
+ // reset the state
+ m_movie_frame = 0;
+ m_movie_next_frame_time = machine().time();
+
+ // start up an AVI recording
+ if (format == MF_AVI)
+ {
+ // build up information about this new movie
+ avi_movie_info info;
+ info.video_format = 0;
+ info.video_timescale = 1000 * ((machine().primary_screen != NULL) ? ATTOSECONDS_TO_HZ(machine().primary_screen->frame_period().attoseconds) : screen_device::DEFAULT_FRAME_RATE);
+ 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
+ file_error filerr;
+ astring fullpath;
+ {
+ emu_file tempfile(machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS);
+ if (name != NULL)
+ filerr = tempfile.open(name);
+ else
+ filerr = open_next(tempfile, "avi");
+
+ // compute the frame time
+ m_movie_frame_period = attotime::from_seconds(1000) / info.video_timescale;
+
+ // if we succeeded, make a copy of the name and create the real file over top
+ if (filerr == FILERR_NONE)
+ fullpath = tempfile.fullpath();
+ }
+
+ if (filerr == FILERR_NONE)
+ {
+ // create the file and free the string
+ avi_error avierr = avi_create(fullpath, &info, &m_avifile);
+ if (avierr != AVIERR_NONE)
+ mame_printf_error("Error creating AVI: %s\n", avi_error_string(avierr));
+ }
+ }
+
+ // start up a MNG recording
+ else if (format == MF_MNG)
+ {
+ // create a new movie file and start recording
+ m_mngfile = auto_alloc(machine(), emu_file(machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS));
+ file_error filerr;
+ if (name != NULL)
+ filerr = m_mngfile->open(name);
+ else
+ filerr = open_next(*m_mngfile, "mng");
+
+ if (filerr == FILERR_NONE)
+ {
+ // start the capture
+ int rate = (machine().primary_screen != NULL) ? ATTOSECONDS_TO_HZ(machine().primary_screen->frame_period().attoseconds) : screen_device::DEFAULT_FRAME_RATE;
+ png_error pngerr = mng_capture_start(*m_mngfile, m_snap_bitmap, rate);
+ if (pngerr != PNGERR_NONE)
+ return end_recording();
+
+ // compute the frame time
+ m_movie_frame_period = attotime::from_hz(rate);
+ }
+ else
+ {
+ mame_printf_error("Error creating MNG\n");
+ global_free(m_mngfile);
+ m_mngfile = NULL;
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// end_recording - stop recording of a movie
+//-------------------------------------------------
+
+void video_manager::end_recording()
+{
+ // close the file if it exists
+ if (m_avifile != NULL)
+ {
+ avi_close(m_avifile);
+ m_avifile = NULL;
+ }
+
+ // close the file if it exists
+ if (m_mngfile != NULL)
+ {
+ mng_capture_stop(*m_mngfile);
+ auto_free(machine(), m_mngfile);
+ m_mngfile = NULL;
+ }
+
+ // reset the state
+ m_movie_frame = 0;
+}
+
+
+//-------------------------------------------------
+// add_sound_to_recording - add sound to a movie
+// recording
+//-------------------------------------------------
+
+void video_manager::add_sound_to_recording(const INT16 *sound, int numsamples)
+{
+ // only record if we have a file
+ if (m_avifile != NULL)
+ {
+ g_profiler.start(PROFILER_MOVIE_REC);
+
+ // write the next frame
+ avi_error avierr = avi_append_sound_samples(m_avifile, 0, sound + 0, numsamples, 1);
+ if (avierr == AVIERR_NONE)
+ avierr = avi_append_sound_samples(m_avifile, 1, sound + 1, numsamples, 1);
+ if (avierr != AVIERR_NONE)
+ end_recording();
+
+ g_profiler.stop();
+ }
+}
+
+
+
+//-------------------------------------------------
+// video_exit - close down the video system
+//-------------------------------------------------
+
+void video_manager::exit()
+{
+ // stop recording any movie
+ end_recording();
+
+ // free all the graphics elements
+ for (int i = 0; i < MAX_GFX_ELEMENTS; i++)
+ gfx_element_free(machine().gfx[i]);
+
+ // free the snapshot target
+ machine().render().target_free(m_snap_target);
+ if (m_snap_bitmap != NULL)
+ global_free(m_snap_bitmap);
+
+ // print a final result if we have at least 5 seconds' worth of data
+ if (m_overall_emutime.seconds >= 5)
+ {
+ 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();
+ mame_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()
+{
+ m_movie_next_frame_time = machine().time();
+}
+
+
+//-------------------------------------------------
+// effective_autoframeskip - return the effective
+// autoframeskip value, accounting for fast
+// forward
+//-------------------------------------------------
+
+inline int 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() || 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 m_throttle;
+}
+
+
+//-------------------------------------------------
+// original_speed_setting - return the original
+// speed setting
+//-------------------------------------------------
+
+inline int video_manager::original_speed_setting() const
+{
+ return machine().options().speed() * 1000.0 + 0.5;
+}
+
+
+//-------------------------------------------------
+// finish_screen_updates - finish updating all
+// the screens
+//-------------------------------------------------
+
+bool video_manager::finish_screen_updates()
+{
+ // finish updating the screens
+ for (screen_device *screen = machine().first_screen(); screen != NULL; screen = screen->next_screen())
+ screen->update_partial(screen->visible_area().max_y);
+
+ // now add the quads for all the screens
+ bool anything_changed = false;
+ for (screen_device *screen = machine().first_screen(); screen != NULL; screen = screen->next_screen())
+ if (screen->update_quads())
+ anything_changed = true;
+
+ // 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 = machine().first_screen(); screen != NULL; screen = screen->next_screen())
+ screen->update_burnin();
+ }
+
+ // draw any crosshairs
+ for (screen_device *screen = machine().first_screen(); screen != NULL; screen = screen->next_screen())
+ 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 UINT8 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;
+
+ // 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)
+ 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)
+ 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)
+ 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 allowed_to_sleep = false;
+ if (machine().options().sleep() && (!effective_autoframeskip() || effective_frameskip() == 0))
+ allowed_to_sleep = true;
+ if (machine().paused())
+ allowed_to_sleep = true;
+
+ // loop until we reach our target
+ g_profiler.start(PROFILER_IDLE);
+ osd_ticks_t minimum_sleep = osd_ticks_per_second() / 1000;
+ 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) * 1000 / (1000 + m_average_oversleep);
+
+ // see if we can sleep
+ bool slept = false;
+ if (allowed_to_sleep && delta >= minimum_sleep)
+ {
+ osd_sleep(delta);
+ slept = true;
+ }
+
+ // read the new value
+ osd_ticks_t 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 actual_ticks = new_ticks - current_ticks;
+ if (actual_ticks > delta)
+ {
+ // take 90% of the previous average plus 10% of the new value
+ osd_ticks_t oversleep_milliticks = 1000 * (actual_ticks - delta) / delta;
+ m_average_oversleep = (m_average_oversleep * 99 + oversleep_milliticks) / 100;
+
+ if (LOG_THROTTLE)
+ 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)
+ {
+ // if we're too fast, attempt to increase the frameskip
+ double speed = m_speed * 0.001;
+ if (m_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 (m_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())
+ {
+ float 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 = machine().first_screen(); screen != NULL; screen = screen->next_screen())
+ {
+ attoseconds_t period = screen->frame_period().attoseconds;
+ if (period != 0)
+ min_frame_period = 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...
+ UINT32 target_speed = floor((minrefresh - 0.25f) * 1000.0 / ATTOSECONDS_TO_HZ(min_frame_period));
+ UINT32 original_speed = original_speed_setting();
+ target_speed = MIN(target_speed, original_speed);
+
+ // if we changed, log that verbosely
+ if (target_speed != m_speed)
+ {
+ mame_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(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)
+ {
+ if (machine().primary_screen != NULL)
+ {
+ // create a final screenshot
+ emu_file file(machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS);
+ file_error filerr = file.open(machine().basename(), PATH_SEPARATOR "final.png");
+ if (filerr == FILERR_NONE)
+ save_snapshot(machine().primary_screen, file);
+ }
+
+ // schedule our demise
+ machine().schedule_exit();
+ }
+}
+
+
+//-------------------------------------------------
+// create_snapshot_bitmap - creates a
+// bitmap containing the screenshot for the
+// given screen
+//-------------------------------------------------
+
+void video_manager::create_snapshot_bitmap(device_t *screen)
+{
+ // select the appropriate view in our dummy target
+ if (m_snap_native && screen != NULL)
+ {
+ int view_index = machine().devicelist().indexof(SCREEN, screen->tag());
+ assert(view_index != -1);
+ m_snap_target->set_view(view_index);
+ }
+
+ // get the minimum width/height and set it on the target
+ INT32 width = m_snap_width;
+ INT32 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 == NULL || width != m_snap_bitmap->width || height != m_snap_bitmap->height)
+ {
+ if (m_snap_bitmap != NULL)
+ auto_free(machine(), m_snap_bitmap);
+ m_snap_bitmap = auto_alloc(machine(), bitmap_t(width, height, BITMAP_FORMAT_RGB32));
+ }
+
+ // render the screen there
+ render_primitive_list &primlist = m_snap_target->get_primitives();
+ primlist.acquire_lock();
+ rgb888_draw_primitives(primlist, m_snap_bitmap->base, 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
+//-------------------------------------------------
+
+file_error video_manager::open_next(emu_file &file, const char *extension)
+{
+ UINT32 origflags = file.openflags();
+
+ // handle defaults
+ const char *snapname = machine().options().snap_name();
+
+ if (snapname == NULL || snapname[0] == 0)
+ snapname = "%g/%i";
+ astring snapstr(snapname);
+
+ // strip any extension in the provided name
+ int index = snapstr.rchr(0, '.');
+ if (index != -1)
+ snapstr.substr(0, index);
+
+ // handle %d in the template (for image devices)
+ astring snapdev("%d_");
+ int pos = snapstr.find(0, snapdev);
+
+ if (pos != -1)
+ {
+ // if more %d are found, revert to default and ignore them all
+ if (snapstr.find(pos + 3, snapdev) != -1)
+ snapstr.cpy("%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 = -1;
+
+ if ((end1 != -1) && (end2 != -1))
+ end = MIN(end1, end2);
+ else if (end1 != -1)
+ end = end1;
+ else if (end2 != -1)
+ end = end2;
+ else
+ end = snapstr.len();
+
+ if (end - pos < 3)
+ fatalerror("Something very wrong is going on!!!");
+
+ // copy the device name to an astring
+ astring snapdevname;
+ snapdevname.cpysubstr(snapstr, pos + 3, end - pos - 3);
+ //printf("check template: %s\n", snapdevname.cstr());
+
+ // verify that there is such a device for this system
+ device_image_interface *image = NULL;
+ for (bool gotone = machine().devicelist().first(image); gotone; gotone = image->next(image))
+ {
+ // get the device name
+ astring tempdevname(image->brief_instance_name());
+ //printf("check device: %s\n", tempdevname.cstr());
+
+ if (snapdevname.cmp(tempdevname) == 0)
+ {
+ // verify that such a device has an image mounted
+ if (image->basename() != NULL)
+ {
+ astring filename(image->basename());
+
+ // strip extension
+ filename.substr(0, filename.rchr(0, '.'));
+
+ // setup snapname and remove the %d_
+ snapstr.replace(0, snapdevname, filename);
+ snapstr.del(pos, 3);
+ //printf("check image: %s\n", filename.cstr());
+
+ name_found = 1;
+ }
+ }
+ }
+
+ // or fallback to default
+ if (name_found == 0)
+ snapstr.cpy("%g/%i");
+ }
+ }
+
+ // add our own extension
+ snapstr.cat(".").cat(extension);
+
+ // substitute path and gamename up front
+ snapstr.replace(0, "/", PATH_SEPARATOR);
+ snapstr.replace(0, "%g", machine().basename());
+
+ // determine if the template has an index; if not, we always use the same name
+ astring fname;
+ if (snapstr.find(0, "%i") == -1)
+ fname.cpy(snapstr);
+
+ // otherwise, we scan for the next available filename
+ else
+ {
+ // try until we succeed
+ astring seqtext;
+ file.set_openflags(OPEN_FLAG_READ);
+ for (int seq = 0; ; seq++)
+ {
+ // build up the filename
+ fname.cpy(snapstr).replace(0, "%i", seqtext.format("%04d", seq).cstr());
+
+ // try to open the file; stop when we fail
+ file_error filerr = file.open(fname);
+ if (filerr != FILERR_NONE)
+ break;
+ }
+ }
+
+ // create the final file
+ file.set_openflags(origflags);
+ return file.open(fname);
+}
+
+
+
+//-------------------------------------------------
+// record_frame - record a frame of a movie
+//-------------------------------------------------
+
+void video_manager::record_frame()
+{
+ // ignore if nothing to do
+ if (m_mngfile == NULL && m_avifile == NULL)
+ return;
+
+ // start the profiler and get the current time
+ g_profiler.start(PROFILER_MOVIE_REC);
+ attotime curtime = machine().time();
+
+ // create the bitmap
+ create_snapshot_bitmap(NULL);
+
+ // loop until we hit the right time
+ while (m_movie_next_frame_time <= curtime)
+ {
+ // handle an AVI recording
+ if (m_avifile != NULL)
+ {
+ // write the next frame
+ avi_error avierr = avi_append_video_frame_rgb32(m_avifile, m_snap_bitmap);
+ if (avierr != AVIERR_NONE)
+ {
+ g_profiler.stop();
+ return end_recording();
+ }
+ }
+
+ // handle a MNG recording
+ if (m_mngfile != NULL)
+ {
+ // set up the text fields in the movie info
+ png_info pnginfo = { 0 };
+ if (m_movie_frame == 0)
+ {
+ astring text1(APPNAME, " ", build_version);
+ astring text2(machine().system().manufacturer, " ", machine().system().description);
+ png_add_text(&pnginfo, "Software", text1);
+ png_add_text(&pnginfo, "System", text2);
+ }
+
+ // write the next frame
+ const rgb_t *palette = (machine().palette != NULL) ? palette_entry_list_adjusted(machine().palette) : NULL;
+ png_error error = mng_capture_frame(*m_mngfile, &pnginfo, m_snap_bitmap, machine().total_colors(), palette);
+ png_free(&pnginfo);
+ if (error != PNGERR_NONE)
+ {
+ g_profiler.stop();
+ return end_recording();
+ }
+ }
+
+ // advance time
+ m_movie_next_frame_time += m_movie_frame_period;
+ m_movie_frame++;
+ }
+ g_profiler.stop();
+}
+
+
+
+/*-------------------------------------------------
+ video_assert_out_of_range_pixels - assert if
+ any pixels in the given bitmap contain an
+ invalid palette index
+-------------------------------------------------*/
+
+void video_assert_out_of_range_pixels(running_machine &machine, bitmap_t *bitmap)
+{
+#ifdef MAME_DEBUG
+ int maxindex = palette_get_max_index(machine.palette);
+ int x, y;
+
+ // this only applies to indexed16 bitmaps
+ if (bitmap->format != BITMAP_FORMAT_INDEXED16)
+ return;
+
+ // iterate over rows
+ for (y = 0; y < bitmap->height; y++)
+ {
+ UINT16 *rowbase = BITMAP_ADDR16(bitmap, y, 0);
+ for (x = 0; x < bitmap->width; x++)
+ assert(rowbase[x] < maxindex);
+ }
+#endif
+}
+
+
+
+//**************************************************************************
+// SOFTWARE RENDERING
+//**************************************************************************
+
+#define FUNC_PREFIX(x) rgb888_##x
+#define PIXEL_TYPE UINT32
+#define SRCSHIFT_R 0
+#define SRCSHIFT_G 0
+#define SRCSHIFT_B 0
+#define DSTSHIFT_R 16
+#define DSTSHIFT_G 8
+#define DSTSHIFT_B 0
+#define BILINEAR_FILTER 1
+
+#include "rendersw.c"