#include "Common/Log.h"
#include "Common/CommonWindows.h"
#include "Common/GPU/OpenGL/GLCommon.h"
#include "Common/GPU/OpenGL/GLDebugLog.h"
#include "Common/GPU/OpenGL/GLFeatures.h"
#include "Common/GPU/thin3d_create.h"
#include "Common/GPU/OpenGL/GLRenderManager.h"
#include "Common/System/OSD.h"
#include "GL/gl.h"
#include "GL/wglew.h"
#include "Core/Config.h"
#include "Core/ConfigValues.h"
#include "Core/Core.h"
#include "Core/System.h"
#include "Common/Data/Encoding/Utf8.h"
#include "Common/Data/Text/I18n.h"
#include "ext/glslang/glslang/Public/ShaderLang.h"
#include "Windows/W32Util/Misc.h"
#include "Windows/GPU/WindowsGLContext.h"
static const int simulateGLES = false;
void WindowsGLContext::Poll() {
if (pauseRequested) {
SetEvent(pauseEvent);
resumeRequested = true;
DWORD result = WaitForSingleObject(resumeEvent, INFINITE);
if (result == WAIT_TIMEOUT) {
ERROR_LOG(Log::G3D, "Wait for resume timed out. Resuming rendering");
}
pauseRequested = false;
}
}
void WindowsGLContext::Pause() {
if (!hRC) {
return;
}
if (Core_IsStepping()) {
return;
}
pauseRequested = true;
DWORD result = WaitForSingleObject(pauseEvent, INFINITE);
if (result == WAIT_TIMEOUT) {
ERROR_LOG(Log::G3D, "Wait for pause timed out");
}
}
void WindowsGLContext::Resume() {
if (!hRC) {
return;
}
if (Core_IsStepping() && !resumeRequested) {
return;
}
if (!resumeRequested) {
ERROR_LOG(Log::G3D, "Not waiting to get resumed");
} else {
SetEvent(resumeEvent);
}
resumeRequested = false;
}
void FormatDebugOutputARB(char outStr[], size_t outStrSize, GLenum source, GLenum type,
GLuint id, GLenum severity, const char *msg) {
char sourceStr[32]{};
const char *sourceFmt;
switch(source) {
case GL_DEBUG_SOURCE_API_ARB: sourceFmt = "API"; break;
case GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB: sourceFmt = "WINDOW_SYSTEM"; break;
case GL_DEBUG_SOURCE_SHADER_COMPILER_ARB: sourceFmt = "SHADER_COMPILER"; break;
case GL_DEBUG_SOURCE_THIRD_PARTY_ARB: sourceFmt = "THIRD_PARTY"; break;
case GL_DEBUG_SOURCE_APPLICATION_ARB: sourceFmt = "APPLICATION"; break;
case GL_DEBUG_SOURCE_OTHER_ARB: sourceFmt = "OTHER"; break;
default: sourceFmt = "UNDEFINED(0x%04X)"; break;
}
snprintf(sourceStr, sizeof(sourceStr), sourceFmt, source);
char typeStr[32]{};
const char *typeFmt;
switch(type) {
case GL_DEBUG_TYPE_ERROR_ARB: typeFmt = "ERROR"; break;
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB: typeFmt = "DEPRECATED_BEHAVIOR"; break;
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB: typeFmt = "UNDEFINED_BEHAVIOR"; break;
case GL_DEBUG_TYPE_PORTABILITY_ARB: typeFmt = "PORTABILITY"; break;
case GL_DEBUG_TYPE_PERFORMANCE_ARB: typeFmt = "PERFORMANCE"; break;
case GL_DEBUG_TYPE_OTHER_ARB: typeFmt = "OTHER"; break;
default: typeFmt = "UNDEFINED(0x%04X)"; break;
}
snprintf(typeStr, sizeof(typeStr), typeFmt, type);
char severityStr[32]{};
const char *severityFmt;
switch (severity) {
case GL_DEBUG_SEVERITY_HIGH_ARB: severityFmt = "HIGH"; break;
case GL_DEBUG_SEVERITY_MEDIUM_ARB: severityFmt = "MEDIUM"; break;
case GL_DEBUG_SEVERITY_LOW_ARB: severityFmt = "LOW"; break;
default: severityFmt = "UNDEFINED(%d)"; break;
}
snprintf(severityStr, sizeof(severityStr), severityFmt, severity);
snprintf(outStr, outStrSize, "OpenGL: %s [source=%s type=%s severity=%s id=%d]\n", msg, sourceStr, typeStr, severityStr, id);
}
void DebugCallbackARB(GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar *message, GLvoid *userParam) {
if (source == GL_DEBUG_SOURCE_API_ARB && type == GL_DEBUG_TYPE_OTHER_ARB && id == 131185) {
return;
}
if (source == GL_DEBUG_SOURCE_APPLICATION) {
return;
}
(void)length;
FILE *outFile = (FILE *)userParam;
char finalMessage[1024];
FormatDebugOutputARB(finalMessage, sizeof(finalMessage), source, type, id, severity, message);
OutputDebugStringA(finalMessage);
size_t len = strlen(finalMessage);
if (len) {
finalMessage[len - 1] = '\0';
}
switch (type) {
case GL_DEBUG_TYPE_ERROR_ARB:
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB:
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB:
ERROR_LOG(Log::G3D, "GL: %s", finalMessage);
break;
case GL_DEBUG_TYPE_PORTABILITY_ARB:
case GL_DEBUG_TYPE_PERFORMANCE_ARB:
NOTICE_LOG(Log::G3D, "GL: %s", finalMessage);
break;
case GL_DEBUG_TYPE_OTHER_ARB:
default:
VERBOSE_LOG(Log::G3D, "GL: %s", finalMessage);
break;
}
}
bool WindowsGLContext::Init(HINSTANCE hInst, HWND window, std::string *error_message) {
glslang::InitializeProcess();
hInst_ = hInst;
hWnd_ = window;
*error_message = "ok";
return true;
}
bool WindowsGLContext::InitFromRenderThread(std::string *error_message) {
*error_message = "ok";
GLuint PixelFormat;
static const PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW |
PFD_SUPPORT_OPENGL |
PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
24,
0, 0, 0, 0, 0, 0,
8,
0,
0,
0, 0, 0, 0,
16,
8,
0,
PFD_MAIN_PLANE,
0,
0, 0, 0
};
hDC = GetDC(hWnd_);
if (!hDC) {
*error_message = "Failed to get a device context.";
return false;
}
if (!(PixelFormat = ChoosePixelFormat(hDC, &pfd))) {
*error_message = "Can't find a suitable PixelFormat.";
return false;
}
if (!SetPixelFormat(hDC, PixelFormat, &pfd)) {
*error_message = "Can't set the PixelFormat.";
return false;
}
if (!(hRC = wglCreateContext(hDC))) {
*error_message = "Can't create a GL rendering context.";
return false;
}
if (!wglMakeCurrent(hDC, hRC)) {
*error_message = "Can't activate the GL rendering context.";
return false;
}
std::string glVersion = (const char *)glGetString(GL_VERSION);
std::string glRenderer = (const char *)glGetString(GL_RENDERER);
const std::string openGL_1 = "1.";
if (glRenderer == "GDI Generic" || glVersion.substr(0, openGL_1.size()) == openGL_1) {
HDC dc = GetDC(NULL);
u32 colour_depth = GetDeviceCaps(dc, BITSPIXEL);
ReleaseDC(NULL, dc);
if (colour_depth != 32) {
MessageBox(0, L"Please switch your display to 32-bit colour mode", L"OpenGL Error", MB_OK);
ExitProcess(1);
}
const char *defaultError = "Insufficient OpenGL driver support detected!\n\n"
"Your GPU reports that it does not support OpenGL 2.0. Would you like to try using DirectX instead?\n\n"
"DirectX is currently compatible with less games, but on your GPU it may be the only choice.\n\n"
"Visit the forums at https://forums.ppsspp.org for more information.\n\n";
auto err = GetI18NCategory(I18NCat::ERRORS);
std::wstring versionDetected = ConvertUTF8ToWString(glVersion + "\n\n");
std::wstring error = ConvertUTF8ToWString(err->T("InsufficientOpenGLDriver", defaultError));
std::wstring title = ConvertUTF8ToWString(err->T("OpenGLDriverError", "OpenGL driver error"));
std::wstring combined = versionDetected + error;
bool yes = IDYES == MessageBox(hWnd_, combined.c_str(), title.c_str(), MB_ICONERROR | MB_YESNO);
if (yes) {
g_Config.iGPUBackend = (int)GPUBackend::DIRECT3D11;
g_Config.sFailedGPUBackends.clear();
g_Config.Save("save_d3d11_fallback");
W32Util::ExitAndRestart();
}
ExitProcess(1);
}
glewExperimental = true;
if (GLEW_OK != glewInit()) {
*error_message = "Failed to initialize GLEW.";
return false;
}
glGetError();
ResetGLExtensions();
int contextFlags = g_Config.bGfxDebugOutput ? WGL_CONTEXT_DEBUG_BIT_ARB : 0;
HGLRC m_hrc = nullptr;
if (wglewIsSupported("WGL_ARB_create_context") == 1) {
if (simulateGLES) {
const static int simulateVersions[][2] = { {3, 2}, {3, 1}, {3, 0}, {2, 0} };
for (auto ver : simulateVersions) {
const int attribsES[] = {
WGL_CONTEXT_MAJOR_VERSION_ARB, ver[0],
WGL_CONTEXT_MINOR_VERSION_ARB, ver[1],
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_ES2_PROFILE_BIT_EXT,
0
};
m_hrc = wglCreateContextAttribsARB(hDC, 0, attribsES);
if (m_hrc)
break;
}
}
for (int tryCore = 1; tryCore >= 0 && m_hrc == nullptr; --tryCore) {
SetGLCoreContext(tryCore == 1);
for (int minor = 6; minor >= 0 && m_hrc == nullptr; --minor) {
const int attribs4x[] = {
WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
WGL_CONTEXT_MINOR_VERSION_ARB, minor,
WGL_CONTEXT_FLAGS_ARB, contextFlags,
WGL_CONTEXT_PROFILE_MASK_ARB, gl_extensions.IsCoreContext ? WGL_CONTEXT_CORE_PROFILE_BIT_ARB : WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
0
};
m_hrc = wglCreateContextAttribsARB(hDC, 0, attribs4x);
}
const int attribs33[] = {
WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
WGL_CONTEXT_MINOR_VERSION_ARB, 3,
WGL_CONTEXT_FLAGS_ARB, contextFlags,
WGL_CONTEXT_PROFILE_MASK_ARB, gl_extensions.IsCoreContext ? WGL_CONTEXT_CORE_PROFILE_BIT_ARB : WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
0
};
if (!m_hrc)
m_hrc = wglCreateContextAttribsARB(hDC, 0, attribs33);
}
if (!m_hrc) {
m_hrc = hRC;
} else {
wglMakeCurrent(nullptr, nullptr);
wglDeleteContext(hRC);
wglMakeCurrent(hDC, m_hrc);
}
} else {
m_hrc = hRC;
}
if (GLEW_OK != glewInit()) {
*error_message = "Failed to re-initialize GLEW.";
return false;
}
if (gl_extensions.IsCoreContext)
glGetError();
if (!m_hrc) {
*error_message = "No m_hrc";
return false;
}
hRC = m_hrc;
bool validation = g_Config.bGfxDebugOutput;
#ifdef _DEBUG
validation = true;
#endif
if (validation) {
if (wglewIsSupported("GL_KHR_debug") == 1) {
glGetError();
glDebugMessageCallback((GLDEBUGPROC)&DebugCallbackARB, nullptr);
if (glGetError()) {
ERROR_LOG(Log::G3D, "Failed to register a debug log callback");
}
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
if (glGetError()) {
ERROR_LOG(Log::G3D, "Failed to enable synchronous debug output");
}
} else if (glewIsSupported("GL_ARB_debug_output")) {
glGetError();
glDebugMessageCallbackARB((GLDEBUGPROCARB)&DebugCallbackARB, 0);
if (glGetError()) {
ERROR_LOG(Log::G3D, "Failed to register a debug log callback");
}
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
if (glGetError()) {
ERROR_LOG(Log::G3D, "Failed to enable synchronous debug output");
}
}
glEnable(GL_DEBUG_OUTPUT);
}
pauseRequested = false;
resumeRequested = false;
CheckGLExtensions();
draw_ = Draw::T3DCreateGLContext(wglSwapIntervalEXT != nullptr);
bool success = draw_->CreatePresets();
if (!success) {
delete draw_;
draw_ = nullptr;
ReleaseGLContext();
return false;
}
draw_->SetErrorCallback([](const char *shortDesc, const char *details, void *userdata) {
g_OSD.Show(OSDType::MESSAGE_ERROR, details, 0.0f, "error_callback");
}, nullptr);
pauseEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
resumeEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
renderManager_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
renderManager_->SetInflightFrames(g_Config.iInflightFrames);
SetGPUBackend(GPUBackend::OPENGL);
renderManager_->SetSwapFunction([&]() {::SwapBuffers(hDC); });
if (wglSwapIntervalEXT) {
renderManager_->SetSwapIntervalFunction([&](int interval) {
wglSwapIntervalEXT(interval);
});
}
CHECK_GL_ERROR_IF_DEBUG();
return true;
}
void WindowsGLContext::Shutdown() {
glslang::FinalizeProcess();
}
void WindowsGLContext::ReleaseGLContext() {
if (hRC) {
if (!wglMakeCurrent(NULL, NULL)) {
MessageBox(NULL, L"Release of DC and RC failed.", L"SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
}
if (!wglDeleteContext(hRC)) {
MessageBox(NULL, L"Release rendering context failed.", L"SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
}
hRC = NULL;
}
if (hDC && !ReleaseDC(hWnd_, hDC)) {
DWORD err = GetLastError();
if (err != ERROR_DC_NOT_FOUND) {
MessageBox(NULL, L"Release device context failed.", L"SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
}
hDC = NULL;
}
hWnd_ = NULL;
}
void WindowsGLContext::ShutdownFromRenderThread() {
delete draw_;
draw_ = nullptr;
CloseHandle(pauseEvent);
CloseHandle(resumeEvent);
ReleaseGLContext();
}
void WindowsGLContext::Resize() {
}
void WindowsGLContext::ThreadStart() {
renderManager_->ThreadStart(draw_);
}
bool WindowsGLContext::ThreadFrame() {
return renderManager_->ThreadFrame();
}
void WindowsGLContext::ThreadEnd() {
renderManager_->ThreadEnd();
}
void WindowsGLContext::StopThread() {
renderManager_->StopThread();
}