1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4
5 #define FSE_INTERNAL_API
6 #include <fs/emu.h>
7 #include <fs/emu/hacks.h>
8 #include <fs/emu/video.h>
9 #include <stdio.h>
10 #include <string.h>
11 #include <fs/glib.h>
12 #include <fs/ml.h>
13 #include <fs/thread.h>
14 #include <math.h>
15
16 #ifdef USE_OPENGL
17 #include <fs/ml/opengl.h>
18 #endif
19
20 #include "audio.h"
21 #include "emu_lua.h"
22 #include "font.h"
23 #include "libfsemu.h"
24 #include "menu.h"
25 #include "texture.h"
26 #include "theme.h"
27 #include "util.h"
28 #include "video.h"
29 #include "video_buffer.h"
30 #include "xml_shader.h"
31
32 static int g_fs_emu_aspect_correction = 0;
33 int g_fs_emu_disable_aspect_correction = 0;
34
35 int g_fs_emu_video_format = 0;
36 int g_fs_emu_video_bpp = 0;
37 int g_fs_emu_texture_format = 0;
38
39 int g_fs_emu_scanlines = 0;
40 int g_fs_emu_scanlines_dark = 255 * 0.10;
41 int g_fs_emu_scanlines_light = 255 * 0.05;
42
43 fs_emu_zoom_function g_toogle_zoom_function = NULL;
44
45 int g_fs_emu_video_debug = 0;
46 int g_fs_emu_video_fullscreen = 0;
47 char *g_fs_emu_video_fullscreen_mode_string = NULL;
48 int g_fs_emu_video_fullscreen_mode = -1;
49 int g_fs_emu_video_crop_mode = 1;
50 int g_fs_emu_screenshot = 0;
51
52 int g_fs_emu_benchmarking = 0;
53 int64_t g_fs_emu_benchmark_start_time = 0;
54 int g_fs_emu_total_emu_frames = 0;
55 int g_fs_emu_total_sys_frames = 0;
56
57 #define FRAME_TIME_LIST_COUNT 256
58 // time is specified in microseconds
59 fs_emu_stat_queue g_fs_emu_emu_frame_times = {};
60 fs_emu_stat_queue g_fs_emu_emu2_frame_times = {};
61 fs_emu_stat_queue g_fs_emu_sys_frame_times = {};
62 int g_fs_emu_lost_frames = 0;
63 int g_fs_emu_repeated_frames = 0;
64 int g_fs_emu_lost_vblanks = 0;
65 int g_fs_emu_audio_buffer_underruns = 0;
66 int64_t g_fs_emu_lost_frame_time = 0;
67 int64_t g_fs_emu_repeated_frame_time = 0;
68 int64_t g_fs_emu_video_mode_change_time = 0;
69
70 // this is used to make sure that changes to menu etc is not done while
71 // rendering it... handling of input events for menu actions must be done
72 // when holding this lock
73 static fs_mutex *g_video_render_mutex;
74
75 int g_fs_emu_video_sync_to_vblank = 0;
76 int g_fs_emu_video_allow_full_sync = 0;
77 int g_fs_emu_video_frame_rate_host = 0;
78
79 // this is the target frame rate for the video (emulator output)
80 static double g_video_frame_rate = 0;
81
82 // this is the aspect ratio of the video frame from the emulator, defaults
83 // to 1.0 (1:1 pixels)
84 static double g_video_aspect_ratio = 1.0;
85
86 //static int g_emu_video_struct_seq_no = 0;
87 static GQueue *g_emu_video_struct_queue = NULL;
88 static fs_mutex *g_emu_video_struct_mutex = NULL;
89
fs_emu_set_toggle_zoom_function(fs_emu_zoom_function function)90 void fs_emu_set_toggle_zoom_function(fs_emu_zoom_function function) {
91 g_toogle_zoom_function = function;
92 }
93
fs_emu_toggle_zoom(int flags)94 void fs_emu_toggle_zoom(int flags) {
95 if (g_toogle_zoom_function) {
96 g_toogle_zoom_function(flags);
97 }
98 }
99
fs_emu_set_pixel_aspect_ratio(double ratio)100 void fs_emu_set_pixel_aspect_ratio(double ratio) {
101 g_video_aspect_ratio = ratio;
102 }
103
fs_emu_get_video_frame_rate(void)104 double fs_emu_get_video_frame_rate(void)
105 {
106 return g_video_frame_rate;
107 }
108
fs_emu_set_video_frame_rate(double frame_rate)109 void fs_emu_set_video_frame_rate(double frame_rate)
110 {
111 static double last_frame_rate = 0;
112 static int last_frame_rate_host = 0;
113 if (frame_rate == last_frame_rate
114 && last_frame_rate_host == g_fs_emu_video_frame_rate_host) {
115 return;
116 }
117 last_frame_rate = frame_rate;
118 last_frame_rate_host = g_fs_emu_video_frame_rate_host;
119 int frame_rate_i = round(frame_rate);
120 fs_log("[VIDEO] fs_emu_set_video_frame_rate: %0.2f (%d)\n",
121 frame_rate, frame_rate_i);
122 g_video_frame_rate = frame_rate;
123
124 fs_log("[DISPLAY] Sync: g_fs_emu_video_sync_to_vblank = %d\n",
125 g_fs_emu_video_sync_to_vblank);
126 if (g_fs_emu_video_sync_to_vblank) {
127 fs_log("[DISPLAY] Sync: g_fs_emu_video_allow_full_sync = %d\n",
128 g_fs_emu_video_allow_full_sync);
129 if (g_fs_emu_video_allow_full_sync) {
130 if (frame_rate && (frame_rate_i == g_fs_emu_video_frame_rate_host
131 || frame_rate_i == g_fs_emu_video_frame_rate_host + 1)) {
132 fs_log("[DISPLAY] Sync: Frame rate (%0.2f) close enough to "
133 "screen refresh (%d)\n",
134 frame_rate, g_fs_emu_video_frame_rate_host);
135 fs_ml_video_sync_enable(1);
136 } else {
137 fs_log("[DISPLAY] Sync: Frame rate (%0.2f) does not equal "
138 "screen refresh (%d)\n",
139 frame_rate, g_fs_emu_video_frame_rate_host);
140 fs_ml_video_sync_enable(0);
141 }
142 }
143 }
144
145 static int last_frame_wait = 0;
146 if (fs_emu_frame_time) {
147 fs_emu_frame_wait = (1000 / frame_rate) - fs_emu_frame_time;
148 if (fs_emu_frame_wait != last_frame_wait) {
149 fs_log("[VIDEO] Frame wait is now %d ms\n", fs_emu_frame_wait);
150 last_frame_wait = fs_emu_frame_wait;
151 }
152 }
153 }
154
initialize()155 static void initialize()
156 {
157 static int initialized = 0;
158 if (initialized == 1) {
159 return;
160 }
161 initialized = 1;
162 fs_emu_stat_queue_init(&g_fs_emu_emu_frame_times, FRAME_TIME_LIST_COUNT);
163 fs_emu_stat_queue_init(&g_fs_emu_emu2_frame_times, FRAME_TIME_LIST_COUNT);
164 fs_emu_stat_queue_init(&g_fs_emu_sys_frame_times, FRAME_TIME_LIST_COUNT);
165 }
166
fs_emu_video_get_aspect_correction()167 int fs_emu_video_get_aspect_correction() {
168 return g_fs_emu_aspect_correction && !g_fs_emu_disable_aspect_correction;
169 }
170
fs_emu_video_set_aspect_correction(int correct)171 void fs_emu_video_set_aspect_correction(int correct) {
172 g_fs_emu_aspect_correction = correct;
173 }
174
fs_emu_video_render_mutex_lock()175 void fs_emu_video_render_mutex_lock() {
176 if (!g_video_render_mutex) {
177 return;
178 }
179 fs_mutex_lock(g_video_render_mutex);
180 }
181
fs_emu_video_render_mutex_unlock()182 void fs_emu_video_render_mutex_unlock() {
183 if (!g_video_render_mutex) {
184 return;
185 }
186 fs_mutex_unlock(g_video_render_mutex);
187 }
188
fse_init_video_2(void)189 void fse_init_video_2(void)
190 {
191 fs_log("[FSE] fse_init_video_2\n");
192 fse_init_video_options();
193
194 g_video_render_mutex = fs_mutex_create();
195 g_emu_video_struct_queue = g_queue_new();
196 g_emu_video_struct_mutex = fs_mutex_create();
197 }
198
fs_emu_get_video_format()199 int fs_emu_get_video_format()
200 {
201 return g_fs_emu_video_format;
202 }
203
fs_emu_get_texture_format()204 int fs_emu_get_texture_format()
205 {
206 return g_fs_emu_texture_format;
207 }
208
fse_init_video_opengl()209 void fse_init_video_opengl() {
210 fs_log("fse_init_video_opengl\n");
211 fs_emu_initialize_opengl();
212 initialize();
213 fs_emu_menu_init_opengl();
214 #ifdef WITH_XML_SHADER
215 fs_emu_xml_shader_init();
216 #endif
217 #ifdef WITH_LUA
218 fs_emu_lua_run_handler("on_fs_emu_init_video");
219 #endif
220 }
221
fs_emu_toggle_fullscreen(void)222 void fs_emu_toggle_fullscreen(void)
223 {
224 fs_ml_toggle_fullscreen();
225 }
226
227 static int64_t g_frame_time_last = 0;
228 static int64_t g_frame_time_first = 0;
229
fs_emu_update_video_stats_1()230 void fs_emu_update_video_stats_1() {
231 int t = fs_emu_monotonic_time();
232 if (g_frame_time_first == 0) {
233 return;
234 }
235 int time_ms = (t - g_frame_time_first) / 1000;
236
237 int dt = (int) (time_ms - g_frame_time_last);
238 // more than 5 seconds => do not record entry (abnormality)
239 fs_emu_stat_queue_add_entry(&g_fs_emu_emu2_frame_times, dt, 5 * 1000);
240 }
241
fs_emu_update_video_stats_2()242 void fs_emu_update_video_stats_2() {
243 int t = fs_emu_monotonic_time();
244 if (g_frame_time_first == 0) {
245 g_frame_time_first = t;
246 }
247 int time_ms = (t - g_frame_time_first) / 1000;
248 fs_emu_audio_video_sync(time_ms);
249
250 int dt = (int) (time_ms - g_frame_time_last);
251 // more than 5 seconds => do not record entry (abnormality)
252 fs_emu_stat_queue_add_entry(&g_fs_emu_emu_frame_times, dt, 5 * 1000);
253 g_frame_time_last = time_ms;
254 }
255
update_video_stats_system_video()256 static void update_video_stats_system_video()
257 {
258 if (g_fs_emu_benchmark_start_time > 0) {
259 g_fs_emu_total_sys_frames++;
260 }
261
262 static int64_t frame_time_first = 0;
263 static int64_t frame_time_last = 0;
264 int t = fs_emu_monotonic_time();
265 if (frame_time_first == 0) {
266 frame_time_first = t;
267 }
268 int time_ms = (t - frame_time_first) / 1000;
269 int dt = (int) (time_ms - frame_time_last);
270 // more than 5 seconds => do not record entry (abnormality)
271 fs_emu_stat_queue_add_entry(&g_fs_emu_sys_frame_times, dt, 5 * 1000);
272 frame_time_last = time_ms;
273 double refresh_rate = fs_ml_get_refresh_rate();
274 // check if we have missed a vblank internal, but only after 2 seconds
275 // after first render
276 if (time_ms > 2000 && refresh_rate > 0) {
277 if (dt > 1.5 * 1000.0 / refresh_rate) {
278 g_fs_emu_lost_vblanks++;
279 }
280 }
281 }
282
fs_emu_get_average_emu_fps()283 double fs_emu_get_average_emu_fps() {
284 return 1000.0 / (((double) g_fs_emu_emu_frame_times.total) /
285 ((double) g_fs_emu_emu_frame_times.count));
286 }
287
fs_emu_get_average_sys_fps()288 double fs_emu_get_average_sys_fps() {
289 return 1000.0 / (((double) g_fs_emu_sys_frame_times.total) /
290 ((double) g_fs_emu_sys_frame_times.count));
291 }
292
293
294 int g_fs_emu_audio_enabled;
295
update_leds(int64_t t)296 static void update_leds(int64_t t) {
297 if (g_fs_emu_video_mode_change_time == 0) {
298 // we use this variable to ignore sync warnings for a short while
299 // after the emulation has started and/or video mode changes, since
300 // it will be temporarily "unstable" then. (normal)
301 g_fs_emu_video_mode_change_time = t;
302 }
303
304 int vsync_led_state = 0;
305 int fps_led_state = 0;
306 int audio_led_state = 0;
307
308 double diff;
309 int ignore_lossed_frames = 0;
310 int ignore_repeated_frames = 0;
311 if (fs_ml_get_vblank_sync()) {
312 if (g_fs_emu_video_frame_rate_host == 0) {
313 // ?
314 }
315 else if (g_fs_emu_video_frame_rate_host == g_video_frame_rate) {
316 // should ideally not lose / get repeated frames
317 }
318 else if (g_fs_emu_video_frame_rate_host > g_video_frame_rate) {
319 ignore_repeated_frames = 1;
320 }
321 else if (g_fs_emu_video_frame_rate_host < g_video_frame_rate) {
322 ignore_lossed_frames = 1;
323 }
324 diff = g_fs_emu_video_frame_rate_host - fs_emu_get_average_sys_fps();
325 }
326 else {
327 diff = g_video_frame_rate - fs_emu_get_average_sys_fps();
328 }
329
330 if (g_fs_emu_video_frame_rate_host == 0) {
331 // unknown host frame rate
332 diff = 0;
333 }
334 if (diff < 0) {
335 diff = diff * -1;
336 }
337
338 if (diff > 0.2) {
339 vsync_led_state = 3;
340 }
341 else if (fs_ml_get_vblank_sync()) {
342 if (fs_ml_get_video_sync()) {
343 vsync_led_state = 1;
344 }
345 else {
346 vsync_led_state = 2;
347 }
348 }
349 else {
350 // leave at 0
351 }
352
353 diff = g_video_frame_rate - fs_emu_get_average_emu_fps();
354 if (diff < 0) {
355 diff = diff * -1;
356 }
357
358 if (diff > 0.1) {
359 fps_led_state = 3;
360 }
361 else if (!ignore_lossed_frames &&
362 t - g_fs_emu_lost_frame_time < 100000) {
363 fps_led_state = 3;
364 }
365 else if (!ignore_repeated_frames &&
366 t - g_fs_emu_repeated_frame_time < 100000) {
367 fps_led_state = 3;
368 }
369 else if (g_video_frame_rate == 60) {
370 fps_led_state = 2;
371 }
372 else {
373 fps_led_state = 1;
374 }
375
376 if (t - g_fs_emu_audio_buffer_underrun_time < 100000) {
377 audio_led_state = 3;
378 }
379 else {
380 audio_led_state = g_fs_emu_audio_stream_playing[0];
381 }
382
383 int64_t time_since_change = t - g_fs_emu_video_mode_change_time;
384 if (time_since_change < 6000000) { // 6 seconds
385 //int state = ((t - g_fs_emu_video_mode_change_time) / 250000) % 2;
386 //vsync_led_state = state ? vsync_led_state : 0;
387 //fps_led_state = state ? fps_led_state : 0;
388 //audio_led_state = state;
389 fps_led_state = 0;
390 }
391 if (time_since_change < 5000000) {
392 vsync_led_state = 0;
393 }
394 if (time_since_change < 2000000) {
395 audio_led_state = 0;
396 }
397
398 fs_emu_set_overlay_state(FS_EMU_VSYNC_LED_OVERLAY, vsync_led_state);
399 fs_emu_set_overlay_state(FS_EMU_FPS_LED_OVERLAY, fps_led_state);
400 fs_emu_set_overlay_state(FS_EMU_AUDIO_LED_OVERLAY, audio_led_state);
401
402 // adding 0.1 so 49.9 is rounded up to 50
403 int emu_fps = fs_emu_get_average_emu_fps() + 0.1;
404 int digit;
405 digit = emu_fps / 10;
406 if (digit == 0) digit = 10;
407 fs_emu_set_overlay_state(FS_EMU_FPS_D1_OVERLAY, digit);
408 digit = emu_fps % 10;
409 if (digit == 0) digit = 10;
410 fs_emu_set_overlay_state(FS_EMU_FPS_D0_OVERLAY, digit);
411 }
412
fs_emu_video_after_update()413 void fs_emu_video_after_update() {
414 fs_emu_video_buffer_unlock();
415 int64_t t = fs_emu_monotonic_time();
416
417 if (fs_emu_cursor_is_visible_to() > 0) {
418 if (fs_emu_cursor_is_visible_to() < fs_emu_monotonic_time()) {
419 //fs_log("%lld\n", fs_emu_monotonic_time());
420 fs_emu_show_cursor(0);
421 }
422 }
423
424 update_leds(t);
425
426 update_video_stats_system_video();
427
428 if (g_fs_emu_benchmark_start_time) {
429 static int64_t last_report = 0;
430 if (t - last_report > 5000000) {
431 double ttime = ((t - g_fs_emu_benchmark_start_time) / 1000000.0);
432 double sys_fps = g_fs_emu_total_sys_frames / ttime;
433 double emu_fps = g_fs_emu_total_emu_frames / ttime;
434 //fs_log("average fps sys: %0.1f emu: %0.1f\n", sys_fps, emu_fps);
435 printf("average fps sys: %0.1f emu: %0.1f\n", sys_fps, emu_fps);
436 last_report = t;
437 }
438 }
439 }
440