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