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