1 #include <vector>
2
3 #include "SDLGLGraphicsContext.h"
4
5 #include "Common/GPU/OpenGL/GLFeatures.h"
6 #include "Common/GPU/thin3d_create.h"
7
8 #include "Common/System/NativeApp.h"
9 #include "Common/System/System.h"
10 #include "Common/System/Display.h"
11 #include "Core/Config.h"
12 #include "Core/ConfigValues.h"
13 #include "Core/System.h"
14
15 #if defined(USING_EGL)
16 #include "EGL/egl.h"
17 #endif
18
19 class GLRenderManager;
20
21 #if defined(USING_EGL)
22
23 // TODO: Move these into the class.
24 static EGLDisplay g_eglDisplay = EGL_NO_DISPLAY;
25 static EGLContext g_eglContext = nullptr;
26 static EGLSurface g_eglSurface = nullptr;
27 static EGLNativeDisplayType g_Display = nullptr;
28 static bool g_XDisplayOpen = false;
29 static EGLNativeWindowType g_Window = (EGLNativeWindowType)nullptr;
30 static bool useEGLSwap = false;
31
CheckEGLErrors(const char * file,int line)32 int CheckEGLErrors(const char *file, int line) {
33 EGLenum error;
34 const char *errortext = "unknown";
35 error = eglGetError();
36 switch (error)
37 {
38 case EGL_SUCCESS: case 0: return 0;
39 case EGL_NOT_INITIALIZED: errortext = "EGL_NOT_INITIALIZED"; break;
40 case EGL_BAD_ACCESS: errortext = "EGL_BAD_ACCESS"; break;
41 case EGL_BAD_ALLOC: errortext = "EGL_BAD_ALLOC"; break;
42 case EGL_BAD_ATTRIBUTE: errortext = "EGL_BAD_ATTRIBUTE"; break;
43 case EGL_BAD_CONTEXT: errortext = "EGL_BAD_CONTEXT"; break;
44 case EGL_BAD_CONFIG: errortext = "EGL_BAD_CONFIG"; break;
45 case EGL_BAD_CURRENT_SURFACE: errortext = "EGL_BAD_CURRENT_SURFACE"; break;
46 case EGL_BAD_DISPLAY: errortext = "EGL_BAD_DISPLAY"; break;
47 case EGL_BAD_SURFACE: errortext = "EGL_BAD_SURFACE"; break;
48 case EGL_BAD_MATCH: errortext = "EGL_BAD_MATCH"; break;
49 case EGL_BAD_PARAMETER: errortext = "EGL_BAD_PARAMETER"; break;
50 case EGL_BAD_NATIVE_PIXMAP: errortext = "EGL_BAD_NATIVE_PIXMAP"; break;
51 case EGL_BAD_NATIVE_WINDOW: errortext = "EGL_BAD_NATIVE_WINDOW"; break;
52 default: errortext = "unknown"; break;
53 }
54 printf( "ERROR: EGL Error %s detected in file %s at line %d (0x%X)\n", errortext, file, line, error );
55 return 1;
56 }
57
58 #define EGL_ERROR(str, check) { \
59 if (check) CheckEGLErrors( __FILE__, __LINE__ ); \
60 printf("EGL ERROR: " str "\n"); \
61 return 1; \
62 }
63
EGL_OpenInit()64 static bool EGL_OpenInit() {
65 if ((g_eglDisplay = eglGetDisplay(g_Display)) == EGL_NO_DISPLAY) {
66 EGL_ERROR("Unable to create EGL display.", true);
67 return false;
68 }
69 if (eglInitialize(g_eglDisplay, NULL, NULL) != EGL_TRUE) {
70 EGL_ERROR("Unable to initialize EGL display.", true);
71 eglTerminate(g_eglDisplay);
72 g_eglDisplay = EGL_NO_DISPLAY;
73 return false;
74 }
75
76 return true;
77 }
78
EGL_Open(SDL_Window * window)79 static int8_t EGL_Open(SDL_Window *window) {
80 #if defined(USING_FBDEV)
81 g_Display = (EGLNativeDisplayType)nullptr;
82 g_Window = (EGLNativeWindowType)nullptr;
83 #elif defined(__APPLE__)
84 g_Display = (EGLNativeDisplayType)XOpenDisplay(nullptr);
85 g_XDisplayOpen = g_Display != nullptr;
86 if (!g_XDisplayOpen)
87 EGL_ERROR("Unable to get display!", false);
88 g_Window = (EGLNativeWindowType)nullptr;
89 #else
90 // Get the SDL window native handle
91 SDL_SysWMinfo sysInfo{};
92 SDL_VERSION(&sysInfo.version);
93 if (!SDL_GetWindowWMInfo(window, &sysInfo)) {
94 printf("ERROR: Unable to retrieve native window handle\n");
95 g_Display = (EGLNativeDisplayType)XOpenDisplay(nullptr);
96 g_XDisplayOpen = g_Display != nullptr;
97 if (!g_XDisplayOpen)
98 EGL_ERROR("Unable to get display!", false);
99 g_Window = (EGLNativeWindowType)nullptr;
100 } else {
101 switch (sysInfo.subsystem) {
102 case SDL_SYSWM_X11:
103 g_Display = (EGLNativeDisplayType)sysInfo.info.x11.display;
104 g_Window = (EGLNativeWindowType)sysInfo.info.x11.window;
105 break;
106 #if defined(SDL_VIDEO_DRIVER_DIRECTFB)
107 case SDL_SYSWM_DIRECTFB:
108 g_Display = (EGLNativeDisplayType)EGL_DEFAULT_DISPLAY;
109 g_Window = (EGLNativeWindowType)sysInfo.info.dfb.surface;
110 break;
111 #endif
112 #if SDL_VERSION_ATLEAST(2, 0, 2) && defined(SDL_VIDEO_DRIVER_WAYLAND)
113 case SDL_SYSWM_WAYLAND:
114 g_Display = (EGLNativeDisplayType)sysInfo.info.wl.display;
115 g_Window = (EGLNativeWindowType)sysInfo.info.wl.shell_surface;
116 break;
117 #endif
118 #if SDL_VERSION_ATLEAST(2, 0, 5) && defined(SDL_VIDEO_DRIVER_VIVANTE)
119 case SDL_SYSWM_VIVANTE:
120 g_Display = (EGLNativeDisplayType)sysInfo.info.vivante.display;
121 g_Window = (EGLNativeWindowType)sysInfo.info.vivante.window;
122 break;
123 #endif
124 }
125
126 if (!EGL_OpenInit()) {
127 // Let's try again with X11.
128 g_Display = (EGLNativeDisplayType)XOpenDisplay(nullptr);
129 g_XDisplayOpen = g_Display != nullptr;
130 if (!g_XDisplayOpen)
131 EGL_ERROR("Unable to get display!", false);
132 g_Window = (EGLNativeWindowType)nullptr;
133 }
134 }
135
136 #endif
137 if (g_eglDisplay == EGL_NO_DISPLAY)
138 EGL_OpenInit();
139 return g_eglDisplay == EGL_NO_DISPLAY ? 1 : 0;
140 }
141
142 #ifndef EGL_OPENGL_ES3_BIT_KHR
143 #define EGL_OPENGL_ES3_BIT_KHR (1 << 6)
144 #endif
145
EGL_FindConfig(int * contextVersion)146 EGLConfig EGL_FindConfig(int *contextVersion) {
147 std::vector<EGLConfig> configs;
148 EGLint numConfigs = 0;
149
150 EGLBoolean result = eglGetConfigs(g_eglDisplay, nullptr, 0, &numConfigs);
151 if (result != EGL_TRUE || numConfigs == 0) {
152 return nullptr;
153 }
154
155 configs.resize(numConfigs);
156 result = eglGetConfigs(g_eglDisplay, &configs[0], numConfigs, &numConfigs);
157 if (result != EGL_TRUE || numConfigs == 0) {
158 return nullptr;
159 }
160
161 // Mali (ARM) seems to have compositing issues with alpha backbuffers.
162 // EGL_TRANSPARENT_TYPE doesn't help.
163 const char *vendorName = eglQueryString(g_eglDisplay, EGL_VENDOR);
164 const bool avoidAlphaGLES = vendorName && !strcmp(vendorName, "ARM");
165
166 EGLConfig best = nullptr;
167 int bestScore = 0;
168 int bestContextVersion = 0;
169 for (const EGLConfig &config : configs) {
170 auto readConfig = [&](EGLint attr) -> EGLint {
171 EGLint val = 0;
172 eglGetConfigAttrib(g_eglDisplay, config, attr, &val);
173 return val;
174 };
175
176 // We don't want HDR modes with more than 8 bits per component.
177 // But let's assume some color is better than no color at all.
178 auto readConfigMax = [&](EGLint attr, EGLint m, EGLint def = 1) -> EGLint {
179 EGLint val = readConfig(attr);
180 return val > m ? def : val;
181 };
182
183 int colorScore = readConfigMax(EGL_RED_SIZE, 8) + readConfigMax(EGL_BLUE_SIZE, 8) + readConfigMax(EGL_GREEN_SIZE, 8);
184 int alphaScore = readConfigMax(EGL_ALPHA_SIZE, 8);
185 int depthScore = readConfig(EGL_DEPTH_SIZE);
186 int levelScore = readConfig(EGL_LEVEL) == 0 ? 100 : 0;
187 int samplesScore = readConfig(EGL_SAMPLES) == 0 ? 100 : 0;
188 int sampleBufferScore = readConfig(EGL_SAMPLE_BUFFERS) == 0 ? 100 : 0;
189 int stencilScore = readConfig(EGL_STENCIL_SIZE);
190 int transparentScore = readConfig(EGL_TRANSPARENT_TYPE) == EGL_NONE ? 50 : 0;
191
192 EGLint caveat = readConfig(EGL_CONFIG_CAVEAT);
193 // Let's assume that non-conformant configs aren't so awful.
194 int caveatScore = caveat == EGL_NONE ? 100 : (caveat == EGL_NON_CONFORMANT_CONFIG ? 95 : 0);
195
196 #ifndef USING_FBDEV
197 EGLint surfaceType = readConfig(EGL_SURFACE_TYPE);
198 // Only try a non-Window config in the worst case when there are only non-Window configs.
199 int surfaceScore = (surfaceType & EGL_WINDOW_BIT) ? 1000 : 0;
200 #endif
201
202 EGLint renderable = readConfig(EGL_RENDERABLE_TYPE);
203 bool renderableGLES3 = (renderable & EGL_OPENGL_ES3_BIT_KHR) != 0;
204 bool renderableGLES2 = (renderable & EGL_OPENGL_ES2_BIT) != 0;
205 bool renderableGL = (renderable & EGL_OPENGL_BIT) != 0;
206 #ifdef USING_GLES2
207 int renderableScoreGLES = renderableGLES3 ? 100 : (renderableGLES2 ? 80 : 0);
208 int renderableScoreGL = 0;
209 #else
210 int renderableScoreGLES = 0;
211 int renderableScoreGL = renderableGL ? 100 : (renderableGLES3 ? 80 : 0);
212 #endif
213
214 if (avoidAlphaGLES && renderableScoreGLES > 0) {
215 alphaScore = 8 - alphaScore;
216 }
217
218 int score = 0;
219 // Here's a good place to play with the weights to pick a better config.
220 score += colorScore * 10 + alphaScore * 2;
221 score += depthScore * 5 + stencilScore;
222 score += levelScore + samplesScore + sampleBufferScore + transparentScore;
223 score += caveatScore + renderableScoreGLES + renderableScoreGL;
224
225 #ifndef USING_FBDEV
226 score += surfaceScore;
227 #endif
228
229 if (score > bestScore) {
230 bestScore = score;
231 best = config;
232 bestContextVersion = renderableGLES3 ? 3 : (renderableGLES2 ? 2 : 0);
233 }
234 }
235
236 *contextVersion = bestContextVersion;
237 return best;
238 }
239
EGL_Init(SDL_Window * window)240 int8_t EGL_Init(SDL_Window *window) {
241 int contextVersion = 0;
242 EGLConfig eglConfig = EGL_FindConfig(&contextVersion);
243 if (!eglConfig) {
244 EGL_ERROR("Unable to find a usable EGL config.", true);
245 return 1;
246 }
247
248 EGLint contextAttributes[] = {
249 EGL_CONTEXT_CLIENT_VERSION, contextVersion,
250 EGL_NONE,
251 };
252 if (contextVersion == 0) {
253 contextAttributes[0] = EGL_NONE;
254 }
255
256 g_eglContext = eglCreateContext(g_eglDisplay, eglConfig, nullptr, contextAttributes);
257 if (g_eglContext == EGL_NO_CONTEXT) {
258 EGL_ERROR("Unable to create GLES context!", true);
259 return 1;
260 }
261
262 g_eglSurface = eglCreateWindowSurface(g_eglDisplay, eglConfig, g_Window, nullptr);
263 if (g_eglSurface == EGL_NO_SURFACE) {
264 EGL_ERROR("Unable to create EGL surface!", true);
265 return 1;
266 }
267
268 if (eglMakeCurrent(g_eglDisplay, g_eglSurface, g_eglSurface, g_eglContext) != EGL_TRUE) {
269 EGL_ERROR("Unable to make GLES context current.", true);
270 return 1;
271 }
272
273 return 0;
274 }
275
EGL_Close()276 void EGL_Close() {
277 if (g_eglDisplay != EGL_NO_DISPLAY) {
278 eglMakeCurrent(g_eglDisplay, NULL, NULL, EGL_NO_CONTEXT);
279 if (g_eglContext != NULL) {
280 eglDestroyContext(g_eglDisplay, g_eglContext);
281 }
282 if (g_eglSurface != NULL) {
283 eglDestroySurface(g_eglDisplay, g_eglSurface);
284 }
285 eglTerminate(g_eglDisplay);
286 g_eglDisplay = EGL_NO_DISPLAY;
287 }
288 if (g_Display != nullptr) {
289 #if !defined(USING_FBDEV)
290 if (g_XDisplayOpen)
291 XCloseDisplay((Display *)g_Display);
292 #endif
293 g_XDisplayOpen = false;
294 g_Display = nullptr;
295 }
296 g_eglSurface = NULL;
297 g_eglContext = NULL;
298 }
299
300 #endif // USING_EGL
301
302 // Returns 0 on success.
Init(SDL_Window * & window,int x,int y,int mode,std::string * error_message)303 int SDLGLGraphicsContext::Init(SDL_Window *&window, int x, int y, int mode, std::string *error_message) {
304 struct GLVersionPair {
305 int major;
306 int minor;
307 };
308 GLVersionPair attemptVersions[] = {
309 #ifdef USING_GLES2
310 {3, 2}, {3, 1}, {3, 0}, {2, 0},
311 #else
312 {4, 6}, {4, 5}, {4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0},
313 {3, 3}, {3, 2}, {3, 1}, {3, 0},
314 #endif
315 };
316
317 // We start hidden because we have to try several windows.
318 // On Mac, full screen animates so each attempt is slow.
319 mode |= SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN;
320
321 SDL_GLContext glContext = nullptr;
322 for (size_t i = 0; i < ARRAY_SIZE(attemptVersions); ++i) {
323 const auto &ver = attemptVersions[i];
324 // Make sure to request a somewhat modern GL context at least - the
325 // latest supported by MacOS X (really, really sad...)
326 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, ver.major);
327 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, ver.minor);
328 #ifdef USING_GLES2
329 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
330 SetGLCoreContext(false);
331 #else
332 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
333 SetGLCoreContext(true);
334 #endif
335
336 window = SDL_CreateWindow("PPSSPP", x, y, pixel_xres, pixel_yres, mode);
337 if (!window) {
338 // Definitely don't shutdown here: we'll keep trying more GL versions.
339 fprintf(stderr, "SDL_CreateWindow failed for GL %d.%d: %s\n", ver.major, ver.minor, SDL_GetError());
340 // Skip the DestroyWindow.
341 continue;
342 }
343
344 glContext = SDL_GL_CreateContext(window);
345 if (glContext != nullptr) {
346 // Victory, got one.
347 break;
348 }
349
350 // Let's keep trying. To be safe, destroy the window - docs say needed to change profile.
351 // in practice, it doesn't seem to matter, but maybe it differs by platform.
352 SDL_DestroyWindow(window);
353 }
354
355 if (glContext == nullptr) {
356 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);
357 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 0);
358 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
359 SetGLCoreContext(false);
360
361 window = SDL_CreateWindow("PPSSPP", x, y, pixel_xres, pixel_yres, mode);
362 if (window == nullptr) {
363 NativeShutdown();
364 fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError());
365 SDL_Quit();
366 return 2;
367 }
368
369 glContext = SDL_GL_CreateContext(window);
370 if (glContext == nullptr) {
371 NativeShutdown();
372 fprintf(stderr, "SDL_GL_CreateContext failed: %s\n", SDL_GetError());
373 SDL_Quit();
374 return 2;
375 }
376 }
377
378 // At this point, we have a window that we can show finally.
379 SDL_ShowWindow(window);
380
381 #ifdef USING_EGL
382 if (EGL_Open(window) != 0) {
383 printf("EGL_Open() failed\n");
384 } else if (EGL_Init(window) != 0) {
385 printf("EGL_Init() failed\n");
386 } else {
387 useEGLSwap = true;
388 }
389 #endif
390
391 #ifndef USING_GLES2
392 // Some core profile drivers elide certain extensions from GL_EXTENSIONS/etc.
393 // glewExperimental allows us to force GLEW to search for the pointers anyway.
394 if (gl_extensions.IsCoreContext) {
395 glewExperimental = true;
396 }
397 if (GLEW_OK != glewInit()) {
398 printf("Failed to initialize glew!\n");
399 return 1;
400 }
401 // Unfortunately, glew will generate an invalid enum error, ignore.
402 if (gl_extensions.IsCoreContext)
403 glGetError();
404
405 if (GLEW_VERSION_2_0) {
406 printf("OpenGL 2.0 or higher.\n");
407 } else {
408 printf("Sorry, this program requires OpenGL 2.0.\n");
409 return 1;
410 }
411 #endif
412
413 // Finally we can do the regular initialization.
414 CheckGLExtensions();
415 draw_ = Draw::T3DCreateGLContext();
416 renderManager_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
417 SetGPUBackend(GPUBackend::OPENGL);
418 bool success = draw_->CreatePresets();
419 _assert_(success);
420 renderManager_->SetSwapFunction([&]() {
421 #ifdef USING_EGL
422 if (useEGLSwap)
423 eglSwapBuffers(g_eglDisplay, g_eglSurface);
424 else
425 SDL_GL_SwapWindow(window_);
426 #else
427 SDL_GL_SwapWindow(window_);
428 #endif
429 });
430
431 renderManager_->SetSwapIntervalFunction([&](int interval) {
432 INFO_LOG(G3D, "SDL SwapInterval: %d", interval);
433 SDL_GL_SetSwapInterval(interval);
434 });
435 window_ = window;
436 return 0;
437 }
438
SwapInterval(int interval)439 void SDLGLGraphicsContext::SwapInterval(int interval) {
440 renderManager_->SwapInterval(interval);
441 }
442
Shutdown()443 void SDLGLGraphicsContext::Shutdown() {
444 }
445
ShutdownFromRenderThread()446 void SDLGLGraphicsContext::ShutdownFromRenderThread() {
447 delete draw_;
448 draw_ = nullptr;
449
450 #ifdef USING_EGL
451 EGL_Close();
452 #endif
453 SDL_GL_DeleteContext(glContext);
454 }
455