1/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2 * Mupen64plus-core - api/vidext.c *
3 * Mupen64Plus homepage: https://mupen64plus.org/ *
4 * Copyright (C) 2009 Richard Goedeken *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the *
18 * Free Software Foundation, Inc., *
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
20 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
21
22/* This file contains the Core video extension functions which will be exported
23 * outside of the core library.
24 */
25
26#include <SDL.h>
27#include <stdlib.h>
28#include <string.h>
29
30#define M64P_CORE_PROTOTYPES 1
31#include "osal/preproc.h"
32#include "../osd/osd.h"
33#include "callbacks.h"
34#include "m64p_types.h"
35#include "m64p_vidext.h"
36#include "vidext.h"
37
38#if SDL_VERSION_ATLEAST(2,0,0)
39 #ifndef USE_GLES
40 static int l_ForceCompatibilityContext = 1;
41 #endif
42#include "vidext_sdl2_compat.h"
43#endif
44
45/* local variables */
46static m64p_video_extension_functions l_ExternalVideoFuncTable = {12, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
47static int l_VideoExtensionActive = 0;
48static int l_VideoOutputActive = 0;
49static int l_Fullscreen = 0;
50static int l_SwapControl = 0;
51static SDL_Surface *l_pScreen = NULL;
52
53/* global function for use by frontend.c */
54m64p_error OverrideVideoFunctions(m64p_video_extension_functions *VideoFunctionStruct)
55{
56 /* check input data */
57 if (VideoFunctionStruct == NULL)
58 return M64ERR_INPUT_ASSERT;
59 if (VideoFunctionStruct->Functions < 12)
60 return M64ERR_INPUT_INVALID;
61
62 /* disable video extension if any of the function pointers are NULL */
63 if (VideoFunctionStruct->VidExtFuncInit == NULL ||
64 VideoFunctionStruct->VidExtFuncQuit == NULL ||
65 VideoFunctionStruct->VidExtFuncListModes == NULL ||
66 VideoFunctionStruct->VidExtFuncSetMode == NULL ||
67 VideoFunctionStruct->VidExtFuncGLGetProc == NULL ||
68 VideoFunctionStruct->VidExtFuncGLSetAttr == NULL ||
69 VideoFunctionStruct->VidExtFuncGLGetAttr == NULL ||
70 VideoFunctionStruct->VidExtFuncGLSwapBuf == NULL ||
71 VideoFunctionStruct->VidExtFuncSetCaption == NULL ||
72 VideoFunctionStruct->VidExtFuncToggleFS == NULL ||
73 VideoFunctionStruct->VidExtFuncResizeWindow == NULL ||
74 VideoFunctionStruct->VidExtFuncGLGetDefaultFramebuffer == NULL)
75 {
76 l_ExternalVideoFuncTable.Functions = 12;
77 memset(&l_ExternalVideoFuncTable.VidExtFuncInit, 0, 12 * sizeof(void *));
78 l_VideoExtensionActive = 0;
79 return M64ERR_SUCCESS;
80 }
81
82 /* otherwise copy in the override function pointers */
83 memcpy(&l_ExternalVideoFuncTable, VideoFunctionStruct, sizeof(m64p_video_extension_functions));
84 l_VideoExtensionActive = 1;
85 return M64ERR_SUCCESS;
86}
87
88int VidExt_InFullscreenMode(void)
89{
90 return l_Fullscreen;
91}
92
93int VidExt_VideoRunning(void)
94{
95 return l_VideoOutputActive;
96}
97
98/* video extension functions to be called by the video plugin */
99EXPORT m64p_error CALL VidExt_Init(void)
100{
101 /* call video extension override if necessary */
102 if (l_VideoExtensionActive)
103 return (*l_ExternalVideoFuncTable.VidExtFuncInit)();
104
105#if SDL_VERSION_ATLEAST(2,0,0)
106 SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
107 /* retrieve default swap interval/VSync */
108 l_SwapControl = SDL_GL_GetSwapInterval();
109#endif
110
111 if (SDL_InitSubSystem(SDL_INIT_VIDEO) == -1)
112 {
113 DebugMessage(M64MSG_ERROR, "SDL video subsystem init failed: %s", SDL_GetError());
114 return M64ERR_SYSTEM_FAIL;
115 }
116
117 return M64ERR_SUCCESS;
118}
119
120EXPORT m64p_error CALL VidExt_Quit(void)
121{
122 /* call video extension override if necessary */
123 if (l_VideoExtensionActive)
124 {
125 m64p_error rval = (*l_ExternalVideoFuncTable.VidExtFuncQuit)();
126 if (rval == M64ERR_SUCCESS)
127 {
128 l_VideoOutputActive = 0;
129 StateChanged(M64CORE_VIDEO_MODE, M64VIDEO_NONE);
130 }
131 return rval;
132 }
133
134 if (!SDL_WasInit(SDL_INIT_VIDEO))
135 return M64ERR_NOT_INIT;
136
137 SDL_ShowCursor(SDL_ENABLE);
138#if SDL_VERSION_ATLEAST(2,0,0)
139 SDL2_DestroyWindow();
140#endif
141 SDL_QuitSubSystem(SDL_INIT_VIDEO);
142 l_pScreen = NULL;
143 l_VideoOutputActive = 0;
144 StateChanged(M64CORE_VIDEO_MODE, M64VIDEO_NONE);
145
146 return M64ERR_SUCCESS;
147}
148
149EXPORT m64p_error CALL VidExt_ListFullscreenModes(m64p_2d_size *SizeArray, int *NumSizes)
150{
151 const SDL_VideoInfo *videoInfo;
152 unsigned int videoFlags;
153 SDL_Rect **modes;
154 int i;
155
156 /* call video extension override if necessary */
157 if (l_VideoExtensionActive)
158 return (*l_ExternalVideoFuncTable.VidExtFuncListModes)(SizeArray, NumSizes);
159
160 if (!SDL_WasInit(SDL_INIT_VIDEO))
161 return M64ERR_NOT_INIT;
162
163 /* get a list of SDL video modes */
164 videoFlags = SDL_OPENGL | SDL_FULLSCREEN;
165
166 if ((videoInfo = SDL_GetVideoInfo()) == NULL)
167 {
168 DebugMessage(M64MSG_ERROR, "SDL_GetVideoInfo query failed: %s", SDL_GetError());
169 return M64ERR_SYSTEM_FAIL;
170 }
171
172 if(videoInfo->hw_available)
173 videoFlags |= SDL_HWSURFACE;
174 else
175 videoFlags |= SDL_SWSURFACE;
176
177 modes = SDL_ListModes(NULL, videoFlags);
178
179 if (modes == (SDL_Rect **) 0 || modes == (SDL_Rect **) -1)
180 {
181 DebugMessage(M64MSG_WARNING, "No fullscreen SDL video modes available");
182 *NumSizes = 0;
183 return M64ERR_SUCCESS;
184 }
185
186 i = 0;
187 while (i < *NumSizes && modes[i] != NULL)
188 {
189 SizeArray[i].uiWidth = modes[i]->w;
190 SizeArray[i].uiHeight = modes[i]->h;
191 i++;
192 }
193
194 *NumSizes = i;
195
196 return M64ERR_SUCCESS;
197}
198
199EXPORT m64p_error CALL VidExt_SetVideoMode(int Width, int Height, int BitsPerPixel, m64p_video_mode ScreenMode, m64p_video_flags Flags)
200{
201 const SDL_VideoInfo *videoInfo;
202 int videoFlags = 0;
203
204 /* call video extension override if necessary */
205 if (l_VideoExtensionActive)
206 {
207 m64p_error rval = (*l_ExternalVideoFuncTable.VidExtFuncSetMode)(Width, Height, BitsPerPixel, ScreenMode, Flags);
208 l_Fullscreen = (rval == M64ERR_SUCCESS && ScreenMode == M64VIDEO_FULLSCREEN);
209 l_VideoOutputActive = (rval == M64ERR_SUCCESS);
210 if (l_VideoOutputActive)
211 {
212 StateChanged(M64CORE_VIDEO_MODE, ScreenMode);
213 StateChanged(M64CORE_VIDEO_SIZE, (Width << 16) | Height);
214 }
215 return rval;
216 }
217
218 if (!SDL_WasInit(SDL_INIT_VIDEO))
219 return M64ERR_NOT_INIT;
220
221 /* Get SDL video flags to use */
222 if (ScreenMode == M64VIDEO_WINDOWED)
223 {
224 videoFlags = SDL_OPENGL;
225 if (Flags & M64VIDEOFLAG_SUPPORT_RESIZING)
226 videoFlags |= SDL_RESIZABLE;
227 }
228 else if (ScreenMode == M64VIDEO_FULLSCREEN)
229 {
230 videoFlags = SDL_OPENGL | SDL_FULLSCREEN;
231 }
232 else
233 {
234 return M64ERR_INPUT_INVALID;
235 }
236
237 if ((videoInfo = SDL_GetVideoInfo()) == NULL)
238 {
239 DebugMessage(M64MSG_ERROR, "SDL_GetVideoInfo query failed: %s", SDL_GetError());
240 return M64ERR_SYSTEM_FAIL;
241 }
242 if (videoInfo->hw_available)
243 videoFlags |= SDL_HWSURFACE;
244 else
245 videoFlags |= SDL_SWSURFACE;
246
247 /* set the mode */
248 if (BitsPerPixel > 0)
249 DebugMessage(M64MSG_INFO, "Setting %i-bit video mode: %ix%i", BitsPerPixel, Width, Height);
250 else
251 DebugMessage(M64MSG_INFO, "Setting video mode: %ix%i", Width, Height);
252
253 l_pScreen = SDL_SetVideoMode(Width, Height, BitsPerPixel, videoFlags);
254 if (l_pScreen == NULL)
255 {
256 DebugMessage(M64MSG_ERROR, "SDL_SetVideoMode failed: %s", SDL_GetError());
257 return M64ERR_SYSTEM_FAIL;
258 }
259
260 SDL_ShowCursor(SDL_DISABLE);
261
262#if SDL_VERSION_ATLEAST(2,0,0)
263 /* set swap interval/VSync */
264 if (SDL_GL_SetSwapInterval(l_SwapControl) != 0)
265 {
266 DebugMessage(M64MSG_ERROR, "SDL swap interval (VSync) set failed: %s", SDL_GetError());
267 return M64ERR_SYSTEM_FAIL;
268 }
269#endif
270
271 l_Fullscreen = (ScreenMode == M64VIDEO_FULLSCREEN);
272 l_VideoOutputActive = 1;
273 StateChanged(M64CORE_VIDEO_MODE, ScreenMode);
274 StateChanged(M64CORE_VIDEO_SIZE, (Width << 16) | Height);
275 return M64ERR_SUCCESS;
276}
277
278EXPORT m64p_error CALL VidExt_ResizeWindow(int Width, int Height)
279{
280 const SDL_VideoInfo *videoInfo;
281 int videoFlags = 0;
282
283 /* call video extension override if necessary */
284 if (l_VideoExtensionActive)
285 {
286 m64p_error rval;
287 // shut down the OSD
288 osd_exit();
289 // re-create the OGL context
290 rval = (*l_ExternalVideoFuncTable.VidExtFuncResizeWindow)(Width, Height);
291 if (rval == M64ERR_SUCCESS)
292 {
293 StateChanged(M64CORE_VIDEO_SIZE, (Width << 16) | Height);
294 // re-create the On-Screen Display
295 osd_init(Width, Height);
296 }
297 return rval;
298 }
299
300 if (!l_VideoOutputActive || !SDL_WasInit(SDL_INIT_VIDEO))
301 return M64ERR_NOT_INIT;
302
303 if (l_Fullscreen)
304 {
305 DebugMessage(M64MSG_ERROR, "VidExt_ResizeWindow() called in fullscreen mode.");
306 return M64ERR_INVALID_STATE;
307 }
308
309 /* Get SDL video flags to use */
310 videoFlags = SDL_OPENGL | SDL_RESIZABLE;
311 if ((videoInfo = SDL_GetVideoInfo()) == NULL)
312 {
313 DebugMessage(M64MSG_ERROR, "SDL_GetVideoInfo query failed: %s", SDL_GetError());
314 return M64ERR_SYSTEM_FAIL;
315 }
316 if (videoInfo->hw_available)
317 videoFlags |= SDL_HWSURFACE;
318 else
319 videoFlags |= SDL_SWSURFACE;
320
321 // destroy the On-Screen Display
322 osd_exit();
323
324 /* set the re-sizing the screen will create a new OpenGL context */
325 l_pScreen = SDL_SetVideoMode(Width, Height, 0, videoFlags);
326 if (l_pScreen == NULL)
327 {
328 DebugMessage(M64MSG_ERROR, "SDL_SetVideoMode failed: %s", SDL_GetError());
329 return M64ERR_SYSTEM_FAIL;
330 }
331
332 StateChanged(M64CORE_VIDEO_SIZE, (Width << 16) | Height);
333 // re-create the On-Screen Display
334 osd_init(Width, Height);
335 return M64ERR_SUCCESS;
336}
337
338EXPORT m64p_error CALL VidExt_SetCaption(const char *Title)
339{
340 /* call video extension override if necessary */
341 if (l_VideoExtensionActive)
342 return (*l_ExternalVideoFuncTable.VidExtFuncSetCaption)(Title);
343
344 if (!SDL_WasInit(SDL_INIT_VIDEO))
345 return M64ERR_NOT_INIT;
346
347 SDL_WM_SetCaption(Title, "M64+ Video");
348
349 return M64ERR_SUCCESS;
350}
351
352EXPORT m64p_error CALL VidExt_ToggleFullScreen(void)
353{
354 /* call video extension override if necessary */
355 if (l_VideoExtensionActive)
356 {
357 m64p_error rval = (*l_ExternalVideoFuncTable.VidExtFuncToggleFS)();
358 if (rval == M64ERR_SUCCESS)
359 {
360 l_Fullscreen = !l_Fullscreen;
361 StateChanged(M64CORE_VIDEO_MODE, l_Fullscreen ? M64VIDEO_FULLSCREEN : M64VIDEO_WINDOWED);
362 }
363 return rval;
364 }
365
366 if (!SDL_WasInit(SDL_INIT_VIDEO))
367 return M64ERR_NOT_INIT;
368
369 /* TODO:
370 * SDL_WM_ToggleFullScreen doesn't work under Windows and others
371 * (see http://wiki.libsdl.org/moin.cgi/FAQWindows for explanation).
372 * Instead, we should call SDL_SetVideoMode with the SDL_FULLSCREEN flag.
373 * (see http://sdl.beuc.net/sdl.wiki/SDL_SetVideoMode), but on Windows
374 * this resets the OpenGL context and video plugins don't support it yet.
375 * Uncomment the next line to test it: */
376 //return VidExt_SetVideoMode(l_pScreen->w, l_pScreen->h, l_pScreen->format->BitsPerPixel, l_Fullscreen ? M64VIDEO_WINDOWED : M64VIDEO_FULLSCREEN);
377 if (SDL_WM_ToggleFullScreen(l_pScreen) == 1)
378 {
379 l_Fullscreen = !l_Fullscreen;
380 StateChanged(M64CORE_VIDEO_MODE, l_Fullscreen ? M64VIDEO_FULLSCREEN : M64VIDEO_WINDOWED);
381 return M64ERR_SUCCESS;
382 }
383
384 return M64ERR_SYSTEM_FAIL;
385}
386
387EXPORT m64p_function CALL VidExt_GL_GetProcAddress(const char* Proc)
388{
389 /* call video extension override if necessary */
390 if (l_VideoExtensionActive)
391 return (*l_ExternalVideoFuncTable.VidExtFuncGLGetProc)(Proc);
392
393 if (!SDL_WasInit(SDL_INIT_VIDEO))
394 return NULL;
395
396/* WARN: assume cast to m64p_function is supported by platform and disable warning accordingly */
397OSAL_WARNING_PUSH
398OSAL_NO_WARNING_FPTR_VOIDP_CAST
399 return (m64p_function)SDL_GL_GetProcAddress(Proc);
400OSAL_WARNING_POP
401}
402
403typedef struct {
404 m64p_GLattr m64Attr;
405 SDL_GLattr sdlAttr;
406} GLAttrMapNode;
407
408static const GLAttrMapNode GLAttrMap[] = {
409 { M64P_GL_DOUBLEBUFFER, SDL_GL_DOUBLEBUFFER },
410 { M64P_GL_BUFFER_SIZE, SDL_GL_BUFFER_SIZE },
411 { M64P_GL_DEPTH_SIZE, SDL_GL_DEPTH_SIZE },
412 { M64P_GL_RED_SIZE, SDL_GL_RED_SIZE },
413 { M64P_GL_GREEN_SIZE, SDL_GL_GREEN_SIZE },
414 { M64P_GL_BLUE_SIZE, SDL_GL_BLUE_SIZE },
415 { M64P_GL_ALPHA_SIZE, SDL_GL_ALPHA_SIZE },
416#if !SDL_VERSION_ATLEAST(1,3,0)
417 { M64P_GL_SWAP_CONTROL, SDL_GL_SWAP_CONTROL },
418#endif
419 { M64P_GL_MULTISAMPLEBUFFERS, SDL_GL_MULTISAMPLEBUFFERS },
420 { M64P_GL_MULTISAMPLESAMPLES, SDL_GL_MULTISAMPLESAMPLES }
421#if SDL_VERSION_ATLEAST(2,0,0)
422 ,{ M64P_GL_CONTEXT_MAJOR_VERSION, SDL_GL_CONTEXT_MAJOR_VERSION },
423 { M64P_GL_CONTEXT_MINOR_VERSION, SDL_GL_CONTEXT_MINOR_VERSION },
424 { M64P_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_MASK }
425#endif
426};
427static const int mapSize = sizeof(GLAttrMap) / sizeof(GLAttrMapNode);
428
429EXPORT m64p_error CALL VidExt_GL_SetAttribute(m64p_GLattr Attr, int Value)
430{
431 int i;
432
433 /* call video extension override if necessary */
434 if (l_VideoExtensionActive)
435 return (*l_ExternalVideoFuncTable.VidExtFuncGLSetAttr)(Attr, Value);
436
437 if (!SDL_WasInit(SDL_INIT_VIDEO))
438 return M64ERR_NOT_INIT;
439
440 if (Attr == M64P_GL_SWAP_CONTROL)
441 {
442 /* SDL swap interval/vsync needs to be set on current GL context, so save it for later */
443 l_SwapControl = Value;
444 }
445
446 /* translate the GL context type mask if necessary */
447#if SDL_VERSION_ATLEAST(2,0,0)
448 if (Attr == M64P_GL_CONTEXT_PROFILE_MASK)
449 {
450 switch (Value)
451 {
452 case M64P_GL_CONTEXT_PROFILE_CORE:
453 Value = SDL_GL_CONTEXT_PROFILE_CORE;
454#ifndef USE_GLES
455 l_ForceCompatibilityContext = 0;
456#endif
457 break;
458 case M64P_GL_CONTEXT_PROFILE_COMPATIBILITY:
459 Value = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
460 break;
461 case M64P_GL_CONTEXT_PROFILE_ES:
462 Value = SDL_GL_CONTEXT_PROFILE_ES;
463 break;
464 default:
465 Value = 0;
466 }
467 }
468#endif
469
470 for (i = 0; i < mapSize; i++)
471 {
472 if (GLAttrMap[i].m64Attr == Attr)
473 {
474 if (SDL_GL_SetAttribute(GLAttrMap[i].sdlAttr, Value) != 0)
475 return M64ERR_SYSTEM_FAIL;
476 return M64ERR_SUCCESS;
477 }
478 }
479
480 return M64ERR_INPUT_INVALID;
481}
482
483EXPORT m64p_error CALL VidExt_GL_GetAttribute(m64p_GLattr Attr, int *pValue)
484{
485 int i;
486
487 /* call video extension override if necessary */
488 if (l_VideoExtensionActive)
489 return (*l_ExternalVideoFuncTable.VidExtFuncGLGetAttr)(Attr, pValue);
490
491 if (!SDL_WasInit(SDL_INIT_VIDEO))
492 return M64ERR_NOT_INIT;
493
494#if SDL_VERSION_ATLEAST(2,0,0)
495 if (Attr == M64P_GL_SWAP_CONTROL)
496 {
497 *pValue = SDL_GL_GetSwapInterval();
498 return M64ERR_SUCCESS;
499 }
500#endif
501
502 for (i = 0; i < mapSize; i++)
503 {
504 if (GLAttrMap[i].m64Attr == Attr)
505 {
506 int NewValue = 0;
507 if (SDL_GL_GetAttribute(GLAttrMap[i].sdlAttr, &NewValue) != 0)
508 return M64ERR_SYSTEM_FAIL;
509 /* translate the GL context type mask if necessary */
510#if SDL_VERSION_ATLEAST(2,0,0)
511 if (Attr == M64P_GL_CONTEXT_PROFILE_MASK)
512 {
513 switch (NewValue)
514 {
515 case SDL_GL_CONTEXT_PROFILE_CORE:
516 NewValue = M64P_GL_CONTEXT_PROFILE_CORE;
517 break;
518 case SDL_GL_CONTEXT_PROFILE_COMPATIBILITY:
519 NewValue = M64P_GL_CONTEXT_PROFILE_COMPATIBILITY;
520 break;
521 case SDL_GL_CONTEXT_PROFILE_ES:
522 NewValue = M64P_GL_CONTEXT_PROFILE_ES;
523 break;
524 default:
525 NewValue = 0;
526 }
527 }
528#endif
529 *pValue = NewValue;
530 return M64ERR_SUCCESS;
531 }
532 }
533
534 return M64ERR_INPUT_INVALID;
535}
536
537EXPORT m64p_error CALL VidExt_GL_SwapBuffers(void)
538{
539 /* call video extension override if necessary */
540 if (l_VideoExtensionActive)
541 return (*l_ExternalVideoFuncTable.VidExtFuncGLSwapBuf)();
542
543 if (!SDL_WasInit(SDL_INIT_VIDEO))
544 return M64ERR_NOT_INIT;
545
546 SDL_GL_SwapBuffers();
547 return M64ERR_SUCCESS;
548}
549
550EXPORT uint32_t CALL VidExt_GL_GetDefaultFramebuffer(void)
551{
552 if (l_VideoExtensionActive)
553 return (*l_ExternalVideoFuncTable.VidExtFuncGLGetDefaultFramebuffer)();
554
555 return 0;
556}
557