From 9b822402b95d2b9babb8978e2769e4df40e3dc30 Mon Sep 17 00:00:00 2001 From: Owen Taylor Date: Sun, 23 Nov 2008 04:12:34 +0000 Subject: [PATCH] Use a Tweener "Frame Ticker" with a ClutterTimeline backend Call Tweener.setFrameTicker() with a custom object that bridges to ClutterTimeline to get new frame notifications. Combined with a hack to dynamically adjust the frame ticker's frame rate when Clutter drops frames, this means that our animations play in the intended time even if rendering is too slow to maintain a full 60HZ frame rate. http://bugzilla.gnome.org/show_bug.cgi?id=561745 svn path=/trunk/; revision=79 --- js/ui/main.js | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/js/ui/main.js b/js/ui/main.js index 6b394e8e4..c52a2460b 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -1,7 +1,9 @@ /* -*- mode: js2; js2-basic-offset: 4; -*- */ -const Shell = imports.gi.Shell; const Clutter = imports.gi.Clutter; +const Shell = imports.gi.Shell; +const Signals = imports.signals; +const Tweener = imports.tweener.tweener; const Panel = imports.ui.panel; const Overlay = imports.ui.overlay; @@ -16,9 +18,76 @@ let overlay = null; let run_dialog = null; let wm = null; +// The "FrameTicker" object is an object used to feed new frames to Tweener +// so it can update values and redraw. The default frame ticker for +// Tweener just uses a simple timeout at a fixed frame rate and has no idea +// of "catching up" by dropping frames. +// +// We substitute it with custom frame ticker here that connects Tweener to +// a Clutter.TimeLine. Now, Clutter.Timeline itself isn't a whole lot more +// sophisticated than a simple timeout at a fixed frame rate, but at least +// it knows how to drop frames. (See HippoAnimationManager for a more +// sophisticated view of continous time updates; even better is to pay +// attention to the vertical vblank and sync to that when possible.) +// +function ClutterFrameTicker() { + this._init(); +} + +ClutterFrameTicker.prototype = { + TARGET_FRAME_RATE : 60, + + _init : function() { + // We don't have a finite duration; tweener will tell us to stop + // when we need to stop, so use 1000 seconds as "infinity" + this._timeline = new Clutter.Timeline({ fps: this.TARGET_FRAME_RATE, + duration: 1000*1000 }); + this._frame = 0; + + let me = this; + this._timeline.connect('new-frame', + function(timeline, frame) { + me._onNewFrame(frame); + }); + }, + + _onNewFrame : function(frame) { + // Unfortunately the interface to to send a new frame to tweener + // is a simple "next frame" and there is no provision for signaling + // that frames have been skipped or just telling it the new time. + // But what it actually does internally is just: + // + // _currentTime += 1000/_ticker.FRAME_RATE; + // + // So by dynamically adjusting the value of FRAME_RATE we can trick + // it into dealing with dropped frames. + + let delta = frame - this._frame; + if (delta == 0) + this.FRAME_RATE = this.TARGET_FRAME_RATE; + else + this.FRAME_RATE = this.TARGET_FRAME_RATE / delta; + this._frame = frame; + this.emit('prepare-frame'); + }, + + start : function() { + this._timeline.start(); + }, + + stop : function() { + this._timeline.stop(); + this._frame = 0; + } +}; + +Signals.addSignalMethods(ClutterFrameTicker.prototype); + function start() { let global = Shell.global_get(); + Tweener.setFrameTicker(new ClutterFrameTicker()); + // The background color really only matters if there is no desktop // window (say, nautilus) running. We set it mostly so things look good // when we are running inside Xephyr.