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