1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4
5 #ifdef USE_SDL_VIDEO
6
7 // FIXME: make libfsml independent of libfsemu
8 #include "../emu/video.h"
9
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <stddef.h>
13 #include <string.h>
14
15 #ifdef USE_SDL2
16 #define USE_SDL
17 #endif
18
19 #ifdef USE_SDL
20 #include <SDL.h>
21 #endif
22
23 //#ifdef USE_GLIB
24 //#include <glib.h>
25 //#endif
26
27 #include <fs/conf.h>
28 #include <fs/lazyness.h>
29 #ifdef WITH_GLEW
30 #include <GL/glew.h>
31 #endif
32 #include <fs/glib.h>
33 #include <fs/ml.h>
34 #include <fs/thread.h>
35
36 #ifdef USE_OPENGL
37 #include <fs/ml/opengl.h>
38 #endif
39 #include <fs/ml/options.h>
40
41 #define FSE_INTERNAL_API
42 #include <fs/emu/input.h>
43 #include <fs/emu/monitor.h>
44 #include <fs/emu/video.h>
45 #include "ml_internal.h"
46
47 SDL_Window *g_fs_ml_window = NULL;
48 SDL_GLContext g_fs_ml_context = 0;
49 int g_fs_ml_had_input_grab = 0;
50 int g_fs_ml_was_fullscreen = 0;
51
52 static GQueue *g_video_event_queue;
53 static fs_mutex *g_video_event_mutex;
54 static fs_thread_id_t g_video_thread_id;
55 static fs_condition *g_video_cond;
56 static fs_mutex *g_video_mutex;
57 static int g_display;
58 static int g_has_input_grab = 0;
59 static int g_initial_input_grab = 0;
60 static bool g_grab_input_on_activate;
61 static int g_fs_ml_automatic_input_grab = 1;
62 static int g_fs_ml_keyboard_input_grab = 1;
63 static int g_fsaa = 0;
64 static int g_f12_state, g_f11_state;
65 static char *g_window_title;
66 static int g_window_width, g_window_height;
67 static int g_window_x, g_window_y;
68 static int g_window_resizable;
69 static int g_fullscreen_width, g_fullscreen_height;
70 static GLint g_max_texture_size;
71
72 #define FS_ML_VIDEO_EVENT_GRAB_INPUT 1
73 #define FS_ML_VIDEO_EVENT_UNGRAB_INPUT 2
74 #define FS_ML_VIDEO_EVENT_SHOW_CURSOR 3
75 #define FS_ML_VIDEO_EVENT_HIDE_CURSOR 4
76 #define FS_ML_VIDEO_EVENT_TOGGLE_FULLSCREEN 5
77 #define FS_ML_VIDEO_EVENT_ENABLE_FULLSCREEN 6
78 #define FS_ML_VIDEO_EVENT_DISABLE_FULLSCREEN 7
79 #define FS_ML_VIDEO_EVENT_ACTIVATE_WINDOW_SWITCHER 8
80
81 #define FULLSCREEN_FULLSCREEN 0
82 #define FULLSCREEN_WINDOW 1
83 #define FULLSCREEN_DESKTOP 2
84
is_video_thread(void)85 static inline bool is_video_thread(void)
86 {
87 return fs_thread_id() == g_video_thread_id;
88 }
89
fs_ml_get_max_texture_size()90 int fs_ml_get_max_texture_size()
91 {
92 return g_max_texture_size;
93 }
94
fs_ml_get_fullscreen_width()95 int fs_ml_get_fullscreen_width()
96 {
97 return g_fullscreen_width;
98 }
99
fs_ml_get_fullscreen_height()100 int fs_ml_get_fullscreen_height()
101 {
102 return g_fullscreen_height;
103 }
104
fs_ml_get_windowed_width()105 int fs_ml_get_windowed_width()
106 {
107 return g_window_width;
108 }
109
fs_ml_get_windowed_height()110 int fs_ml_get_windowed_height()
111 {
112 return g_window_height;
113 }
114
post_video_event(int event)115 static void post_video_event(int event)
116 {
117 if (fse_drivers()) {
118 // printf("FSE_DRIVERS: ignoring post_video_event\n");
119 } else {
120 fs_mutex_lock(g_video_event_mutex);
121 g_queue_push_head(g_video_event_queue, FS_INT_TO_POINTER(event));
122 fs_mutex_unlock(g_video_event_mutex);
123 }
124 }
125
process_video_events(void)126 static void process_video_events(void)
127 {
128 fs_mutex_lock(g_video_event_mutex);
129 int count = g_queue_get_length(g_video_event_queue);
130 for (int i = 0; i < count; i++) {
131 int event = FS_POINTER_TO_INT(g_queue_pop_tail(g_video_event_queue));
132 if (event == FS_ML_VIDEO_EVENT_GRAB_INPUT) {
133 fs_ml_set_input_grab(true);
134 } else if (event == FS_ML_VIDEO_EVENT_UNGRAB_INPUT) {
135 fs_ml_set_input_grab(false);
136 } else if (event == FS_ML_VIDEO_EVENT_SHOW_CURSOR) {
137 fs_ml_show_cursor(1, 1);
138 } else if (event == FS_ML_VIDEO_EVENT_HIDE_CURSOR) {
139 fs_ml_show_cursor(0, 1);
140 } else if (event == FS_ML_VIDEO_EVENT_TOGGLE_FULLSCREEN) {
141 fs_ml_toggle_fullscreen();
142 } else if (event == FS_ML_VIDEO_EVENT_ENABLE_FULLSCREEN) {
143 fs_ml_set_fullscreen(true);
144 } else if (event == FS_ML_VIDEO_EVENT_DISABLE_FULLSCREEN) {
145 fs_ml_set_fullscreen(false);
146 }
147 }
148 fs_mutex_unlock(g_video_event_mutex);
149 }
150
fs_ml_input_grab(void)151 bool fs_ml_input_grab(void)
152 {
153 return g_has_input_grab;
154 }
155
fs_ml_automatic_input_grab(void)156 bool fs_ml_automatic_input_grab(void)
157 {
158 return g_fs_ml_automatic_input_grab;
159 }
160
fs_ml_set_input_grab(bool grab)161 void fs_ml_set_input_grab(bool grab)
162 {
163 if (!is_video_thread()) {
164 post_video_event(grab ? FS_ML_VIDEO_EVENT_GRAB_INPUT :
165 FS_ML_VIDEO_EVENT_UNGRAB_INPUT);
166 /* FIXME: Not really, yet */
167 g_has_input_grab = grab ? 1 : 0;
168 return;
169 }
170
171 if (grab) {
172 fs_log("[INPUT] Grabbing input\n");
173 } else {
174 fs_log("[INPUT] Releasing input\n");
175 }
176 SDL_SetWindowGrab(g_fs_ml_window, grab ? SDL_TRUE : SDL_FALSE);
177 SDL_SetRelativeMouseMode(grab ? SDL_TRUE : SDL_FALSE);
178 if (fs_ml_cursor_allowed())
179 fs_ml_show_cursor(!grab, 1);
180 g_has_input_grab = grab ? 1 : 0;
181 }
182
fs_ml_activate_window_switcher(void)183 void fs_ml_activate_window_switcher(void)
184 {
185 if (!is_video_thread()) {
186 post_video_event(FS_ML_VIDEO_EVENT_ACTIVATE_WINDOW_SWITCHER);
187 return;
188 }
189
190 fs_ml_activate_window_switcher_impl();
191 }
192
fs_ml_set_input_grab_on_activate(bool grab)193 void fs_ml_set_input_grab_on_activate(bool grab)
194 {
195 g_grab_input_on_activate = grab;
196 }
197
fs_ml_set_video_fsaa(int fsaa)198 void fs_ml_set_video_fsaa(int fsaa)
199 {
200 g_fsaa = fsaa;
201 }
202
fs_ml_show_cursor(int show,int immediate)203 void fs_ml_show_cursor(int show, int immediate)
204 {
205 if (immediate) {
206 SDL_ShowCursor(show);
207 }
208 else {
209 post_video_event(show ? FS_ML_VIDEO_EVENT_SHOW_CURSOR :
210 FS_ML_VIDEO_EVENT_HIDE_CURSOR);
211 }
212 }
213
log_opengl_information(void)214 static void log_opengl_information(void)
215 {
216 static int written = 0;
217 if (written) {
218 return;
219 }
220 written = 1;
221 char *software_renderer = NULL;
222 const char *str;
223 str = (const char*) glGetString(GL_VENDOR);
224 if (str) {
225 fs_log("opengl vendor: %s\n", str);
226 }
227 str = (const char*) glGetString(GL_RENDERER);
228 if (str) {
229 fs_log("opengl renderer: %s\n", str);
230 if (strstr(str, "GDI Generic") != NULL) {
231 software_renderer = g_strdup(str);
232 } else if (strstr(str, "llvmpipe") != NULL) {
233 software_renderer = g_strdup(str);
234 }
235 }
236 str = (const char*) glGetString(GL_VERSION);
237 if (str) {
238 fs_log("opengl version: %s\n", str);
239 }
240 str = (const char*) glGetString(GL_SHADING_LANGUAGE_VERSION);
241 if (str) {
242 fs_log("opengl shading language version: %s\n", str);
243 }
244 str = (const char*) glGetString(GL_EXTENSIONS);
245 if (str) {
246 fs_log("opengl extensions: %s\n", str);
247 }
248 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &g_max_texture_size);
249 fs_log("opengl max texture size (estimate): %dx%d\n", g_max_texture_size,
250 g_max_texture_size);
251
252 if (software_renderer) {
253 fs_emu_warning("No HW OpenGL driver: %s", software_renderer);
254 g_free(software_renderer);
255 }
256 }
257
set_video_mode()258 static void set_video_mode()
259 {
260 int flags = SDL_WINDOW_OPENGL;
261 if (g_fs_emu_video_fullscreen_mode != FULLSCREEN_WINDOW &&
262 g_window_resizable) {
263 flags |= SDL_WINDOW_RESIZABLE;
264 }
265 int x = g_window_x, y = g_window_y;
266 int w = -1, h = -1;
267
268 // if (g_initial_input_grab) {
269 // flags |= SDL_WINDOW_INPUT_GRABBED;
270 // g_has_input_grab = 1;
271 // }
272
273 if (g_fs_emu_video_fullscreen == 1) {
274 w = g_fullscreen_width;
275 h = g_fullscreen_height;
276 //w = g_window_width;
277 //h = g_window_height;
278
279 if (g_fs_emu_video_fullscreen_mode == FULLSCREEN_WINDOW) {
280 fs_log("using fullscreen window mode\n");
281 //x = 0;
282 //y = 0;
283 //w = g_fullscreen_width;
284 //h = g_fullscreen_height;
285 flags |= SDL_WINDOW_BORDERLESS;
286
287 FSEmuMonitor monitor;
288 fs_emu_monitor_get_by_index(g_display, &monitor);
289 x = monitor.rect.x;
290 y = monitor.rect.y;
291 w = monitor.rect.w;
292 h = monitor.rect.h;
293 }
294 else if (g_fs_emu_video_fullscreen_mode == FULLSCREEN_DESKTOP) {
295 fs_log("using fullscreen desktop mode\n");
296 // the width and height will not be used for the fullscreen
297 // desktop mode, only for the window when toggling fullscreen
298 // state
299 #if 0
300 w = g_window_width;
301 h = g_window_height;
302 #else
303 FSEmuMonitor monitor;
304 fs_emu_monitor_get_by_index(g_display, &monitor);
305 x = monitor.rect.x;
306 y = monitor.rect.y;
307 w = monitor.rect.w;
308 h = monitor.rect.h;
309 #endif
310 flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
311 }
312 else {
313 fs_log("using SDL_FULLSCREEN mode\n");
314 flags |= SDL_WINDOW_FULLSCREEN;
315 }
316 fs_log("setting (fullscreen) video mode %d %d\n", w, h);
317 }
318 else {
319 w = g_window_width;
320 h = g_window_height;
321
322 fs_log("using windowed mode\n");
323 //SDL_putenv("SDL_VIDEO_WINDOW_POS=");
324 fs_log("setting (windowed) video mode %d %d\n", w, h);
325 }
326
327 if (fs_config_get_boolean("window_border") == 0) {
328 fs_log("borderless window requested\n");
329 flags |= SDL_WINDOW_BORDERLESS;
330 }
331
332 // special flags for command line usage
333 if (fs_config_get_boolean("window_hidden") == 1) {
334 fs_log("hidden window requested\n");
335 flags |= SDL_WINDOW_HIDDEN;
336 }
337 if (fs_config_get_boolean("window_minimized") == 1) {
338 fs_log("minimized window requested\n");
339 flags |= SDL_WINDOW_MINIMIZED;
340 }
341
342 #if 0
343 Uint8 data[] = "\0";
344 SDL_Cursor *cursor = SDL_CreateCursor(data, data, 8, 1, 0, 0);
345 SDL_SetCursor(cursor);
346 #endif
347
348 g_fs_ml_video_width = w;
349 g_fs_ml_video_height = h;
350 fs_log("[SDL] CreateWindow(x=%d, y=%d, w=%d, h=%d, flags=%d)\n",
351 x, y, w, h, flags);
352 g_fs_ml_window = SDL_CreateWindow(g_window_title, x, y, w, h, flags);
353
354 int assume_refresh_rate = fs_config_get_int("assume_refresh_rate");
355 if (assume_refresh_rate != FS_CONFIG_NONE) {
356 fs_log("[DISPLAY] Assuming host refresh rate: %d Hz (from config)\n",
357 assume_refresh_rate);
358 g_fs_emu_video_frame_rate_host = assume_refresh_rate;
359 } else {
360 SDL_DisplayMode mode;
361 if (SDL_GetWindowDisplayMode(g_fs_ml_window, &mode) == 0) {
362 g_fs_emu_video_frame_rate_host = mode.refresh_rate;
363 } else {
364 g_fs_emu_video_frame_rate_host = 0;
365 }
366 fs_log("[DISPLAY] Host refresh rate: %d Hz\n",
367 g_fs_emu_video_frame_rate_host);
368 }
369
370 if (g_fs_emu_video_frame_rate_host) {
371 g_fs_ml_target_frame_time = 1000000 / g_fs_emu_video_frame_rate_host;
372 }
373
374 g_fs_ml_context = SDL_GL_CreateContext(g_fs_ml_window);
375 #ifdef WITH_GLEW
376 static int glew_initialized = 0;
377 if (!glew_initialized) {
378 GLenum err = glewInit();
379 if (GLEW_OK != err) {
380 fprintf(stderr, "[GLEW] Error: %s\n", glewGetErrorString(err));
381 fs_emu_fatal("[GLEW] Error initializing glew");
382 }
383 fs_log("[GLEW] Version %s\n", glewGetString(GLEW_VERSION));
384 glew_initialized = 1;
385 }
386 #elif defined(WITH_GLAD)
387 static int glad_initialized = 0;
388 if (!glad_initialized) {
389 if (!gladLoadGLLoader((GLADloadproc) SDL_GL_GetProcAddress)) {
390 fs_emu_fatal("[GLAD] Failed to initialize OpenGL context");
391 }
392 glad_initialized = 1;
393 }
394 #endif
395 fs_ml_configure_window();
396
397 // FIXME: this can be removed
398 g_fs_ml_opengl_context_stamp++;
399
400 log_opengl_information();
401 }
402
fs_ml_fullscreen(void)403 bool fs_ml_fullscreen(void)
404 {
405 /* FIXME: This can return (kind of) false answer if a fullscreen
406 * event is unprocessed in the event queue. */
407 return g_fs_emu_video_fullscreen;
408 }
409
fs_ml_set_fullscreen(bool fullscreen)410 void fs_ml_set_fullscreen(bool fullscreen)
411 {
412 if (!is_video_thread()) {
413 if (fullscreen) {
414 fs_log("Posting enable fullscreen event\n");
415 post_video_event(FS_ML_VIDEO_EVENT_ENABLE_FULLSCREEN);
416 } else {
417 fs_log("Posting disable fullscreen event\n");
418 post_video_event(FS_ML_VIDEO_EVENT_DISABLE_FULLSCREEN);
419 }
420 return;
421 }
422
423 if (fullscreen == g_fs_emu_video_fullscreen)
424 return;
425
426 if (g_fs_emu_video_fullscreen_mode == FULLSCREEN_WINDOW) {
427 fs_emu_warning("Cannot toggle fullscreen with fullscreen-mode=window");
428 return;
429 }
430
431 int display_index = 0;
432 SDL_DisplayMode mode;
433 memset(&mode, 0, sizeof(SDL_DisplayMode));
434 if (SDL_GetDesktopDisplayMode(display_index, &mode) == 0) {
435 SDL_SetWindowDisplayMode(g_fs_ml_window, &mode);
436 }
437
438 int flags = 0;
439 if (fullscreen) {
440 if (g_fs_emu_video_fullscreen_mode == FULLSCREEN_DESKTOP)
441 flags = SDL_WINDOW_FULLSCREEN_DESKTOP;
442 else
443 flags = SDL_WINDOW_FULLSCREEN;
444 }
445 SDL_SetWindowFullscreen(g_fs_ml_window, flags);
446 g_fs_emu_video_fullscreen = fullscreen;
447 }
448
fs_ml_toggle_fullscreen(void)449 void fs_ml_toggle_fullscreen(void)
450 {
451 if (!is_video_thread()) {
452 fs_log("Posting toggle video event\n");
453 post_video_event(FS_ML_VIDEO_EVENT_TOGGLE_FULLSCREEN);
454 return;
455 }
456 fs_ml_set_fullscreen(!fs_ml_fullscreen());
457 }
458
459 static int g_fs_emu_monitor_count;
460 // static FSEmuMonitor g_fs_emu_monitors[FS_EMU_MONITOR_MAX_COUNT];
461 static GArray *g_fs_emu_monitors;
462
fs_emu_monitor_compare(gconstpointer a,gconstpointer b)463 static gint fs_emu_monitor_compare(gconstpointer a, gconstpointer b)
464 {
465 FSEmuMonitor *am = (FSEmuMonitor *) a;
466 FSEmuMonitor *bm = (FSEmuMonitor *) b;
467
468 return am->rect.x - bm->rect.x;
469 }
470
fs_ml_video_mode_get_current(fs_ml_video_mode * mode)471 int fs_ml_video_mode_get_current(fs_ml_video_mode *mode)
472 {
473 mode->width = 0;
474 mode->height = 0;
475 mode->fps = 0;
476 mode->bpp = 0;
477 mode->flags = 0;
478
479 FSEmuMonitor monitor;
480 fs_emu_monitor_get_by_index(g_display, &monitor);
481 mode->width = monitor.rect.w;
482 mode->height = monitor.rect.h;
483 mode->fps = monitor.refresh_rate;
484
485 if (mode->fps == 0) {
486 fs_log("WARNING: refresh rate was not detected\n");
487 fs_log("full video sync will not be enabled automatically, but can "
488 "be forced\n");
489 }
490 return 0;
491 }
492
fs_emu_monitor_init()493 static void fs_emu_monitor_init()
494 {
495 static bool initialized = false;
496 if (initialized) {
497 return;
498 }
499 initialized = true;
500
501 g_fs_emu_monitors = g_array_new(false, true, sizeof(FSEmuMonitor));
502
503 int display_index = 0;
504 while (true) {
505 SDL_DisplayMode mode;
506 int error = SDL_GetDesktopDisplayMode(display_index, &mode);
507 if (error) {
508 break;
509 }
510
511 FSEmuMonitor monitor;
512 monitor.index = display_index;
513 SDL_Rect rect;
514 error = SDL_GetDisplayBounds(display_index, &rect);
515 if (error) {
516 fs_log("Error retrieving display bounds for display %d: %s\n",
517 display_index, SDL_GetError());
518 monitor.rect.x = 0;
519 monitor.rect.y = 0;
520 monitor.rect.w = 1024;
521 monitor.rect.h = 768;
522 monitor.refresh_rate = 1;
523 } else {
524 monitor.rect.x = rect.x;
525 monitor.rect.y = rect.y;
526 monitor.rect.w = rect.w;
527 monitor.rect.h = rect.h;
528 monitor.refresh_rate = mode.refresh_rate;
529 }
530 fs_log("[DISPLAY] %d: %dx%d+%d+%d @%d\n", display_index,
531 monitor.rect.w, monitor.rect.h, monitor.rect.x, monitor.rect.y,
532 monitor.refresh_rate);
533 g_array_append_val(g_fs_emu_monitors, monitor);
534 display_index += 1;
535 }
536 g_fs_emu_monitor_count = display_index;
537
538 #if 0
539 SDL_DisplayMode mode;
540 int error = SDL_GetCurrentDisplayMode(display_index, &mode);
541 if (error) {
542 fs_log("SDL_GetCurrentDisplayMode failed\n");
543 SDL_ShowSimpleMessageBox(
544 SDL_MESSAGEBOX_ERROR, "Display Error",
545 "SDL_GetCurrentDisplayMode failed.", NULL);
546 exit(1);
547 }
548 g_fs_emu_monitor_count = SDL_GetNumVideoDisplays();
549
550 if (g_fs_emu_monitor_count < 1) {
551 fs_log("Error %d retrieving number of displays/monitors\n",
552 g_fs_emu_monitor_count);
553 g_fs_emu_monitor_count = 1;
554 }
555 if (g_fs_emu_monitor_count > FS_EMU_MONITOR_MAX_COUNT) {
556 fs_log("Limiting number of displays to %d\n",
557 FS_EMU_MONITOR_MAX_COUNT);
558 g_fs_emu_monitor_count = FS_EMU_MONITOR_MAX_COUNT;
559 }
560
561 for (int i = 0; i < g_fs_emu_monitor_count; i++) {
562 SDL_Rect rect;
563 FSEmuMonitor monitor;
564 int error = SDL_GetDisplayBounds(i, &rect);
565 if (error) {
566 fs_log("Error retrieving display bounds for display %d: %s\n",
567 i, SDL_GetError());
568 /* Setting dummy values on error*/
569 rect.x = 0;
570 rect.y = 0;
571 rect.w = 1024;
572 rect.h = 768;
573 }
574
575 monitor.rect.x = rect.x;
576 monitor.rect.y = rect.y;
577 monitor.rect.w = rect.w;
578 monitor.rect.h = rect.h;
579 monitor.index = i;
580
581 g_array_append_val(g_fs_emu_monitors, monitor);
582 }
583 #endif
584
585 g_array_sort(g_fs_emu_monitors, fs_emu_monitor_compare);
586 for (int i = 0; i < g_fs_emu_monitor_count; i++) {
587 g_array_index(g_fs_emu_monitors, FSEmuMonitor, i).index = i;
588 /* Set physical position flags (left, m-left, m-right, right) */
589 int flags = 0;
590 for (int j = 0; j < 4; j++) {
591 int pos = (g_fs_emu_monitor_count - 1.0) * j / 3.0 + 0.5;
592 fs_log("Monitor - j %d pos %d\n", j, pos);
593 if (pos == i) {
594 flags |= (1 << j);
595 }
596 }
597 fs_log("Monitor index %d flags %d\n", i, flags);
598 g_array_index(g_fs_emu_monitors, FSEmuMonitor, i).flags = flags;
599 }
600 }
601
fs_emu_monitor_count()602 int fs_emu_monitor_count()
603 {
604 return g_fs_emu_monitor_count;
605 }
606
fs_emu_monitor_get_by_index(int index,FSEmuMonitor * monitor)607 bool fs_emu_monitor_get_by_index(int index, FSEmuMonitor* monitor)
608 {
609 if (index < 0 || index >= g_fs_emu_monitor_count) {
610 monitor->index = -1;
611 monitor->flags = 0;
612 monitor->rect.x = 0;
613 monitor->rect.y = 0;
614 monitor->rect.w = 1024;
615 monitor->rect.h = 768;
616 monitor->refresh_rate = 1;
617 return false;
618 }
619 SDL_assert(monitor != NULL);
620 memcpy(monitor, &g_array_index(g_fs_emu_monitors, FSEmuMonitor, index),
621 sizeof(FSEmuMonitor));
622 return true;
623 }
624
fs_emu_monitor_get_by_flag(int flag,FSEmuMonitor * monitor)625 bool fs_emu_monitor_get_by_flag(int flag, FSEmuMonitor* monitor)
626 {
627 for (int i = 0; i < g_fs_emu_monitor_count; i++) {
628 if ((g_array_index(g_fs_emu_monitors,
629 FSEmuMonitor, i).flags & flag) == flag) {
630 fs_log("Monitor: found index %d for flag %d\n", i, flag);
631 return fs_emu_monitor_get_by_index(i, monitor);
632 }
633 }
634 fs_emu_monitor_get_by_index(0, monitor);
635 return false;
636 }
637
fs_ml_video_create_window(const char * title)638 int fs_ml_video_create_window(const char *title)
639 {
640 fs_log("fs_ml_video_create_window\n");
641 g_window_title = g_strdup(title);
642
643 g_fs_ml_keyboard_input_grab = fs_config_get_boolean(
644 "keyboard_input_grab");
645 if (g_fs_ml_automatic_input_grab == FS_CONFIG_NONE) {
646 g_fs_ml_keyboard_input_grab = 1;
647 }
648 fs_log("keyboard input grab: %d\n", g_fs_ml_keyboard_input_grab);
649
650 static int initialized = 0;
651
652 SDL_SetHint(SDL_HINT_GRAB_KEYBOARD,
653 g_fs_ml_keyboard_input_grab ? "1" : "0");
654 SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
655 #ifdef WINDOWS
656 SDL_SetHint(SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4, "1");
657 #endif
658
659 SDL_Init(SDL_INIT_VIDEO);
660
661 SDL_version cversion, lversion;
662 SDL_VERSION(&cversion);
663 SDL_GetVersion(&lversion);
664 fs_log("[SDL] Version %d.%d.%d (Compiled against %d.%d.%d)\n",
665 lversion.major, lversion.minor, lversion.patch,
666 cversion.major, cversion.minor, cversion.patch);
667
668 if (!initialized) {
669 int display_index = 0;
670 SDL_DisplayMode mode;
671 int error = SDL_GetCurrentDisplayMode(display_index, &mode);
672 if (error) {
673 fs_log("SDL_GetCurrentDisplayMode failed\n");
674 SDL_ShowSimpleMessageBox(
675 SDL_MESSAGEBOX_ERROR, "Display Error",
676 "SDL_GetCurrentDisplayMode failed.", NULL);
677 exit(1);
678 }
679
680 fs_emu_monitor_init();
681
682 const char *mon = fs_config_get_const_string("monitor");
683 int mon_flag = -1;
684 if (mon == NULL) {
685 mon = "middle-left";
686 }
687 if (strcmp(mon, "left") == 0) {
688 mon_flag = FS_EMU_MONITOR_FLAG_LEFT;
689 } else if (strcmp(mon, "middle-left") == 0) {
690 mon_flag = FS_EMU_MONITOR_FLAG_MIDDLE_LEFT;
691 } else if (strcmp(mon, "middle-right") == 0) {
692 mon_flag = FS_EMU_MONITOR_FLAG_MIDDLE_RIGHT;
693 } else if (strcmp(mon, "right") == 0) {
694 mon_flag = FS_EMU_MONITOR_FLAG_RIGHT;
695 }
696 else {
697 mon_flag = FS_EMU_MONITOR_FLAG_MIDDLE_LEFT;
698 }
699 FSEmuMonitor monitor;
700 fs_emu_monitor_get_by_flag(mon_flag, &monitor);
701 fs_log("Monitor \"%s\" (flag %d) => index %d\n",
702 mon, mon_flag, monitor.index);
703 g_display = monitor.index;
704
705 g_fullscreen_width = fs_config_get_int("fullscreen_width");
706 if (g_fullscreen_width == FS_CONFIG_NONE) {
707 g_fullscreen_width = mode.w;
708 }
709 g_fullscreen_height = fs_config_get_int("fullscreen_height");
710 if (g_fullscreen_height == FS_CONFIG_NONE) {
711 g_fullscreen_height = mode.h;
712 }
713
714 if (g_fs_emu_video_fullscreen_mode_string == NULL) {
715 g_fs_emu_video_fullscreen_mode = -1;
716 }
717 else if (g_ascii_strcasecmp(g_fs_emu_video_fullscreen_mode_string,
718 "window") == 0) {
719 g_fs_emu_video_fullscreen_mode = FULLSCREEN_WINDOW;
720 }
721 else if (g_ascii_strcasecmp(g_fs_emu_video_fullscreen_mode_string,
722 "fullscreen") == 0) {
723 g_fs_emu_video_fullscreen_mode = FULLSCREEN_FULLSCREEN;
724 }
725 else if (g_ascii_strcasecmp(g_fs_emu_video_fullscreen_mode_string,
726 "desktop") == 0) {
727 g_fs_emu_video_fullscreen_mode = FULLSCREEN_DESKTOP;
728 }
729 if (g_fs_emu_video_fullscreen_mode == -1) {
730 #ifdef MACOSX
731 g_fs_emu_video_fullscreen_mode = FULLSCREEN_FULLSCREEN;
732 #else
733 g_fs_emu_video_fullscreen_mode = FULLSCREEN_FULLSCREEN;
734 #endif
735 fs_log("[SDL] Defaulting to fullscreen_mode = desktop for SDL 2\n");
736 g_fs_emu_video_fullscreen_mode = FULLSCREEN_DESKTOP;
737 }
738
739 initialized = 1;
740 }
741
742 if (g_fs_ml_video_sync) {
743 g_fs_ml_vblank_sync = 1;
744 }
745
746 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
747
748 if (g_fsaa) {
749 fs_log("setting FSAA samples to %d\n", g_fsaa);
750 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
751 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, g_fsaa);
752 }
753
754 g_window_width = fs_config_get_int("window_width");
755 if (g_window_width == FS_CONFIG_NONE) {
756 g_window_width = 1920 / 2;
757 }
758 g_window_height = fs_config_get_int("window_height");
759 if (g_window_height == FS_CONFIG_NONE) {
760 g_window_height = 1080/ 2;
761 }
762 g_window_x = fs_config_get_int("window_x");
763 if (g_window_x == FS_CONFIG_NONE) {
764 g_window_x = SDL_WINDOWPOS_CENTERED;
765 }
766 g_window_y = fs_config_get_int("window_y");
767 if (g_window_y == FS_CONFIG_NONE) {
768 g_window_y = SDL_WINDOWPOS_CENTERED;
769 }
770 g_window_resizable = fs_config_get_boolean("window_resizable");
771 if (g_window_resizable == FS_CONFIG_NONE) {
772 g_window_resizable = 1;
773 }
774
775 g_fs_ml_automatic_input_grab = fs_config_get_boolean(
776 "automatic_input_grab");
777 if (g_fs_ml_automatic_input_grab == FS_CONFIG_NONE) {
778 if (fs_ml_mouse_integration()) {
779 g_fs_ml_automatic_input_grab = 0;
780 } else {
781 g_fs_ml_automatic_input_grab = 1;
782 }
783 }
784 fs_log("automatic input grab: %d\n", g_fs_ml_automatic_input_grab);
785
786 g_initial_input_grab = g_fs_ml_automatic_input_grab;
787 if (fs_config_get_boolean("initial_input_grab") == 1) {
788 g_initial_input_grab = 1;
789 }
790 else if (fs_config_get_boolean("initial_input_grab") == 0 ||
791 // deprecated names:
792 fs_config_get_boolean("input_grab") == 0 ||
793 fs_config_get_boolean("grab_input") == 0) {
794 g_initial_input_grab = 0;
795 }
796
797 set_video_mode();
798
799 if (g_fs_ml_vblank_sync) {
800 fs_emu_log("*** Setting swap interval to 1 ***\n");
801 if (SDL_GL_SetSwapInterval(1) != 0) {
802 fs_emu_warning("SDL_GL_SetSwapInterval(1) failed");
803 }
804 }
805 else {
806 fs_emu_log("*** Setting swap interval to 0 ***\n");
807 SDL_GL_SetSwapInterval(0);
808 }
809
810 fs_log("initial input grab: %d\n", g_initial_input_grab);
811 if (g_initial_input_grab && !g_has_input_grab) {
812 fs_ml_set_input_grab(true);
813 }
814 fs_ml_show_cursor(0, 1);
815
816 /* This looks a bit peculiar, but it helps to show the window in
817 * fullscreen mode as soon as possible to reduce flickering,
818 at least under GNOME 3. */
819 glClearColor(0.0, 0.0, 0.0, 1.0);
820 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
821 SDL_GL_SwapWindow(g_fs_ml_window);
822 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
823 SDL_GL_SwapWindow(g_fs_ml_window);
824 int64_t start_time = fs_emu_monotonic_time();
825 SDL_Event event;
826 while (fs_emu_monotonic_time() - start_time < 100 * 1000) {
827 SDL_WaitEventTimeout(&event, 10);
828 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
829 SDL_GL_SwapWindow(g_fs_ml_window);
830 }
831
832 // this function must be called from the video thread
833 fs_log("init_opengl\n");
834 fse_init_video_opengl();
835
836 SDL_StartTextInput();
837
838 #ifdef WINDOWS
839 if (!fs_config_false(OPTION_RAW_INPUT)) {
840 fs_ml_init_raw_input();
841 }
842 #endif
843
844 fs_log("create windows is done\n");
845 return 1;
846 }
847
848 int g_fs_ml_running = 1;
849
850 #ifndef WINDOWS
851 // using separate implementation on Windows with raw input
fs_ml_clear_keyboard_modifier_state()852 void fs_ml_clear_keyboard_modifier_state()
853 {
854
855 }
856 #endif
857
858 #include "sdl2_keys.h"
859 // modifiers have values in SDL and SDL2, except META is renamed to GUI
860 #define KMOD_LMETA KMOD_LGUI
861 #define KMOD_RMETA KMOD_RGUI
862 #define KMOD_META (KMOD_LMETA|KMOD_RMETA)
863
on_resize(int width,int height)864 static void on_resize(int width, int height)
865 {
866 if (width == g_fs_ml_video_width && height == g_fs_ml_video_height) {
867 fs_log("got resize event, but size was unchanged\n");
868 return;
869 }
870 if (g_fs_emu_video_fullscreen) {
871 fs_log("not updating window size in fullscreen\n");
872 }
873 else if (width == g_fullscreen_width &&
874 height == g_fullscreen_height) {
875 fs_log("not setting window size to fullscreen size\n");
876 }
877 else {
878 g_window_width = width;
879 g_window_height = height;
880 fs_log("resize event %d %d\n", width, height);
881 }
882 g_fs_ml_video_width = width;
883 g_fs_ml_video_height = height;
884 }
885
fs_ml_event_loop(void)886 int fs_ml_event_loop(void)
887 {
888 // printf("fs_ml_event_loop\n");
889 int result = 0;
890 SDL_Event event;
891 while (SDL_PollEvent(&event)) {
892 switch(event.type) {
893 case SDL_QUIT:
894 fs_log("Received SDL_QUIT\n");
895 fs_ml_maybe_quit();
896 #ifdef FSE_DRIVERS
897 printf("returning 1 from fs_ml_event_loop\n");
898 result = 1;
899 #endif
900 continue;
901 case SDL_WINDOWEVENT:
902 if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
903 on_resize(event.window.data1, event.window.data2);
904 } else if (event.window.event == SDL_WINDOWEVENT_CLOSE) {
905 event.type = SDL_QUIT;
906 SDL_PushEvent(&event);
907 } else if (event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) {
908 if (g_grab_input_on_activate) {
909 fs_log("Window focus gained - grabbing input\n");
910 g_grab_input_on_activate = false;
911 fs_ml_set_input_grab(true);
912 #ifdef MACOSX
913 } else if (fs_ml_input_grab()) {
914 /* Input grab could be "lost" due to Cmd+Tab */
915 fs_log("[INPUT] Forcing re-grab of input on macOS\n");
916 fs_ml_set_input_grab(false);
917 fs_ml_set_input_grab(true);
918 #endif
919 }
920 }
921 continue;
922 case SDL_KEYDOWN:
923 case SDL_KEYUP:
924 if (g_fs_log_input) {
925 fs_log("SDL key sym %d mod %d scancode %d state %d repeat %d\n",
926 event.key.keysym.sym, event.key.keysym.mod,
927 event.key.keysym.scancode, event.key.state,
928 event.key.repeat);
929 }
930 if (event.key.repeat) {
931 continue;
932 }
933 if (event.key.keysym.sym == 0 && event.key.keysym.scancode == 0) {
934 /* ignore "ghost key" seen on OS X which without this
935 * specific check will cause the A key to be mysteriously
936 * pressed. */
937 if (g_fs_log_input) {
938 fs_log("- ignored key with keysym 0 and scancode 0\n");
939 }
940 continue;
941 }
942 /*
943 if (event.key.keysym.sym == SDLK_F12) {
944 g_f12_state = event.key.state ? FS_ML_KEY_MOD_F12 : 0;
945 printf("-- g_f12_state is %d\n", g_f12_state);
946 }
947 else if (event.key.keysym.sym == SDLK_F11) {
948 g_f11_state = event.key.state ? FS_ML_KEY_MOD_F11 : 0;
949 }
950 */
951
952 const Uint8* key_state;
953 int num_keys;
954 key_state = SDL_GetKeyboardState(&num_keys);
955 g_f11_state = key_state[SDL_SCANCODE_F11] ? FS_ML_KEY_MOD_F11 : 0;
956 g_f12_state = key_state[SDL_SCANCODE_F12] ? FS_ML_KEY_MOD_F12 : 0;
957
958 int key = -1;
959 if (event.key.keysym.scancode <= LAST_SDL2_SCANCODE) {
960 key = g_sdl2_keys[event.key.keysym.scancode];
961 }
962 #if defined(MACOSX)
963 #elif defined(WINDOWS)
964 #else
965 else if (event.key.keysym.sym == SDLK_MODE) {
966 key = SDLK_RALT;
967 }
968 #endif
969 else {
970 key = fs_ml_scancode_to_key(event.key.keysym.scancode);
971 }
972
973 #ifdef USE_SDL2
974 if (0) {
975 // the below trick does not currently work for SDL2, as
976 // there is no mapping yet for translated keys
977 }
978 #else
979 if (g_f12_state || g_f11_state) {
980 // leave translated key code in keysym
981 }
982 #endif
983 else if (key >= 0) {
984 if (g_fs_log_input) {
985 fs_log("- key code set to %d (was %d) based on "
986 "scancode %d\n", key, event.key.keysym.sym,
987 event.key.keysym.scancode);
988 }
989 event.key.keysym.sym = key;
990 }
991
992 int mod = event.key.keysym.mod;
993 if (mod & KMOD_LSHIFT || mod & KMOD_RSHIFT)
994 event.key.keysym.mod |= KMOD_SHIFT;
995 #if 0
996 if (mod & KMOD_LALT || mod & KMOD_RALT)
997 event.key.keysym.mod |= KMOD_ALT;
998 #endif
999 if (mod & KMOD_LCTRL || mod & KMOD_RCTRL)
1000 event.key.keysym.mod |= KMOD_CTRL;
1001 #if 0
1002 if (mod & KMOD_LMETA || mod & KMOD_RMETA)
1003 event.key.keysym.mod |= KMOD_META;
1004 #endif
1005
1006 /* Filter out other modidifers */
1007 event.key.keysym.mod &=
1008 KMOD_SHIFT | KMOD_ALT | KMOD_CTRL | KMOD_META;
1009 /* Add F11/F12 modifier state */
1010 event.key.keysym.mod |= g_f11_state | g_f12_state;
1011
1012 //printf("%d %d %d %d\n", event.key.keysym.mod,
1013 // KMOD_ALT, KMOD_LALT, KMOD_RALT);
1014 break;
1015 //case SDL_MOUSEBUTTONDOWN:
1016 // printf("--- mousebutton down ---\n");
1017 }
1018 fs_ml_event *new_event = NULL;
1019
1020 if (event.type == SDL_KEYDOWN) {
1021 new_event = fs_ml_alloc_event();
1022 new_event->type = FS_ML_KEYDOWN;
1023 new_event->key.keysym.sym = event.key.keysym.sym;
1024 new_event->key.keysym.mod = event.key.keysym.mod;
1025 new_event->key.state = event.key.state;
1026 }
1027 else if (event.type == SDL_KEYUP) {
1028 new_event = fs_ml_alloc_event();
1029 new_event->type = FS_ML_KEYUP;
1030 new_event->key.keysym.sym = event.key.keysym.sym;
1031 new_event->key.keysym.mod = event.key.keysym.mod;
1032 new_event->key.state = event.key.state;
1033 }
1034 else if (event.type == SDL_JOYBUTTONDOWN) {
1035 if (g_fs_log_input) {
1036 fs_log("SDL_JOYBUTTONDOWN which %d button %d state %d\n",
1037 event.jbutton.which, event.jbutton.button,
1038 event.jbutton.state);
1039 }
1040 new_event = fs_ml_alloc_event();
1041 new_event->type = FS_ML_JOYBUTTONDOWN;
1042 new_event->jbutton.which = \
1043 g_fs_ml_sdl_joystick_index_map[event.jbutton.which];
1044 new_event->jbutton.button = event.jbutton.button;
1045 new_event->jbutton.state = event.jbutton.state;
1046 }
1047 else if (event.type == SDL_JOYBUTTONUP) {
1048 if (g_fs_log_input) {
1049 fs_log("SDL_JOYBUTTONUP which %d button %d state %d\n",
1050 event.jbutton.which, event.jbutton.button,
1051 event.jbutton.state);
1052 }
1053 new_event = fs_ml_alloc_event();
1054 new_event->type = FS_ML_JOYBUTTONUP;
1055 new_event->jbutton.which = \
1056 g_fs_ml_sdl_joystick_index_map[event.jbutton.which];
1057 new_event->jbutton.button = event.jbutton.button;
1058 new_event->jbutton.state = event.jbutton.state;
1059 }
1060 else if (event.type == SDL_JOYAXISMOTION) {
1061 /* Not logging axis motion, too much noise */
1062 new_event = fs_ml_alloc_event();
1063 new_event->type = FS_ML_JOYAXISMOTION;
1064 new_event->jaxis.which = \
1065 g_fs_ml_sdl_joystick_index_map[event.jaxis.which];
1066 new_event->jaxis.axis = event.jaxis.axis;
1067 new_event->jaxis.value = event.jaxis.value;
1068 }
1069 else if (event.type == SDL_JOYHATMOTION) {
1070 if (g_fs_log_input) {
1071 fs_log("SDL_JOYHATMOTION which %d hat %d value %d\n",
1072 event.jhat.which, event.jhat.hat, event.jhat.value);
1073 }
1074 new_event = fs_ml_alloc_event();
1075 new_event->type = FS_ML_JOYHATMOTION;
1076 new_event->jhat.which = \
1077 g_fs_ml_sdl_joystick_index_map[event.jhat.which];
1078 new_event->jhat.hat = event.jhat.hat;
1079 new_event->jhat.value = event.jhat.value;
1080 }
1081 else if (event.type == SDL_MOUSEMOTION) {
1082 new_event = fs_ml_alloc_event();
1083 new_event->type = FS_ML_MOUSEMOTION;
1084 new_event->motion.device = g_fs_ml_first_mouse_index;
1085 new_event->motion.xrel = event.motion.xrel;
1086 new_event->motion.yrel = event.motion.yrel;
1087 /* Absolute window coordinates */
1088 new_event->motion.x = event.motion.x;
1089 new_event->motion.y = event.motion.y;
1090 //printf("ISREL %d\n", SDL_GetRelativeMouseMode());
1091
1092 if (g_fs_log_input) {
1093 fs_log("SDL mouse event x: %4d y: %4d xrel: %4d yrel: %4d\n",
1094 event.motion.x, event.motion.y,
1095 event.motion.xrel, event.motion.yrel);
1096 }
1097 }
1098 else if (event.type == SDL_MOUSEBUTTONDOWN) {
1099 new_event = fs_ml_alloc_event();
1100 new_event->type = FS_ML_MOUSEBUTTONDOWN;
1101 new_event->button.device = g_fs_ml_first_mouse_index;
1102 new_event->button.button = event.button.button;
1103 #ifdef MACOSX
1104 if (new_event->button.button == 1) {
1105 int mod = SDL_GetModState();
1106 if (mod & KMOD_ALT) {
1107 new_event->button.button = 2;
1108 }
1109 else if (mod & KMOD_CTRL) {
1110 new_event->button.button = 3;
1111 }
1112 }
1113 #endif
1114 new_event->button.state = event.button.state;
1115 }
1116 else if (event.type == SDL_MOUSEBUTTONUP) {
1117 new_event = fs_ml_alloc_event();
1118 new_event->type = FS_ML_MOUSEBUTTONUP;
1119 new_event->button.device = g_fs_ml_first_mouse_index;
1120 new_event->button.button = event.button.button;
1121 #ifdef MACOSX
1122 if (new_event->button.button == 1) {
1123 int mod = SDL_GetModState();
1124 if (mod & KMOD_ALT) {
1125 new_event->button.button = 2;
1126 }
1127 else if (mod & KMOD_CTRL) {
1128 new_event->button.button = 3;
1129 }
1130 }
1131 #endif
1132 new_event->button.state = event.button.state;
1133 }
1134 else if (event.type == SDL_MOUSEWHEEL) {
1135 /*
1136 if (event.wheel.which == SDL_TOUCH_MOUSEID) {
1137
1138 }
1139 */
1140 if (event.wheel.y) {
1141 if (g_fs_log_input) {
1142 fs_log("SDL mouse event y-scroll: %4d\n",
1143 event.wheel.y);
1144 }
1145 new_event = fs_ml_alloc_event();
1146 new_event->type = FS_ML_MOUSEBUTTONDOWN;
1147 if (event.wheel.y > 0) {
1148 new_event->button.button = FS_ML_BUTTON_WHEELUP;
1149 }
1150 else {
1151 new_event->button.button = FS_ML_BUTTON_WHEELDOWN;
1152 }
1153 new_event->button.device = g_fs_ml_first_mouse_index;
1154 new_event->button.state = 1;
1155 }
1156 }
1157 else if (event.type == SDL_TEXTINPUT) {
1158 new_event = fs_ml_alloc_event();
1159 new_event->type = FS_ML_TEXTINPUT;
1160 memcpy(&(new_event->text.text), &(event.text.text),
1161 MIN(TEXTINPUTEVENT_TEXT_SIZE, SDL_TEXTINPUTEVENT_TEXT_SIZE));
1162 new_event->text.text[TEXTINPUTEVENT_TEXT_SIZE - 1] = 0;
1163 }
1164
1165 if (new_event) {
1166 fs_ml_post_event(new_event);
1167 }
1168 }
1169 return result;
1170 }
1171
fs_ml_video_swap_buffers()1172 void fs_ml_video_swap_buffers()
1173 {
1174 SDL_GL_SwapWindow(g_fs_ml_window);
1175 }
1176
post_main_loop(void)1177 static void post_main_loop(void)
1178 {
1179 /* We want to improve the transitioning from FS-UAE back to e.g.
1180 * FS-UAE Game Center - avoid blinking cursor - so we try to move it (to
1181 * the bottom right of the screen). This probably requires that the
1182 * cursor is not grabbed (SDL often keeps the cursor in the center of the
1183 * screen then). */
1184 if (g_fs_emu_video_fullscreen) {
1185 if (SDL_getenv("FSGS_RETURN_CURSOR_TO") &&
1186 SDL_getenv("FSGS_RETURN_CURSOR_TO")[0]) {
1187 int x = -1; int y = -1;
1188 sscanf(SDL_getenv("FSGS_RETURN_CURSOR_TO"), "%d,%d", &x, &y);
1189 if (x != -1 && y != -1) {
1190 #if 0
1191 fs_log("trying to move mouse cursor to x=%d y=%d\n", x, y);
1192 #endif
1193 Uint8 data[] = "\0";
1194 SDL_SetWindowGrab(g_fs_ml_window, SDL_FALSE);
1195 /* Setting invisible cursor so we won't see it when we
1196 * enable the cursor in order to move it. */
1197 SDL_Cursor *cursor = SDL_CreateCursor(data, data, 8, 1, 0, 0);
1198 SDL_SetCursor(cursor);
1199 SDL_ShowCursor(SDL_ENABLE);
1200 SDL_WarpMouseInWindow(g_fs_ml_window, x, y);
1201 }
1202 }
1203 }
1204 }
1205
fs_ml_main_loop(void)1206 int fs_ml_main_loop(void)
1207 {
1208 while (g_fs_ml_running) {
1209 fs_ml_event_loop();
1210 process_video_events();
1211 fs_ml_prevent_power_saving();
1212 fs_ml_render_iteration();
1213 }
1214 post_main_loop();
1215 return 0;
1216 }
1217
fs_ml_video_init()1218 void fs_ml_video_init()
1219 {
1220 FS_ML_INIT_ONCE;
1221
1222 g_video_thread_id = fs_thread_id();
1223 g_video_cond = fs_condition_create();
1224 g_video_mutex = fs_mutex_create();
1225 g_video_event_queue = g_queue_new();
1226 g_video_event_mutex = fs_mutex_create();
1227
1228 fs_ml_render_init();
1229 }
1230
1231 #endif
1232