1 /** \file   vsyncarch.c
2  * \brief   End-of-frame handling for native GTK3 UI
3  *
4  * \note    This is altered and trimmed down to fit into the GTK3-native
5  *          world, but it's still heavily reliant on UNIX internals.
6  *
7  * \author  Dag Lem <resid@nimrod.no>
8  */
9 
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 #include "vice.h"
32 
33 #include "kbdbuf.h"
34 #include "ui.h"
35 #include "vsyncapi.h"
36 #include "videoarch.h"
37 
38 #ifdef HAS_JOYSTICK
39 #include "joy.h"
40 #endif
41 #ifdef WIN32_COMPILE
42 #include "joy-win32.h"
43 #endif
44 
45 #ifdef HAVE_NANOSLEEP
46 #include <time.h>
47 #else
48 #include <unistd.h>
49 #include <errno.h>
50 #endif
51 #include <sys/time.h>
52 
53 #ifdef MACOSX_SUPPORT
54 #include <mach/mach.h>
55 #include <mach/mach_time.h>
56 #endif
57 
58 /* for ui_display_speed() */
59 #include "uistatusbar.h"
60 
61 
62 /* hook to ui event dispatcher */
63 static void_hook_t ui_dispatch_hook;
64 static int pause_pending = 0;
65 
66 /* ------------------------------------------------------------------------- */
67 #ifdef HAVE_NANOSLEEP
68 #define TICKSPERSECOND  1000000000L  /* Nanoseconds resolution. */
69 #define TICKSPERMSEC    1000000L
70 #define TICKSPERUSEC    1000L
71 #define TICKSPERNSEC    1L
72 #else
73 #define TICKSPERSECOND  1000000L     /* Microseconds resolution. */
74 #define TICKSPERMSEC    1000L
75 #define TICKSPERUSEC    1L
76 #endif
77 
78 /* FIXME: this function should be a constant */
79 /* Number of timer units per second. */
vsyncarch_frequency(void)80 unsigned long vsyncarch_frequency(void)
81 {
82     return TICKSPERSECOND;
83 }
84 
85 /* Get time in timer units. */
vsyncarch_gettime(void)86 unsigned long vsyncarch_gettime(void)
87 {
88 /* FIXME: configure checks for clock_gettime need to be added */
89 #ifdef HAVE_NANOSLEEP
90  #ifdef MACOSX_SUPPORT
91     static uint64_t factor = 0;
92     uint64_t time = mach_absolute_time();
93     if (!factor) {
94         mach_timebase_info_data_t info;
95         kern_return_t ret = mach_timebase_info(&info);
96         factor = info.numer / info.denom;
97     }
98     return time * factor;
99  #else
100     struct timespec now;
101     clock_gettime(CLOCK_MONOTONIC, &now);
102     return (TICKSPERSECOND * now.tv_sec) + (TICKSPERNSEC * now.tv_nsec);
103  #endif
104 #else
105     /* this is really really bad, we should never use the wallclock
106        see: https://blog.habets.se/2010/09/gettimeofday-should-never-be-used-to-measure-time.html */
107     struct timeval now;
108     gettimeofday(&now, NULL);
109     return (TICKSPERSECOND * now.tv_sec) + (TICKSPERUSEC * now.tv_usec);
110 #endif
111 }
112 
vsyncarch_init(void)113 void vsyncarch_init(void)
114 {
115     vsync_set_event_dispatcher(ui_dispatch_events);
116 }
117 
118 /* Display speed (percentage) and frame rate (frames per second). */
119 /* TODO: Why does this exist? ui_* is more generic and more widely exposed! */
vsyncarch_display_speed(double speed,double frame_rate,int warp_enabled)120 void vsyncarch_display_speed(double speed, double frame_rate, int warp_enabled)
121 {
122     ui_display_speed((float)speed, (float)frame_rate, warp_enabled);
123 }
124 
125 /* for error measurement */
126 static unsigned long delay_error;
127 
128 /* Sleep a number of timer units. */
vsyncarch_sleep(unsigned long delay)129 void vsyncarch_sleep(unsigned long delay)
130 {
131 #ifdef HAVE_NANOSLEEP
132     struct timespec ts;
133 #endif
134     unsigned long thistime, timewait, targetdelay;
135 #if 0
136     /* HACK: to prevent any multitasking stuff getting in the way, we return
137              immediately on delays up to 0.1ms */
138     if (delay < (TICKSPERMSEC / 10)) {
139         return;
140     }
141 #endif
142     thistime = vsyncarch_gettime();
143 
144     /* compensate for delay inaccuracy */
145     targetdelay = (delay <= delay_error) ? delay : (delay - delay_error);
146 
147     /* repeatedly sleep until the requested delay is over. we do this so we get
148        a somewhat accurate delay even if the sleep function itself uses the
149        wall clock, which under certain circumstance may wait less than the
150        requested time */
151     for (;;) {
152         timewait = vsyncarch_gettime() - thistime;
153 
154         if (timewait >= targetdelay) {
155             delay_error = (delay_error * 3 + timewait - targetdelay + 1) / 4;
156             break;
157         }
158         timewait = targetdelay - timewait;
159 
160 #ifdef HAVE_NANOSLEEP
161         if (timewait < TICKSPERSECOND) {
162             ts.tv_sec = 0;
163             ts.tv_nsec = timewait;
164         } else {
165             ts.tv_sec = timewait / TICKSPERSECOND;
166             ts.tv_nsec = (timewait % TICKSPERSECOND);
167         }
168         nanosleep(&ts, NULL);
169 #else
170         if (usleep(timewait) == -EINVAL) usleep(999999);
171 #endif
172     }
173 }
174 
vsyncarch_presync(void)175 void vsyncarch_presync(void)
176 {
177     ui_update_lightpen();
178     kbdbuf_flush();
179 #ifdef HAS_JOYSTICK
180     joystick();
181 #endif
182 #ifdef WIN32_COMPILE
183     joystick_update();
184 #endif
185 
186 }
187 
vsync_set_event_dispatcher(void_hook_t hook)188 void_hook_t vsync_set_event_dispatcher(void_hook_t hook)
189 {
190     void_hook_t t = ui_dispatch_hook;
191 
192     ui_dispatch_hook = hook;
193     return t;
194 }
195 
196 /* FIXME: ui_pause_emulation is not implemented in the OSX port */
vsyncarch_postsync(void)197 void vsyncarch_postsync(void)
198 {
199     (*ui_dispatch_hook)();
200 
201     /* this function is called once a frame, so this
202        handles single frame advance */
203     if (pause_pending) {
204         ui_pause_emulation(1);
205         pause_pending = 0;
206     }
207 }
208 
vsyncarch_advance_frame(void)209 void vsyncarch_advance_frame(void)
210 {
211     ui_pause_emulation(0);
212     pause_pending = 1;
213 }
214