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