1 /*
2  * vsync.c - End-of-frame handling
3  *
4  * Written by
5  *  Ettore Perazzoli <ettore@comm2000.it>
6  *  Teemu Rantanen <tvr@cs.hut.fi>
7  *  Andreas Boose <viceteam@t-online.de>
8  *  Dag Lem <resid@nimrod.no>
9  *  Thomas Bretz <tbretz@gsi.de>
10  *
11  * This file is part of VICE, the Versatile Commodore Emulator.
12  * See README for copyright notice.
13  *
14  *  This program is free software; you can redistribute it and/or modify
15  *  it under the terms of the GNU General Public License as published by
16  *  the Free Software Foundation; either version 2 of the License, or
17  *  (at your option) any later version.
18  *
19  *  This program is distributed in the hope that it will be useful,
20  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  *  GNU General Public License for more details.
23  *
24  *  You should have received a copy of the GNU General Public License
25  *  along with this program; if not, write to the Free Software
26  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
27  *  02111-1307  USA.
28  *
29  */
30 
31 /* This does what has to be done at the end of each screen frame (50 times per
32    second on PAL machines). */
33 
34 /* NB! The timing code depends on two's complement arithmetic.
35    unsigned long is used for the timer variables, and the difference
36    between two time points a and b is calculated with (signed long)(b - a)
37    This allows timer variables to overflow without any explicit
38    overflow handling.
39 */
40 
41 #include "vice.h"
42 
43 /* Port me... */
44 
45 #include <stdio.h>
46 #include <stdlib.h>
47 
48 #ifdef HAVE_LIMITS_H
49 #include <limits.h>
50 #endif
51 
52 #include "archdep_exit.h"
53 #include "clkguard.h"
54 #include "cmdline.h"
55 #include "debug.h"
56 #include "joy.h"
57 #include "kbdbuf.h"
58 #include "lib.h"
59 #include "log.h"
60 #include "maincpu.h"
61 #include "machine.h"
62 #ifdef HAVE_NETWORK
63 #include "monitor_network.h"
64 #include "monitor_binary.h"
65 #endif
66 #include "network.h"
67 #include "resources.h"
68 #include "sound.h"
69 #include "types.h"
70 #include "tick.h"
71 #include "vsync.h"
72 #include "vsyncapi.h"
73 
74 #include "ui.h"
75 
76 #ifdef MACOSX_SUPPORT
77 #include "macOS-util.h"
78 #endif
79 
80 /* public metrics, updated every vsync */
81 static double vsync_metric_cpu_percent;
82 static double vsync_metric_emulated_fps;
83 static int    vsync_metric_warp_enabled;
84 
85 #ifdef USE_VICE_THREAD
86 #   include <pthread.h>
87     pthread_mutex_t vsync_metric_lock = PTHREAD_MUTEX_INITIALIZER;
88 #   define METRIC_LOCK() pthread_mutex_lock(&vsync_metric_lock)
89 #   define METRIC_UNLOCK() pthread_mutex_unlock(&vsync_metric_lock)
90 #else
91 #   define METRIC_LOCK()
92 #   define METRIC_UNLOCK()
93 #endif
94 
95 /* ------------------------------------------------------------------------- */
96 
97 static vsync_callback_t *vsync_callback_queue;
98 static int vsync_callback_queue_size_max;
99 static int vsync_callback_queue_size;
100 
101 /** \brief Call callback_func(callback_param) once at vsync time (or machine reset) */
vsync_on_vsync_do(vsync_callback_func_t callback_func,void * callback_param)102 void vsync_on_vsync_do(vsync_callback_func_t callback_func, void *callback_param)
103 {
104     mainlock_assert_lock_obtained();
105 
106     /* Grow the queue as needed */
107     if (vsync_callback_queue_size == vsync_callback_queue_size_max) {
108         vsync_callback_queue_size_max += 1;
109         vsync_callback_queue = lib_realloc(vsync_callback_queue, vsync_callback_queue_size_max * sizeof(vsync_callback_t));
110     }
111 
112     vsync_callback_queue[vsync_callback_queue_size].callback = callback_func;
113     vsync_callback_queue[vsync_callback_queue_size].param = callback_param;
114 
115     vsync_callback_queue_size++;
116 }
117 
execute_vsync_callbacks(void)118 static void execute_vsync_callbacks(void)
119 {
120     int i;
121 
122     /* Execute each callback in turn. */
123     if (vsync_callback_queue_size) {
124         for (i = 0; i < vsync_callback_queue_size; i++) {
125             vsync_callback_queue[i].callback(vsync_callback_queue[i].param);
126         }
127         vsync_callback_queue_size = 0;
128     }
129 }
130 
131 /* ------------------------------------------------------------------------- */
132 
133 static int set_timer_speed(int speed);
134 
135 /* Relative speed of the emulation (%) (negative values target FPS rather than cpu %) */
136 static int relative_speed;
137 
138 /* "Warp mode".  If nonzero, attempt to run as fast as possible. */
139 static int warp_enabled;
140 static unsigned long warp_render_tick_interval;
141 static unsigned long warp_next_render_tick;
142 
143 /* Triggers the vice thread to update its priorty */
144 static volatile int update_thread_priority = 1;
145 
set_relative_speed(int val,void * param)146 static int set_relative_speed(int val, void *param)
147 {
148     if (val == 0) {
149         val = 100;
150         log_warning(LOG_DEFAULT, "Setting speed to 0 is no longer supported - use warp instead.");
151     }
152 
153     relative_speed = val;
154     sound_set_relative_speed(relative_speed);
155     set_timer_speed(relative_speed);
156 
157     return 0;
158 }
159 
set_warp_mode(int val,void * param)160 static int set_warp_mode(int val, void *param)
161 {
162     warp_enabled = val ? 1 : 0;
163 
164     sound_set_warp_mode(warp_enabled);
165     vsync_suspend_speed_eval();
166 
167     if (warp_enabled) {
168         warp_next_render_tick = tick_now() + warp_render_tick_interval;
169     }
170 
171     update_thread_priority = 1;
172 
173     return 0;
174 }
175 
176 
177 /* Vsync-related resources. */
178 static const resource_int_t resources_int[] = {
179     { "Speed", 100, RES_EVENT_SAME, NULL,
180       &relative_speed, set_relative_speed, NULL },
181     { "WarpMode", 0, RES_EVENT_STRICT, (resource_value_t)0,
182       /* FIXME: maybe RES_EVENT_NO */
183       &warp_enabled, set_warp_mode, NULL },
184     RESOURCE_INT_LIST_END
185 };
186 
187 
vsync_resources_init(void)188 int vsync_resources_init(void)
189 {
190     return resources_register_int(resources_int);
191 }
192 
193 /* ------------------------------------------------------------------------- */
194 
195 /* Vsync-related command-line options. */
196 static const cmdline_option_t cmdline_options[] =
197 {
198     { "-speed", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS,
199       NULL, NULL, "Speed", NULL,
200       "<percent or negative fps>", "Limit emulation speed to specified value" },
201     { "-warp", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
202       NULL, NULL, "WarpMode", (resource_value_t)1,
203       NULL, "Enable warp mode" },
204     { "+warp", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
205       NULL, NULL, "WarpMode", (resource_value_t)0,
206       NULL, "Disable warp mode" },
207     CMDLINE_LIST_END
208 };
209 
vsync_cmdline_options_init(void)210 int vsync_cmdline_options_init(void)
211 {
212     return cmdline_register_options(cmdline_options);
213 }
214 
215 /* ------------------------------------------------------------------------- */
216 
217 /* Maximum consecutive length of time we can skip rendering frames when
218    adjusting the refresh rate dynamically for slow host CPU situations. */
219 #define MAX_RENDER_SKIP_MS (1000 / 10)
220 
221 /* Number of frames per second on the real machine. */
222 static double refresh_frequency;
223 
224 /* Number of clock cycles per seconds on the real machine. */
225 static long cycles_per_sec;
226 
227 /* Function to call at the end of every screen frame. */
228 static void (*vsync_hook)(void);
229 
230 /* ------------------------------------------------------------------------- */
231 
232 /* static guarantees zero values. */
233 static double ticks_per_frame;
234 static double emulated_clk_per_second;
235 
236 static unsigned long last_sync_emulated_tick;
237 static unsigned long last_sync_tick;
238 static CLOCK last_sync_clk;
239 
240 static unsigned long sync_target_tick;
241 
242 static int timer_speed = 0;
243 static int speed_eval_suspended = 1;
244 static int sync_reset = 1;
245 
246 /* Initialize vsync timers and set relative speed of emulation in percent. */
set_timer_speed(int speed)247 static int set_timer_speed(int speed)
248 {
249     double cpu_percent;
250 
251     timer_speed = speed;
252 
253     vsync_suspend_speed_eval();
254 
255     if (refresh_frequency <= 0) {
256         /* Happens during init */
257         return -1;
258     }
259 
260     if (speed < 0) {
261         /* negative speeds are fps targets */
262 
263         cpu_percent = 100.0 * ((0 - speed) / refresh_frequency);
264     } else {
265         /* positive speeds are cpu percent targets */
266         cpu_percent = speed;
267     }
268 
269     ticks_per_frame = tick_per_second() * 100 / cpu_percent / refresh_frequency;
270     emulated_clk_per_second = cycles_per_sec * cpu_percent / 100;
271 
272     return 0;
273 }
274 
275 /* ------------------------------------------------------------------------- */
276 
vsync_set_machine_parameter(double refresh,long cycles)277 void vsync_set_machine_parameter(double refresh, long cycles)
278 {
279     refresh_frequency = refresh;
280     cycles_per_sec = cycles;
281     set_timer_speed(relative_speed);
282 }
283 
vsync_get_refresh_frequency(void)284 double vsync_get_refresh_frequency(void)
285 {
286     return refresh_frequency;
287 }
288 
vsync_init(void (* hook)(void))289 void vsync_init(void (*hook)(void))
290 {
291     /* Limit warp rendering to 10fps */
292     warp_render_tick_interval = tick_per_second() / 10.0;
293 
294     vsync_hook = hook;
295     vsync_suspend_speed_eval();
296 }
297 
298 /* FIXME: This function is not needed here anymore, however it is
299    called from sound.c and can only be removed if all other ports are
300    changed to use similar vsync code. */
vsync_disable_timer(void)301 int vsync_disable_timer(void)
302 {
303     return 0;
304 }
305 
306 /* This should be called whenever something that has nothing to do with the
307    emulation happens, so that we don't display bogus speed values. */
vsync_suspend_speed_eval(void)308 void vsync_suspend_speed_eval(void)
309 {
310     /* TODO - Is this needed any more now that late vsync is detected
311        in vsync_do_vsync() */
312     network_suspend();
313     speed_eval_suspended = 1;
314 }
315 
vsync_reset_hook(void)316 void vsync_reset_hook(void)
317 {
318     execute_vsync_callbacks();
319 
320     vsync_suspend_speed_eval();
321 }
322 
vsyncarch_get_metrics(double * cpu_percent,double * emulated_fps,int * is_warp_enabled)323 void vsyncarch_get_metrics(double *cpu_percent, double *emulated_fps, int *is_warp_enabled)
324 {
325     METRIC_LOCK();
326 
327     *cpu_percent = vsync_metric_cpu_percent;
328     *emulated_fps = vsync_metric_emulated_fps;
329     *is_warp_enabled = vsync_metric_warp_enabled;
330 
331     METRIC_UNLOCK();
332 }
333 
334 #define MEASUREMENT_SMOOTH_FACTOR 0.99
335 #define MEASUREMENT_FRAME_WINDOW  250
336 
update_performance_metrics(unsigned long frame_time)337 static void update_performance_metrics(unsigned long frame_time)
338 {
339     static int oldest_measurement_index;
340     static int next_measurement_index;
341     static int frames_counted;
342 
343     /* For knowing the relevant timespan */
344     static unsigned long frame_times[MEASUREMENT_FRAME_WINDOW];
345 
346     /* For measuring emulator cpu cycles per second */
347     static CLOCK clocks[MEASUREMENT_FRAME_WINDOW];
348 
349     /* how many seconds of wallclock time the measurement window covers */
350     double frame_timespan_seconds;
351 
352     /* how many emulated seconds of cpu time have been emulated */
353     double clock_delta_seconds;
354 
355     METRIC_LOCK();
356 
357     if (sync_reset) {
358         /*
359          * The emulator is just starting, or resuming from pause, or entering
360          * warp, or exiting warp. So we reset the fps calculations. We also
361          * throw away this initial measurement, because the emulator is just
362          * about to skip the timing sleep and immediately produce the next frame.
363          *
364          * TODO: Don't reset numbers on unpause, and account for the gap.
365          */
366         sync_reset = 0;
367 
368         frame_times[0] = frame_time;
369         clocks[0] = maincpu_clk;
370 
371         next_measurement_index = 1;
372 
373         if (warp_enabled) {
374             /* Don't bother with seed measurements when entering warp mode, just reset */
375             oldest_measurement_index = 0;
376             frames_counted = 1;
377         } else {
378             int i;
379             /*
380              * For normal speed changes, exiting warp etc, initialise with a full set
381              * of fake perfect measurements
382              */
383             for (i = 1; i < MEASUREMENT_FRAME_WINDOW; i++) {
384                 frame_times[i] = frame_time - ((MEASUREMENT_FRAME_WINDOW - i) * ticks_per_frame);
385                 clocks[i] = maincpu_clk - (CLOCK)((MEASUREMENT_FRAME_WINDOW - i) * cycles_per_sec / refresh_frequency);
386             }
387 
388             oldest_measurement_index = 1;
389             frames_counted = MEASUREMENT_FRAME_WINDOW;
390         }
391 
392         /* The final smoothing function requires that we initialise the public metrics */
393         if (timer_speed > 0) {
394             vsync_metric_emulated_fps = (double)timer_speed * refresh_frequency / 100.0;
395             vsync_metric_cpu_percent  = timer_speed;
396         } else {
397             vsync_metric_emulated_fps = (0.0 - timer_speed);
398             vsync_metric_cpu_percent  = (0.0 - timer_speed) / refresh_frequency * 100;
399         }
400 
401         /* printf("INIT frames_counted %d %.3f seconds - %0.3f%% cpu, %.3f fps\n", frames_counted, frame_timespan_seconds, vsync_metric_cpu_percent, vsync_metric_emulated_fps); fflush(stdout); */
402 
403         METRIC_UNLOCK();
404 
405         return;
406     }
407 
408     /* Capure this frame's measurements */
409     frame_times[next_measurement_index] = frame_time;
410     clocks[next_measurement_index] = maincpu_clk;
411 
412     if(frames_counted == MEASUREMENT_FRAME_WINDOW) {
413         if (++oldest_measurement_index == MEASUREMENT_FRAME_WINDOW) {
414             oldest_measurement_index = 0;
415         }
416     } else {
417         frames_counted++;
418     }
419 
420     /* Calculate our final metrics */
421     frame_timespan_seconds = (double)(frame_time - frame_times[oldest_measurement_index]) / tick_per_second();
422     clock_delta_seconds = (double)(maincpu_clk - clocks[oldest_measurement_index]) / cycles_per_sec;
423 
424     /* smooth and make public */
425     vsync_metric_cpu_percent  = (MEASUREMENT_SMOOTH_FACTOR * vsync_metric_cpu_percent)  + (1.0 - MEASUREMENT_SMOOTH_FACTOR) * (clock_delta_seconds / frame_timespan_seconds * 100.0);
426     vsync_metric_emulated_fps = (MEASUREMENT_SMOOTH_FACTOR * vsync_metric_emulated_fps) + (1.0 - MEASUREMENT_SMOOTH_FACTOR) * ((double)(frames_counted - 1) / frame_timespan_seconds);
427     vsync_metric_warp_enabled = warp_enabled;
428 
429     /* printf("frames_counted %d %.3f seconds - %0.3f%% cpu, %.3f fps\n", frames_counted, frame_timespan_seconds, vsync_metric_cpu_percent, vsync_metric_emulated_fps); fflush(stdout); */
430 
431     /* Get ready for next invoke */
432     if (++next_measurement_index == MEASUREMENT_FRAME_WINDOW) {
433         next_measurement_index = 0;
434     }
435 
436     METRIC_UNLOCK();
437 }
438 
vsync_do_end_of_line(void)439 void vsync_do_end_of_line(void)
440 {
441     const int microseconds_between_sync = 2 * 1000;
442 
443     unsigned long tick_between_sync = tick_per_second() * microseconds_between_sync / 1000000;
444     unsigned long tick_now;
445     unsigned long tick_delta;
446 
447     bool tick_based_sync_timing;
448 
449     CLOCK sync_clk_delta;
450     unsigned long sync_emulated_ticks;
451 
452     /*
453      * Ideally the vic chip draw alarm wouldn't be triggered
454      * during shutdown but here we are - apply workaround.
455      */
456 
457     if (archdep_is_exiting()) {
458         mainlock_yield_once();
459         return;
460     }
461 
462     /* deal with any accumulated sound immediately */
463     tick_based_sync_timing = sound_flush();
464 
465     tick_now = tick_after(last_sync_tick);
466 
467     /*
468      * If it's been a long time between scanlines, (such as when paused in
469      * debuggeretc) then reset speed eval and sync. Otherwise, the emulator
470      * will warp until it catches up, which is rarely good when this far out
471      * of sync. This means the emulator will run slower overall than hoped.
472      */
473 
474     if (!speed_eval_suspended && (tick_now - last_sync_tick) > ticks_per_frame * 5) {
475         if (last_sync_tick != 0) {
476             log_warning(LOG_DEFAULT, "sync is far too late, resetting sync");
477         }
478         vsync_suspend_speed_eval();
479     }
480 
481     if (speed_eval_suspended) {
482         last_sync_emulated_tick = tick_now;
483         last_sync_tick = tick_now;
484         last_sync_clk = maincpu_clk;
485         sync_target_tick = tick_now;
486 
487         speed_eval_suspended = 0;
488         sync_reset = 1;
489         return;
490     }
491 
492     /* how many host ticks since last sync. */
493     tick_delta = tick_now - last_sync_tick;
494 
495     /* is it time to consider keyboard, joystick ? */
496     if (tick_delta >= tick_between_sync) {
497 
498         /* deal with user input */
499         joystick();
500 
501         /* Do we need to slow down the emulation here or can we rely on the audio device? */
502         if (tick_based_sync_timing && !warp_enabled) {
503 
504             /* add the emulated clock cycles since last sync. */
505             sync_clk_delta = maincpu_clk - last_sync_clk;
506 
507             /* amount of host ticks that represents the emulated duration */
508             sync_emulated_ticks = (double)tick_per_second() * sync_clk_delta / emulated_clk_per_second;
509 
510             /*
511              * We sleep so that our host is blocked for long enough to catch up
512              * with how long the emulated machine would have taken.
513              */
514 
515             sync_target_tick += sync_emulated_ticks;
516 
517             /* Some tricky wrap around cases to deal with */
518             if (sync_target_tick - tick_now > 0 && sync_target_tick - tick_now < tick_per_second()) {
519                 tick_sleep(sync_target_tick - tick_now);
520             }
521         }
522 
523         last_sync_tick = tick_now;
524         last_sync_clk = maincpu_clk;
525     }
526 
527     /* Do we need to update the thread priority? */
528     if (update_thread_priority) {
529         update_thread_priority = 0;
530 
531 #if defined(MACOSX_SUPPORT)
532         vice_macos_set_vice_thread_priority(warp_enabled);
533 #elif defined(__linux__)
534         /* TODO: Linux thread prio stuff, need root or some 'capability' though */
535 #else
536         /* TODO: BSD thread prio stuff */
537 #endif
538     }
539 }
540 
541 /* This is called at the end of each screen frame. */
vsync_do_vsync(struct video_canvas_s * c,int been_skipped)542 int vsync_do_vsync(struct video_canvas_s *c, int been_skipped)
543 {
544     // static unsigned long next_frame_start = 0;
545     static int skipped_redraw_count = 0;
546     static unsigned long last_vsync;
547 
548     unsigned long now;
549     unsigned long network_hook_time = 0;
550     // long delay;
551     int skip_next_frame = 0;
552 
553     /*
554      * Ideally the vic chip draw alarm wouldn't be triggered
555      * during shutdown but here we are - apply workaround.
556      */
557 
558     if (archdep_is_exiting()) {
559         return 1;
560     }
561 
562     monitor_vsync_hook();
563 
564     /*
565      * process everything wich should be done before the synchronisation
566      * e.g. OS/2: exit the programm if trigger_shutdown set
567      */
568     vsyncarch_presync();
569 
570     /* Run vsync jobs. */
571     if (network_connected()) {
572         network_hook_time = tick_now();
573     }
574 
575     vsync_hook();
576 
577     if (network_connected()) {
578         /* TODO - re-eval if any of this network stuff makes sense */
579         network_hook_time = tick_delta(network_hook_time);
580 
581         if (network_hook_time > (unsigned long)ticks_per_frame) {
582             // next_frame_start += network_hook_time;
583             last_vsync += network_hook_time;
584         }
585     }
586 
587 #ifdef DEBUG
588     /* switch between recording and playback in history debug mode */
589     debug_check_autoplay_mode();
590 #endif
591 
592     now = tick_after(last_vsync);
593     update_performance_metrics(now);
594 
595     /*
596      * Limit rendering fps if we're in warp mode.
597      * It's ugly enough for dqh to weep but makes warp faster.
598      */
599 
600     if (warp_enabled) {
601         if (now < warp_next_render_tick) {
602             skip_next_frame = 1;
603             skipped_redraw_count++;
604         } else {
605             warp_next_render_tick += warp_render_tick_interval;
606             skipped_redraw_count = 0;
607         }
608     }
609 
610     vsyncarch_postsync();
611 
612 #ifdef VSYNC_DEBUG
613     log_debug("vsync: start:%lu  delay:%ld  sound-delay:%lf  end:%lu  next-frame:%lu  frame-ticks:%lu",
614                 now, delay, sound_delay * 1000000, tick_now(), next_frame_start, ticks_per_frame);
615 #endif
616 
617     execute_vsync_callbacks();
618 
619     kbdbuf_flush();
620 
621     last_vsync = now;
622 
623     return skip_next_frame;
624 }
625