ppsspp/SDL/SDLGLGraphicsContext.cpp
Henrik Rydgård 0e3a84b4a8 Move most GPU things to Common.
It works after the move, on Windows and Android at least.

Deletes the D3DX9 shader compiler loader, which was not used.
2020-10-04 23:39:02 +02:00

455 lines
14 KiB
C++

#include <vector>
#include "SDLGLGraphicsContext.h"
#include "Common/GPU/OpenGL/GLFeatures.h"
#include "Common/GPU/thin3d_create.h"
#include "Common/System/NativeApp.h"
#include "Common/System/System.h"
#include "Common/System/Display.h"
#include "Core/Config.h"
#include "Core/ConfigValues.h"
#include "Core/System.h"
#if defined(USING_EGL)
#include "EGL/egl.h"
#endif
class GLRenderManager;
#if defined(USING_EGL)
// TODO: Move these into the class.
static EGLDisplay g_eglDisplay = EGL_NO_DISPLAY;
static EGLContext g_eglContext = nullptr;
static EGLSurface g_eglSurface = nullptr;
static EGLNativeDisplayType g_Display = nullptr;
static bool g_XDisplayOpen = false;
static EGLNativeWindowType g_Window = (EGLNativeWindowType)nullptr;
static bool useEGLSwap = false;
int CheckEGLErrors(const char *file, int line) {
EGLenum error;
const char *errortext = "unknown";
error = eglGetError();
switch (error)
{
case EGL_SUCCESS: case 0: return 0;
case EGL_NOT_INITIALIZED: errortext = "EGL_NOT_INITIALIZED"; break;
case EGL_BAD_ACCESS: errortext = "EGL_BAD_ACCESS"; break;
case EGL_BAD_ALLOC: errortext = "EGL_BAD_ALLOC"; break;
case EGL_BAD_ATTRIBUTE: errortext = "EGL_BAD_ATTRIBUTE"; break;
case EGL_BAD_CONTEXT: errortext = "EGL_BAD_CONTEXT"; break;
case EGL_BAD_CONFIG: errortext = "EGL_BAD_CONFIG"; break;
case EGL_BAD_CURRENT_SURFACE: errortext = "EGL_BAD_CURRENT_SURFACE"; break;
case EGL_BAD_DISPLAY: errortext = "EGL_BAD_DISPLAY"; break;
case EGL_BAD_SURFACE: errortext = "EGL_BAD_SURFACE"; break;
case EGL_BAD_MATCH: errortext = "EGL_BAD_MATCH"; break;
case EGL_BAD_PARAMETER: errortext = "EGL_BAD_PARAMETER"; break;
case EGL_BAD_NATIVE_PIXMAP: errortext = "EGL_BAD_NATIVE_PIXMAP"; break;
case EGL_BAD_NATIVE_WINDOW: errortext = "EGL_BAD_NATIVE_WINDOW"; break;
default: errortext = "unknown"; break;
}
printf( "ERROR: EGL Error %s detected in file %s at line %d (0x%X)\n", errortext, file, line, error );
return 1;
}
#define EGL_ERROR(str, check) { \
if (check) CheckEGLErrors( __FILE__, __LINE__ ); \
printf("EGL ERROR: " str "\n"); \
return 1; \
}
static bool EGL_OpenInit() {
if ((g_eglDisplay = eglGetDisplay(g_Display)) == EGL_NO_DISPLAY) {
EGL_ERROR("Unable to create EGL display.", true);
return false;
}
if (eglInitialize(g_eglDisplay, NULL, NULL) != EGL_TRUE) {
EGL_ERROR("Unable to initialize EGL display.", true);
eglTerminate(g_eglDisplay);
g_eglDisplay = EGL_NO_DISPLAY;
return false;
}
return true;
}
static int8_t EGL_Open(SDL_Window *window) {
#if defined(USING_FBDEV)
g_Display = (EGLNativeDisplayType)nullptr;
g_Window = (EGLNativeWindowType)nullptr;
#elif defined(__APPLE__)
g_Display = (EGLNativeDisplayType)XOpenDisplay(nullptr);
g_XDisplayOpen = g_Display != nullptr;
if (!g_XDisplayOpen)
EGL_ERROR("Unable to get display!", false);
g_Window = (EGLNativeWindowType)nullptr;
#else
// Get the SDL window native handle
SDL_SysWMinfo sysInfo{};
SDL_VERSION(&sysInfo.version);
if (!SDL_GetWindowWMInfo(window, &sysInfo)) {
printf("ERROR: Unable to retrieve native window handle\n");
g_Display = (EGLNativeDisplayType)XOpenDisplay(nullptr);
g_XDisplayOpen = g_Display != nullptr;
if (!g_XDisplayOpen)
EGL_ERROR("Unable to get display!", false);
g_Window = (EGLNativeWindowType)nullptr;
} else {
switch (sysInfo.subsystem) {
case SDL_SYSWM_X11:
g_Display = (EGLNativeDisplayType)sysInfo.info.x11.display;
g_Window = (EGLNativeWindowType)sysInfo.info.x11.window;
break;
#if defined(SDL_VIDEO_DRIVER_DIRECTFB)
case SDL_SYSWM_DIRECTFB:
g_Display = (EGLNativeDisplayType)EGL_DEFAULT_DISPLAY;
g_Window = (EGLNativeWindowType)sysInfo.info.dfb.surface;
break;
#endif
#if SDL_VERSION_ATLEAST(2, 0, 2) && defined(SDL_VIDEO_DRIVER_WAYLAND)
case SDL_SYSWM_WAYLAND:
g_Display = (EGLNativeDisplayType)sysInfo.info.wl.display;
g_Window = (EGLNativeWindowType)sysInfo.info.wl.shell_surface;
break;
#endif
#if SDL_VERSION_ATLEAST(2, 0, 5) && defined(SDL_VIDEO_DRIVER_VIVANTE)
case SDL_SYSWM_VIVANTE:
g_Display = (EGLNativeDisplayType)sysInfo.info.vivante.display;
g_Window = (EGLNativeWindowType)sysInfo.info.vivante.window;
break;
#endif
}
if (!EGL_OpenInit()) {
// Let's try again with X11.
g_Display = (EGLNativeDisplayType)XOpenDisplay(nullptr);
g_XDisplayOpen = g_Display != nullptr;
if (!g_XDisplayOpen)
EGL_ERROR("Unable to get display!", false);
g_Window = (EGLNativeWindowType)nullptr;
}
}
#endif
if (g_eglDisplay == EGL_NO_DISPLAY)
EGL_OpenInit();
return g_eglDisplay == EGL_NO_DISPLAY ? 1 : 0;
}
#ifndef EGL_OPENGL_ES3_BIT_KHR
#define EGL_OPENGL_ES3_BIT_KHR (1 << 6)
#endif
EGLConfig EGL_FindConfig(int *contextVersion) {
std::vector<EGLConfig> configs;
EGLint numConfigs = 0;
EGLBoolean result = eglGetConfigs(g_eglDisplay, nullptr, 0, &numConfigs);
if (result != EGL_TRUE || numConfigs == 0) {
return nullptr;
}
configs.resize(numConfigs);
result = eglGetConfigs(g_eglDisplay, &configs[0], numConfigs, &numConfigs);
if (result != EGL_TRUE || numConfigs == 0) {
return nullptr;
}
// Mali (ARM) seems to have compositing issues with alpha backbuffers.
// EGL_TRANSPARENT_TYPE doesn't help.
const char *vendorName = eglQueryString(g_eglDisplay, EGL_VENDOR);
const bool avoidAlphaGLES = vendorName && !strcmp(vendorName, "ARM");
EGLConfig best = nullptr;
int bestScore = 0;
int bestContextVersion = 0;
for (const EGLConfig &config : configs) {
auto readConfig = [&](EGLint attr) -> EGLint {
EGLint val = 0;
eglGetConfigAttrib(g_eglDisplay, config, attr, &val);
return val;
};
// We don't want HDR modes with more than 8 bits per component.
// But let's assume some color is better than no color at all.
auto readConfigMax = [&](EGLint attr, EGLint m, EGLint def = 1) -> EGLint {
EGLint val = readConfig(attr);
return val > m ? def : val;
};
int colorScore = readConfigMax(EGL_RED_SIZE, 8) + readConfigMax(EGL_BLUE_SIZE, 8) + readConfigMax(EGL_GREEN_SIZE, 8);
int alphaScore = readConfigMax(EGL_ALPHA_SIZE, 8);
int depthScore = readConfig(EGL_DEPTH_SIZE);
int levelScore = readConfig(EGL_LEVEL) == 0 ? 100 : 0;
int samplesScore = readConfig(EGL_SAMPLES) == 0 ? 100 : 0;
int sampleBufferScore = readConfig(EGL_SAMPLE_BUFFERS) == 0 ? 100 : 0;
int stencilScore = readConfig(EGL_STENCIL_SIZE);
int transparentScore = readConfig(EGL_TRANSPARENT_TYPE) == EGL_NONE ? 50 : 0;
EGLint caveat = readConfig(EGL_CONFIG_CAVEAT);
// Let's assume that non-conformant configs aren't so awful.
int caveatScore = caveat == EGL_NONE ? 100 : (caveat == EGL_NON_CONFORMANT_CONFIG ? 95 : 0);
#ifndef USING_FBDEV
EGLint surfaceType = readConfig(EGL_SURFACE_TYPE);
// Only try a non-Window config in the worst case when there are only non-Window configs.
int surfaceScore = (surfaceType & EGL_WINDOW_BIT) ? 1000 : 0;
#endif
EGLint renderable = readConfig(EGL_RENDERABLE_TYPE);
bool renderableGLES3 = (renderable & EGL_OPENGL_ES3_BIT_KHR) != 0;
bool renderableGLES2 = (renderable & EGL_OPENGL_ES2_BIT) != 0;
bool renderableGL = (renderable & EGL_OPENGL_BIT) != 0;
#ifdef USING_GLES2
int renderableScoreGLES = renderableGLES3 ? 100 : (renderableGLES2 ? 80 : 0);
int renderableScoreGL = 0;
#else
int renderableScoreGLES = 0;
int renderableScoreGL = renderableGL ? 100 : (renderableGLES3 ? 80 : 0);
#endif
if (avoidAlphaGLES && renderableScoreGLES > 0) {
alphaScore = 8 - alphaScore;
}
int score = 0;
// Here's a good place to play with the weights to pick a better config.
score += colorScore * 10 + alphaScore * 2;
score += depthScore * 5 + stencilScore;
score += levelScore + samplesScore + sampleBufferScore + transparentScore;
score += caveatScore + renderableScoreGLES + renderableScoreGL;
#ifndef USING_FBDEV
score += surfaceScore;
#endif
if (score > bestScore) {
bestScore = score;
best = config;
bestContextVersion = renderableGLES3 ? 3 : (renderableGLES2 ? 2 : 0);
}
}
*contextVersion = bestContextVersion;
return best;
}
int8_t EGL_Init(SDL_Window *window) {
int contextVersion = 0;
EGLConfig eglConfig = EGL_FindConfig(&contextVersion);
if (!eglConfig) {
EGL_ERROR("Unable to find a usable EGL config.", true);
return 1;
}
EGLint contextAttributes[] = {
EGL_CONTEXT_CLIENT_VERSION, contextVersion,
EGL_NONE,
};
if (contextVersion == 0) {
contextAttributes[0] = EGL_NONE;
}
g_eglContext = eglCreateContext(g_eglDisplay, eglConfig, nullptr, contextAttributes);
if (g_eglContext == EGL_NO_CONTEXT) {
EGL_ERROR("Unable to create GLES context!", true);
return 1;
}
g_eglSurface = eglCreateWindowSurface(g_eglDisplay, eglConfig, g_Window, nullptr);
if (g_eglSurface == EGL_NO_SURFACE) {
EGL_ERROR("Unable to create EGL surface!", true);
return 1;
}
if (eglMakeCurrent(g_eglDisplay, g_eglSurface, g_eglSurface, g_eglContext) != EGL_TRUE) {
EGL_ERROR("Unable to make GLES context current.", true);
return 1;
}
return 0;
}
void EGL_Close() {
if (g_eglDisplay != EGL_NO_DISPLAY) {
eglMakeCurrent(g_eglDisplay, NULL, NULL, EGL_NO_CONTEXT);
if (g_eglContext != NULL) {
eglDestroyContext(g_eglDisplay, g_eglContext);
}
if (g_eglSurface != NULL) {
eglDestroySurface(g_eglDisplay, g_eglSurface);
}
eglTerminate(g_eglDisplay);
g_eglDisplay = EGL_NO_DISPLAY;
}
if (g_Display != nullptr) {
#if !defined(USING_FBDEV)
if (g_XDisplayOpen)
XCloseDisplay((Display *)g_Display);
#endif
g_XDisplayOpen = false;
g_Display = nullptr;
}
g_eglSurface = NULL;
g_eglContext = NULL;
}
#endif // USING_EGL
// Returns 0 on success.
int SDLGLGraphicsContext::Init(SDL_Window *&window, int x, int y, int mode, std::string *error_message) {
struct GLVersionPair {
int major;
int minor;
};
GLVersionPair attemptVersions[] = {
#ifdef USING_GLES2
{3, 2}, {3, 1}, {3, 0}, {2, 0},
#else
{4, 6}, {4, 5}, {4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0},
{3, 3}, {3, 2}, {3, 1}, {3, 0},
#endif
};
// We start hidden because we have to try several windows.
// On Mac, full screen animates so each attempt is slow.
mode |= SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN;
SDL_GLContext glContext = nullptr;
for (size_t i = 0; i < ARRAY_SIZE(attemptVersions); ++i) {
const auto &ver = attemptVersions[i];
// Make sure to request a somewhat modern GL context at least - the
// latest supported by MacOS X (really, really sad...)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, ver.major);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, ver.minor);
#ifdef USING_GLES2
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SetGLCoreContext(false);
#else
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SetGLCoreContext(true);
#endif
window = SDL_CreateWindow("PPSSPP", x, y, pixel_xres, pixel_yres, mode);
if (!window) {
// Definitely don't shutdown here: we'll keep trying more GL versions.
fprintf(stderr, "SDL_CreateWindow failed for GL %d.%d: %s\n", ver.major, ver.minor, SDL_GetError());
// Skip the DestroyWindow.
continue;
}
glContext = SDL_GL_CreateContext(window);
if (glContext != nullptr) {
// Victory, got one.
break;
}
// Let's keep trying. To be safe, destroy the window - docs say needed to change profile.
// in practice, it doesn't seem to matter, but maybe it differs by platform.
SDL_DestroyWindow(window);
}
if (glContext == nullptr) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
SetGLCoreContext(false);
window = SDL_CreateWindow("PPSSPP", x, y, pixel_xres, pixel_yres, mode);
if (window == nullptr) {
NativeShutdown();
fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError());
SDL_Quit();
return 2;
}
glContext = SDL_GL_CreateContext(window);
if (glContext == nullptr) {
NativeShutdown();
fprintf(stderr, "SDL_GL_CreateContext failed: %s\n", SDL_GetError());
SDL_Quit();
return 2;
}
}
// At this point, we have a window that we can show finally.
SDL_ShowWindow(window);
#ifdef USING_EGL
if (EGL_Open(window) != 0) {
printf("EGL_Open() failed\n");
} else if (EGL_Init(window) != 0) {
printf("EGL_Init() failed\n");
} else {
useEGLSwap = true;
}
#endif
#ifndef USING_GLES2
// Some core profile drivers elide certain extensions from GL_EXTENSIONS/etc.
// glewExperimental allows us to force GLEW to search for the pointers anyway.
if (gl_extensions.IsCoreContext) {
glewExperimental = true;
}
if (GLEW_OK != glewInit()) {
printf("Failed to initialize glew!\n");
return 1;
}
// Unfortunately, glew will generate an invalid enum error, ignore.
if (gl_extensions.IsCoreContext)
glGetError();
if (GLEW_VERSION_2_0) {
printf("OpenGL 2.0 or higher.\n");
} else {
printf("Sorry, this program requires OpenGL 2.0.\n");
return 1;
}
#endif
// Finally we can do the regular initialization.
CheckGLExtensions();
draw_ = Draw::T3DCreateGLContext();
renderManager_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
SetGPUBackend(GPUBackend::OPENGL);
bool success = draw_->CreatePresets();
_assert_(success);
renderManager_->SetSwapFunction([&]() {
#ifdef USING_EGL
if (useEGLSwap)
eglSwapBuffers(g_eglDisplay, g_eglSurface);
else
SDL_GL_SwapWindow(window_);
#else
SDL_GL_SwapWindow(window_);
#endif
});
renderManager_->SetSwapIntervalFunction([&](int interval) {
INFO_LOG(G3D, "SDL SwapInterval: %d", interval);
SDL_GL_SetSwapInterval(interval);
});
window_ = window;
return 0;
}
void SDLGLGraphicsContext::SwapInterval(int interval) {
renderManager_->SwapInterval(interval);
}
void SDLGLGraphicsContext::Shutdown() {
}
void SDLGLGraphicsContext::ShutdownFromRenderThread() {
delete draw_;
draw_ = nullptr;
#ifdef USING_EGL
EGL_Close();
#endif
SDL_GL_DeleteContext(glContext);
}