1 /* frate.c -- Frame rate code. */
2
3 #include "config.h"
4
5 #include <signal.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #ifdef HAVE_SYS_TIME_H
9 # include <sys/time.h>
10 #endif
11 #ifdef HAVE_UNISTD_H
12 # include <unistd.h>
13 #endif
14
15 #include "defs.h"
16
17
18 #define FPS 30
19
20
21 /*****************************************************************************/
22 #if defined(linux)
23
24 /* ITIMER_REAL is used for the time base, in a "one-shot" mode. SIGALRM is
25 generated when it expires, but this signal is held blocked. sigsuspend()
26 waits for SIGALRM to go off, or lets it go immediately if it's already
27 pending. Then a new ITIMER_REAL is started. If for any reason the main
28 loop takes longer than the desired time, no catch-ups are attempted,
29 further loops just run normal times.
30
31 The time desired for each loop varies. A normal kernel runs the clock at
32 10 milliseconds, so to get 30 frames per second loops are run in the
33 pattern 30, 30, 40 milliseconds for an average 33.3ms. The pattern is
34 generated based on sysconf(_SC_CLK_TCK) and FPS, and it should work for
35 any values of those settings. Essentially, CLK_TCK ticks per second are
36 divided up into FPS many buckets, as evenly as possible.
37
38 Linux ITIMER_REAL does the traditional Unix timing thing which is to
39 round up the requested delay to get clock ticks, and add this to the
40 current tick. For example if "jiffies" tick is 12345 and an interval of
41 35ms is requested the interval is converted to 4 ticks and the alarm goes
42 off when jiffies tick 12349 is reached. This pays no attention to how
43 much of the current tick has elapsed when the request is made.
44
45 select(), poll(), usleep() and nanosleep() do it differently, they don't
46 start counting time until the end of the current tick (by adding one tick
47 to the requested time). See /usr/src/linux/kernel/sched.c
48 sys_nanosleep() where it adds "(t.tv_sec || t.tv_nsec)". In the example
49 the alarm would go off at jiffies tick 12350. As far as I can tell it's
50 impossible for those functions to do a delay that goes only to the next
51 tick boundary, and because that could well be wanted on a slowish system,
52 ITIMER_REAL is used instead.
53
54 An alternative to the code here, for x86 PCs at least, would be /dev/rtc,
55 which can be set to give high resolution timer interrupts that can be
56 counted and blocked on (with read()). See
57 /usr/src/linux/Documentation/rtc.txt.
58
59 */
60
61 static struct itimerval table[FPS];
62 static int upto = 0;
63 static sigset_t alrmonly;
64
65
sigalrm(int signum)66 static void sigalrm(int signum)
67 {
68 }
69
init_framerate(void)70 void init_framerate(void)
71 {
72 /* create table[] times */
73 {
74 long clktck, period, quot, rem, r, t;
75 int i;
76
77 clktck = sysconf (_SC_CLK_TCK);
78 if (clktck == -1)
79 {
80 perror ("sysconf _SC_CLK_TCK");
81 exit (1);
82 }
83 period = 1000000 / clktck;
84
85 quot = clktck / FPS;
86 rem = clktck % FPS;
87 r = 0;
88 for (i = 0; i < FPS; i++)
89 {
90 t = quot;
91 r += rem;
92 if (r >= FPS)
93 {
94 t++;
95 r -= FPS;
96 }
97
98 /* The actual tv_usec set up is just under "t" ticks because the
99 kernel will round up the requested value. */
100 table[i].it_value.tv_usec = t*period - period/2;
101
102 /* printf ("table[%d] = %ld\n", i, table[i].it_value.tv_usec); */
103 }
104 }
105
106 /* make SIGALRM blocked */
107 {
108 sigset_t set;
109 sigemptyset (&set);
110 sigaddset (&set, SIGALRM);
111 sigprocmask (SIG_BLOCK, &set, NULL);
112 }
113
114 /* install dummy SIGALRM handler */
115 {
116 struct sigaction act;
117 act.sa_handler = sigalrm;
118 sigemptyset (&act.sa_mask);
119 act.sa_flags = SA_RESTART;
120 sigaction (SIGALRM, &act, NULL);
121 }
122
123 /* sigset with only SIGALRM unmasked */
124 sigfillset (&alrmonly);
125 sigdelset (&alrmonly, SIGALRM);
126
127 /* start initial timer */
128 setitimer (ITIMER_REAL, &table[0], NULL);
129 }
130
do_framerate(void)131 void do_framerate(void)
132 {
133 /* wait for SIGALRM */
134 sigsuspend (&alrmonly);
135
136 /* new timer */
137 setitimer (ITIMER_REAL, &table[upto], NULL);
138 if (++upto >= FPS)
139 upto = 0;
140
141 /* Enable this code to print how long each loop is taking. Redirect
142 output to a file so results aren't affected by the time taken to draw
143 the screen. First print is bogus because "t" starts out zero. */
144 #if 0
145 {
146 static struct timeval t;
147 struct timeval n;
148 long d;
149 gettimeofday (&n, NULL);
150 d = 1000000*(n.tv_sec-t.tv_sec) + n.tv_usec-t.tv_usec;
151 t = n;
152
153 printf ("%ld\n", d);
154 /* if (d < 29000 || d > 41000) printf ("BAD %ld\n", d); */
155 }
156 #endif
157 }
158
159
160 /*****************************************************************************/
161 #else /* !defined(linux) */
162
163 /* Old code, using select(). */
164
165
166 static struct timeval otv;
167
init_framerate(void)168 void init_framerate(void)
169 {
170 gettimeofday(&otv, 0);
171 }
172
do_framerate(void)173 void do_framerate(void)
174 {
175 static struct timeval ut;
176 struct timeval tv;
177
178 gettimeofday(&tv, 0);
179 ut.tv_usec = UTIMER
180 - (1000000 * (tv.tv_sec - otv.tv_sec) + (tv.tv_usec - otv.tv_usec));
181
182 /* Checking u<2*UTIMER guards against a hang if the system time changes. */
183 if (ut.tv_usec > 0 && ut.tv_usec < 2*UTIMER)
184 (void) select (0, 0, 0, 0, &ut);
185
186 gettimeofday(&otv, 0);
187
188 /* Enable this code to print how long each loop is taking. Redirect
189 output to a file so results aren't affected by the time taken to draw
190 the screen. First print is bogus because "t" starts out zero. */
191 #if 0
192 {
193 static struct timeval t;
194 struct timeval n;
195 long d;
196 gettimeofday (&n, 0);
197 d = 1000000*(n.tv_sec-t.tv_sec) + n.tv_usec-t.tv_usec;
198 t = n;
199
200 printf ("%ld\n", d);
201 }
202 #endif
203 }
204
205 #endif
206