1 #include "fps_counter.h"
2 
3 #include <SDL2/SDL_assert.h>
4 #include <SDL2/SDL_timer.h>
5 
6 #include "lock_util.h"
7 #include "log.h"
8 
9 #define FPS_COUNTER_INTERVAL_MS 1000
10 
11 bool
fps_counter_init(struct fps_counter * counter)12 fps_counter_init(struct fps_counter *counter) {
13     counter->mutex = SDL_CreateMutex();
14     if (!counter->mutex) {
15         return false;
16     }
17 
18     counter->state_cond = SDL_CreateCond();
19     if (!counter->state_cond) {
20         SDL_DestroyMutex(counter->mutex);
21         return false;
22     }
23 
24     counter->thread = NULL;
25     SDL_AtomicSet(&counter->started, 0);
26     // no need to initialize the other fields, they are unused until started
27 
28     return true;
29 }
30 
31 void
fps_counter_destroy(struct fps_counter * counter)32 fps_counter_destroy(struct fps_counter *counter) {
33     SDL_DestroyCond(counter->state_cond);
34     SDL_DestroyMutex(counter->mutex);
35 }
36 
37 // must be called with mutex locked
38 static void
display_fps(struct fps_counter * counter)39 display_fps(struct fps_counter *counter) {
40     unsigned rendered_per_second =
41         counter->nr_rendered * 1000 / FPS_COUNTER_INTERVAL_MS;
42     if (counter->nr_skipped) {
43         LOGI("%u fps (+%u frames skipped)", rendered_per_second,
44                                             counter->nr_skipped);
45     } else {
46         LOGI("%u fps", rendered_per_second);
47     }
48 }
49 
50 // must be called with mutex locked
51 static void
check_interval_expired(struct fps_counter * counter,uint32_t now)52 check_interval_expired(struct fps_counter *counter, uint32_t now) {
53     if (now < counter->next_timestamp) {
54         return;
55     }
56 
57     display_fps(counter);
58     counter->nr_rendered = 0;
59     counter->nr_skipped = 0;
60     // add a multiple of the interval
61     uint32_t elapsed_slices =
62         (now - counter->next_timestamp) / FPS_COUNTER_INTERVAL_MS + 1;
63     counter->next_timestamp += FPS_COUNTER_INTERVAL_MS * elapsed_slices;
64 }
65 
66 static int
run_fps_counter(void * data)67 run_fps_counter(void *data) {
68     struct fps_counter *counter = data;
69 
70     mutex_lock(counter->mutex);
71     while (!counter->interrupted) {
72         while (!counter->interrupted && !SDL_AtomicGet(&counter->started)) {
73             cond_wait(counter->state_cond, counter->mutex);
74         }
75         while (!counter->interrupted && SDL_AtomicGet(&counter->started)) {
76             uint32_t now = SDL_GetTicks();
77             check_interval_expired(counter, now);
78 
79             SDL_assert(counter->next_timestamp > now);
80             uint32_t remaining = counter->next_timestamp - now;
81 
82             // ignore the reason (timeout or signaled), we just loop anyway
83             cond_wait_timeout(counter->state_cond, counter->mutex, remaining);
84         }
85     }
86     mutex_unlock(counter->mutex);
87     return 0;
88 }
89 
90 bool
fps_counter_start(struct fps_counter * counter)91 fps_counter_start(struct fps_counter *counter) {
92     mutex_lock(counter->mutex);
93     counter->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS;
94     counter->nr_rendered = 0;
95     counter->nr_skipped = 0;
96     mutex_unlock(counter->mutex);
97 
98     SDL_AtomicSet(&counter->started, 1);
99     cond_signal(counter->state_cond);
100 
101     // counter->thread is always accessed from the same thread, no need to lock
102     if (!counter->thread) {
103         counter->thread =
104             SDL_CreateThread(run_fps_counter, "fps counter", counter);
105         if (!counter->thread) {
106             LOGE("Could not start FPS counter thread");
107             return false;
108         }
109     }
110 
111     return true;
112 }
113 
114 void
fps_counter_stop(struct fps_counter * counter)115 fps_counter_stop(struct fps_counter *counter) {
116     SDL_AtomicSet(&counter->started, 0);
117     cond_signal(counter->state_cond);
118 }
119 
120 bool
fps_counter_is_started(struct fps_counter * counter)121 fps_counter_is_started(struct fps_counter *counter) {
122     return SDL_AtomicGet(&counter->started);
123 }
124 
125 void
fps_counter_interrupt(struct fps_counter * counter)126 fps_counter_interrupt(struct fps_counter *counter) {
127     if (!counter->thread) {
128         return;
129     }
130 
131     mutex_lock(counter->mutex);
132     counter->interrupted = true;
133     mutex_unlock(counter->mutex);
134     // wake up blocking wait
135     cond_signal(counter->state_cond);
136 }
137 
138 void
fps_counter_join(struct fps_counter * counter)139 fps_counter_join(struct fps_counter *counter) {
140     if (counter->thread) {
141         SDL_WaitThread(counter->thread, NULL);
142     }
143 }
144 
145 void
fps_counter_add_rendered_frame(struct fps_counter * counter)146 fps_counter_add_rendered_frame(struct fps_counter *counter) {
147     if (!SDL_AtomicGet(&counter->started)) {
148         return;
149     }
150 
151     mutex_lock(counter->mutex);
152     uint32_t now = SDL_GetTicks();
153     check_interval_expired(counter, now);
154     ++counter->nr_rendered;
155     mutex_unlock(counter->mutex);
156 }
157 
158 void
fps_counter_add_skipped_frame(struct fps_counter * counter)159 fps_counter_add_skipped_frame(struct fps_counter *counter) {
160     if (!SDL_AtomicGet(&counter->started)) {
161         return;
162     }
163 
164     mutex_lock(counter->mutex);
165     uint32_t now = SDL_GetTicks();
166     check_interval_expired(counter, now);
167     ++counter->nr_skipped;
168     mutex_unlock(counter->mutex);
169 }
170