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