CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/SDL/SDLGLGraphicsContext.cpp
Views: 1401
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
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
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
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
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
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
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
bool SDLGLGraphicsContext::InitFromRenderThread(std::string *errorMessage) {
303
bool retval = GraphicsContext::InitFromRenderThread(errorMessage);
304
// HACK: Ensure that the swap interval is set after context creation (needed for kmsdrm)
305
SDL_GL_SetSwapInterval(1);
306
return retval;
307
}
308
309
// Returns 0 on success.
310
int SDLGLGraphicsContext::Init(SDL_Window *&window, int x, int y, int w, int h, int mode, std::string *error_message) {
311
struct GLVersionPair {
312
int major;
313
int minor;
314
};
315
GLVersionPair attemptVersions[] = {
316
#ifdef USING_GLES2
317
{3, 2}, {3, 1}, {3, 0}, {2, 0},
318
#else
319
{4, 6}, {4, 5}, {4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0},
320
{3, 3}, {3, 2}, {3, 1}, {3, 0},
321
#endif
322
};
323
324
// We start hidden because we have to try several windows.
325
// On Mac, full screen animates so each attempt is slow.
326
mode |= SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN;
327
328
SDL_GLContext glContext = nullptr;
329
for (size_t i = 0; i < ARRAY_SIZE(attemptVersions); ++i) {
330
const auto &ver = attemptVersions[i];
331
// Make sure to request a somewhat modern GL context at least - the
332
// latest supported by MacOS X (really, really sad...)
333
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, ver.major);
334
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, ver.minor);
335
#ifdef USING_GLES2
336
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
337
SetGLCoreContext(false);
338
#else
339
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
340
SetGLCoreContext(true);
341
#endif
342
343
window = SDL_CreateWindow("PPSSPP", x, y, w, h, mode);
344
if (!window) {
345
// Definitely don't shutdown here: we'll keep trying more GL versions.
346
fprintf(stderr, "SDL_CreateWindow failed for GL %d.%d: %s\n", ver.major, ver.minor, SDL_GetError());
347
// Skip the DestroyWindow.
348
continue;
349
}
350
351
glContext = SDL_GL_CreateContext(window);
352
if (glContext != nullptr) {
353
// Victory, got one.
354
break;
355
}
356
357
// Let's keep trying. To be safe, destroy the window - docs say needed to change profile.
358
// in practice, it doesn't seem to matter, but maybe it differs by platform.
359
SDL_DestroyWindow(window);
360
}
361
362
if (glContext == nullptr) {
363
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);
364
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 0);
365
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
366
SetGLCoreContext(false);
367
368
window = SDL_CreateWindow("PPSSPP", x, y, w, h, mode);
369
if (window == nullptr) {
370
NativeShutdown();
371
fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError());
372
SDL_Quit();
373
return 2;
374
}
375
376
glContext = SDL_GL_CreateContext(window);
377
if (glContext == nullptr) {
378
// OK, now we really have tried everything.
379
NativeShutdown();
380
fprintf(stderr, "SDL_GL_CreateContext failed: %s\n", SDL_GetError());
381
SDL_Quit();
382
return 2;
383
}
384
}
385
386
// At this point, we have a window that we can show finally.
387
SDL_ShowWindow(window);
388
389
#ifdef USING_EGL
390
if (EGL_Open(window) != 0) {
391
printf("EGL_Open() failed\n");
392
} else if (EGL_Init(window) != 0) {
393
printf("EGL_Init() failed\n");
394
} else {
395
useEGLSwap = true;
396
}
397
#endif
398
399
#ifndef USING_GLES2
400
// Some core profile drivers elide certain extensions from GL_EXTENSIONS/etc.
401
// glewExperimental allows us to force GLEW to search for the pointers anyway.
402
if (gl_extensions.IsCoreContext) {
403
glewExperimental = true;
404
}
405
GLenum glew_err = glewInit();
406
// glx is not required, igore.
407
if (glew_err != GLEW_OK && glew_err != GLEW_ERROR_NO_GLX_DISPLAY) {
408
printf("Failed to initialize glew!\n");
409
return 1;
410
}
411
// Unfortunately, glew will generate an invalid enum error, ignore.
412
if (gl_extensions.IsCoreContext)
413
glGetError();
414
415
if (GLEW_VERSION_2_0) {
416
printf("OpenGL 2.0 or higher.\n");
417
} else {
418
printf("Sorry, this program requires OpenGL 2.0.\n");
419
return 1;
420
}
421
#endif
422
423
// Finally we can do the regular initialization.
424
CheckGLExtensions();
425
draw_ = Draw::T3DCreateGLContext(true);
426
renderManager_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
427
renderManager_->SetInflightFrames(g_Config.iInflightFrames);
428
SetGPUBackend(GPUBackend::OPENGL);
429
bool success = draw_->CreatePresets();
430
_assert_(success);
431
renderManager_->SetSwapFunction([&]() {
432
#ifdef USING_EGL
433
if (useEGLSwap)
434
eglSwapBuffers(g_eglDisplay, g_eglSurface);
435
else
436
SDL_GL_SwapWindow(window_);
437
#else
438
SDL_GL_SwapWindow(window_);
439
#endif
440
});
441
442
renderManager_->SetSwapIntervalFunction([&](int interval) {
443
INFO_LOG(Log::G3D, "SDL SwapInterval: %d", interval);
444
SDL_GL_SetSwapInterval(interval);
445
});
446
447
window_ = window;
448
return 0;
449
}
450
451
void SDLGLGraphicsContext::ShutdownFromRenderThread() {
452
delete draw_;
453
draw_ = nullptr;
454
renderManager_ = nullptr;
455
456
#ifdef USING_EGL
457
EGL_Close();
458
#endif
459
SDL_GL_DeleteContext(glContext);
460
glContext = nullptr;
461
window_ = nullptr;
462
}
463
464