text: /*
   * vsync.c - End-of-frame handling
   *
   * Written by
   *  Ettore Perazzoli <ettore@comm2000.it>
   *  Teemu Rantanen <tvr@cs.hut.fi>
   *  Andreas Boose <viceteam@t-online.de>
   *  Dag Lem <resid@nimrod.no>
   *  Thomas Bretz <tbretz@gsi.de>
   *
   * This file is part of VICE, the Versatile Commodore Emulator.
   * See README for copyright notice.
   *
   *  This program is free software; you can redistribute it and/or modify
   *  it under the terms of the GNU General Public License as published by
   *  the Free Software Foundation; either version 2 of the License, or
   *  (at your option) any later version.
   *
   *  This program is distributed in the hope that it will be useful,
   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *  GNU General Public License for more details.
   *
   *  You should have received a copy of the GNU General Public License
   *  along with this program; if not, write to the Free Software
   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   *  02111-1307  USA.
   *
   */
  
  /* This does what has to be done at the end of each screen frame (50 times per
     second on PAL machines). */
  
  /* NB! The timing code depends on two's complement arithmetic.
     unsigned long is used for the timer variables, and the difference
     between two time points a and b is calculated with (signed long)(b - a)
     This allows timer variables to overflow without any explicit
     overflow handling.
  */
  
  int vsync_frame_counter;
  
  #include "vice.h"
  
  /* Port me... */
  
  #include <stdio.h>
  #include <stdlib.h>
  
  #ifdef HAVE_LIMITS_H
  #include <limits.h>
  #endif
  
  #include "clkguard.h"
  #include "cmdline.h"
  #include "debug.h"
  #include "log.h"
  #include "maincpu.h"
  #include "machine.h"
  #ifdef HAVE_NETWORK
  #include "monitor_network.h"
  #endif
  #include "network.h"
  #include "resources.h"
  #include "sound.h"
  #include "types.h"
  #include "vsync.h"
  #include "vsyncapi.h"
  
  /* ------------------------------------------------------------------------- */
  
  static int set_timer_speed(int speed);
  
  /* Relative speed of the emulation (%).  0 means "don't limit speed". */
  static int relative_speed;
  
  /* Refresh rate.  0 means "auto". */
  static int refresh_rate;
  
  /* "Warp mode".  If nonzero, attempt to run as fast as possible. */
  static int warp_mode_enabled;
  
  
  static int set_relative_speed(int val, void *param)
  {
      relative_speed = val;
      sound_set_relative_speed(relative_speed);
      set_timer_speed(relative_speed);
  
      return 0;
  }
  
  static int set_refresh_rate(int val, void *param)
  {
      if (val < 0) {
          return -1;
      }
  
      refresh_rate = val;
  
      return 0;
  }
  
  static int set_warp_mode(int val, void *param)
  {
      warp_mode_enabled = val ? 1 : 0;
  
      sound_set_warp_mode(warp_mode_enabled);
      set_timer_speed(relative_speed);
  
      return 0;
  }
  
  
  /* Vsync-related resources. */
  static const resource_int_t resources_int[] = {
      { "Speed", 100, RES_EVENT_SAME, NULL,
        &relative_speed, set_relative_speed, NULL },
      { "RefreshRate", 0, RES_EVENT_STRICT, (resource_value_t)1,
        &refresh_rate, set_refresh_rate, NULL },
      { "WarpMode", 0, RES_EVENT_STRICT, (resource_value_t)0,
        /* FIXME: maybe RES_EVENT_NO */
        &warp_mode_enabled, set_warp_mode, NULL },
      RESOURCE_INT_LIST_END
  };
  
  
  static const resource_int_t resources_int_vsid[] = {
      { "Speed", 100, RES_EVENT_SAME, NULL,
        &relative_speed, set_relative_speed, NULL },
      { "WarpMode", 0, RES_EVENT_STRICT, (resource_value_t)0,
        /* FIXME: maybe RES_EVENT_NO */
        &warp_mode_enabled, set_warp_mode, NULL },
      RESOURCE_INT_LIST_END
  };
  
  
  
  int vsync_resources_init(void)
  {
      if (machine_class == VICE_MACHINE_VSID) {
          return resources_register_int(resources_int_vsid);
      }
      return resources_register_int(resources_int);
  }
  
  /* ------------------------------------------------------------------------- */
  
  /* Vsync-related command-line options. */
  static const cmdline_option_t cmdline_options[] =
  {
      { "-speed", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS,
        NULL, NULL, "Speed", NULL,
        "<percent>", "Limit emulation speed to specified value" },
      { "-refresh", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS,
        NULL, NULL, "RefreshRate", NULL,
        "<value>", "Update every <value> frames (`0' for automatic)" },
      { "-warp", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
        NULL, NULL, "WarpMode", (resource_value_t)1,
        NULL, "Enable warp mode" },
      { "+warp", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
        NULL, NULL, "WarpMode", (resource_value_t)0,
        NULL, "Disable warp mode" },
      CMDLINE_LIST_END
  };
  
  
  static const cmdline_option_t cmdline_options_vsid[] =
  {
      { "-speed", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS,
        NULL, NULL, "Speed", NULL,
        "<percent>", "Limit emulation speed to specified value" },
      { "-warp", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
        NULL, NULL, "WarpMode", (resource_value_t)1,
        NULL, "Enable warp mode" },
      { "+warp", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
        NULL, NULL, "WarpMode", (resource_value_t)0,
        NULL, "Disable warp mode" },
      CMDLINE_LIST_END
  };
  
  
  int vsync_cmdline_options_init(void)
  {
      if (machine_class == VICE_MACHINE_VSID) {
          return cmdline_register_options(cmdline_options_vsid);
      }
      return cmdline_register_options(cmdline_options);
  }
  
  /* ------------------------------------------------------------------------- */
  
  /* Maximum number of frames we can skip consecutively when adjusting the
     refresh rate dynamically.  */
  #define MAX_SKIPPED_FRAMES        10
  
  /* Number of frames per second on the real machine. */
  static double refresh_frequency;
  
  /* Number of clock cycles per seconds on the real machine. */
  static long cycles_per_sec;
  
  /* Function to call at the end of every screen frame. */
  static void (*vsync_hook)(void);
  
  /* ------------------------------------------------------------------------- */
  
  /* static guarantees zero values. */
  static long vsyncarch_freq = 0;
  static unsigned long now;
  static unsigned long display_start;
  static long frame_ticks, frame_ticks_orig;
  
  static int timer_speed = 0;
  static int speed_eval_suspended = 1;
  static int sync_reset = 1;
  static CLOCK speed_eval_prev_clk;
  
  /* Initialize vsync timers and set relative speed of emulation in percent. */
  static int set_timer_speed(int speed)
  {
      speed_eval_suspended = 1;
      vsync_sync_reset();
  
      if (speed > 0 && refresh_frequency > 0) {
          timer_speed = speed;
          frame_ticks = (long)(((vsyncarch_freq / refresh_frequency) * 100) / speed);
          frame_ticks_orig = frame_ticks;
      } else {
          timer_speed = 0;
          frame_ticks = 0;
      }
  
      return 0;
  }
  
  /* Display speed (percentage) and frame rate (frames per second). */
  static void display_speed(int num_frames)
  {
      CLOCK diff_clk;
      double diff_sec;
      double speed_index;
      double frame_rate;
  
      /* Lie a bit by correcting for sound speed.  This yields a nice
         100%, 50 fps instead of e.g. 98%, 49fps if the sound hardware
         should have a slightly different understanding of time. */
      double factor = timer_speed ? (double)frame_ticks / frame_ticks_orig : 1.0;
  
      diff_clk = maincpu_clk - speed_eval_prev_clk;
      diff_sec = (double)(signed long)(now - display_start) / vsyncarch_freq
                 / factor;
      frame_rate = num_frames / diff_sec;
      speed_index = 100.0 * diff_clk / (cycles_per_sec * diff_sec);
  
      if (!console_mode && machine_class != VICE_MACHINE_VSID) {
          vsyncarch_display_speed(speed_index, frame_rate, warp_mode_enabled);
      }
  
      speed_eval_prev_clk = maincpu_clk;
  }
  
  static void clk_overflow_callback(CLOCK amount, void *data)
  {
      speed_eval_prev_clk -= amount;
  }
  
  /* ------------------------------------------------------------------------- */
  
  void vsync_set_machine_parameter(double refresh, long cycles)
  {
      refresh_frequency = refresh;
      cycles_per_sec = cycles;
      set_timer_speed(relative_speed);
  }
  
  double vsync_get_refresh_frequency(void)
  {
      return refresh_frequency;
  }
  
  void vsync_init(void (*hook)(void))
  {
      vsync_hook = hook;
      vsync_suspend_speed_eval();
      clk_guard_add_callback(maincpu_clk_guard, clk_overflow_callback, NULL);
  
      vsyncarch_init();
  
      vsyncarch_freq = vsyncarch_frequency();  /* number of units per second */
      /* log_message(LOG_DEFAULT, "VSYNC Init freq: %u", (unsigned int)vsyncarch_freq); */
  }
  
  /* FIXME: This function is not needed here anymore, however it is
     called from sound.c and can only be removed if all other ports are
     changed to use similar vsync code. */
  int vsync_disable_timer(void)
  {
      return 0;
  }
  
  /* This should be called whenever something that has nothing to do with the
     emulation happens, so that we don't display bogus speed values. */
  void vsync_suspend_speed_eval(void)
  {
      network_suspend();
      sound_suspend();
      vsync_sync_reset();
      speed_eval_suspended = 1;
  }
  
  /* This resets sync calculation after a "too slow" or "sound buffer
     drained" case. */
  void vsync_sync_reset(void)
  {
      sync_reset = 1;
  }
  
  /* This is called at the end of each screen frame. It flushes the
     audio buffer and keeps control of the emulation speed. */
  int vsync_do_vsync(struct video_canvas_s *c, int been_skipped)
  {
      static unsigned long next_frame_start = 0;
      unsigned long network_hook_time = 0;
  
      /*
       * these are the counters to show how many frames are skipped
       * since the last vsync_display_speed
       */
      static int frame_counter = 0;
      static int skipped_frames = 0;
  
      /*
       * This are the frames which are skipped in a row
       */
      static int skipped_redraw = 0;
  
      /* Adjustment of frame output frequency. */
      static unsigned long adjust_start;
      static int frames_adjust;
      static signed long avg_sdelay, prev_sdelay;
  
      double sound_delay;
      int skip_next_frame;
  
      signed long delay;
  
      long frame_ticks_remainder, frame_ticks_integer;
      long compval;
  
  #ifdef HAVE_NETWORK
      /* check if someone wants to connect remotely to the monitor */
      monitor_check_remote();
  #endif
  
      vsync_frame_counter++;
  
      /*
       * process everything wich should be done before the synchronisation
       * e.g. OS/2: exit the programm if trigger_shutdown set
       */
      vsyncarch_presync();
  
      /* Run vsync jobs. */
      if (network_connected()) {
          network_hook_time = vsyncarch_gettime();
      }
  
      vsync_hook();
  
      if (network_connected()) {
          network_hook_time = vsyncarch_gettime() - network_hook_time;
  
          if (network_hook_time > (unsigned long)frame_ticks) {
              next_frame_start += network_hook_time;
              now += network_hook_time;
          }
      }
  
  #ifdef DEBUG
      /* switch between recording and playback in history debug mode */
      debug_check_autoplay_mode();
  #endif
  
      /*
       * Update display every two second (pc system time)
       * This has some reasons:
       *  - we have a better statistic in case of a fastly running emulator
       *  - we don't slow down fast emulations by updating this value
       *    too often (eg more then 10 times a second)
       *  - I don't want to have a value jumping around for example
       *    between 99% and 101% if the user chooses 100% (s.above)
       *  - We need some statistict to get an avarage number for the
       *    frame-rate without staticstics it would also jump around
       */
      frame_counter++;
  
      if (!speed_eval_suspended &&
          (signed long)(now - display_start) >= (2 * vsyncarch_freq)) {
          display_speed(frame_counter - skipped_frames);
          display_start = now;
          frame_counter = 0;
          skipped_frames = 0;
      }
  
      if (been_skipped) {
          skipped_frames++;
      }
  
      /* Flush sound buffer, get delay in seconds. */
      sound_delay = sound_flush();
  
      /* Get current time, directly after getting the sound delay. */
      now = vsyncarch_gettime();
  
      /* Start afresh after pause in frame output. */
      if (speed_eval_suspended) {
          speed_eval_suspended = 0;
  
          speed_eval_prev_clk = maincpu_clk;
  
          display_start = now;
          frame_counter = 0;
          skipped_frames = 0;
  
          next_frame_start = now;
          skipped_redraw = 0;
      }
  
      /* Start afresh after "out of sync" cases. */
      if (sync_reset) {
          sync_reset = 0;
  
          adjust_start = now;
          frames_adjust = 0;
          avg_sdelay = 0;
          prev_sdelay = 0;
  
          frame_ticks = (frame_ticks_orig + frame_ticks) / 2;
      }
  
  
      /* This is the time between the start of the next frame and now. */
      delay = (signed long)(now - next_frame_start);
      /*
       * We sleep until the start of the next frame, if:
       *  - warp_mode is disabled
       *  - a limiting speed is given
       *  - we have not reached next_frame_start yet
       *
       * We could optimize by sleeping only if a frame is to be output.
       */
      /*log_debug("vsync_do_vsync: sound_delay=%f  frame_ticks=%d  delay=%d", sound_delay, frame_ticks, delay);*/
      if (!warp_mode_enabled && timer_speed && (skipped_redraw == 0) && (delay < 0)) {
          /* FIXME: this is likely implemented as a regular sleep(), which means
             it will wait *at least* the given time (but may just as well wait
             much longer. its doomed to break on those archs - we should instead
             "lean against" the sound output, and let the sound hardware be the
             timing reference */
          vsyncarch_sleep(-delay);
      }
      /*
       * Check whether we should skip the next frame or not.
       * Allow delay of up to one frame before skipping frames.
       * Frames are skipped:
       *  - only if maximum skipped frames are not reached
       *  - if warp_mode enabled
       *  - if speed is not limited or we are too slow and
       *    refresh rate is automatic or fixed and needs correction
       *
       * Remark: The time_deviation should be the equivalent of two
       *         frames and must be scaled to make sure, that we
       *         don't start skipping frames before the CPU reaches 100%.
       *         If we are becoming faster a small deviation because of
       *         threading results in a frame rate correction suddenly.
       */
  
      /* this doesnt really work correctly, and it shouldnt be neceassary either */
      frame_ticks_remainder = frame_ticks % 100;
      frame_ticks_integer = frame_ticks / 100;
      compval = (frame_ticks_integer * 3 * timer_speed)
                + ((frame_ticks_remainder * 3 * timer_speed) / 100);
  
      if ((skipped_redraw < MAX_SKIPPED_FRAMES)
          && (warp_mode_enabled
              || (skipped_redraw < (refresh_rate - 1))
              || ((!timer_speed || delay > compval) && !refresh_rate))
          ) {
          /* printf("skipped redraw:%d timer_speed:%3d refresh_rate:%2d delay:%6lx compval:%6lx frame_ticks:%lx\n",
                 skipped_redraw,timer_speed,refresh_rate,delay,compval,frame_ticks); */
          skip_next_frame = 1;
          skipped_redraw++;
      } else {
          skip_next_frame = 0;
          skipped_redraw = 0;
      }
  
      /*
       * Check whether the hardware can keep up.
       * Allow up to 0,25 second error before forcing a correction.
       */
      if ((signed long)(now - next_frame_start) >= vsyncarch_freq / 8) {
          vsync_sync_reset();
          next_frame_start = now;
      }
  
      /* Adjust frame output frequency to match sound speed.
         This only kicks in for cycle based sound and SOUND_ADJUST_EXACT. */
      if (frames_adjust < INT_MAX) {
          frames_adjust++;
      }
  
      /* Adjust audio-video sync */
      if (!network_connected()
          && (signed long)(now - adjust_start) >= vsyncarch_freq / 5) {
          signed long adjust;
          avg_sdelay /= frames_adjust;
          /* Account for both relative and absolute delay. */
          adjust = (avg_sdelay - prev_sdelay + avg_sdelay / 8) / frames_adjust;
          /* Maximum adjustment step 1%. */
          if (labs(adjust) > (frame_ticks / 100)) {
              adjust = adjust / labs(adjust) * frame_ticks / 100;
          }
          frame_ticks -= adjust;
  
          frames_adjust = 0;
          prev_sdelay = avg_sdelay;
          avg_sdelay = 0;
  
          adjust_start = now;
      } else {
          /* Actual sound delay is sound delay minus vsync delay. */
          signed long sdelay = (signed long)(sound_delay * vsyncarch_freq);
          avg_sdelay += sdelay;
      }
  
      /* FIXME: The #if 0ed code below improves the FPS rate when
         "SoundDeviceName" == "pulse" && "RefreshRate" == 0 ("auto") but 
         seems to cause problems with every other sound driver I have tested. */
  #if 0
      /* if the frame was skipped, don't advance the time for the next frame, this
         helps with catching up when rendering falls behind */
      if ((frame_ticks > 0) && (skipped_redraw < 1)) {
          next_frame_start += frame_ticks;
      }
  #else
      next_frame_start += frame_ticks;
  #endif
  
      vsyncarch_postsync();
  
  #ifdef VSYNC_DEBUG
      log_debug("vsync: start:%lu  delay:%ld  sound-delay:%lf  end:%lu  next-frame:%lu  frame-ticks:%lu", 
                  now, delay, sound_delay * 1000000, vsyncarch_gettime(), next_frame_start, frame_ticks);
  #endif
      return skip_next_frame;
  }
  