1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4 
5 // FIXME: make libfsml independent of libfsmeu
6 #include "../emu/util.h"
7 #include "../emu/video.h"
8 
9 #include <stdlib.h>
10 #include <string.h>
11 #include <stdio.h>
12 #include <stddef.h>
13 
14 #ifdef USE_SDL2
15 #define USE_SDL
16 #endif
17 
18 #ifdef USE_SDL
19 #include <SDL.h>
20 #endif
21 
22 #include <fs/conf.h>
23 #include <fs/i18n.h>
24 #include <fs/image.h>
25 
26 #ifdef USE_OPENGL
27 #include <fs/ml/opengl.h>
28 #endif
29 
30 #include <fs/ml.h>
31 //#include "fs/emu.h"
32 #include "ml_internal.h"
33 
34 #include <fs/thread.h>
35 
36 #if defined(WITH_GLEW)
37 #elif defined(WITH_GLAD)
38 #else
39 static FS_PFNGLWAITSYNCPROC _glWaitSync;
40 #define glWaitSync _glWaitSync
41 static FS_PFNGLCLIENTWAITSYNCPROC _glClientWaitSync;
42 #define glClientWaitSync _glClientWaitSync
43 static FS_PFNGLFENCESYNCPROC _glFenceSync;
44 #define glFenceSync _glFenceSync
45 static FS_PFNGLGENFENCESNVPROC _glGenFencesNV;
46 #define glGenFencesNV _glGenFencesNV
47 static FS_PFNGLSETFENCENVPROC _glSetFenceNV;
48 #define glSetFenceNV _glSetFenceNV
49 static FS_PFNGLTESTFENCENVPROC _glTestFenceNV;
50 #define glTestFenceNV _glTestFenceNV
51 static FS_PFNGLGENFENCESAPPLEPROC _glGenFencesAPPLE;
52 #define glGenFencesAPPLE _glGenFencesAPPLE
53 static FS_PFNGLSETFENCEAPPLEPROC _glSetFenceAPPLE;
54 #define glSetFenceAPPLE _glSetFenceAPPLE
55 static FS_PFNGLTESTFENCEAPPLEPROC _glTestFenceAPPLE;
56 #define glTestFenceAPPLE _glTestFenceAPPLE
57 #endif
58 
59 #if 0
60 #define GL_OBJECT_TYPE                 0x9112
61 #define GL_SYNC_CONDITION              0x9113
62 #define GL_SYNC_STATUS                 0x9114
63 #define GL_SYNC_FLAGS                  0x9115
64 #define GL_SYNC_GPU_COMMANDS_COMPLETE  0x9117
65 #define GL_ALREADY_SIGNALED            0x911A
66 #define GL_TIMEOUT_EXPIRED             0x911B
67 #define GL_CONDITION_SATISFIED         0x911C
68 #define GL_WAIT_FAILED                 0x911D
69 #define GL_SYNC_FLUSH_COMMANDS_BIT     0x00000001
70 #define GL_UNSIGNALED                  0x9118
71 #define GL_SIGNALED                    0x9119
72 #define TIMEOUT_IGNORED                0xFFFFFFFFFFFFFFFFull
73 #endif
74 
75 #ifdef USE_GLES
76 
77 #else
78 static GLuint g_fence;
79 static GLsync g_sync;
80 #endif
81 
82 #define FENCE_SET 1
83 #define FENCE_WAIT 2
84 
85 #define SYNC_SWAP 1
86 #define SYNC_SWAP_FINISH 2
87 #define SYNC_FINISH_SWAP_FINISH 3
88 #define SYNC_SLEEP_SWAP_FINISH 4
89 #define SYNC_FINISH_SLEEP_SWAP_FINISH 5
90 #define SYNC_SWAP_FENCE_START 6
91 #define SYNC_SWAP_FENCE 7
92 #define SYNC_SWAP_SLEEP_FENCE 8
93 
94 static int g_sync_method = 0;
95 
96 static int g_vblank_count = 0;
97 static int64_t g_measured_vblank_time = 0;
98 static int64_t g_adjusted_vblank_time = 0;
99 static int64_t g_estimated_next_vblank_time = 0;
100 
101 // FIXME
102 static int g_estimated_upload_render_duration = 5000;
103 
104 static fs_condition *g_frame_available_cond = NULL;
105 static fs_mutex *g_frame_available_mutex = NULL;
106 
107 static fs_condition *g_buffer_swap_cond = NULL;
108 static fs_mutex *g_buffer_swap_mutex = NULL;
109 
110 static volatile int g_start_new_frame = 0;
111 static fs_condition *g_start_new_frame_cond = NULL;
112 static fs_mutex *g_start_new_frame_mutex = NULL;
113 
114 static int g_available_frame = -1;
115 static int g_uploaded_frame = -1;
116 static int g_rendered_frame = -1;
117 
118 static int g_has_apple_fence = 0;
119 static int g_has_nv_fence = 0;
120 static int g_has_arb_sync = 0;
121 
122 #define SYNC_FLAG_GLFINISH 1
123 #define SYNC_FLAG_SLEEP 2
124 #define SYNC_FLAG_FENCE 4
125 
126 static int64_t g_sleep_until_vsync_last_time = 0;
127 
128 #define VBLANK_COUNT 100
129 //static fs_emu_stat_queue g_measured_vblank_times;
130 static int64_t g_measured_vblank_times[VBLANK_COUNT] = {};
131 static int g_vblank_index = 0;
132 
133 static fs_mutex *g_vblank_mutex = NULL;
134 
135 static int64_t g_epoch = 0;
136 
137 #define CHECK_GL_ERROR_MSG(msg)
138 
fs_ml_frame_update_begin(int frame)139 void fs_ml_frame_update_begin(int frame)
140 {
141     if (g_fs_ml_video_sync) {
142 
143     } else if (g_fs_ml_vblank_sync) {
144         /* Emulation running independently on the video renderer. */
145     } else if (g_fs_ml_benchmarking) {
146         /* Run as fast as possible. */
147     } else {
148         /* Video renderer is waiting for a new frame -signal that a new
149          * frame is ready. */
150         //fs_condition_signal(g_video_cond);
151     }
152 }
153 
fs_ml_frame_update_end(int frame)154 void fs_ml_frame_update_end(int frame)
155 {
156 
157     //printf("%d\n", frame);
158 
159     // in timed mode only (non-vsync), the video renderer is waiting for
160     // a new frame signal
161     fs_mutex_lock(g_frame_available_mutex);
162     g_available_frame = frame;
163     fs_condition_signal(g_frame_available_cond);
164     fs_mutex_unlock(g_frame_available_mutex);
165 
166     if (g_fs_ml_video_sync) {
167         fs_mutex_lock(g_start_new_frame_mutex);
168         while (!g_start_new_frame) {
169             fs_condition_wait (g_start_new_frame_cond,
170                     g_start_new_frame_mutex);
171         }
172         g_start_new_frame = 0;
173         fs_mutex_unlock(g_start_new_frame_mutex);
174     } else if (g_fs_ml_vblank_sync) {
175         // emulation running independently on the video renderer
176     } else if (g_fs_ml_benchmarking) {
177         // run as fast as possible
178     } else {
179         // video renderer is waiting for a new frame -signal that a new
180         // frame is ready
181         //fs_condition_signal(g_video_cond);
182     }
183 }
184 
save_screenshot_of_opengl_framebuffer(const char * path)185 static void save_screenshot_of_opengl_framebuffer(const char *path)
186 {
187 #if 0
188     static int count = 0;
189     count += 1;
190 
191     time_t t = time(NULL);
192 #ifdef WINDOWS
193     struct tm *tm_p = localtime(&t);
194 #else
195     struct tm tm_struct;
196     struct tm *tm_p = &tm_struct;
197     localtime_r(&t, tm_p);
198 #endif
199     char strbuf[20];
200     strftime(strbuf, 20, "%Y-%m-%d-%H-%M", tm_p);
201     char *name = g_strdup_printf("%s-%s-%03d.png",
202             g_fs_ml_video_screenshots_prefix,
203             strbuf, g_fs_ml_video_screenshot);
204     char *path = g_build_filename(g_fs_ml_video_screenshots_dir, name, NULL);
205 #endif
206     fs_log("writing screenshot to %s\n", path);
207 
208     int w = fs_ml_video_width();
209     int h = fs_ml_video_height();
210     fs_log("reading opengl frame buffer (%d x %d)\n", w, h);
211     void *out_data = g_malloc(w * h * 4);
212 
213     // when using GL_RGB, remeber to temporarily set GL_UNPACK_ALIGNMENT so
214     // all rows will be contiguous (the OpenGL default is to align rows on
215     // 4-byte boundaries
216     //GLint unpack_alignment;
217     //glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpack_alignment);
218     //glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
219     glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, out_data);
220     //glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_alignment);
221 
222     // flip image vertically
223     int stride = w * 4;
224     void *tmp = g_malloc(stride);
225     void *line1 = out_data;
226     void *line2 = out_data + stride * (h - 1);
227     for (int i = 0; i < h / 2; i++) {
228         memcpy(tmp, line1, stride);
229         memcpy(line1, line2, stride);
230         memcpy(line2, tmp, stride);
231         line1 += stride;
232         line2 -= stride;
233     }
234     g_free(tmp);
235 
236     int result = fs_image_save_data(path, out_data, w, h, 4);
237     if (result) {
238         fs_log("saved screenshot\n");
239     } else {
240         fs_log("error saving screenshot\n");
241     }
242     g_free(out_data);
243 #if 0
244     g_free(name);
245     g_free(path);
246 #endif
247 }
248 
fs_ml_get_vblank_count(void)249 int fs_ml_get_vblank_count(void)
250 {
251     return g_vblank_count;
252 }
253 
fs_ml_get_vblank_time(void)254 int64_t fs_ml_get_vblank_time(void)
255 {
256     return g_measured_vblank_time;
257 }
258 
fs_ml_stop(void)259 void fs_ml_stop(void)
260 {
261     g_fs_ml_running = 0;
262     // signal g_frame_available_cond because video (main) thread may be
263     // blocking on this condition
264     // fs_condition_signal(g_frame_available_cond);
265 }
266 
update_frame(void)267 static void update_frame(void)
268 {
269     if (g_fs_ml_video_update_function) {
270         g_uploaded_frame = g_fs_ml_video_update_function();
271     }
272 }
273 
render_frame(void)274 static void render_frame(void)
275 {
276     if (g_fs_ml_video_render_function) {
277         g_fs_ml_video_render_function();
278         g_rendered_frame = g_uploaded_frame;
279     }
280 }
281 
282 #ifdef USE_SDL2
283 extern SDL_Window* g_fs_ml_window;
284 #endif
285 
swap_opengl_buffers(void)286 static void swap_opengl_buffers(void)
287 {
288     //int64_t t1 = fs_get_monotonic_time();
289 #if defined(USE_SDL2)
290     SDL_GL_SwapWindow(g_fs_ml_window);
291 #elif defined(USE_SDL)
292     SDL_GL_SwapBuffers();
293 #else
294     printf("ERROR: no swap\n");
295 #endif
296     //int64_t t2 = fs_get_monotonic_time();
297 }
298 
gl_finish()299 static void gl_finish() {
300     //int64_t t1 = fs_get_monotonic_time();
301     fs_gl_finish();
302     CHECK_GL_ERROR_MSG("render_frame");
303     //int64_t t3 = fs_get_monotonic_time();
304     //printf("          %lld : %lld\n", t2 - t1, t3 - t2);
305 }
306 
307 //void fs_ml_wait_vblank() {
308 //}
309 
sleep_until_vsync(void)310 static void sleep_until_vsync(void)
311 {
312     int sleep_time = 5000;
313     int64_t t = fs_emu_monotonic_time();
314     //int64_t sleep_until = 0;
315     if (g_fs_ml_target_frame_time > 0) {
316         // calculate sleep time based on frame rate
317         // (allowing 4 ms busy-waiting by OpenGL driver, if necessary)
318         //sleep_time = g_fs_ml_target_frame_time - 4000;
319         sleep_time = g_sleep_until_vsync_last_time +
320                 g_fs_ml_target_frame_time - t - 5000;
321     }
322     if (sleep_time > g_fs_ml_target_frame_time - 4000) {
323         sleep_time = 0;
324     }
325     if (sleep_time > 0) {
326         //printf("sleep %d\n", sleep_time);
327         fs_ml_usleep(sleep_time);
328     }
329 }
330 
331 #if 0
332 static void full_sleep_until_vsync(void)
333 {
334     // FIXME: use this instead of sleep_until_vsync
335     int sleep_time = 0;
336     int time_left = 3000;
337     int64_t t = fs_emu_monotonic_time();
338     if (g_fs_ml_target_frame_time > 0) {
339         sleep_time = g_sleep_until_vsync_last_time +
340                 g_fs_ml_target_frame_time - t - time_left;
341     }
342     if (sleep_time > g_fs_ml_target_frame_time - time_left) {
343         sleep_time = 0;
344     }
345     if (sleep_time > 0) {
346         fs_ml_usleep(sleep_time);
347     }
348 }
349 #endif
350 
check_sync_method(const char * a,const char * b)351 static int check_sync_method(const char *a, const char *b)
352 {
353     if (a && g_ascii_strcasecmp(a, b) == 0) {
354         return 1;
355     }
356     return 0;
357 }
358 
decide_opengl_sync_method(void)359 static void decide_opengl_sync_method(void)
360 {
361     fs_log("[OPENGL] Deciding video sync method\n");
362     const char *c = fs_config_get_const_string("video_sync_method");
363     if (check_sync_method(c, "auto")) {
364     } else if (check_sync_method(c, "swap")) {
365         fs_log("[OPENGL] SYNC_SWAP\n");
366         g_sync_method = SYNC_SWAP;
367     } else if (check_sync_method(c, "swap-finish")) {
368         fs_log("[OPENGL] SYNC_SWAP_FINISH\n");
369         g_sync_method = SYNC_SWAP_FINISH;
370     } else if (check_sync_method(c, "finish-swap-finish")) {
371         fs_log("[OPENGL] SYNC_FINISH_SWAP_FINISH\n");
372         g_sync_method = SYNC_FINISH_SWAP_FINISH;
373     } else if (check_sync_method(c, "sleep-swap-finish")) {
374         fs_log("[OPENGL] SYNC_SLEEP_SWAP_FINISH\n");
375         g_sync_method = SYNC_SLEEP_SWAP_FINISH;
376     } else if (check_sync_method(c, "finish-sleep-swap-finish")) {
377         fs_log("[OPENGL] SYNC_FINISH_SLEEP_SWAP_FINISH\n");
378         g_sync_method = SYNC_FINISH_SLEEP_SWAP_FINISH;
379     } else if (check_sync_method(c, "swap-fence")) {
380         fs_log("[OPENGL] SYNC_SWAP_FENCE\n");
381         g_sync_method = SYNC_SWAP_FENCE;
382     } else if (check_sync_method(c, "swap-sleep-fence")) {
383         fs_log("[OPENGL] SYNC_SWAP_SLEEP_FENCE\n");
384         g_sync_method = SYNC_SWAP_SLEEP_FENCE;
385     } else if (c) {
386         fs_log("[OPENGL] Unknown sync method specified\n");
387         g_sync_method = 0;
388     }
389     int fence_support = g_has_nv_fence || g_has_apple_fence || g_has_arb_sync;
390     if (g_sync_method >= SYNC_SWAP_FENCE_START && !fence_support) {
391         fs_log("[OPENGL] No fence support, cannot use this sync method\n");
392         g_sync_method = 0;
393     }
394     if (g_sync_method == 0) {
395         fs_log("[OPENGL] Using default sync method\n");
396 #if defined(WINDOWS)
397         fs_log("- SYNC_SWAP_FINISH\n");
398         g_sync_method = SYNC_SWAP_FINISH;
399 #elif defined(MACOS)
400         fs_log("- SYNC_FINISH_SLEEP_SWAP_FINISH\n");
401         g_sync_method = SYNC_FINISH_SLEEP_SWAP_FINISH;
402 #else
403         fs_log("- SYNC_FINISH_SWAP_FINISH\n");
404         g_sync_method = SYNC_FINISH_SWAP_FINISH;
405 #endif
406     }
407 }
408 
check_opengl_sync_capabilities(void)409 static void check_opengl_sync_capabilities(void)
410 {
411     fs_log("checking OpenGL capabilities\n");
412     const char *ext = (const char *) glGetString(GL_EXTENSIONS);
413     if (ext) {
414         if (strstr(ext, "GL_NV_fence") != NULL) {
415             g_has_nv_fence = 1;
416             fs_log("GL_NV_fence extension found \n");
417 #if defined(WITH_GLEW)
418 #elif defined(WITH_GLAD)
419 #else
420             glGenFencesNV = SDL_GL_GetProcAddress("glGenFencesNV");
421             glSetFenceNV = SDL_GL_GetProcAddress("glSetFenceNV");
422             glTestFenceNV = SDL_GL_GetProcAddress("glTestFenceNV");
423 #endif
424         }
425         if (strstr(ext, "GL_APPLE_fence") != NULL) {
426             g_has_apple_fence = 1;
427             fs_log("GL_APPLE_fence extension found\n");
428 #if defined(WITH_GLEW)
429 #elif defined(WITH_GLAD)
430 #else
431             glGenFencesAPPLE = SDL_GL_GetProcAddress("glGenFencesAPPLE");
432             glSetFenceAPPLE = SDL_GL_GetProcAddress("glSetFenceAPPLE");
433             glTestFenceAPPLE = SDL_GL_GetProcAddress("glTestFenceAPPLE");
434 #endif
435         }
436         if (strstr(ext, "GL_ARB_sync") != NULL) {
437             fs_log("GL_ARB_sync extension found\n");
438 #if defined(WITH_GLEW)
439 #elif defined(WITH_GLAD)
440 #else
441             glFenceSync = SDL_GL_GetProcAddress("glFenceSync");
442             glWaitSync = SDL_GL_GetProcAddress("glWaitSync");
443             glClientWaitSync = SDL_GL_GetProcAddress("glClientWaitSync");
444 #endif
445             if (glFenceSync && glClientWaitSync) {
446                 g_has_arb_sync = 1;
447             } else {
448                 fs_log("error looking up functions\n");
449             }
450         }
451     }
452 }
453 
initialize_opengl_sync(void)454 static void initialize_opengl_sync(void)
455 {
456     check_opengl_sync_capabilities();
457     decide_opengl_sync_method();
458 #ifdef USE_GLES
459 
460 #else
461     if (g_has_nv_fence) {
462         glGenFencesNV(1, &g_fence);
463         CHECK_GL_ERROR_MSG("glGenFencesNV(1, &g_fence)");
464     }
465     else if (g_has_apple_fence) {
466         glGenFencesAPPLE(1, &g_fence);
467         CHECK_GL_ERROR_MSG("glGenFencesAPPLE(1, &g_fence)");
468     }
469 #endif
470 }
471 
opengl_fence(int command)472 static void opengl_fence(int command)
473 {
474 #ifdef USE_GLES
475 
476 #else
477     if (command == FENCE_SET) {
478         if (g_has_nv_fence) {
479             //printf("...\n");
480             glSetFenceNV(g_fence, GL_ALL_COMPLETED_NV);
481             CHECK_GL_ERROR_MSG("glSetFenceNV(g_fence, GL_ALL_COMPLETED_NV)");
482         } else if (g_has_apple_fence) {
483             glSetFenceAPPLE(g_fence);
484             CHECK_GL_ERROR_MSG("glSetFenceAPPLE(g_fence)");
485         } else if (g_has_arb_sync) {
486             g_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
487             CHECK_GL_ERROR_MSG("glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)");
488         }
489     }
490     else if (command == FENCE_WAIT) {
491         if (g_has_nv_fence) {
492             //printf("-- f --\n");
493             //glFinishFenceNV(g_fence);
494             //int64_t t1 = fs_get_monotonic_time();
495             //fs_ml_usleep(10000);
496             while (!glTestFenceNV(g_fence)) {
497                 CHECK_GL_ERROR_MSG("glTestFenceNV(g_fence)");
498                 //printf("-> %lld\n", fs_get_monotonic_time() - t1);
499                 //printf("%d\n", glGetError());
500                 fs_ml_usleep(1000);
501                 //printf("-> %lld\n", fs_get_monotonic_time() - t1);
502             }
503             CHECK_GL_ERROR_MSG("glTestFenceNV(g_fence)");
504         } else if (g_has_apple_fence) {
505             while (!glTestFenceAPPLE(g_fence)) {
506                 CHECK_GL_ERROR_MSG("glTestFenceAPPLE(g_fence)");
507                 fs_ml_usleep(1000);
508             }
509             CHECK_GL_ERROR_MSG("glTestFenceAPPLE(g_fence)");
510         } else if (g_has_arb_sync) {
511             int flags = GL_SYNC_FLUSH_COMMANDS_BIT;
512             while (glClientWaitSync(g_sync, flags, 0)
513                     == GL_TIMEOUT_EXPIRED) {
514                 CHECK_GL_ERROR_MSG("glClientWaitSync(g_sync, flags, 0)");
515                 flags = 0;
516                 fs_ml_usleep(1000);
517             }
518             CHECK_GL_ERROR_MSG("glClientWaitSync(g_sync, flags, 0)");
519         }
520     }
521 #endif
522 }
523 
opengl_swap_synchronous(void)524 static void opengl_swap_synchronous(void)
525 {
526     if (g_sync_method == SYNC_SWAP) {
527         swap_opengl_buffers();
528     } else if (g_sync_method == SYNC_SWAP_FINISH) {
529         swap_opengl_buffers();
530         gl_finish();
531     } else if (g_sync_method == SYNC_FINISH_SWAP_FINISH) {
532         gl_finish();
533         swap_opengl_buffers();
534         gl_finish();
535     } else if (g_sync_method == SYNC_SLEEP_SWAP_FINISH) {
536         sleep_until_vsync();
537         swap_opengl_buffers();
538         gl_finish();
539     } else if (g_sync_method == SYNC_FINISH_SLEEP_SWAP_FINISH) {
540         gl_finish();
541         sleep_until_vsync();
542         swap_opengl_buffers();
543         gl_finish();
544     } else if (g_sync_method == SYNC_SWAP_FENCE) {
545         swap_opengl_buffers();
546         opengl_fence(FENCE_SET);
547         glFlush();
548         opengl_fence(FENCE_WAIT);
549     } else if (g_sync_method == SYNC_SWAP_SLEEP_FENCE) {
550         swap_opengl_buffers();
551         sleep_until_vsync();
552         opengl_fence(FENCE_SET);
553         glFlush();
554         opengl_fence(FENCE_WAIT);
555     }
556 }
557 
render_iteration_vsync(void)558 static void render_iteration_vsync(void)
559 {
560     if (g_fs_ml_video_sync_low_latency) {
561         int current_frame_at_start = g_available_frame;
562 
563         //int64_t t1 = fs_ml_monotonic_time();
564 
565         int sleep_time = 0;
566         int time_left = g_estimated_upload_render_duration;
567         int64_t t = fs_emu_monotonic_time();
568         if (g_fs_ml_target_frame_time > 0) {
569             sleep_time = g_estimated_next_vblank_time - t - time_left;
570         }
571         if (sleep_time > g_fs_ml_target_frame_time - time_left) {
572             sleep_time = 0;
573         }
574         if (sleep_time > 0) {
575             fs_ml_usleep(sleep_time);
576         }
577 
578         if (g_available_frame > current_frame_at_start) {
579             //printf("low latency %d\n", g_available_frame);
580         } else {
581             //printf("...\n");
582         }
583     }
584 
585     update_frame();
586     CHECK_GL_ERROR_MSG("update_frame");
587     render_frame();
588     CHECK_GL_ERROR_MSG("render_frame");
589 
590     //opengl_fence(FENCE_SET);
591     //glFlush();
592     //opengl_fence(FENCE_WAIT);
593     //int64_t upload_render_time = fs_ml_monotonic_time() - t1;
594     //printf("urt %lld\n", upload_render_time);
595 
596     opengl_swap_synchronous();
597 
598     g_measured_vblank_time = fs_ml_monotonic_time();
599     g_vblank_count++;
600     fs_mutex_lock(g_vblank_mutex);
601     g_measured_vblank_times[g_vblank_index] = g_measured_vblank_time;
602     g_vblank_index = (g_vblank_index + 1) % VBLANK_COUNT;
603     fs_mutex_unlock(g_vblank_mutex);
604 
605     // FIXME: adjust g_measured_vblank_time based on historical data (smooth out
606     // irregularities) and save the result in g_adjusted_vblank_time
607     g_adjusted_vblank_time = g_measured_vblank_time;
608 
609     g_sleep_until_vsync_last_time = g_adjusted_vblank_time;
610     g_estimated_next_vblank_time = g_adjusted_vblank_time + \
611             g_fs_ml_target_frame_time;
612 
613     // g_start_new_frame_cond is used to signal that a new frame can be
614     // generated when the emulation is running in sync - this is not used
615     // when only display flipping is synced to vblank
616 
617     fs_mutex_lock(g_start_new_frame_mutex);
618     g_start_new_frame = 1;
619     fs_condition_signal(g_start_new_frame_cond);
620     fs_mutex_unlock(g_start_new_frame_mutex);
621 }
622 
fs_ml_render_iteration(void)623 void fs_ml_render_iteration(void)
624 {
625     static int first = 1;
626     if (first) {
627         first = 0;
628         initialize_opengl_sync();
629     }
630 
631     if (g_fs_ml_vblank_sync) {
632         render_iteration_vsync();
633     } else if (g_fs_ml_benchmarking) {
634         update_frame();
635         render_frame();
636         swap_opengl_buffers();
637     } else {
638         // when vsync is off, we wait until a new frame is ready and
639         // then we display it immediately
640 
641         if (fs_ml_is_quitting()) {
642             // but when the emulation is quitting, we can't expect any new
643             // frames so there's no point waiting for them. Instead, we just
644             // sleep a bit to throttle the frame rate for the quit animation
645             fs_ml_usleep(10000);
646         } else {
647             // wait max 33 ms to allow the user interface to work even if
648             // the emu hangs
649             // int64_t dest_time = fs_get_real_time() + 33 * 1000;
650             int64_t end_time = fs_condition_get_wait_end_time(33 * 1000);
651             int64_t check_time = 0;
652 
653             fs_mutex_lock(g_frame_available_mutex);
654             // fs_log("cond wait until %lld\n", end_time);
655             while (g_rendered_frame == g_available_frame) {
656                 fs_condition_wait_until(
657                     g_frame_available_cond, g_frame_available_mutex, end_time);
658                 check_time = fs_condition_get_wait_end_time(0);
659                 if (check_time >= end_time) {
660                     // fs_log("timed out at %lld\n", check_time);
661                     break;
662                 } else {
663                     // fs_log("wake-up at %lld (end_time = %lld)\n", check_time, end_time);
664                 }
665             }
666             fs_mutex_unlock(g_frame_available_mutex);
667         }
668 
669         update_frame();
670         render_frame();
671         swap_opengl_buffers();
672         //gl_finish();
673     }
674 
675     if (g_fs_ml_video_screenshot_path) {
676         fs_mutex_lock(g_fs_ml_video_screenshot_mutex);
677         if (g_fs_ml_video_screenshot_path) {
678             save_screenshot_of_opengl_framebuffer(
679                     g_fs_ml_video_screenshot_path);
680             g_free(g_fs_ml_video_screenshot_path);
681             g_fs_ml_video_screenshot_path = NULL;
682         }
683         fs_mutex_unlock(g_fs_ml_video_screenshot_mutex);
684     }
685 
686     if (g_fs_ml_video_post_render_function) {
687         g_fs_ml_video_post_render_function();
688     }
689 }
690 
fs_ml_render_init(void)691 void fs_ml_render_init(void)
692 {
693     g_frame_available_cond = fs_condition_create();
694     g_frame_available_mutex = fs_mutex_create();
695 
696     g_start_new_frame_cond = fs_condition_create();
697     g_start_new_frame_mutex = fs_mutex_create();
698 
699     g_buffer_swap_cond = fs_condition_create();
700     g_buffer_swap_mutex = fs_mutex_create();
701 
702     g_epoch = fs_get_monotonic_time();
703     g_vblank_mutex = fs_mutex_create();
704     //fs_emu_stat_queue_init(&g_measured_vblank_times, VBLANK_TIMES_COUNT);
705 
706     if (fs_config_get_boolean("low_latency_vsync") == 0) {
707         fs_log("[VIDEO] Disabling use of low latency vsync\n");
708         g_fs_ml_video_sync_low_latency = 0;
709     } else {
710         fs_log("[VIDEO] Using low latency vsync when full vsync is enabled\n");
711         g_fs_ml_video_sync_low_latency = 1;
712     }
713 }
714