/* PCSX2 - PS2 Emulator for PCs * Copyright (C) 2002-2021 PCSX2 Dev Team * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with PCSX2. * If not, see . */ #include "common/GL/ContextAGL.h" #include "common/Assertions.h" #include "common/Console.h" #include "glad.h" #include #if ! __has_feature(objc_arc) #error "Compile this with -fobjc-arc" #endif namespace GL { ContextAGL::ContextAGL(const WindowInfo& wi) : Context(wi) { m_opengl_module_handle = dlopen("/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL", RTLD_NOW); if (!m_opengl_module_handle) Console.Error("Could not open OpenGL.framework, function lookups will probably fail"); } ContextAGL::~ContextAGL() { if ([NSOpenGLContext currentContext] == m_context) [NSOpenGLContext clearCurrentContext]; CleanupView(); if (m_opengl_module_handle) dlclose(m_opengl_module_handle); } std::unique_ptr ContextAGL::Create(const WindowInfo& wi, gsl::span versions_to_try) { std::unique_ptr context = std::make_unique(wi); if (!context->Initialize(versions_to_try)) return nullptr; return context; } bool ContextAGL::Initialize(gsl::span versions_to_try) { for (const Version& cv : versions_to_try) { if (cv.profile == Profile::NoProfile && CreateContext(nullptr, NSOpenGLProfileVersionLegacy, true)) { // we already have the dummy context, so just use that m_version = cv; return true; } else if (cv.profile == Profile::Core) { if (cv.major_version > 4 || cv.minor_version > 1) continue; const NSOpenGLPixelFormatAttribute profile = (cv.major_version > 3 || cv.minor_version > 2) ? NSOpenGLProfileVersion4_1Core : NSOpenGLProfileVersion3_2Core; if (CreateContext(nullptr, static_cast(profile), true)) { m_version = cv; return true; } } } return false; } void* ContextAGL::GetProcAddress(const char* name) { void* addr = m_opengl_module_handle ? dlsym(m_opengl_module_handle, name) : nullptr; if (addr) return addr; return dlsym(RTLD_NEXT, name); } bool ContextAGL::ChangeSurface(const WindowInfo& new_wi) { m_wi = new_wi; BindContextToView(); return true; } void ContextAGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/) { UpdateDimensions(); } bool ContextAGL::UpdateDimensions() { if (![NSThread isMainThread]) { bool ret; dispatch_sync(dispatch_get_main_queue(), [this, &ret]{ ret = UpdateDimensions(); }); return ret; } const NSSize window_size = [m_view frame].size; const CGFloat window_scale = [[m_view window] backingScaleFactor]; const u32 new_width = static_cast(window_size.width * window_scale); const u32 new_height = static_cast(window_size.height * window_scale); if (m_wi.surface_width == new_width && m_wi.surface_height == new_height) return false; m_wi.surface_width = new_width; m_wi.surface_height = new_height; [m_context update]; return true; } bool ContextAGL::SwapBuffers() { [m_context flushBuffer]; return true; } bool ContextAGL::MakeCurrent() { [m_context makeCurrentContext]; return true; } bool ContextAGL::DoneCurrent() { [NSOpenGLContext clearCurrentContext]; return true; } bool ContextAGL::SetSwapInterval(s32 interval) { GLint gl_interval = static_cast(interval); [m_context setValues:&gl_interval forParameter:NSOpenGLCPSwapInterval]; return true; } std::unique_ptr ContextAGL::CreateSharedContext(const WindowInfo& wi) { std::unique_ptr context = std::make_unique(wi); context->m_context = [[NSOpenGLContext alloc] initWithFormat:m_pixel_format shareContext:m_context]; if (context->m_context == nil) return nullptr; context->m_version = m_version; context->m_pixel_format = m_pixel_format; if (wi.type == WindowInfo::Type::MacOS) context->BindContextToView(); return context; } bool ContextAGL::CreateContext(NSOpenGLContext* share_context, int profile, bool make_current) { if (m_context) m_context = nullptr; const NSOpenGLPixelFormatAttribute attribs[] = { NSOpenGLPFADoubleBuffer, NSOpenGLPFAOpenGLProfile, static_cast(profile), NSOpenGLPFAAccelerated, 0 }; m_pixel_format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs]; if (m_pixel_format == nil) { Console.Error("(ContextAGL) Failed to initialize pixel format"); return false; } m_context = [[NSOpenGLContext alloc] initWithFormat:m_pixel_format shareContext:nil]; if (m_context == nil) return false; if (m_wi.type == WindowInfo::Type::MacOS) BindContextToView(); if (make_current) [m_context makeCurrentContext]; return true; } void ContextAGL::BindContextToView() { if (![NSThread isMainThread]) { dispatch_sync(dispatch_get_main_queue(), [this]{ BindContextToView(); }); return; } #ifdef PCSX2_CORE m_view = (__bridge NSView*)m_wi.window_handle; #else // Drawing to wx's wxView somehow causes fighting between us and wx, resulting in massive CPU usage on the main thread and no image // Avoid that by adding our own subview CleanupView(); NSView* const superview = (__bridge NSView*)m_wi.window_handle; m_view = [[NSView alloc] initWithFrame:[superview frame]]; [superview addSubview:m_view]; [m_view setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; #endif [m_view setWantsBestResolutionOpenGLSurface:YES]; UpdateDimensions(); [m_context setView:m_view]; } void ContextAGL::CleanupView() { #ifndef PCSX2_CORE if (![NSThread isMainThread]) { dispatch_sync(dispatch_get_main_queue(), [this]{ CleanupView(); }); return; } if (m_view) [m_view removeFromSuperview]; #endif m_view = nullptr; } } // namespace GL