/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2014 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/ui/gl/gl_context.h" #include #include #include #include "xenia/base/assert.h" #include "xenia/base/logging.h" #include "xenia/base/math.h" #include "xenia/base/profiling.h" #include "xenia/ui/gl/gl_immediate_drawer.h" #include "xenia/ui/window.h" DEFINE_bool(thread_safe_gl, false, "Only allow one GL context to be active at a time."); DEFINE_bool(disable_gl_context_reset, false, "Do not aggressively reset the GL context (helps with capture " "programs such as OBS or FRAPS)."); DEFINE_bool(random_clear_color, false, "Randomizes GL clear color."); DEFINE_bool(gl_debug, false, "Enable OpenGL debug validation layer."); DEFINE_bool(gl_debug_output, false, "Dump ARB_debug_output to stderr."); DEFINE_bool(gl_debug_output_synchronous, true, "ARB_debug_output will synchronize to be thread safe."); namespace xe { namespace ui { namespace gl { std::recursive_mutex GLContext::global_gl_mutex_; void GLContext::FatalGLError(std::string error) { xe::FatalError( error + "\nEnsure you have the latest drivers for your GPU and that it supports " "OpenGL 4.5. See http://xenia.jp/faq/ for more information and a list" "of supported GPUs."); } GLContext::GLContext(GraphicsProvider* provider, Window* target_window) : GraphicsContext(provider, target_window) {} GLContext::~GLContext() {} void GLContext::AssertExtensionsPresent() { if (!MakeCurrent()) { FatalGLError("Unable to make GL context current."); return; } // Check shader version at least 4.5 (matching GL 4.5). auto glsl_version_raw = reinterpret_cast(glGetString(GL_SHADING_LANGUAGE_VERSION)); std::string glsl_version(glsl_version_raw); if (glsl_version.find("4.50") != 0) { FatalGLError("OpenGL GLSL version 4.50 is required."); return; } if (!GLEW_ARB_bindless_texture || !glMakeTextureHandleResidentARB) { FatalGLError("OpenGL extension ARB_bindless_texture is required."); return; } if (!GLEW_ARB_fragment_coord_conventions) { FatalGLError( "OpenGL extension ARB_fragment_coord_conventions is required."); return; } ClearCurrent(); } void GLContext::DebugMessage(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message) { const char* source_name = nullptr; switch (source) { case GL_DEBUG_SOURCE_API_ARB: source_name = "OpenGL"; break; case GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB: source_name = "Windows"; break; case GL_DEBUG_SOURCE_SHADER_COMPILER_ARB: source_name = "Shader Compiler"; break; case GL_DEBUG_SOURCE_THIRD_PARTY_ARB: source_name = "Third Party"; break; case GL_DEBUG_SOURCE_APPLICATION_ARB: source_name = "Application"; break; case GL_DEBUG_SOURCE_OTHER_ARB: source_name = "Other"; break; default: source_name = "(unknown source)"; break; } const char* type_name = nullptr; switch (type) { case GL_DEBUG_TYPE_ERROR: type_name = "error"; break; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: type_name = "deprecated behavior"; break; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: type_name = "undefined behavior"; break; case GL_DEBUG_TYPE_PORTABILITY: type_name = "portability"; break; case GL_DEBUG_TYPE_PERFORMANCE: type_name = "performance"; break; case GL_DEBUG_TYPE_OTHER: type_name = "message"; break; case GL_DEBUG_TYPE_MARKER: type_name = "marker"; break; case GL_DEBUG_TYPE_PUSH_GROUP: type_name = "push group"; break; case GL_DEBUG_TYPE_POP_GROUP: type_name = "pop group"; break; default: type_name = "(unknown type)"; break; } const char* severity_name = nullptr; switch (severity) { case GL_DEBUG_SEVERITY_HIGH_ARB: severity_name = "high"; break; case GL_DEBUG_SEVERITY_MEDIUM_ARB: severity_name = "medium"; break; case GL_DEBUG_SEVERITY_LOW_ARB: severity_name = "low"; break; case GL_DEBUG_SEVERITY_NOTIFICATION: severity_name = "notification"; break; default: severity_name = "(unknown severity)"; break; } XELOGE("GL4 %s: %s(%s) %d: %s", source_name, type_name, severity_name, id, message); } void GLAPIENTRY GLContext::DebugMessageThunk(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, GLvoid* user_param) { reinterpret_cast(user_param) ->DebugMessage(source, type, id, severity, length, message); } void GLContext::SetupDebugging() { if (!FLAGS_gl_debug || !FLAGS_gl_debug_output) { return; } glEnable(GL_DEBUG_OUTPUT); // Synchronous output hurts, but is required if we want to line up the logs. if (FLAGS_gl_debug_output_synchronous) { glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); } else { glDisable(GL_DEBUG_OUTPUT_SYNCHRONOUS); } // Enable everything by default. glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, GL_TRUE); // Disable annoying messages. GLuint disable_message_ids[] = { 0x00020004, // Usage warning: Generic vertex attribute array 0 uses a // pointer with a small value (0x0000000000000000). Is this // intended to be used as an offset into a buffer object? }; glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_OTHER, GL_DONT_CARE, GLsizei(xe::countof(disable_message_ids)), disable_message_ids, GL_FALSE); // Callback will be made from driver threads. glDebugMessageCallback(reinterpret_cast(&DebugMessageThunk), this); } ImmediateDrawer* GLContext::immediate_drawer() { return immediate_drawer_.get(); } bool GLContext::WasLost() { if (!robust_access_supported_) { // Can't determine if we lost the context. return false; } if (context_lost_) { return true; } auto status = glGetGraphicsResetStatusARB(); if (status != GL_NO_ERROR) { // Graphics card reset. XELOGE("============= TDR detected on context %p! Context %s =============", handle(), status == GL_GUILTY_CONTEXT_RESET ? "guilty" : "innocent"); context_lost_ = true; return true; } return false; } std::unique_ptr GLContext::Capture() { GraphicsContextLock lock(this); std::unique_ptr raw_image(new RawImage()); raw_image->width = target_window_->width(); raw_image->stride = raw_image->width * 4; raw_image->height = target_window_->height(); raw_image->data.resize(raw_image->stride * raw_image->height); glReadPixels(0, 0, target_window_->width(), target_window_->height(), GL_RGBA, GL_UNSIGNED_BYTE, raw_image->data.data()); // Flip vertically in-place. size_t yt = 0; size_t yb = (raw_image->height - 1) * raw_image->stride; while (yt < yb) { for (size_t i = 0; i < raw_image->stride; ++i) { std::swap(raw_image->data[yt + i], raw_image->data[yb + i]); } yt += raw_image->stride; yb -= raw_image->stride; } return raw_image; } } // namespace gl } // namespace ui } // namespace xe