/* GDK - The GIMP Drawing Kit
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 */

/*
 * Modified by the GTK+ Team and others 1997-2010.  See the AUTHORS
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
 */

#include "config.h"

#include "gdkframeclockidleprivate.h"

#include "gdkdebugprivate.h"
#include "gdkframeclockprivate.h"
#include "gdkframetimingsprivate.h"
#include "gdkprivate.h"
#include "gdkprofilerprivate.h"

#define FRAME_INTERVAL 16667 /* microseconds */

typedef enum {
  SMOOTH_PHASE_STATE_VALID = 0,    /* explicit, since we count on zero-init */
  SMOOTH_PHASE_STATE_AWAIT_FIRST,
  SMOOTH_PHASE_STATE_AWAIT_DRAWN,
} SmoothDeltaState;

typedef struct _GdkFrameClockIdlePrivate GdkFrameClockIdlePrivate;
struct _GdkFrameClockIdlePrivate
{
  uint64_t smoothed_frame_time_base;     /* A grid-aligned version of frame_time (grid size == refresh period), never more than half a grid from frame_time */
  uint64_t smoothed_frame_time_period;   /* The grid size that smoothed_frame_time_base is aligned to */
  uint64_t smoothed_frame_time_reported; /* Ensures we are always monotonic */
  uint64_t smoothed_frame_time_phase;    /* The offset of the first reported frame time, in the current animation sequence, from the preceding vsync */
  uint64_t min_next_frame_time;          /* We're not synced to vblank, so wait at least until this before next cycle to avoid busy looping */
  SmoothDeltaState smooth_phase_state;   /* The state of smoothed_frame_time_phase - is it valid, awaiting vsync etc. Thanks to zero-init, the initial value
                                            of smoothed_frame_time_phase is `0`. This is valid, since we didn't get a "frame drawn" event yet. Accordingly,
                                            the initial value of smooth_phase_state is SMOOTH_PHASE_STATE_VALID. See the comment in gdk_frame_clock_paint_idle()
                                            for details. */

  gint64 freeze_time; /* in nanoseconds */

  GSource *source;

  guint paint_is_thaw : 1;
};

G_DEFINE_TYPE_WITH_PRIVATE (GdkFrameClockIdle, gdk_frame_clock_idle, GDK_TYPE_FRAME_CLOCK)

static void
gdk_frame_clock_idle_init (GdkFrameClockIdle *frame_clock_idle)
{
  GdkFrameClockIdlePrivate *priv = gdk_frame_clock_idle_get_instance_private (frame_clock_idle);

  priv->smoothed_frame_time_period = FRAME_INTERVAL;
}

static void
gdk_frame_clock_idle_dispose (GObject *object)
{
  GdkFrameClockIdle *self = GDK_FRAME_CLOCK_IDLE (object);
  GdkFrameClockIdlePrivate *priv = gdk_frame_clock_idle_get_instance_private (self);

  g_clear_pointer (&priv->source, g_source_destroy);

  G_OBJECT_CLASS (gdk_frame_clock_idle_parent_class)->dispose (object);
}

/* Note: This is never called on first frame, so
 * smoothed_frame_time_base != 0 and we have a valid frame_interval. */
static uint64_t
compute_smooth_frame_time (GdkFrameClockIdle *self,
                           uint64_t           new_frame_time,
                           gboolean           new_frame_time_is_vsync_related,
                           uint64_t           smoothed_frame_time_base,
                           uint64_t           frame_interval)
{
  GdkFrameClockIdlePrivate *priv = gdk_frame_clock_idle_get_instance_private (self);
  int frames_passed;
  uint64_t new_smoothed_time;
  uint64_t current_error;
  uint64_t correction_magnitude;

  /* Consecutive frame, assume it is an integer number of frames later, so round to nearest such */
  if (new_frame_time < smoothed_frame_time_base + frame_interval / 2)
    frames_passed = 0;
  else
    frames_passed = (new_frame_time - smoothed_frame_time_base + frame_interval / 2) / frame_interval;

  /* We use an approximately whole number of frames in the future from
   * last smoothed frame time. This way we avoid minor jitter in the
   * frame times making the animation speed uneven, but still animate
   * evenly in case of whole frame skips. */
  new_smoothed_time = smoothed_frame_time_base + frames_passed * frame_interval;

  /* However, sometimes the smoothed time is too much off from the
   * real time. For example, if the first frame clock cycle happened
   * not due to a frame rendering but an input event, then
   * new_frame_time could happen to be near the middle between two
   * frames. If that happens and we then start regularly animating at
   * the refresh_rate, then the jitter in the real time may cause us
   * to randomly sometimes round up, and sometimes down.
   *
   * To combat this we converge the smooth time towards the real time
   * in a way that is slow when they are near and fast when they are
   * far from each other.
   *
   * This is done by using the square of the error as the correction
   * magnitude. I.e. if the error is 0.5 frame, we correct by
   * 0.5*0.5=0.25 frame, if the error is 0.25 we correct by 0.125, if
   * the error is 0.1, frame we correct by 0.01 frame, etc.
   *
   * The actual computation is:
   *   (current_error/frame_interval)*(current_error/frame_interval)*frame_interval
   * But this can be simplified as below.
   *
   * Note: We only do this correction if the new frame is caused by a
   * thaw of the frame clock, so that we know the time is actually
   * related to the physical vblank. For frameclock cycles triggered
   * by other events we always step up in whole frames from the last
   * reported time.
   */
  if (new_frame_time_is_vsync_related)
    {
      current_error = MAX (new_smoothed_time, new_frame_time) - MIN (new_smoothed_time, new_frame_time);
      correction_magnitude = current_error * current_error / frame_interval;
      if (new_smoothed_time > new_frame_time)
        new_smoothed_time -= correction_magnitude;
      else
        new_smoothed_time += correction_magnitude;
    }

  /* Ensure we're always monotonic  */
  if (new_smoothed_time <= priv->smoothed_frame_time_reported)
    new_smoothed_time = priv->smoothed_frame_time_reported;

  return new_smoothed_time;
}

/* The reason why we track updating_count separately here and don't
 * just add GDK_FRAME_CLOCK_PHASE_UPDATE into ->request on every frame
 * is so that we can avoid doing one more frame when an animation
 * is cancelled.
 */
static inline gboolean
should_run_source (GdkFrameClockIdle *self)
{
  GdkFrameClock *clock = GDK_FRAME_CLOCK (self);

  return !gdk_frame_clock_is_stopped (clock) &&
         !gdk_frame_clock_is_in_frame (clock) &&
         (gdk_frame_clock_get_requested (clock) != 0 || gdk_frame_clock_is_updating (clock));
}

static inline gint
get_source_priority (GdkFrameClockIdle *self)
{
  if (gdk_frame_clock_get_requested (GDK_FRAME_CLOCK (self)) & GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS)
    return GDK_PRIORITY_EVENTS + 1;
  else
    return GDK_PRIORITY_REDRAW;
}

static gboolean gdk_frame_clock_source_cb (void *data);

static void
maybe_start_idle (GdkFrameClockIdle *self,
                  gboolean           caused_by_thaw)
{
  GdkFrameClockIdlePrivate *priv = gdk_frame_clock_idle_get_instance_private (self);

  if (should_run_source (self) && priv->source == NULL)
    {
      uint64_t interval = 0;

      if (priv->min_next_frame_time != 0 &&
          !GDK_DEBUG_CHECK (NO_VSYNC))
        {
          uint64_t now = g_get_monotonic_time_ns ();
          if (now < priv->min_next_frame_time)
            interval = priv->min_next_frame_time - now;
        }

G_GNUC_BEGIN_IGNORE_DEPRECATIONS
      priv->source = g_timeout_source_new_ns (interval);
G_GNUC_END_IGNORE_DEPRECATIONS
      g_source_set_static_name (priv->source, "[gtk] gdk_frame_clock_frame");
      g_source_set_callback (priv->source,
                             gdk_frame_clock_source_cb, 
                             g_object_ref (self),
                             (GDestroyNotify) g_object_unref);
      g_source_set_priority (priv->source, get_source_priority (self));
      g_source_attach (priv->source, NULL);
      g_source_unref (priv->source);

      priv->paint_is_thaw = caused_by_thaw;
    }
}

static void
maybe_stop_idle (GdkFrameClockIdle *self)
{
  GdkFrameClockIdlePrivate *priv = gdk_frame_clock_idle_get_instance_private (self);

  if (!should_run_source (self))
    g_clear_pointer (&priv->source, g_source_destroy);
}

static uint64_t
gdk_frame_clock_idle_update_frame_time (GdkFrameClockIdle *self,
                                        uint64_t           frame_time)
{
  GdkFrameClockIdlePrivate *priv = gdk_frame_clock_idle_get_instance_private (self);
  uint64_t frame_interval;

  frame_interval = gdk_frame_clock_get_refresh_interval (GDK_FRAME_CLOCK (self));

  /*
   * The first clock cycle of an animation might have been triggered by some external event. An external
   * event can be an input event, an expired timer, data arriving over the network etc. This can happen at
   * any time, so the cycle could have been scheduled at some random time rather then immediately after a
   * frame completion. The offset between the start of the first animation cycle and the preceding vsync is
   * called the "phase" of the clock cycle start time (not to be confused with the phase of the frame
   * clock).
   *
   * In this first clock cycle, the "smooth" frame time is simply the time when the cycle was started. This
   * could be followed by several cycles which are not vsync-related. As long as we don't get a "frame
   * drawn" signal from the compositor, the clock cycles will occur every about frame_interval. Once we do
   * get a "frame drawn" signal, from this point on the frame clock cycles will start shortly after the
   * corresponding vsync signals, again every about frame_interval. The first vsync-related clock cycle
   * might occur less than a refresh interval away from the last non-vsync-related cycle. See the diagram
   * below for details. So while the cadence stays the same - a frame clock cycle every about frame_interval
   * - the phase of the cycles start time has changed.
   *
   * Since we might have already reported the frame time to the application in the previous clock cycles, we
   * have to adjust future reported frame times. We want the first vsync-related smooth time to be separated
   * by exactly 1 frame_interval from the previous one, in order to maintain the regularity of the reported
   * frame times. To achieve that, from this point on we add the phase of the first clock cycle start time to
   * the smooth time. In order to compute that phase, accounting for possible skipped frames (e.g. due to
   * compositor stalls), we want the following to be true:
   *
   *   first_vsync_smooth_time = last_non_vsync_smooth_time + frame_interval * (1 + frames_skipped)
   *
   * We can assign the following known/desired values to the above equation:
   *
   *   last_non_vsync_smooth_time = smoothed_frame_time_base
   *   first_vsync_smooth_time = frame_time + smoothed_frame_time_phase
   *
   * That leads us to the following, from which we can extract smoothed_frame_time_phase:
   *
   *   frame_time + smoothed_frame_time_phase = smoothed_frame_time_base +
   *                                            frame_interval * (1 + frames_skipped)
   *
   * In the following diagram, '|' mark a vsync, '*' mark the start of a clock cycle, '+' is the adjusted
   * frame time, '!' marks the reception of "frame drawn" events from the compositor. Note that the clock
   * cycle cadence changed after the first vsync-related cycle. This cadence is kept even if we don't
   * receive a 'frame drawn' signal in a subsequent frame, since then we schedule the clock at intervals of
   * refresh_interval.
   *
   * vsync             |           |           |           |           |           |...
   * frame drawn       |           |           |!          |!          |           |...
   * cycle start       |       *   |       *   |*          |*          |*          |...
   * adjusted times    |       *   |       *   |       +   |       +   |       +   |...
   * phase                                      ^------^
   */
  if (priv->smooth_phase_state == SMOOTH_PHASE_STATE_AWAIT_FIRST)
    {
      /* First animation cycle - usually unrelated to vsync */
      priv->smoothed_frame_time_base = 0;
      priv->smoothed_frame_time_phase = 0;
      priv->smooth_phase_state = SMOOTH_PHASE_STATE_AWAIT_DRAWN;
    }
  else if (priv->smooth_phase_state == SMOOTH_PHASE_STATE_AWAIT_DRAWN &&
           priv->paint_is_thaw)
    {
      /* First vsync-related animation cycle, we can now compute the phase. We want the phase to satisfy
         0 <= phase < frame_interval */
      if (priv->smoothed_frame_time_base >= frame_time)
        priv->smoothed_frame_time_phase = (priv->smoothed_frame_time_base - frame_time) % frame_interval;
      else
        priv->smoothed_frame_time_phase = frame_interval - (frame_time - priv->smoothed_frame_time_base) % frame_interval;
      priv->smooth_phase_state = SMOOTH_PHASE_STATE_VALID;
    }

  if (priv->smoothed_frame_time_base == 0)
    {
      /* First frame ever, or first cycle in a new animation sequence. Ensure monotonicity */
      priv->smoothed_frame_time_base = MAX (frame_time, priv->smoothed_frame_time_reported);
    }
  else
    {
      /* compute_smooth_frame_time() ensures monotonicity */
      priv->smoothed_frame_time_base =
          compute_smooth_frame_time (self, frame_time + priv->smoothed_frame_time_phase,
                                     priv->paint_is_thaw,
                                     priv->smoothed_frame_time_base,
                                     priv->smoothed_frame_time_period);
    }

  priv->smoothed_frame_time_period = frame_interval;
  priv->smoothed_frame_time_reported = priv->smoothed_frame_time_base;

  return priv->smoothed_frame_time_base;
}

static uint64_t
gdk_frame_clock_idle_compute_frame_time (GdkFrameClock *clock,
                                         uint64_t       now,
                                         gboolean       start_of_frame)
{
  GdkFrameClockIdle *self = GDK_FRAME_CLOCK_IDLE (clock);
  GdkFrameClockIdlePrivate *priv = gdk_frame_clock_idle_get_instance_private (self);
  gint64 new_smoothed_time;

  if (start_of_frame)
    return gdk_frame_clock_idle_update_frame_time (self, now);

  /* Outside a paint, pick something smoothed close to now */

  /* First time frame, just return something */
  if (priv->smoothed_frame_time_base == 0)
    {
      priv->smoothed_frame_time_reported = now;
      return now;
    }

  /* Since time is monotonic this is <= what we will pick for the next cycle, but
     more likely than not it will be equal if we're doing a constant animation. */
  new_smoothed_time = compute_smooth_frame_time (self, now, FALSE,
                                                 priv->smoothed_frame_time_base,
                                                 priv->smoothed_frame_time_period);

  priv->smoothed_frame_time_reported = new_smoothed_time;
  return new_smoothed_time;
}

static gboolean
gdk_frame_clock_source_cb (void *data)
{
  GdkFrameClockIdle *self = GDK_FRAME_CLOCK_IDLE (data);
  GdkFrameClockIdlePrivate *priv = gdk_frame_clock_idle_get_instance_private (self);
  GdkFrameClock *clock = GDK_FRAME_CLOCK (self);

  priv->source = NULL;
  priv->min_next_frame_time = 0;

  gdk_frame_clock_frame (GDK_FRAME_CLOCK (self));

  /* If there is throttling in the backend layer, then we'll do another
   * update as soon as the backend unthrottles (if there is work to do),
   * otherwise we need to figure when the next frame should be.
   */
  if (!gdk_frame_clock_is_stopped (clock))
    {
      /*
       * If we don't receive "frame drawn" events, smooth_cycle_start will simply be advanced in constant increments of
       * the refresh interval. That way we get absolute target times for the next cycles, which should prevent skewing
       * in the scheduling of the frame clock.
       *
       * Once we do receive "frame drawn" events, smooth_cycle_start will track the vsync, and do so in a more stable
       * way compared to frame_time. If we then no longer receive "frame drawn" events, smooth_cycle_start will again be
       * simply advanced in increments of the refresh interval, but this time we are in sync with the vsync. If we start
       * receiving "frame drawn" events shortly after losing them, then we should still be in sync.
       */
      uint64_t smooth_cycle_start = priv->smoothed_frame_time_base - priv->smoothed_frame_time_phase;
      priv->min_next_frame_time = smooth_cycle_start + priv->smoothed_frame_time_period;

      maybe_start_idle (self, FALSE);
    }

  return G_SOURCE_REMOVE;
}

static void
gdk_frame_clock_idle_request_phase (GdkFrameClock      *clock,
                                    GdkFrameClockPhase  phase)
{
  GdkFrameClockIdle *self = GDK_FRAME_CLOCK_IDLE (clock);
  GdkFrameClockIdlePrivate *priv = gdk_frame_clock_idle_get_instance_private (self);

  if (priv->source)
    {
      int priority = get_source_priority (self);

      if (priority != g_source_get_priority (priv->source))
        g_source_set_priority (priv->source, priority);
    }
  else
    {
      maybe_start_idle (self, FALSE);
    }
}

static void
gdk_frame_clock_idle_begin_updating (GdkFrameClock *clock)
{
  GdkFrameClockIdle *self = GDK_FRAME_CLOCK_IDLE (clock);
  GdkFrameClockIdlePrivate *priv = gdk_frame_clock_idle_get_instance_private (self);

  priv->smooth_phase_state = SMOOTH_PHASE_STATE_AWAIT_FIRST;
  maybe_start_idle (self, FALSE);
}

static void
gdk_frame_clock_idle_end_updating (GdkFrameClock *clock)
{
  GdkFrameClockIdle *self = GDK_FRAME_CLOCK_IDLE (clock);
  GdkFrameClockIdlePrivate *priv = gdk_frame_clock_idle_get_instance_private (self);

  maybe_stop_idle (self);
  priv->smooth_phase_state = SMOOTH_PHASE_STATE_VALID;
}

static void
gdk_frame_clock_idle_stop (GdkFrameClock *clock)
{
  GdkFrameClockIdle *self = GDK_FRAME_CLOCK_IDLE (clock);
  GdkFrameClockIdlePrivate *priv = gdk_frame_clock_idle_get_instance_private (self);

  if (GDK_PROFILER_IS_RUNNING)
    priv->freeze_time = GDK_PROFILER_CURRENT_TIME;

  maybe_stop_idle (self);
}

static void
gdk_frame_clock_idle_start (GdkFrameClock *clock)
{
  GdkFrameClockIdle *self = GDK_FRAME_CLOCK_IDLE (clock);
  GdkFrameClockIdlePrivate *priv = gdk_frame_clock_idle_get_instance_private (self);

  maybe_start_idle (self, TRUE);

  if (GDK_PROFILER_IS_RUNNING)
    {
      if (priv->freeze_time != 0)
        {
          gdk_profiler_end_mark (priv->freeze_time, "frameclock frozen", NULL);
          priv->freeze_time = 0;
        }
    }
}

static void
gdk_frame_clock_idle_class_init (GdkFrameClockIdleClass *klass)
{
  GObjectClass *gobject_class = (GObjectClass*) klass;
  GdkFrameClockClass *frame_clock_class = (GdkFrameClockClass *)klass;

  gobject_class->dispose = gdk_frame_clock_idle_dispose;

  frame_clock_class->compute_frame_time = gdk_frame_clock_idle_compute_frame_time;
  frame_clock_class->request_phase = gdk_frame_clock_idle_request_phase;
  frame_clock_class->begin_updating = gdk_frame_clock_idle_begin_updating;
  frame_clock_class->end_updating = gdk_frame_clock_idle_end_updating;
  frame_clock_class->start = gdk_frame_clock_idle_start;
  frame_clock_class->stop = gdk_frame_clock_idle_stop;
}

GdkFrameClock *
_gdk_frame_clock_idle_new (void)
{
  GdkFrameClockIdle *clock;

  clock = g_object_new (GDK_TYPE_FRAME_CLOCK_IDLE, NULL);

  return GDK_FRAME_CLOCK (clock);
}
