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