Frontends: Use common GL context wrapper

This commit is contained in:
Connor McLaughlin 2020-05-07 22:49:04 +10:00
parent 4f4c4f4146
commit 2156236f52
11 changed files with 162 additions and 264 deletions

View file

@ -155,15 +155,6 @@ std::tuple<s32, s32> GPU_HW_OpenGL::ConvertToFramebufferCoordinates(s32 x, s32 y
void GPU_HW_OpenGL::SetCapabilities(HostDisplay* host_display) void GPU_HW_OpenGL::SetCapabilities(HostDisplay* host_display)
{ {
Log_InfoPrintf("Context Type: %s", IsGLES() ? "OpenGL ES" : "OpenGL");
const char* gl_vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
const char* gl_renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
const char* gl_version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
Log_InfoPrintf("GL_VENDOR: %s", gl_vendor);
Log_InfoPrintf("GL_RENDERER: %s", gl_renderer);
Log_InfoPrintf("GL_VERSION: %s", gl_version);
GLint max_texture_size = VRAM_WIDTH; GLint max_texture_size = VRAM_WIDTH;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size); glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
Log_InfoPrintf("Max texture size: %dx%d", max_texture_size, max_texture_size); Log_InfoPrintf("Max texture size: %dx%d", max_texture_size, max_texture_size);

View file

@ -58,6 +58,7 @@ add_executable(duckstation-qt
settingsdialog.ui settingsdialog.ui
) )
target_include_directories(duckstation-qt PRIVATE "${Qt5Gui_PRIVATE_INCLUDE_DIRS}")
target_link_libraries(duckstation-qt PRIVATE frontend-common core common imgui glad minizip scmversion Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network) target_link_libraries(duckstation-qt PRIVATE frontend-common core common imgui glad minizip scmversion Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network)
if(WIN32) if(WIN32)
@ -89,9 +90,4 @@ if(WIN32)
add_custom_command(TARGET duckstation-qt POST_BUILD add_custom_command(TARGET duckstation-qt POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/qt.conf.win" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qt.conf" COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/qt.conf.win" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qt.conf"
) )
else()
if(OpenGL_GLX_FOUND)
target_compile_definitions(duckstation-qt PRIVATE "HAS_GLX")
target_link_libraries(duckstation-qt PRIVATE OpenGL::GLX)
endif()
endif() endif()

View file

@ -102,14 +102,14 @@ void MainWindow::createDisplay(QThread* worker_thread, bool use_debug_device, bo
return; return;
} }
if (!m_host_display->createSurface() || !m_host_display->makeDeviceContextCurrent()) if (!m_host_display->createSurface())
{ {
reportError(tr("Failed to create host display surface.")); reportError(tr("Failed to create host display surface."));
m_host_display->destroyDeviceContext(); m_host_display->destroyDeviceContext();
return; return;
} }
m_host_display->moveContextToThread(worker_thread); m_host_display->deactivateDeviceContext();
} }
void MainWindow::updateDisplay(QThread* worker_thread, bool fullscreen, bool render_to_main) void MainWindow::updateDisplay(QThread* worker_thread, bool fullscreen, bool render_to_main)
@ -117,10 +117,7 @@ void MainWindow::updateDisplay(QThread* worker_thread, bool fullscreen, bool ren
const bool is_fullscreen = m_display_widget->isFullScreen(); const bool is_fullscreen = m_display_widget->isFullScreen();
const bool is_rendering_to_main = (!is_fullscreen && m_display_widget->parent()); const bool is_rendering_to_main = (!is_fullscreen && m_display_widget->parent());
if (fullscreen == is_fullscreen && is_rendering_to_main == render_to_main) if (fullscreen == is_fullscreen && is_rendering_to_main == render_to_main)
{
m_host_display->moveContextToThread(worker_thread);
return; return;
}
m_host_display->destroySurface(); m_host_display->destroySurface();
@ -160,8 +157,6 @@ void MainWindow::updateDisplay(QThread* worker_thread, bool fullscreen, bool ren
QSignalBlocker blocker(m_ui.actionFullscreen); QSignalBlocker blocker(m_ui.actionFullscreen);
m_ui.actionFullscreen->setChecked(fullscreen); m_ui.actionFullscreen->setChecked(fullscreen);
m_host_display->moveContextToThread(worker_thread);
} }
void MainWindow::destroyDisplay() void MainWindow::destroyDisplay()

View file

@ -10,87 +10,12 @@
#include <QtGui/QWindow> #include <QtGui/QWindow>
#include <array> #include <array>
#include <imgui_impl_opengl3.h> #include <imgui_impl_opengl3.h>
#if !defined(WIN32) && !defined(APPLE)
#include <qpa/qplatformnativeinterface.h>
#endif
#include <tuple> #include <tuple>
Log_SetChannel(OpenGLHostDisplay); Log_SetChannel(OpenGLHostDisplay);
static thread_local QOpenGLContext* s_thread_gl_context;
static void* GetProcAddressCallback(const char* name)
{
QOpenGLContext* ctx = s_thread_gl_context;
if (!ctx)
return nullptr;
return (void*)ctx->getProcAddress(name);
}
#if defined(WIN32)
#include "common/windows_headers.h"
#elif defined(HAS_GLX)
#include <GL/glx.h>
#endif
/// Changes the swap interval on a window. Since Qt doesn't expose this functionality, we need to change it manually
/// ourselves it by calling system-specific functions. Assumes the context is current.
static void SetSwapInterval(QOpenGLContext* context, int interval)
{
static QOpenGLContext* last_context = nullptr;
#ifdef WIN32
static void(WINAPI * wgl_swap_interval_ext)(int) = nullptr;
if (last_context != context)
{
wgl_swap_interval_ext = nullptr;
last_context = context;
HMODULE gl_module = GetModuleHandleA("opengl32.dll");
if (!gl_module)
return;
const auto wgl_get_proc_address =
reinterpret_cast<PROC(WINAPI*)(LPCSTR)>(GetProcAddress(gl_module, "wglGetProcAddress"));
if (!wgl_get_proc_address)
return;
wgl_swap_interval_ext =
reinterpret_cast<decltype(wgl_swap_interval_ext)>(wgl_get_proc_address("wglSwapIntervalEXT"));
}
if (wgl_swap_interval_ext)
wgl_swap_interval_ext(interval);
#elif __linux__
const QString platform_name(QGuiApplication::platformName());
if (platform_name == QStringLiteral("xcb"))
{
static void (*glx_swap_interval_ext)(Display*, GLXDrawable, int) = nullptr;
if (last_context != context)
{
glx_swap_interval_ext = nullptr;
last_context = context;
glx_swap_interval_ext = reinterpret_cast<decltype(glx_swap_interval_ext)>(
glXGetProcAddress(reinterpret_cast<const GLubyte*>("glXSwapIntervalEXT")));
if (!glx_swap_interval_ext)
return;
}
if (!glx_swap_interval_ext)
return;
Display* dpy = glXGetCurrentDisplay();
GLXDrawable drawable = glXGetCurrentDrawable();
if (dpy && drawable != GLX_NONE)
glx_swap_interval_ext(dpy, drawable, interval);
}
else
{
qCritical() << "Unknown platform: " << platform_name;
}
#endif
}
class OpenGLDisplayWidgetTexture : public HostDisplayTexture class OpenGLDisplayWidgetTexture : public HostDisplayTexture
{ {
public: public:
@ -148,7 +73,7 @@ QtDisplayWidget* OpenGLHostDisplay::createWidget(QWidget* parent)
HostDisplay::RenderAPI OpenGLHostDisplay::GetRenderAPI() const HostDisplay::RenderAPI OpenGLHostDisplay::GetRenderAPI() const
{ {
return m_gl_context->isOpenGLES() ? HostDisplay::RenderAPI::OpenGLES : HostDisplay::RenderAPI::OpenGL; return m_gl_context->IsGLES() ? HostDisplay::RenderAPI::OpenGLES : HostDisplay::RenderAPI::OpenGL;
} }
void* OpenGLHostDisplay::GetRenderDevice() const void* OpenGLHostDisplay::GetRenderDevice() const
@ -161,6 +86,12 @@ void* OpenGLHostDisplay::GetRenderContext() const
return m_gl_context.get(); return m_gl_context.get();
} }
void OpenGLHostDisplay::WindowResized(s32 new_window_width, s32 new_window_height)
{
QtHostDisplay::WindowResized(new_window_width, new_window_height);
m_gl_context->ResizeSurface(static_cast<u32>(new_window_width), static_cast<u32>(new_window_height));
}
std::unique_ptr<HostDisplayTexture> OpenGLHostDisplay::CreateTexture(u32 width, u32 height, const void* initial_data, std::unique_ptr<HostDisplayTexture> OpenGLHostDisplay::CreateTexture(u32 width, u32 height, const void* initial_data,
u32 initial_data_stride, bool dynamic) u32 initial_data_stride, bool dynamic)
{ {
@ -213,13 +144,13 @@ void OpenGLHostDisplay::SetVSync(bool enabled)
GLint current_fbo = 0; GLint current_fbo = 0;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &current_fbo); glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &current_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
SetSwapInterval(m_gl_context.get(), enabled ? 1 : 0); m_gl_context->SetSwapInterval(enabled ? 1 : 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo);
} }
const char* OpenGLHostDisplay::GetGLSLVersionString() const const char* OpenGLHostDisplay::GetGLSLVersionString() const
{ {
if (m_gl_context->isOpenGLES()) if (m_gl_context->IsGLES())
{ {
if (GLAD_GL_ES_VERSION_3_0) if (GLAD_GL_ES_VERSION_3_0)
return "#version 300 es"; return "#version 300 es";
@ -239,7 +170,7 @@ std::string OpenGLHostDisplay::GetGLSLVersionHeader() const
{ {
std::string header = GetGLSLVersionString(); std::string header = GetGLSLVersionString();
header += "\n\n"; header += "\n\n";
if (m_gl_context->isOpenGLES()) if (m_gl_context->IsGLES())
{ {
header += "precision highp float;\n"; header += "precision highp float;\n";
header += "precision highp int;\n\n"; header += "precision highp int;\n\n";
@ -273,80 +204,60 @@ bool OpenGLHostDisplay::hasDeviceContext() const
return static_cast<bool>(m_gl_context); return static_cast<bool>(m_gl_context);
} }
WindowInfo OpenGLHostDisplay::getWindowInfo() const
{
WindowInfo wi;
// Windows and Apple are easy here since there's no display connection.
#if defined(WIN32)
wi.type = WindowInfo::Type::Win32;
wi.window_handle = reinterpret_cast<void*>(m_widget->winId());
#elif defined(__APPLE__)
wi.type = WindowInfo::Type::MacOS;
wi.window_handle = reinterpret_cast<void*>(m_widget->winId());
#else
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
const QString platform_name = QGuiApplication::platformName();
if (platform_name == QStringLiteral("xcb"))
{
wi.type = WindowInfo::Type::X11;
wi.display_connection = pni->nativeResourceForWindow("display", m_widget->windowHandle());
wi.window_handle = reinterpret_cast<void*>(m_widget->winId());
}
else if (platform_name == QStringLiteral("wayland"))
{
wi.type = WindowInfo::Type::Wayland;
wi.display_connection = pni->nativeResourceForWindow("display", m_widget->windowHandle());
wi.window_handle = pni->nativeResourceForWindow("surface", m_widget->windowHandle());
}
else
{
qCritical() << "Unknown PNI platform " << platform_name;
return wi;
}
#endif
wi.surface_width = m_widget->width();
wi.surface_height = m_widget->height();
wi.surface_format = WindowInfo::SurfaceFormat::RGB8;
return wi;
}
bool OpenGLHostDisplay::createDeviceContext(bool debug_device) bool OpenGLHostDisplay::createDeviceContext(bool debug_device)
{ {
m_gl_context = std::make_unique<QOpenGLContext>(); m_gl_context = GL::Context::Create(getWindowInfo());
if (!m_gl_context)
// Prefer a desktop OpenGL context where possible. If we can't get this, try OpenGL ES.
static constexpr std::array<std::tuple<int, int>, 11> desktop_versions_to_try = {
{{4, 6}, {4, 5}, {4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0}, {3, 3}, {3, 2}, {3, 1}, {3, 0}}};
static constexpr std::array<std::tuple<int, int>, 4> es_versions_to_try = {{{3, 2}, {3, 1}, {3, 0}}};
QSurfaceFormat surface_format; // = requestedFormat();
surface_format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
surface_format.setSwapInterval(0);
surface_format.setRenderableType(QSurfaceFormat::OpenGL);
surface_format.setProfile(QSurfaceFormat::CoreProfile);
if (debug_device)
surface_format.setOption(QSurfaceFormat::DebugContext);
for (const auto [major, minor] : desktop_versions_to_try)
{
surface_format.setVersion(major, minor);
m_gl_context->setFormat(surface_format);
if (m_gl_context->create())
break;
}
if (!m_gl_context->isValid())
{
// try forcing ES
surface_format.setRenderableType(QSurfaceFormat::OpenGLES);
surface_format.setProfile(QSurfaceFormat::NoProfile);
if (debug_device)
surface_format.setOption(QSurfaceFormat::DebugContext, false);
for (const auto [major, minor] : es_versions_to_try)
{
surface_format.setVersion(major, minor);
m_gl_context->setFormat(surface_format);
if (m_gl_context->create())
break;
}
}
if (!m_gl_context->isValid())
{ {
Log_ErrorPrintf("Failed to create any GL context"); Log_ErrorPrintf("Failed to create any GL context");
m_gl_context.reset();
return false; return false;
} }
surface_format = m_gl_context->format();
Log_InfoPrintf("Got a %s %d.%d context", (m_gl_context->isOpenGLES() ? "OpenGL ES" : "desktop OpenGL"),
surface_format.majorVersion(), surface_format.minorVersion());
return true; return true;
} }
bool OpenGLHostDisplay::initializeDeviceContext(bool debug_device) bool OpenGLHostDisplay::initializeDeviceContext(bool debug_device)
{ {
if (!m_gl_context->makeCurrent(m_widget->windowHandle()))
return false;
s_thread_gl_context = m_gl_context.get();
// Load GLAD.
const auto load_result =
m_gl_context->isOpenGLES() ? gladLoadGLES2Loader(GetProcAddressCallback) : gladLoadGLLoader(GetProcAddressCallback);
if (!load_result)
{
Log_ErrorPrintf("Failed to load GL functions");
s_thread_gl_context = nullptr;
m_gl_context->doneCurrent();
return false;
}
if (debug_device && GLAD_GL_KHR_debug) if (debug_device && GLAD_GL_KHR_debug)
{ {
glad_glDebugMessageCallbackKHR(GLDebugCallback, nullptr); glad_glDebugMessageCallbackKHR(GLDebugCallback, nullptr);
@ -356,17 +267,16 @@ bool OpenGLHostDisplay::initializeDeviceContext(bool debug_device)
if (!QtHostDisplay::initializeDeviceContext(debug_device)) if (!QtHostDisplay::initializeDeviceContext(debug_device))
{ {
s_thread_gl_context = nullptr; m_gl_context->DoneCurrent();
m_gl_context->doneCurrent();
return false; return false;
} }
return true; return true;
} }
bool OpenGLHostDisplay::makeDeviceContextCurrent() bool OpenGLHostDisplay::activateDeviceContext()
{ {
if (!m_gl_context->makeCurrent(m_widget->windowHandle())) if (!m_gl_context->MakeCurrent())
{ {
Log_ErrorPrintf("Failed to make GL context current"); Log_ErrorPrintf("Failed to make GL context current");
return false; return false;
@ -375,20 +285,15 @@ bool OpenGLHostDisplay::makeDeviceContextCurrent()
return true; return true;
} }
void OpenGLHostDisplay::moveContextToThread(QThread* new_thread) void OpenGLHostDisplay::deactivateDeviceContext()
{ {
m_gl_context->doneCurrent(); m_gl_context->DoneCurrent();
m_gl_context->moveToThread(new_thread);
} }
void OpenGLHostDisplay::destroyDeviceContext() void OpenGLHostDisplay::destroyDeviceContext()
{ {
Assert(m_gl_context && s_thread_gl_context == m_gl_context.get());
QtHostDisplay::destroyDeviceContext(); QtHostDisplay::destroyDeviceContext();
m_gl_context->DoneCurrent();
s_thread_gl_context = nullptr;
m_gl_context->doneCurrent();
m_gl_context.reset(); m_gl_context.reset();
} }
@ -397,6 +302,10 @@ bool OpenGLHostDisplay::createSurface()
m_window_width = m_widget->scaledWindowWidth(); m_window_width = m_widget->scaledWindowWidth();
m_window_height = m_widget->scaledWindowHeight(); m_window_height = m_widget->scaledWindowHeight();
emit m_widget->windowResizedEvent(m_window_width, m_window_height); emit m_widget->windowResizedEvent(m_window_width, m_window_height);
if (m_gl_context)
m_gl_context->ChangeSurface(getWindowInfo());
return true; return true;
} }
@ -455,7 +364,7 @@ void main()
return false; return false;
} }
if (!m_gl_context->isOpenGLES()) if (!m_gl_context->IsGLES())
m_display_program.BindFragData(0, "o_col0"); m_display_program.BindFragData(0, "o_col0");
if (!m_display_program.Link()) if (!m_display_program.Link())
@ -508,9 +417,7 @@ void OpenGLHostDisplay::Render()
ImGui::Render(); ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
QWindow* window_handle = m_widget->windowHandle(); m_gl_context->SwapBuffers();
m_gl_context->makeCurrent(window_handle);
m_gl_context->swapBuffers(window_handle);
ImGui::NewFrame(); ImGui::NewFrame();
ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplOpenGL3_NewFrame();

View file

@ -8,12 +8,13 @@
#define __glext_h_ #define __glext_h_
#endif #endif
#include "common/gl/context.h"
#include "common/gl/program.h" #include "common/gl/program.h"
#include "common/gl/texture.h" #include "common/gl/texture.h"
#include "common/window_info.h"
#include "core/host_display.h" #include "core/host_display.h"
#include "qtdisplaywidget.h" #include "qtdisplaywidget.h"
#include "qthostdisplay.h" #include "qthostdisplay.h"
#include <QtGui/QOpenGLContext>
#include <memory> #include <memory>
class QtHostInterface; class QtHostInterface;
@ -29,8 +30,8 @@ public:
bool hasDeviceContext() const override; bool hasDeviceContext() const override;
bool createDeviceContext(bool debug_device) override; bool createDeviceContext(bool debug_device) override;
bool initializeDeviceContext(bool debug_device) override; bool initializeDeviceContext(bool debug_device) override;
bool makeDeviceContextCurrent() override; bool activateDeviceContext() override;
void moveContextToThread(QThread* new_thread) override; void deactivateDeviceContext() override;
void destroyDeviceContext() override; void destroyDeviceContext() override;
bool createSurface() override; bool createSurface() override;
void destroySurface(); void destroySurface();
@ -38,6 +39,7 @@ public:
RenderAPI GetRenderAPI() const override; RenderAPI GetRenderAPI() const override;
void* GetRenderDevice() const override; void* GetRenderDevice() const override;
void* GetRenderContext() const override; void* GetRenderContext() const override;
void WindowResized(s32 new_window_width, s32 new_window_height) override;
std::unique_ptr<HostDisplayTexture> CreateTexture(u32 width, u32 height, const void* initial_data, std::unique_ptr<HostDisplayTexture> CreateTexture(u32 width, u32 height, const void* initial_data,
u32 initial_data_stride, bool dynamic) override; u32 initial_data_stride, bool dynamic) override;
@ -54,6 +56,8 @@ private:
const char* GetGLSLVersionString() const; const char* GetGLSLVersionString() const;
std::string GetGLSLVersionHeader() const; std::string GetGLSLVersionHeader() const;
WindowInfo getWindowInfo() const;
bool createImGuiContext() override; bool createImGuiContext() override;
void destroyImGuiContext() override; void destroyImGuiContext() override;
bool createDeviceResources() override; bool createDeviceResources() override;
@ -61,7 +65,7 @@ private:
void renderDisplay(); void renderDisplay();
std::unique_ptr<QOpenGLContext> m_gl_context = nullptr; std::unique_ptr<GL::Context> m_gl_context = nullptr;
GL::Program m_display_program; GL::Program m_display_program;
GLuint m_display_vao = 0; GLuint m_display_vao = 0;

View file

@ -50,12 +50,12 @@ bool QtHostDisplay::initializeDeviceContext(bool debug_device)
return true; return true;
} }
bool QtHostDisplay::makeDeviceContextCurrent() bool QtHostDisplay::activateDeviceContext()
{ {
return true; return true;
} }
void QtHostDisplay::moveContextToThread(QThread* new_thread) {} void QtHostDisplay::deactivateDeviceContext() {}
void QtHostDisplay::destroyDeviceContext() void QtHostDisplay::destroyDeviceContext()
{ {

View file

@ -23,8 +23,8 @@ public:
virtual bool hasDeviceContext() const; virtual bool hasDeviceContext() const;
virtual bool createDeviceContext(bool debug_device); virtual bool createDeviceContext(bool debug_device);
virtual bool initializeDeviceContext(bool debug_device); virtual bool initializeDeviceContext(bool debug_device);
virtual bool makeDeviceContextCurrent(); virtual bool activateDeviceContext();
virtual void moveContextToThread(QThread* new_thread); virtual void deactivateDeviceContext();
virtual void destroyDeviceContext(); virtual void destroyDeviceContext();
virtual bool createSurface(); virtual bool createSurface();
virtual void destroySurface(); virtual void destroySurface();

View file

@ -317,7 +317,7 @@ bool QtHostInterface::AcquireHostDisplay()
return false; return false;
} }
if (!getHostDisplay()->makeDeviceContextCurrent() || if (!getHostDisplay()->activateDeviceContext() ||
!getHostDisplay()->initializeDeviceContext(m_settings.gpu_use_debug_device)) !getHostDisplay()->initializeDeviceContext(m_settings.gpu_use_debug_device))
{ {
getHostDisplay()->destroyDeviceContext(); getHostDisplay()->destroyDeviceContext();
@ -366,9 +366,9 @@ void QtHostInterface::disconnectDisplaySignals()
void QtHostInterface::updateDisplayState() void QtHostInterface::updateDisplayState()
{ {
// this expects the context to get moved back to us afterwards // this expects the context to get moved back to us afterwards
getHostDisplay()->moveContextToThread(m_original_thread); getHostDisplay()->deactivateDeviceContext();
emit updateDisplayRequested(m_worker_thread, m_is_fullscreen, m_is_rendering_to_main); emit updateDisplayRequested(m_worker_thread, m_is_fullscreen, m_is_rendering_to_main);
if (!getHostDisplay()->makeDeviceContextCurrent()) if (!getHostDisplay()->activateDeviceContext())
Panic("Failed to make device context current after updating"); Panic("Failed to make device context current after updating");
getHostDisplay()->updateImGuiDisplaySize(); getHostDisplay()->updateImGuiDisplaySize();

View file

@ -2,12 +2,24 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/log.h" #include "common/log.h"
#include "imgui_impl_sdl.h" #include "imgui_impl_sdl.h"
#include <SDL_syswm.h>
#include <array> #include <array>
#include <imgui.h> #include <imgui.h>
#include <imgui_impl_opengl3.h> #include <imgui_impl_opengl3.h>
#include <tuple> #include <tuple>
Log_SetChannel(OpenGLHostDisplay); Log_SetChannel(OpenGLHostDisplay);
#ifdef __APPLE__
#include <objc/message.h>
struct NSView;
static NSView* GetContentViewFromWindow(NSWindow* window)
{
// window.contentView
return reinterpret_cast<NSView* (*)(id, SEL)>(objc_msgSend)(reinterpret_cast<id>(window), sel_getUid("contentView"));
}
#endif
class OpenGLDisplayWidgetTexture : public HostDisplayTexture class OpenGLDisplayWidgetTexture : public HostDisplayTexture
{ {
public: public:
@ -67,8 +79,7 @@ OpenGLHostDisplay::~OpenGLHostDisplay()
m_display_program.Destroy(); m_display_program.Destroy();
ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplSDL2_Shutdown(); ImGui_ImplSDL2_Shutdown();
SDL_GL_MakeCurrent(nullptr, nullptr); m_gl_context.reset();
SDL_GL_DeleteContext(m_gl_context);
} }
if (m_window) if (m_window)
@ -77,7 +88,7 @@ OpenGLHostDisplay::~OpenGLHostDisplay()
HostDisplay::RenderAPI OpenGLHostDisplay::GetRenderAPI() const HostDisplay::RenderAPI OpenGLHostDisplay::GetRenderAPI() const
{ {
return m_is_gles ? HostDisplay::RenderAPI::OpenGLES : HostDisplay::RenderAPI::OpenGL; return m_gl_context->IsGLES() ? HostDisplay::RenderAPI::OpenGLES : HostDisplay::RenderAPI::OpenGL;
} }
void* OpenGLHostDisplay::GetRenderDevice() const void* OpenGLHostDisplay::GetRenderDevice() const
@ -87,13 +98,15 @@ void* OpenGLHostDisplay::GetRenderDevice() const
void* OpenGLHostDisplay::GetRenderContext() const void* OpenGLHostDisplay::GetRenderContext() const
{ {
return m_gl_context; return m_gl_context.get();
} }
void OpenGLHostDisplay::WindowResized(s32 new_window_width, s32 new_window_height) void OpenGLHostDisplay::WindowResized(s32 new_window_width, s32 new_window_height)
{ {
HostDisplay::WindowResized(new_window_width, new_window_height); HostDisplay::WindowResized(new_window_width, new_window_height);
SDL_GL_GetDrawableSize(m_window, &m_window_width, &m_window_height); m_gl_context->ResizeSurface(static_cast<u32>(new_window_width), static_cast<u32>(new_window_height));
m_window_width = static_cast<s32>(m_gl_context->GetSurfaceWidth());
m_window_height = static_cast<s32>(m_gl_context->GetSurfaceHeight());
ImGui::GetIO().DisplaySize.x = static_cast<float>(m_window_width); ImGui::GetIO().DisplaySize.x = static_cast<float>(m_window_width);
ImGui::GetIO().DisplaySize.y = static_cast<float>(m_window_height); ImGui::GetIO().DisplaySize.y = static_cast<float>(m_window_height);
} }
@ -150,13 +163,13 @@ void OpenGLHostDisplay::SetVSync(bool enabled)
GLint current_fbo = 0; GLint current_fbo = 0;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &current_fbo); glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &current_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
SDL_GL_SetSwapInterval(enabled ? 1 : 0); m_gl_context->SetSwapInterval(enabled ? 1 : 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo);
} }
const char* OpenGLHostDisplay::GetGLSLVersionString() const const char* OpenGLHostDisplay::GetGLSLVersionString() const
{ {
if (m_is_gles) if (m_gl_context->IsGLES())
{ {
if (GLAD_GL_ES_VERSION_3_0) if (GLAD_GL_ES_VERSION_3_0)
return "#version 300 es"; return "#version 300 es";
@ -176,7 +189,7 @@ std::string OpenGLHostDisplay::GetGLSLVersionHeader() const
{ {
std::string header = GetGLSLVersionString(); std::string header = GetGLSLVersionString();
header += "\n\n"; header += "\n\n";
if (m_is_gles) if (m_gl_context->IsGLES())
{ {
header += "precision highp float;\n"; header += "precision highp float;\n";
header += "precision highp int;\n\n"; header += "precision highp int;\n\n";
@ -207,64 +220,55 @@ static void APIENTRY GLDebugCallback(GLenum source, GLenum type, GLuint id, GLen
bool OpenGLHostDisplay::CreateGLContext(bool debug_device) bool OpenGLHostDisplay::CreateGLContext(bool debug_device)
{ {
// Prefer a desktop OpenGL context where possible. If we can't get this, try OpenGL ES. SDL_SysWMinfo syswm = {};
static constexpr std::array<std::tuple<int, int>, 11> desktop_versions_to_try = { SDL_VERSION(&syswm.version);
{{4, 6}, {4, 5}, {4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0}, {3, 3}, {3, 2}, {3, 1}, {3, 0}}}; if (!SDL_GetWindowWMInfo(m_window, &syswm))
static constexpr std::array<std::tuple<int, int>, 4> es_versions_to_try = {{{3, 2}, {3, 1}, {3, 0}}};
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
if (debug_device)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
for (const auto [major, minor] : desktop_versions_to_try)
{ {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, major); Log_ErrorPrintf("SDL_GetWindowWMInfo failed");
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minor);
Log_InfoPrintf("Trying a Desktop OpenGL %d.%d context", major, minor);
m_gl_context = SDL_GL_CreateContext(m_window);
if (m_gl_context)
{
Log_InfoPrintf("Got a desktop OpenGL %d.%d context", major, minor);
break;
}
}
if (!m_gl_context)
{
// try es
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
for (const auto [major, minor] : es_versions_to_try)
{
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, major);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minor);
Log_InfoPrintf("Trying a OpenGL ES %d.%d context", major, minor);
m_gl_context = SDL_GL_CreateContext(m_window);
if (m_gl_context)
{
Log_InfoPrintf("Got a OpenGL ES %d.%d context", major, minor);
m_is_gles = true;
break;
}
}
}
if (!m_gl_context || SDL_GL_MakeCurrent(m_window, m_gl_context) != 0)
{
Log_ErrorPrintf("Failed to create any GL context");
return false; return false;
} }
// Load GLAD. int window_width, window_height;
const auto load_result = SDL_GetWindowSize(m_window, &window_width, &window_height);
m_is_gles ? gladLoadGLES2Loader(SDL_GL_GetProcAddress) : gladLoadGLLoader(SDL_GL_GetProcAddress);
if (!load_result) WindowInfo wi;
wi.surface_width = static_cast<u32>(window_width);
wi.surface_height = static_cast<u32>(window_height);
wi.surface_format = WindowInfo::SurfaceFormat::RGB8;
switch (syswm.subsystem)
{ {
Log_ErrorPrintf("Failed to load GL functions"); #ifdef SDL_VIDEO_DRIVER_WINDOWS
case SDL_SYSWM_WINDOWS:
wi.type = WindowInfo::Type::Win32;
wi.window_handle = syswm.info.win.window;
break;
#endif
#ifdef SDL_VIDEO_DRIVER_COCOA
case SDL_SYSWM_COCOA:
wi.type = WindowInfo::Type::MacOS;
wi.window_handle = GetContentViewFromWindow(syswm.info.cocoa.window);
break;
#endif
#ifdef SDL_VIDEO_DRIVER_X11
case SDL_SYSWM_X11:
wi.type = WindowInfo::Type::X11;
wi.window_handle = reinterpret_cast<void*>(static_cast<uintptr_t>(syswm.info.x11.window));
wi.display_connection = syswm.info.x11.display;
break;
#endif
default:
Log_ErrorPrintf("Unhandled syswm subsystem %u", static_cast<u32>(syswm.subsystem));
return false;
}
m_gl_context = GL::Context::Create(wi);
if (!m_gl_context)
{
Log_ErrorPrintf("Failed to create a GL context of any kind.");
return false; return false;
} }
@ -276,10 +280,11 @@ bool OpenGLHostDisplay::CreateGLContext(bool debug_device)
} }
// this can change due to retina scaling on macos? // this can change due to retina scaling on macos?
SDL_GL_GetDrawableSize(m_window, &m_window_width, &m_window_height); m_window_width = static_cast<s32>(m_gl_context->GetSurfaceWidth());
m_window_height = static_cast<s32>(m_gl_context->GetSurfaceHeight());
// start with vsync on // start with vsync on
SDL_GL_SetSwapInterval(1); m_gl_context->SetSwapInterval(1);
return true; return true;
} }
@ -288,7 +293,7 @@ bool OpenGLHostDisplay::CreateImGuiContext()
ImGui::GetIO().DisplaySize.x = static_cast<float>(m_window_width); ImGui::GetIO().DisplaySize.x = static_cast<float>(m_window_width);
ImGui::GetIO().DisplaySize.y = static_cast<float>(m_window_height); ImGui::GetIO().DisplaySize.y = static_cast<float>(m_window_height);
if (!ImGui_ImplSDL2_InitForOpenGL(m_window, m_gl_context) || !ImGui_ImplOpenGL3_Init(GetGLSLVersionString())) if (!ImGui_ImplSDL2_InitForOpenGL(m_window, nullptr) || !ImGui_ImplOpenGL3_Init(GetGLSLVersionString()))
return false; return false;
ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplOpenGL3_NewFrame();
@ -329,7 +334,7 @@ void main()
return false; return false;
} }
if (!m_is_gles) if (!m_gl_context->IsGLES())
m_display_program.BindFragData(0, "o_col0"); m_display_program.BindFragData(0, "o_col0");
if (!m_display_program.Link()) if (!m_display_program.Link())
@ -377,7 +382,7 @@ void OpenGLHostDisplay::Render()
ImGui::Render(); ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
SDL_GL_SwapWindow(m_window); m_gl_context->SwapBuffers();
ImGui::NewFrame(); ImGui::NewFrame();
ImGui_ImplSDL2_NewFrame(m_window); ImGui_ImplSDL2_NewFrame(m_window);

View file

@ -1,4 +1,5 @@
#pragma once #pragma once
#include "common/gl/context.h"
#include "common/gl/program.h" #include "common/gl/program.h"
#include "common/gl/texture.h" #include "common/gl/texture.h"
#include "core/host_display.h" #include "core/host_display.h"
@ -41,12 +42,10 @@ private:
void RenderDisplay(); void RenderDisplay();
SDL_Window* m_window = nullptr; SDL_Window* m_window = nullptr;
SDL_GLContext m_gl_context = nullptr; std::unique_ptr<GL::Context> m_gl_context;
GL::Program m_display_program; GL::Program m_display_program;
GLuint m_display_vao = 0; GLuint m_display_vao = 0;
GLuint m_display_nearest_sampler = 0; GLuint m_display_nearest_sampler = 0;
GLuint m_display_linear_sampler = 0; GLuint m_display_linear_sampler = 0;
bool m_is_gles = false;
}; };

View file

@ -81,8 +81,7 @@ bool SDLHostInterface::CreateSDLWindow()
static constexpr u32 DEFAULT_WINDOW_HEIGHT = 700; static constexpr u32 DEFAULT_WINDOW_HEIGHT = 700;
// Create window. // Create window.
const u32 window_flags = const u32 window_flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | (UseOpenGLRenderer() ? SDL_WINDOW_OPENGL : 0);
u32 window_width = DEFAULT_WINDOW_WIDTH; u32 window_width = DEFAULT_WINDOW_WIDTH;
u32 window_height = DEFAULT_WINDOW_HEIGHT; u32 window_height = DEFAULT_WINDOW_HEIGHT;
@ -116,6 +115,8 @@ bool SDLHostInterface::CreateSDLWindow()
if (m_fullscreen) if (m_fullscreen)
SDL_SetWindowFullscreen(m_window, SDL_WINDOW_FULLSCREEN_DESKTOP); SDL_SetWindowFullscreen(m_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
// Process events so that we have everything sorted out before creating a child window for the GL context (X11).
SDL_PumpEvents();
return true; return true;
} }
@ -818,7 +819,7 @@ void SDLHostInterface::DrawDebugMenu()
for (u32 i = LOGLEVEL_NONE; i < LOGLEVEL_COUNT; i++) for (u32 i = LOGLEVEL_NONE; i < LOGLEVEL_COUNT; i++)
{ {
if (ImGui::MenuItem(Settings::GetLogLevelDisplayName(static_cast<LOGLEVEL>(i)), nullptr, if (ImGui::MenuItem(Settings::GetLogLevelDisplayName(static_cast<LOGLEVEL>(i)), nullptr,
m_settings.log_level == static_cast<LOGLEVEL>(i))) m_settings.log_level == static_cast<LOGLEVEL>(i)))
{ {
m_settings_copy.log_level = static_cast<LOGLEVEL>(i); m_settings_copy.log_level = static_cast<LOGLEVEL>(i);
settings_changed = true; settings_changed = true;