From 0ed418834ad021e48c59205a775538d74a897656 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 5 May 2023 20:58:40 +1000 Subject: [PATCH] GS: Move OpenGL helpers from common to GS --- common/CMakeLists.txt | 58 +- common/GL/Context.cpp | 171 ------ common/GL/Context.h | 80 --- common/GL/ContextAGL.h | 62 -- common/GL/ContextAGL.mm | 243 -------- common/GL/ContextEGL.cpp | 391 ------------- common/GL/ContextEGL.h | 63 -- common/GL/ContextEGLWayland.cpp | 104 ---- common/GL/ContextEGLWayland.h | 49 -- common/GL/ContextEGLX11.cpp | 59 -- common/GL/ContextWGL.cpp | 487 ---------------- common/GL/ContextWGL.h | 71 --- common/GL/Program.cpp | 534 ----------------- common/GL/Program.h | 105 ---- common/GL/ShaderCache.cpp | 547 ----------------- common/GL/ShaderCache.h | 112 ---- common/GL/StreamBuffer.cpp | 357 ------------ common/GL/StreamBuffer.h | 61 -- common/common.vcxproj | 12 +- common/common.vcxproj.filters | 52 +- pcsx2/CMakeLists.txt | 33 ++ pcsx2/GS/Renderers/OpenGL/GLContext.cpp | 92 +++ pcsx2/GS/Renderers/OpenGL/GLContext.h | 55 ++ pcsx2/GS/Renderers/OpenGL/GLContextAGL.h | 62 ++ pcsx2/GS/Renderers/OpenGL/GLContextAGL.mm | 236 ++++++++ pcsx2/GS/Renderers/OpenGL/GLContextEGL.cpp | 366 ++++++++++++ pcsx2/GS/Renderers/OpenGL/GLContextEGL.h | 62 ++ .../Renderers/OpenGL/GLContextEGLWayland.cpp | 105 ++++ .../GS/Renderers/OpenGL/GLContextEGLWayland.h | 46 ++ pcsx2/GS/Renderers/OpenGL/GLContextEGLX11.cpp | 56 ++ .../GS/Renderers/OpenGL/GLContextEGLX11.h | 27 +- pcsx2/GS/Renderers/OpenGL/GLContextWGL.cpp | 452 ++++++++++++++ pcsx2/GS/Renderers/OpenGL/GLContextWGL.h | 67 +++ pcsx2/GS/Renderers/OpenGL/GLLoader.cpp | 2 + pcsx2/GS/Renderers/OpenGL/GLLoader.h | 7 - pcsx2/GS/Renderers/OpenGL/GLProgram.cpp | 538 +++++++++++++++++ pcsx2/GS/Renderers/OpenGL/GLProgram.h | 103 ++++ pcsx2/GS/Renderers/OpenGL/GLShaderCache.cpp | 550 ++++++++++++++++++ pcsx2/GS/Renderers/OpenGL/GLShaderCache.h | 108 ++++ pcsx2/GS/Renderers/OpenGL/GLState.h | 2 + pcsx2/GS/Renderers/OpenGL/GLStreamBuffer.cpp | 343 +++++++++++ pcsx2/GS/Renderers/OpenGL/GLStreamBuffer.h | 61 ++ pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp | 122 ++-- pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h | 60 +- pcsx2/GS/Renderers/OpenGL/GSTextureOGL.cpp | 8 +- pcsx2/pcsx2core.vcxproj | 10 + pcsx2/pcsx2core.vcxproj.filters | 30 + 47 files changed, 3478 insertions(+), 3743 deletions(-) delete mode 100644 common/GL/Context.cpp delete mode 100644 common/GL/Context.h delete mode 100644 common/GL/ContextAGL.h delete mode 100644 common/GL/ContextAGL.mm delete mode 100644 common/GL/ContextEGL.cpp delete mode 100644 common/GL/ContextEGL.h delete mode 100644 common/GL/ContextEGLWayland.cpp delete mode 100644 common/GL/ContextEGLWayland.h delete mode 100644 common/GL/ContextEGLX11.cpp delete mode 100644 common/GL/ContextWGL.cpp delete mode 100644 common/GL/ContextWGL.h delete mode 100644 common/GL/Program.cpp delete mode 100644 common/GL/Program.h delete mode 100644 common/GL/ShaderCache.cpp delete mode 100644 common/GL/ShaderCache.h delete mode 100644 common/GL/StreamBuffer.cpp delete mode 100644 common/GL/StreamBuffer.h create mode 100644 pcsx2/GS/Renderers/OpenGL/GLContext.cpp create mode 100644 pcsx2/GS/Renderers/OpenGL/GLContext.h create mode 100644 pcsx2/GS/Renderers/OpenGL/GLContextAGL.h create mode 100644 pcsx2/GS/Renderers/OpenGL/GLContextAGL.mm create mode 100644 pcsx2/GS/Renderers/OpenGL/GLContextEGL.cpp create mode 100644 pcsx2/GS/Renderers/OpenGL/GLContextEGL.h create mode 100644 pcsx2/GS/Renderers/OpenGL/GLContextEGLWayland.cpp create mode 100644 pcsx2/GS/Renderers/OpenGL/GLContextEGLWayland.h create mode 100644 pcsx2/GS/Renderers/OpenGL/GLContextEGLX11.cpp rename common/GL/ContextEGLX11.h => pcsx2/GS/Renderers/OpenGL/GLContextEGLX11.h (54%) create mode 100644 pcsx2/GS/Renderers/OpenGL/GLContextWGL.cpp create mode 100644 pcsx2/GS/Renderers/OpenGL/GLContextWGL.h create mode 100644 pcsx2/GS/Renderers/OpenGL/GLProgram.cpp create mode 100644 pcsx2/GS/Renderers/OpenGL/GLProgram.h create mode 100644 pcsx2/GS/Renderers/OpenGL/GLShaderCache.cpp create mode 100644 pcsx2/GS/Renderers/OpenGL/GLShaderCache.h create mode 100644 pcsx2/GS/Renderers/OpenGL/GLStreamBuffer.cpp create mode 100644 pcsx2/GS/Renderers/OpenGL/GLStreamBuffer.h diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index d1f84e3308..26351f6f9e 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -128,22 +128,6 @@ target_sources(common PRIVATE emitter/x86types.h ) -if(USE_OPENGL) - target_sources(common PRIVATE - GL/Context.cpp - GL/Program.cpp - GL/ShaderCache.cpp - GL/StreamBuffer.cpp - ) - target_sources(common PRIVATE - GL/Context.h - GL/Program.h - GL/ShaderCache.h - GL/StreamBuffer.h - ) - target_link_libraries(common PUBLIC glad) -endif() - if(USE_VULKAN) target_link_libraries(common PUBLIC Vulkan-Headers glslang @@ -208,45 +192,9 @@ if(DBUS_API) target_link_libraries(common PRIVATE ${DBUS_LINK_LIBRARIES}) endif() -if(USE_OPENGL) - if(WIN32) - target_sources(common PRIVATE - GL/ContextWGL.cpp - GL/ContextWGL.h - ) - target_link_libraries(common PUBLIC opengl32.lib) - elseif(APPLE) - target_sources(common PRIVATE - GL/ContextAGL.mm - GL/ContextAGL.h - ) - else() - if(X11_API OR WAYLAND_API) - target_sources(common PRIVATE - GL/ContextEGL.cpp - GL/ContextEGL.h - ) - target_link_libraries(common PRIVATE PkgConfig::EGL) - endif() - - if(X11_API) - target_sources(common PRIVATE - GL/ContextEGLX11.cpp - GL/ContextEGLX11.h - ) - if(TARGET PkgConfig::XRANDR) - target_link_libraries(common PRIVATE PkgConfig::XRANDR) - target_compile_definitions(common PRIVATE "HAS_XRANDR=1") - endif() - endif() - - if(WAYLAND_API) - target_sources(common PRIVATE - GL/ContextEGLWayland.cpp - GL/ContextEGLWayland.h - ) - endif() - endif() +if(X11_API AND TARGET PkgConfig::XRANDR) + target_link_libraries(common PRIVATE PkgConfig::XRANDR) + target_compile_definitions(common PRIVATE "HAS_XRANDR=1") endif() if(USE_VULKAN) diff --git a/common/GL/Context.cpp b/common/GL/Context.cpp deleted file mode 100644 index 1bda16cd32..0000000000 --- a/common/GL/Context.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/* 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/PrecompiledHeader.h" - -#include "common/Console.h" -#include "common/GL/Context.h" -#include "glad.h" - -#include -#include -#include -#ifdef __APPLE__ -#include -#else -#include -#endif - -#if defined(_WIN32) && !defined(_M_ARM64) -#include "common/GL/ContextWGL.h" -#elif defined(__APPLE__) -#include "common/GL/ContextAGL.h" -#else -#ifdef X11_API -#include "common/GL/ContextEGLX11.h" -#endif -#ifdef WAYLAND_API -#include "common/GL/ContextEGLWayland.h" -#endif -#endif - -namespace GL -{ - static bool ShouldPreferESContext() - { -#ifndef _MSC_VER - const char* value = std::getenv("PREFER_GLES_CONTEXT"); - return (value && std::strcmp(value, "1") == 0); -#else - char buffer[2] = {}; - size_t buffer_size = sizeof(buffer); - getenv_s(&buffer_size, buffer, "PREFER_GLES_CONTEXT"); - return (std::strcmp(buffer, "1") == 0); -#endif - } - - Context::Context(const WindowInfo& wi) - : m_wi(wi) - { - } - - Context::~Context() = default; - - std::vector Context::EnumerateFullscreenModes() - { - return {}; - } - - std::unique_ptr Context::Create(const WindowInfo& wi, gsl::span versions_to_try) - { - if (ShouldPreferESContext()) - { - // move ES versions to the front - Version* new_versions_to_try = static_cast(alloca(sizeof(Version) * versions_to_try.size())); - size_t count = 0; - for (size_t i = 0; i < versions_to_try.size(); i++) - { - if (versions_to_try[i].profile == Profile::ES) - new_versions_to_try[count++] = versions_to_try[i]; - } - for (size_t i = 0; i < versions_to_try.size(); i++) - { - if (versions_to_try[i].profile != Profile::ES) - new_versions_to_try[count++] = versions_to_try[i]; - } - versions_to_try = gsl::span(new_versions_to_try, versions_to_try.size()); - } - - std::unique_ptr context; -#if defined(_WIN32) && !defined(_M_ARM64) - context = ContextWGL::Create(wi, versions_to_try); -#elif defined(__APPLE__) - context = ContextAGL::Create(wi, versions_to_try); -#endif - -#if defined(X11_API) - if (wi.type == WindowInfo::Type::X11) - context = ContextEGLX11::Create(wi, versions_to_try); -#endif - -#if defined(WAYLAND_API) - if (wi.type == WindowInfo::Type::Wayland) - context = ContextEGLWayland::Create(wi, versions_to_try); -#endif - - if (!context) - return nullptr; - - Console.WriteLn("Created an %s context", context->IsGLES() ? "OpenGL ES" : "OpenGL"); - - // NOTE: Not thread-safe. But this is okay, since we're not going to be creating more than one context at a time. - static Context* context_being_created; - context_being_created = context.get(); - - // load up glad - if (!context->IsGLES()) - { - if (!gladLoadGLLoader([](const char* name) { return context_being_created->GetProcAddress(name); })) - { - Console.Error("Failed to load GL functions for GLAD"); - return nullptr; - } - } - else - { - if (!gladLoadGLES2Loader([](const char* name) { return context_being_created->GetProcAddress(name); })) - { - Console.Error("Failed to load GLES functions for GLAD"); - return nullptr; - } - } - - context_being_created = nullptr; - - const char* gl_vendor = reinterpret_cast(glGetString(GL_VENDOR)); - const char* gl_renderer = reinterpret_cast(glGetString(GL_RENDERER)); - const char* gl_version = reinterpret_cast(glGetString(GL_VERSION)); - const char* gl_shading_language_version = reinterpret_cast(glGetString(GL_SHADING_LANGUAGE_VERSION)); - DevCon.WriteLn(Color_Magenta, "GL_VENDOR: %s", gl_vendor); - DevCon.WriteLn(Color_Magenta, "GL_RENDERER: %s", gl_renderer); - DevCon.WriteLn(Color_Magenta, "GL_VERSION: %s", gl_version); - DevCon.WriteLn(Color_Magenta, "GL_SHADING_LANGUAGE_VERSION: %s", gl_shading_language_version); - - return context; - } - - gsl::span Context::GetAllVersionsList() - { - static constexpr Version vlist[] = { - {Profile::Core, 4, 6}, - {Profile::Core, 4, 5}, - {Profile::Core, 4, 4}, - {Profile::Core, 4, 3}, - {Profile::Core, 4, 2}, - {Profile::Core, 4, 1}, - {Profile::Core, 4, 0}, - {Profile::Core, 3, 3}, - {Profile::Core, 3, 2}, - {Profile::Core, 3, 1}, - {Profile::Core, 3, 0}, - {Profile::ES, 3, 2}, - {Profile::ES, 3, 1}, - {Profile::ES, 3, 0}, - {Profile::ES, 2, 0}, - {Profile::NoProfile, 0, 0} - }; - return vlist; - } -} // namespace GL diff --git a/common/GL/Context.h b/common/GL/Context.h deleted file mode 100644 index 21ea1b2acb..0000000000 --- a/common/GL/Context.h +++ /dev/null @@ -1,80 +0,0 @@ -/* 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 . - */ - -#pragma once - -#include "common/Pcsx2Defs.h" -#include "common/WindowInfo.h" - -#include -#include -#include -#include - -namespace GL { - class Context - { - public: - Context(const WindowInfo& wi); - virtual ~Context(); - - enum class Profile - { - NoProfile, - Core, - ES - }; - - struct Version - { - Profile profile; - int major_version; - int minor_version; - }; - - struct FullscreenModeInfo - { - u32 width; - u32 height; - float refresh_rate; - }; - - __fi const WindowInfo& GetWindowInfo() const { return m_wi; } - __fi bool IsGLES() const { return (m_version.profile == Profile::ES); } - __fi u32 GetSurfaceWidth() const { return m_wi.surface_width; } - __fi u32 GetSurfaceHeight() const { return m_wi.surface_height; } - - virtual void* GetProcAddress(const char* name) = 0; - virtual bool ChangeSurface(const WindowInfo& new_wi) = 0; - virtual void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) = 0; - virtual bool SwapBuffers() = 0; - virtual bool MakeCurrent() = 0; - virtual bool DoneCurrent() = 0; - virtual bool SetSwapInterval(s32 interval) = 0; - virtual std::unique_ptr CreateSharedContext(const WindowInfo& wi) = 0; - - virtual std::vector EnumerateFullscreenModes(); - - static std::unique_ptr Create(const WindowInfo& wi, gsl::span versions_to_try); - - static std::unique_ptr Create(const WindowInfo& wi) { return Create(wi, GetAllVersionsList()); } - - static gsl::span GetAllVersionsList(); - - protected: - WindowInfo m_wi; - Version m_version = {}; - }; -} // namespace GL diff --git a/common/GL/ContextAGL.h b/common/GL/ContextAGL.h deleted file mode 100644 index 3504a34263..0000000000 --- a/common/GL/ContextAGL.h +++ /dev/null @@ -1,62 +0,0 @@ -/* 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 . - */ - -#pragma once -#include "common/GL/Context.h" -#include "glad.h" - -#if defined(__APPLE__) && defined(__OBJC__) -#import -#else -struct NSView; -struct NSOpenGLContext; -struct NSOpenGLPixelFormat; -#endif - -namespace GL -{ - class ContextAGL final : public Context - { - public: - ContextAGL(const WindowInfo& wi); - ~ContextAGL() override; - - static std::unique_ptr Create(const WindowInfo& wi, gsl::span versions_to_try); - - void* GetProcAddress(const char* name) override; - bool ChangeSurface(const WindowInfo& new_wi) override; - void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override; - bool SwapBuffers() override; - bool MakeCurrent() override; - bool DoneCurrent() override; - bool SetSwapInterval(s32 interval) override; - std::unique_ptr CreateSharedContext(const WindowInfo& wi) override; - - private: - bool Initialize(gsl::span versions_to_try); - bool CreateContext(NSOpenGLContext* share_context, int profile, bool make_current); - void BindContextToView(); - void CleanupView(); - - // returns true if dimensions have changed - bool UpdateDimensions(); - - NSView* m_view = nullptr; - NSOpenGLContext* m_context = nullptr; - NSOpenGLPixelFormat* m_pixel_format = nullptr; - void* m_opengl_module_handle = nullptr; - }; - -} // namespace GL diff --git a/common/GL/ContextAGL.mm b/common/GL/ContextAGL.mm deleted file mode 100644 index 944d5002e9..0000000000 --- a/common/GL/ContextAGL.mm +++ /dev/null @@ -1,243 +0,0 @@ -/* 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 diff --git a/common/GL/ContextEGL.cpp b/common/GL/ContextEGL.cpp deleted file mode 100644 index face6ac709..0000000000 --- a/common/GL/ContextEGL.cpp +++ /dev/null @@ -1,391 +0,0 @@ -/* 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/PrecompiledHeader.h" - -#include "common/Console.h" -#include "ContextEGL.h" -#include -#include -#include -#include - -namespace GL -{ - ContextEGL::ContextEGL(const WindowInfo& wi) - : Context(wi) - { - } - - ContextEGL::~ContextEGL() - { - DestroySurface(); - DestroyContext(); - } - - std::unique_ptr ContextEGL::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 ContextEGL::Initialize(gsl::span versions_to_try) - { - if (!gladLoadEGL()) - { - Console.Error("Loading GLAD EGL functions failed"); - return false; - } - - if (!SetDisplay()) - return false; - - int egl_major, egl_minor; - if (!eglInitialize(m_display, &egl_major, &egl_minor)) - { - Console.Error("eglInitialize() failed: %d", eglGetError()); - return false; - } - Console.WriteLn("EGL Version: %d.%d", egl_major, egl_minor); - - const char* extensions = eglQueryString(m_display, EGL_EXTENSIONS); - if (extensions) - { - Console.WriteLn("EGL Extensions: %s", extensions); - m_supports_surfaceless = std::strstr(extensions, "EGL_KHR_surfaceless_context") != nullptr; - } - if (!m_supports_surfaceless) - Console.Warning("EGL implementation does not support surfaceless contexts, emulating with pbuffers"); - - for (const Version& version : versions_to_try) - { - if (CreateContextAndSurface(version, nullptr, true)) - return true; - } - - return false; - } - - bool ContextEGL::SetDisplay() - { - m_display = eglGetDisplay(static_cast(m_wi.display_connection)); - if (!m_display) - { - Console.Error("eglGetDisplay() failed: %d", eglGetError()); - return false; - } - - return true; - } - - void* ContextEGL::GetProcAddress(const char* name) - { - return reinterpret_cast(eglGetProcAddress(name)); - } - - bool ContextEGL::ChangeSurface(const WindowInfo& new_wi) - { - const bool was_current = (eglGetCurrentContext() == m_context); - if (was_current) - eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - - if (m_surface != EGL_NO_SURFACE) - { - eglDestroySurface(m_display, m_surface); - m_surface = EGL_NO_SURFACE; - } - - m_wi = new_wi; - if (!CreateSurface()) - return false; - - if (was_current && !eglMakeCurrent(m_display, m_surface, m_surface, m_context)) - { - Console.Error("Failed to make context current again after surface change"); - return false; - } - - return true; - } - - void ContextEGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/) - { - if (new_surface_width == 0 && new_surface_height == 0) - { - EGLint surface_width, surface_height; - if (eglQuerySurface(m_display, m_surface, EGL_WIDTH, &surface_width) && - eglQuerySurface(m_display, m_surface, EGL_HEIGHT, &surface_height)) - { - m_wi.surface_width = static_cast(surface_width); - m_wi.surface_height = static_cast(surface_height); - return; - } - else - { - Console.Error("eglQuerySurface() failed: %d", eglGetError()); - } - } - - m_wi.surface_width = new_surface_width; - m_wi.surface_height = new_surface_height; - } - - bool ContextEGL::SwapBuffers() - { - return eglSwapBuffers(m_display, m_surface); - } - - bool ContextEGL::MakeCurrent() - { - if (!eglMakeCurrent(m_display, m_surface, m_surface, m_context)) - { - Console.Error("eglMakeCurrent() failed: %d", eglGetError()); - return false; - } - - return true; - } - - bool ContextEGL::DoneCurrent() - { - return eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - } - - bool ContextEGL::SetSwapInterval(s32 interval) - { - return eglSwapInterval(m_display, interval); - } - - std::unique_ptr ContextEGL::CreateSharedContext(const WindowInfo& wi) - { - std::unique_ptr context = std::make_unique(wi); - context->m_display = m_display; - context->m_supports_surfaceless = m_supports_surfaceless; - - if (!context->CreateContextAndSurface(m_version, m_context, false)) - return nullptr; - - return context; - } - - EGLNativeWindowType ContextEGL::GetNativeWindow(EGLConfig config) - { - return {}; - } - - bool ContextEGL::CreateSurface() - { - if (m_wi.type == WindowInfo::Type::Surfaceless) - { - if (m_supports_surfaceless) - return true; - else - return CreatePBufferSurface(); - } - - EGLNativeWindowType native_window = GetNativeWindow(m_config); - m_surface = eglCreateWindowSurface(m_display, m_config, native_window, nullptr); - if (!m_surface) - { - Console.Error("eglCreateWindowSurface() failed: %d", eglGetError()); - return false; - } - - // Some implementations may require the size to be queried at runtime. - EGLint surface_width, surface_height; - if (eglQuerySurface(m_display, m_surface, EGL_WIDTH, &surface_width) && - eglQuerySurface(m_display, m_surface, EGL_HEIGHT, &surface_height)) - { - m_wi.surface_width = static_cast(surface_width); - m_wi.surface_height = static_cast(surface_height); - } - else - { - Console.Error("eglQuerySurface() failed: %d", eglGetError()); - } - - return true; - } - - bool ContextEGL::CreatePBufferSurface() - { - const u32 width = std::max(m_wi.surface_width, 1); - const u32 height = std::max(m_wi.surface_height, 1); - - EGLint attrib_list[] = { - EGL_WIDTH, static_cast(width), - EGL_HEIGHT, static_cast(height), - EGL_NONE, - }; - - m_surface = eglCreatePbufferSurface(m_display, m_config, attrib_list); - if (!m_surface) - { - Console.Error("eglCreatePbufferSurface() failed: %d", eglGetError()); - return false; - } - - Console.WriteLn("Created %ux%u pbuffer surface", width, height); - return true; - } - - bool ContextEGL::CheckConfigSurfaceFormat(EGLConfig config) const - { - int red_size, green_size, blue_size; - if (!eglGetConfigAttrib(m_display, config, EGL_RED_SIZE, &red_size) || - !eglGetConfigAttrib(m_display, config, EGL_GREEN_SIZE, &green_size) || - !eglGetConfigAttrib(m_display, config, EGL_BLUE_SIZE, &blue_size)) - { - return false; - } - - return (red_size == 8 && green_size == 8 && blue_size == 8); - } - - void ContextEGL::DestroyContext() - { - if (eglGetCurrentContext() == m_context) - eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - - if (m_context != EGL_NO_CONTEXT) - { - eglDestroyContext(m_display, m_context); - m_context = EGL_NO_CONTEXT; - } - } - - void ContextEGL::DestroySurface() - { - if (eglGetCurrentSurface(EGL_DRAW) == m_surface) - eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - - if (m_surface != EGL_NO_SURFACE) - { - eglDestroySurface(m_display, m_surface); - m_surface = EGL_NO_SURFACE; - } - } - - bool ContextEGL::CreateContext(const Version& version, EGLContext share_context) - { - Console.WriteLn( - "Trying version %u.%u (%s)", version.major_version, version.minor_version, - version.profile == Context::Profile::ES ? "ES" : (version.profile == Context::Profile::Core ? "Core" : "None")); - - const int renderable_type = version.profile == Profile::ES - ? ((version.major_version >= 3) ? EGL_OPENGL_ES3_BIT : ((version.major_version == 2) ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_ES_BIT)) - : EGL_OPENGL_BIT; - const int surface_attribs[] = { - EGL_RENDERABLE_TYPE, renderable_type, - EGL_SURFACE_TYPE, (m_wi.type != WindowInfo::Type::Surfaceless) ? EGL_WINDOW_BIT : 0, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_NONE - }; - - EGLint num_configs; - if (!eglChooseConfig(m_display, surface_attribs, nullptr, 0, &num_configs) || num_configs == 0) - { - Console.Error("eglChooseConfig() failed: %d", eglGetError()); - return false; - } - - std::vector configs(static_cast(num_configs)); - if (!eglChooseConfig(m_display, surface_attribs, configs.data(), num_configs, &num_configs)) - { - Console.Error("eglChooseConfig() failed: %d", eglGetError()); - return false; - } - configs.resize(static_cast(num_configs)); - - m_config = [this, &configs]() { - const auto found_config = std::find_if(std::begin(configs), std::end(configs), [&](const auto& check_config) { - return CheckConfigSurfaceFormat(check_config); - }); - if (found_config == std::end(configs)) - { - Console.Warning("No EGL configs matched exactly, using first."); - return configs.front(); - } - else - { - return *found_config; - } - }(); - - const auto attribs = [version]() -> std::array { - if (version.profile != Profile::NoProfile) - return { - EGL_CONTEXT_MAJOR_VERSION, version.major_version, - EGL_CONTEXT_MINOR_VERSION, version.minor_version, - EGL_NONE - }; - return {EGL_NONE}; - }(); - - if (!eglBindAPI((version.profile == Profile::ES) ? EGL_OPENGL_ES_API : EGL_OPENGL_API)) - { - Console.Error("eglBindAPI(%s) failed", (version.profile == Profile::ES) ? "EGL_OPENGL_ES_API" : "EGL_OPENGL_API"); - return false; - } - - m_context = eglCreateContext(m_display, m_config, share_context, attribs.data()); - if (!m_context) - { - Console.Error("eglCreateContext() failed: %d", eglGetError()); - return false; - } - - Console.WriteLn( - "Got version %u.%u (%s)", version.major_version, version.minor_version, - version.profile == Context::Profile::ES ? "ES" : (version.profile == Context::Profile::Core ? "Core" : "None")); - - m_version = version; - return true; - } - - bool ContextEGL::CreateContextAndSurface(const Version& version, EGLContext share_context, bool make_current) - { - if (!CreateContext(version, share_context)) - return false; - - if (!CreateSurface()) - { - Console.Error("Failed to create surface for context"); - eglDestroyContext(m_display, m_context); - m_context = EGL_NO_CONTEXT; - return false; - } - - if (make_current && !eglMakeCurrent(m_display, m_surface, m_surface, m_context)) - { - Console.Error("eglMakeCurrent() failed: %d", eglGetError()); - if (m_surface != EGL_NO_SURFACE) - { - eglDestroySurface(m_display, m_surface); - m_surface = EGL_NO_SURFACE; - } - eglDestroyContext(m_display, m_context); - m_context = EGL_NO_CONTEXT; - return false; - } - - return true; - } -} // namespace GL diff --git a/common/GL/ContextEGL.h b/common/GL/ContextEGL.h deleted file mode 100644 index 5fa808efd1..0000000000 --- a/common/GL/ContextEGL.h +++ /dev/null @@ -1,63 +0,0 @@ -/* 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 . - */ - -#pragma once - -#include "common/GL/Context.h" -#include "glad_egl.h" - -namespace GL -{ - class ContextEGL : public Context - { - public: - ContextEGL(const WindowInfo& wi); - ~ContextEGL() override; - - static std::unique_ptr Create(const WindowInfo& wi, gsl::span versions_to_try); - - void* GetProcAddress(const char* name) override; - virtual bool ChangeSurface(const WindowInfo& new_wi) override; - virtual void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override; - bool SwapBuffers() override; - bool MakeCurrent() override; - bool DoneCurrent() override; - bool SetSwapInterval(s32 interval) override; - virtual std::unique_ptr CreateSharedContext(const WindowInfo& wi) override; - - protected: - virtual bool SetDisplay(); - virtual EGLNativeWindowType GetNativeWindow(EGLConfig config); - - bool Initialize(gsl::span versions_to_try); - bool CreateDisplay(); - bool CreateContext(const Version& version, EGLContext share_context); - bool CreateContextAndSurface(const Version& version, EGLContext share_context, bool make_current); - bool CreateSurface(); - bool CreatePBufferSurface(); - bool CheckConfigSurfaceFormat(EGLConfig config) const; - void DestroyContext(); - void DestroySurface(); - - EGLDisplay m_display = EGL_NO_DISPLAY; - EGLSurface m_surface = EGL_NO_SURFACE; - EGLContext m_context = EGL_NO_CONTEXT; - - EGLConfig m_config = {}; - - bool m_supports_surfaceless = false; - }; - -} // namespace GL diff --git a/common/GL/ContextEGLWayland.cpp b/common/GL/ContextEGLWayland.cpp deleted file mode 100644 index a010db1164..0000000000 --- a/common/GL/ContextEGLWayland.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* 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/PrecompiledHeader.h" -#include "common/Console.h" - -#include "ContextEGLWayland.h" - -#include - -namespace GL -{ - static const char* WAYLAND_EGL_MODNAME = "libwayland-egl.so.1"; - - ContextEGLWayland::ContextEGLWayland(const WindowInfo& wi) - : ContextEGL(wi) - { - } - - ContextEGLWayland::~ContextEGLWayland() - { - if (m_wl_window) - m_wl_egl_window_destroy(m_wl_window); - if (m_wl_module) - dlclose(m_wl_module); - } - - std::unique_ptr ContextEGLWayland::Create(const WindowInfo& wi, gsl::span versions_to_try) - { - std::unique_ptr context = std::make_unique(wi); - if (!context->LoadModule() || !context->Initialize(versions_to_try)) - return nullptr; - - return context; - } - - std::unique_ptr ContextEGLWayland::CreateSharedContext(const WindowInfo& wi) - { - std::unique_ptr context = std::make_unique(wi); - context->m_display = m_display; - - if (!context->LoadModule() || !context->CreateContextAndSurface(m_version, m_context, false)) - return nullptr; - - return context; - } - - void ContextEGLWayland::ResizeSurface(u32 new_surface_width, u32 new_surface_height) - { - if (m_wl_window) - m_wl_egl_window_resize(m_wl_window, new_surface_width, new_surface_height, 0, 0); - - ContextEGL::ResizeSurface(new_surface_width, new_surface_height); - } - - EGLNativeWindowType ContextEGLWayland::GetNativeWindow(EGLConfig config) - { - if (m_wl_window) - { - m_wl_egl_window_destroy(m_wl_window); - m_wl_window = nullptr; - } - - m_wl_window = - m_wl_egl_window_create(static_cast(m_wi.window_handle), m_wi.surface_width, m_wi.surface_height); - if (!m_wl_window) - return {}; - - return reinterpret_cast(m_wl_window); - } - - bool ContextEGLWayland::LoadModule() - { - m_wl_module = dlopen(WAYLAND_EGL_MODNAME, RTLD_NOW | RTLD_GLOBAL); - if (!m_wl_module) - { - Console.Error("Failed to load %s.", WAYLAND_EGL_MODNAME); - return false; - } - - m_wl_egl_window_create = reinterpret_cast(dlsym(m_wl_module, "wl_egl_window_create")); - m_wl_egl_window_destroy = reinterpret_cast(dlsym(m_wl_module, "wl_egl_window_destroy")); - m_wl_egl_window_resize = reinterpret_cast(dlsym(m_wl_module, "wl_egl_window_resize")); - if (!m_wl_egl_window_create || !m_wl_egl_window_destroy || !m_wl_egl_window_resize) - { - Console.Error("Failed to load one or more functions from %s.", WAYLAND_EGL_MODNAME); - return false; - } - - return true; - } -} // namespace GL diff --git a/common/GL/ContextEGLWayland.h b/common/GL/ContextEGLWayland.h deleted file mode 100644 index 156c79fec3..0000000000 --- a/common/GL/ContextEGLWayland.h +++ /dev/null @@ -1,49 +0,0 @@ -/* 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 . - */ - -#pragma once - -#include "common/GL/ContextEGL.h" - -struct wl_egl_window; -struct wl_surface; - -namespace GL -{ - class ContextEGLWayland final : public ContextEGL - { - public: - ContextEGLWayland(const WindowInfo& wi); - ~ContextEGLWayland() override; - - static std::unique_ptr Create(const WindowInfo& wi, gsl::span versions_to_try); - - std::unique_ptr CreateSharedContext(const WindowInfo& wi) override; - void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override; - - protected: - EGLNativeWindowType GetNativeWindow(EGLConfig config) override; - - private: - bool LoadModule(); - - wl_egl_window* m_wl_window = nullptr; - - void* m_wl_module = nullptr; - wl_egl_window* (*m_wl_egl_window_create)(struct wl_surface* surface, int width, int height); - void (*m_wl_egl_window_destroy)(struct wl_egl_window* egl_window); - void (*m_wl_egl_window_resize)(struct wl_egl_window* egl_window, int width, int height, int dx, int dy); - }; -} // namespace GL diff --git a/common/GL/ContextEGLX11.cpp b/common/GL/ContextEGLX11.cpp deleted file mode 100644 index c4c4322573..0000000000 --- a/common/GL/ContextEGLX11.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* 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/PrecompiledHeader.h" - -#include "common/GL/ContextEGLX11.h" - -#include - -namespace GL -{ - ContextEGLX11::ContextEGLX11(const WindowInfo& wi) - : ContextEGL(wi) - { - } - ContextEGLX11::~ContextEGLX11() = default; - - std::unique_ptr ContextEGLX11::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; - } - - std::unique_ptr ContextEGLX11::CreateSharedContext(const WindowInfo& wi) - { - std::unique_ptr context = std::make_unique(wi); - context->m_display = m_display; - - if (!context->CreateContextAndSurface(m_version, m_context, false)) - return nullptr; - - return context; - } - - void ContextEGLX11::ResizeSurface(u32 new_surface_width, u32 new_surface_height) - { - ContextEGL::ResizeSurface(new_surface_width, new_surface_height); - } - - EGLNativeWindowType ContextEGLX11::GetNativeWindow(EGLConfig config) - { - return (EGLNativeWindowType)reinterpret_cast(m_wi.window_handle); - } -} // namespace GL diff --git a/common/GL/ContextWGL.cpp b/common/GL/ContextWGL.cpp deleted file mode 100644 index a0d2ee3022..0000000000 --- a/common/GL/ContextWGL.cpp +++ /dev/null @@ -1,487 +0,0 @@ -/* 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/ContextWGL.h" -#include "common/Assertions.h" -#include "common/Console.h" -#include "common/ScopedGuard.h" - -static void* GetProcAddressCallback(const char* name) -{ - void* addr = reinterpret_cast(wglGetProcAddress(name)); - if (addr) - return addr; - - // try opengl32.dll - return reinterpret_cast(::GetProcAddress(GetModuleHandleA("opengl32.dll"), name)); -} - -static bool ReloadWGL(HDC dc) -{ - if (!gladLoadWGLLoader([](const char* name) -> void* { return reinterpret_cast(wglGetProcAddress(name)); }, dc)) - { - Console.Error("Loading GLAD WGL functions failed"); - return false; - } - - return true; -} - -namespace GL -{ - ContextWGL::ContextWGL(const WindowInfo& wi) - : Context(wi) - { - } - - ContextWGL::~ContextWGL() - { - if (wglGetCurrentContext() == m_rc) - wglMakeCurrent(m_dc, nullptr); - - if (m_rc) - wglDeleteContext(m_rc); - - ReleaseDC(); - } - - std::unique_ptr ContextWGL::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 ContextWGL::Initialize(gsl::span versions_to_try) - { - if (m_wi.type == WindowInfo::Type::Win32) - { - if (!InitializeDC()) - return false; - } - else - { - if (!CreatePBuffer()) - return false; - } - - // Everything including core/ES requires a dummy profile to load the WGL extensions. - if (!CreateAnyContext(nullptr, true)) - return false; - - for (const Version& cv : versions_to_try) - { - if (cv.profile == Profile::NoProfile) - { - // we already have the dummy context, so just use that - m_version = cv; - return true; - } - else if (CreateVersionContext(cv, nullptr, true)) - { - m_version = cv; - return true; - } - } - - return false; - } - - void* ContextWGL::GetProcAddress(const char* name) - { - return GetProcAddressCallback(name); - } - - bool ContextWGL::ChangeSurface(const WindowInfo& new_wi) - { - const bool was_current = (wglGetCurrentContext() == m_rc); - - ReleaseDC(); - - m_wi = new_wi; - if (!InitializeDC()) - return false; - - if (was_current && !wglMakeCurrent(m_dc, m_rc)) - { - Console.Error("Failed to make context current again after surface change: 0x%08X", GetLastError()); - return false; - } - - return true; - } - - void ContextWGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/) - { - RECT client_rc = {}; - GetClientRect(GetHWND(), &client_rc); - m_wi.surface_width = static_cast(client_rc.right - client_rc.left); - m_wi.surface_height = static_cast(client_rc.bottom - client_rc.top); - } - - bool ContextWGL::SwapBuffers() - { - return ::SwapBuffers(m_dc); - } - - bool ContextWGL::MakeCurrent() - { - if (!wglMakeCurrent(m_dc, m_rc)) - { - Console.Error("wglMakeCurrent() failed: 0x%08X", GetLastError()); - return false; - } - - return true; - } - - bool ContextWGL::DoneCurrent() - { - return wglMakeCurrent(m_dc, nullptr); - } - - bool ContextWGL::SetSwapInterval(s32 interval) - { - if (!GLAD_WGL_EXT_swap_control) - return false; - - return wglSwapIntervalEXT(interval); - } - - std::unique_ptr ContextWGL::CreateSharedContext(const WindowInfo& wi) - { - std::unique_ptr context = std::make_unique(wi); - if (wi.type == WindowInfo::Type::Win32) - { - if (!context->InitializeDC()) - return nullptr; - } - else - { - if (!context->CreatePBuffer()) - return nullptr; - } - - if (m_version.profile == Profile::NoProfile) - { - if (!context->CreateAnyContext(m_rc, false)) - return nullptr; - } - else - { - if (!context->CreateVersionContext(m_version, m_rc, false)) - return nullptr; - } - - context->m_version = m_version; - return context; - } - - HDC ContextWGL::GetDCAndSetPixelFormat(HWND hwnd) - { - PIXELFORMATDESCRIPTOR pfd = {}; - pfd.nSize = sizeof(pfd); - pfd.nVersion = 1; - pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; - pfd.iPixelType = PFD_TYPE_RGBA; - pfd.dwLayerMask = PFD_MAIN_PLANE; - pfd.cRedBits = 8; - pfd.cGreenBits = 8; - pfd.cBlueBits = 8; - pfd.cColorBits = 24; - - HDC hDC = ::GetDC(hwnd); - if (!hDC) - { - Console.Error("GetDC() failed: 0x%08X", GetLastError()); - return {}; - } - - if (!m_pixel_format.has_value()) - { - const int pf = ChoosePixelFormat(hDC, &pfd); - if (pf == 0) - { - Console.Error("ChoosePixelFormat() failed: 0x%08X", GetLastError()); - ::ReleaseDC(hwnd, hDC); - return {}; - } - - m_pixel_format = pf; - } - - if (!SetPixelFormat(hDC, m_pixel_format.value(), &pfd)) - { - Console.Error("SetPixelFormat() failed: 0x%08X", GetLastError()); - ::ReleaseDC(hwnd, hDC); - return {}; - } - - return hDC; - } - - bool ContextWGL::InitializeDC() - { - if (m_wi.type == WindowInfo::Type::Win32) - { - m_dc = GetDCAndSetPixelFormat(GetHWND()); - if (!m_dc) - { - Console.Error("Failed to get DC for window"); - return false; - } - - return true; - } - else if (m_wi.type == WindowInfo::Type::Surfaceless) - { - return CreatePBuffer(); - } - else - { - Console.Error("Unknown window info type %u", static_cast(m_wi.type)); - return false; - } - } - - void ContextWGL::ReleaseDC() - { - if (m_pbuffer) - { - wglReleasePbufferDCARB(m_pbuffer, m_dc); - m_dc = {}; - - wglDestroyPbufferARB(m_pbuffer); - m_pbuffer = {}; - - ::ReleaseDC(m_dummy_window, m_dummy_dc); - m_dummy_dc = {}; - - DestroyWindow(m_dummy_window); - m_dummy_window = {}; - } - else if (m_dc) - { - ::ReleaseDC(GetHWND(), m_dc); - m_dc = {}; - } - } - - bool ContextWGL::CreatePBuffer() - { - static bool window_class_registered = false; - static const wchar_t* window_class_name = L"ContextWGLPBuffer"; - - if (!window_class_registered) - { - WNDCLASSEXW wc = {}; - wc.cbSize = sizeof(WNDCLASSEXW); - wc.style = 0; - wc.lpfnWndProc = DefWindowProcW; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0; - wc.hInstance = GetModuleHandle(nullptr); - wc.hIcon = NULL; - wc.hCursor = LoadCursor(NULL, IDC_ARROW); - wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); - wc.lpszMenuName = NULL; - wc.lpszClassName = window_class_name; - wc.hIconSm = NULL; - - if (!RegisterClassExW(&wc)) - { - Console.Error("(ContextWGL::CreatePBuffer) RegisterClassExW() failed"); - return false; - } - - window_class_registered = true; - } - - HWND hwnd = CreateWindowExW(0, window_class_name, window_class_name, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL); - if (!hwnd) - { - Console.Error("(ContextWGL::CreatePBuffer) CreateWindowEx() failed"); - return false; - } - - ScopedGuard hwnd_guard([hwnd]() { DestroyWindow(hwnd); }); - - HDC hdc = GetDCAndSetPixelFormat(hwnd); - if (!hdc) - return false; - - ScopedGuard hdc_guard([hdc, hwnd]() { ::ReleaseDC(hwnd, hdc); }); - - static constexpr const int pb_attribs[] = {0, 0}; - - HGLRC temp_rc = nullptr; - ScopedGuard temp_rc_guard([&temp_rc, hdc]() { if (temp_rc) { - wglMakeCurrent(hdc, nullptr); - wglDeleteContext(temp_rc); - } }); - - if (!GLAD_WGL_ARB_pbuffer) - { - // we're probably running completely surfaceless... need a temporary context. - temp_rc = wglCreateContext(hdc); - if (!temp_rc || !wglMakeCurrent(hdc, temp_rc)) - { - Console.Error("Failed to create temporary context to load WGL for pbuffer."); - return false; - } - - if (!ReloadWGL(hdc) || !GLAD_WGL_ARB_pbuffer) - { - Console.Error("Missing WGL_ARB_pbuffer"); - return false; - } - } - - pxAssertRel(m_pixel_format.has_value(), "Has pixel format for pbuffer"); - HPBUFFERARB pbuffer = wglCreatePbufferARB(hdc, m_pixel_format.value(), 1, 1, pb_attribs); - if (!pbuffer) - { - Console.Error("(ContextWGL::CreatePBuffer) wglCreatePbufferARB() failed"); - return false; - } - - ScopedGuard pbuffer_guard([pbuffer]() { wglDestroyPbufferARB(pbuffer); }); - - m_dc = wglGetPbufferDCARB(pbuffer); - if (!m_dc) - { - Console.Error("(ContextWGL::CreatePbuffer) wglGetPbufferDCARB() failed"); - return false; - } - - m_dummy_window = hwnd; - m_dummy_dc = hdc; - m_pbuffer = pbuffer; - - temp_rc_guard.Run(); - pbuffer_guard.Cancel(); - hdc_guard.Cancel(); - hwnd_guard.Cancel(); - return true; - } - - bool ContextWGL::CreateAnyContext(HGLRC share_context, bool make_current) - { - m_rc = wglCreateContext(m_dc); - if (!m_rc) - { - Console.Error("wglCreateContext() failed: 0x%08X", GetLastError()); - return false; - } - - if (make_current) - { - if (!wglMakeCurrent(m_dc, m_rc)) - { - Console.Error("wglMakeCurrent() failed: 0x%08X", GetLastError()); - return false; - } - - // re-init glad-wgl - if (!gladLoadWGLLoader([](const char* name) -> void* { return reinterpret_cast(wglGetProcAddress(name)); }, m_dc)) - { - Console.Error("Loading GLAD WGL functions failed"); - return false; - } - } - - if (share_context && !wglShareLists(share_context, m_rc)) - { - Console.Error("wglShareLists() failed: 0x%08X", GetLastError()); - return false; - } - - return true; - } - - bool ContextWGL::CreateVersionContext(const Version& version, HGLRC share_context, bool make_current) - { - // we need create context attribs - if (!GLAD_WGL_ARB_create_context) - { - Console.Error("Missing GLAD_WGL_ARB_create_context."); - return false; - } - - HGLRC new_rc; - if (version.profile == Profile::Core) - { - const int attribs[] = { - WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, - WGL_CONTEXT_MAJOR_VERSION_ARB, version.major_version, - WGL_CONTEXT_MINOR_VERSION_ARB, version.minor_version, -#ifdef _DEBUG - WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB | WGL_CONTEXT_DEBUG_BIT_ARB, -#else - WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, -#endif - 0, 0}; - - new_rc = wglCreateContextAttribsARB(m_dc, share_context, attribs); - } - else if (version.profile == Profile::ES) - { - if ((version.major_version >= 2 && !GLAD_WGL_EXT_create_context_es2_profile) || - (version.major_version < 2 && !GLAD_WGL_EXT_create_context_es_profile)) - { - Console.Error("WGL_EXT_create_context_es_profile not supported"); - return false; - } - - const int attribs[] = { - WGL_CONTEXT_PROFILE_MASK_ARB, ((version.major_version >= 2) ? WGL_CONTEXT_ES2_PROFILE_BIT_EXT : WGL_CONTEXT_ES_PROFILE_BIT_EXT), - WGL_CONTEXT_MAJOR_VERSION_ARB, version.major_version, - WGL_CONTEXT_MINOR_VERSION_ARB, version.minor_version, - 0, 0}; - - new_rc = wglCreateContextAttribsARB(m_dc, share_context, attribs); - } - else - { - Console.Error("Unknown profile"); - return false; - } - - if (!new_rc) - return false; - - // destroy and swap contexts - if (m_rc) - { - if (!wglMakeCurrent(m_dc, make_current ? new_rc : nullptr)) - { - Console.Error("wglMakeCurrent() failed: 0x%08X", GetLastError()); - wglDeleteContext(new_rc); - return false; - } - - // re-init glad-wgl - if (make_current && !ReloadWGL(m_dc)) - return false; - - wglDeleteContext(m_rc); - } - - m_rc = new_rc; - return true; - } -} // namespace GL diff --git a/common/GL/ContextWGL.h b/common/GL/ContextWGL.h deleted file mode 100644 index 789c8bc9e0..0000000000 --- a/common/GL/ContextWGL.h +++ /dev/null @@ -1,71 +0,0 @@ -/* 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 . - */ - -#pragma once - -#include "common/GL/Context.h" - -#include "glad_wgl.h" -#include "glad.h" - -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#include -#include - -namespace GL -{ - class ContextWGL final : public Context - { - public: - ContextWGL(const WindowInfo& wi); - ~ContextWGL() override; - - static std::unique_ptr Create(const WindowInfo& wi, gsl::span versions_to_try); - - void* GetProcAddress(const char* name) override; - bool ChangeSurface(const WindowInfo& new_wi) override; - void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override; - bool SwapBuffers() override; - bool MakeCurrent() override; - bool DoneCurrent() override; - bool SetSwapInterval(s32 interval) override; - std::unique_ptr CreateSharedContext(const WindowInfo& wi) override; - - private: - __fi HWND GetHWND() const { return static_cast(m_wi.window_handle); } - - HDC GetDCAndSetPixelFormat(HWND hwnd); - - bool Initialize(gsl::span versions_to_try); - bool InitializeDC(); - void ReleaseDC(); - bool CreatePBuffer(); - bool CreateAnyContext(HGLRC share_context, bool make_current); - bool CreateVersionContext(const Version& version, HGLRC share_context, bool make_current); - - HDC m_dc = {}; - HGLRC m_rc = {}; - - // Can't change pixel format once it's set for a RC. - std::optional m_pixel_format; - - // Dummy window for creating a PBuffer off when we're surfaceless. - HWND m_dummy_window = {}; - HDC m_dummy_dc = {}; - HPBUFFERARB m_pbuffer = {}; - }; -} // namespace GL \ No newline at end of file diff --git a/common/GL/Program.cpp b/common/GL/Program.cpp deleted file mode 100644 index bfa0452f08..0000000000 --- a/common/GL/Program.cpp +++ /dev/null @@ -1,534 +0,0 @@ -/* 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/PrecompiledHeader.h" - -#include "common/GL/Program.h" -#include "common/Assertions.h" -#include "common/Console.h" -#include "common/StringUtil.h" -#include -#include - -namespace GL -{ - GLuint Program::s_last_program_id = 0; - static GLuint s_next_bad_shader_id = 1; - - Program::Program() = default; - - Program::Program(Program&& prog) - { - m_program_id = prog.m_program_id; - prog.m_program_id = 0; - m_vertex_shader_id = prog.m_vertex_shader_id; - prog.m_vertex_shader_id = 0; - m_fragment_shader_id = prog.m_fragment_shader_id; - prog.m_fragment_shader_id = 0; - m_uniform_locations = std::move(prog.m_uniform_locations); - } - - Program::~Program() - { - Destroy(); - } - - GLuint Program::CompileShader(GLenum type, const std::string_view source) - { - GLuint id = glCreateShader(type); - - std::array sources = {{source.data()}}; - std::array source_lengths = {{static_cast(source.size())}}; - glShaderSource(id, static_cast(sources.size()), sources.data(), source_lengths.data()); - glCompileShader(id); - - GLint status = GL_FALSE; - glGetShaderiv(id, GL_COMPILE_STATUS, &status); - - GLint info_log_length = 0; - glGetShaderiv(id, GL_INFO_LOG_LENGTH, &info_log_length); - - // Log will create a new line when there are no warnings so let's set a minimum log length of 1. - constexpr int info_log_min_length = 1; - - if (status == GL_FALSE || info_log_length > info_log_min_length) - { - std::string info_log; - info_log.resize(info_log_length + 1); - glGetShaderInfoLog(id, info_log_length, &info_log_length, &info_log[0]); - - if (status == GL_TRUE) - { - Console.Warning("Shader compiled with warnings:\n%s", info_log.c_str()); - } - else - { - Console.Error("Shader failed to compile:\n%s", info_log.c_str()); - - std::ofstream ofs(StringUtil::StdStringFromFormat("pcsx2_bad_shader_%u.txt", s_next_bad_shader_id++).c_str(), - std::ofstream::out | std::ofstream::binary); - if (ofs.is_open()) - { - ofs.write(sources[0], source_lengths[0]); - ofs << "\n\nCompile failed, info log:\n"; - ofs << info_log; - ofs.close(); - } - - glDeleteShader(id); - return 0; - } - } - - return id; - } - - void Program::ResetLastProgram() - { - s_last_program_id = 0; - } - - bool Program::Compile(const std::string_view vertex_shader, const std::string_view fragment_shader) - { - if (!vertex_shader.empty()) - { - m_vertex_shader_id = CompileShader(GL_VERTEX_SHADER, vertex_shader); - if (m_vertex_shader_id == 0) - return false; - } - - if (!fragment_shader.empty()) - { - m_fragment_shader_id = CompileShader(GL_FRAGMENT_SHADER, fragment_shader); - if (m_fragment_shader_id == 0) - return false; - } - - m_program_id = glCreateProgram(); - if (m_vertex_shader_id != 0) - glAttachShader(m_program_id, m_vertex_shader_id); - if (m_fragment_shader_id != 0) - glAttachShader(m_program_id, m_fragment_shader_id); - return true; - } - - bool Program::CompileCompute(const std::string_view glsl) - { - GLuint id = CompileShader(GL_COMPUTE_SHADER, glsl); - if (id == 0) - return false; - - m_program_id = glCreateProgram(); - glAttachShader(m_program_id, id); - return true; - } - - bool Program::CreateFromBinary(const void* data, u32 data_length, u32 data_format) - { - GLuint prog = glCreateProgram(); - glProgramBinary(prog, static_cast(data_format), data, data_length); - - GLint link_status; - glGetProgramiv(prog, GL_LINK_STATUS, &link_status); - if (link_status != GL_TRUE) - { - Console.Error("Failed to create GL program from binary: status %d", link_status); - glDeleteProgram(prog); - return false; - } - - m_program_id = prog; - return true; - } - - bool Program::GetBinary(std::vector* out_data, u32* out_data_format) - { - GLint binary_size = 0; - glGetProgramiv(m_program_id, GL_PROGRAM_BINARY_LENGTH, &binary_size); - if (binary_size == 0) - { - Console.Warning("glGetProgramiv(GL_PROGRAM_BINARY_LENGTH) returned 0"); - return false; - } - - GLenum format = 0; - out_data->resize(static_cast(binary_size)); - glGetProgramBinary(m_program_id, binary_size, &binary_size, &format, out_data->data()); - if (binary_size == 0) - { - Console.Warning("glGetProgramBinary() failed"); - return false; - } - else if (static_cast(binary_size) != out_data->size()) - { - Console.Warning("Size changed from %zu to %d after glGetProgramBinary()", out_data->size(), binary_size); - out_data->resize(static_cast(binary_size)); - } - - *out_data_format = static_cast(format); - DevCon.WriteLn("Program binary retrieved, %zu bytes, format %u", out_data->size(), *out_data_format); - return true; - } - - void Program::SetBinaryRetrievableHint() - { - glProgramParameteri(m_program_id, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE); - } - - void Program::BindAttribute(GLuint index, const char* name) - { - glBindAttribLocation(m_program_id, index, name); - } - - void Program::BindDefaultAttributes() - { - BindAttribute(0, "a_position"); - BindAttribute(1, "a_texcoord"); - BindAttribute(2, "a_color"); - } - - void Program::BindFragData(GLuint index /*= 0*/, const char* name /*= "o_col0"*/) - { - glBindFragDataLocation(m_program_id, index, name); - } - - void Program::BindFragDataIndexed(GLuint color_number /*= 0*/, const char* name /*= "o_col0"*/) - { - if (GLAD_GL_VERSION_3_3 || GLAD_GL_ARB_blend_func_extended) - { - glBindFragDataLocationIndexed(m_program_id, color_number, 0, name); - return; - } - else if (GLAD_GL_EXT_blend_func_extended) - { - glBindFragDataLocationIndexedEXT(m_program_id, color_number, 0, name); - return; - } - - Console.Error("BindFragDataIndexed() called without ARB or EXT extension, we'll probably crash."); - glBindFragDataLocationIndexed(m_program_id, color_number, 0, name); - } - - bool Program::Link() - { - glLinkProgram(m_program_id); - - if (m_vertex_shader_id != 0) - glDeleteShader(m_vertex_shader_id); - m_vertex_shader_id = 0; - if (m_fragment_shader_id != 0) - glDeleteShader(m_fragment_shader_id); - m_fragment_shader_id = 0; - - GLint status = GL_FALSE; - glGetProgramiv(m_program_id, GL_LINK_STATUS, &status); - - GLint info_log_length = 0; - - // Log will create a new line when there are no warnings so let's set a minimum log length of 1. - constexpr int info_log_min_length = 1; - - glGetProgramiv(m_program_id, GL_INFO_LOG_LENGTH, &info_log_length); - - if (status == GL_FALSE || info_log_length > info_log_min_length) - { - std::string info_log; - info_log.resize(info_log_length + 1); - glGetProgramInfoLog(m_program_id, info_log_length, &info_log_length, &info_log[0]); - - if (status == GL_TRUE) - { - Console.Error("Program linked with warnings:\n%s", info_log.c_str()); - } - else - { - Console.Error("Program failed to link:\n%s", info_log.c_str()); - glDeleteProgram(m_program_id); - m_program_id = 0; - return false; - } - } - - return true; - } - - void Program::Bind() const - { - if (s_last_program_id == m_program_id) - return; - - glUseProgram(m_program_id); - s_last_program_id = m_program_id; - } - - void Program::Destroy() - { - if (m_vertex_shader_id != 0) - { - glDeleteShader(m_vertex_shader_id); - m_vertex_shader_id = 0; - } - if (m_fragment_shader_id != 0) - { - glDeleteShader(m_fragment_shader_id); - m_fragment_shader_id = 0; - } - if (m_program_id != 0) - { - glDeleteProgram(m_program_id); - m_program_id = 0; - } - - m_uniform_locations.clear(); - } - - int Program::RegisterUniform(const char* name) - { - int id = static_cast(m_uniform_locations.size()); - m_uniform_locations.push_back(glGetUniformLocation(m_program_id, name)); - return id; - } - - void Program::Uniform1ui(int index, u32 x) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform1ui(location, x); - } - - void Program::Uniform2ui(int index, u32 x, u32 y) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform2ui(location, x, y); - } - - void Program::Uniform3ui(int index, u32 x, u32 y, u32 z) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform3ui(location, x, y, z); - } - - void Program::Uniform4ui(int index, u32 x, u32 y, u32 z, u32 w) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform4ui(location, x, y, z, w); - } - - void Program::Uniform1i(int index, s32 x) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform1i(location, x); - } - - void Program::Uniform2i(int index, s32 x, s32 y) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform2i(location, x, y); - } - - void Program::Uniform3i(int index, s32 x, s32 y, s32 z) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform3i(location, x, y, z); - } - - void Program::Uniform4i(int index, s32 x, s32 y, s32 z, s32 w) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform4i(location, x, y, z, w); - } - - void Program::Uniform1f(int index, float x) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform1f(location, x); - } - - void Program::Uniform2f(int index, float x, float y) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform2f(location, x, y); - } - - void Program::Uniform3f(int index, float x, float y, float z) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform3f(location, x, y, z); - } - - void Program::Uniform4f(int index, float x, float y, float z, float w) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform4f(location, x, y, z, w); - } - - void Program::Uniform2uiv(int index, const u32* v) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform2uiv(location, 1, v); - } - - void Program::Uniform3uiv(int index, const u32* v) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform3uiv(location, 1, v); - } - - void Program::Uniform4uiv(int index, const u32* v) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform4uiv(location, 1, v); - } - - void Program::Uniform2iv(int index, const s32* v) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform2iv(location, 1, v); - } - - void Program::Uniform3iv(int index, const s32* v) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform3iv(location, 1, v); - } - - void Program::Uniform4iv(int index, const s32* v) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform4iv(location, 1, v); - } - - void Program::Uniform2fv(int index, const float* v) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform2fv(location, 1, v); - } - - void Program::Uniform3fv(int index, const float* v) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform3fv(location, 1, v); - } - - void Program::Uniform4fv(int index, const float* v) const - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniform4fv(location, 1, v); - } - - void Program::UniformMatrix2fv(int index, const float* v) - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniformMatrix2fv(location, 1, GL_FALSE, v); - } - - void Program::UniformMatrix3fv(int index, const float* v) - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniformMatrix3fv(location, 1, GL_FALSE, v); - } - - void Program::UniformMatrix4fv(int index, const float* v) - { - pxAssert(static_cast(index) < m_uniform_locations.size()); - const GLint location = m_uniform_locations[index]; - if (location >= 0) - glUniformMatrix4fv(location, 1, GL_FALSE, v); - } - - void Program::BindUniformBlock(const char* name, u32 index) - { - const GLint location = glGetUniformBlockIndex(m_program_id, name); - if (location >= 0) - glUniformBlockBinding(m_program_id, location, index); - } - - void Program::SetName(const std::string_view& name) - { - if (name.empty()) - return; - -#ifdef _DEBUG - glObjectLabel(GL_PROGRAM, m_program_id, name.length(), name.data()); -#endif - } - - void Program::SetFormattedName(const char* format, ...) - { - va_list ap; - va_start(ap, format); - std::string n = StringUtil::StdStringFromFormatV(format, ap); - va_end(ap); - SetName(n); - } - - Program& Program::operator=(Program&& prog) - { - Destroy(); - m_program_id = prog.m_program_id; - prog.m_program_id = 0; - m_vertex_shader_id = prog.m_vertex_shader_id; - prog.m_vertex_shader_id = 0; - m_fragment_shader_id = prog.m_fragment_shader_id; - prog.m_fragment_shader_id = 0; - m_uniform_locations = std::move(prog.m_uniform_locations); - return *this; - } -} // namespace GL diff --git a/common/GL/Program.h b/common/GL/Program.h deleted file mode 100644 index f4f4718cdd..0000000000 --- a/common/GL/Program.h +++ /dev/null @@ -1,105 +0,0 @@ -/* 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 . - */ - -#pragma once -#include "../Pcsx2Defs.h" -#include "glad.h" -#include -#include - -namespace GL -{ - class Program - { - public: - Program(); - Program(const Program&) = delete; - Program(Program&& prog); - ~Program(); - - static GLuint CompileShader(GLenum type, const std::string_view source); - static void ResetLastProgram(); - - bool IsValid() const { return m_program_id != 0; } - - bool Compile(const std::string_view vertex_shader, const std::string_view fragment_shader); - - bool CompileCompute(const std::string_view glsl); - - bool CreateFromBinary(const void* data, u32 data_length, u32 data_format); - - bool GetBinary(std::vector* out_data, u32* out_data_format); - void SetBinaryRetrievableHint(); - - void BindAttribute(GLuint index, const char* name); - void BindDefaultAttributes(); - - void BindFragData(GLuint index = 0, const char* name = "o_col0"); - void BindFragDataIndexed(GLuint color_number = 0, const char* name = "o_col0"); - - bool Link(); - - void Bind() const; - - void Destroy(); - - int RegisterUniform(const char* name); - void Uniform1ui(int index, u32 x) const; - void Uniform2ui(int index, u32 x, u32 y) const; - void Uniform3ui(int index, u32 x, u32 y, u32 z) const; - void Uniform4ui(int index, u32 x, u32 y, u32 z, u32 w) const; - void Uniform1i(int index, s32 x) const; - void Uniform2i(int index, s32 x, s32 y) const; - void Uniform3i(int index, s32 x, s32 y, s32 z) const; - void Uniform4i(int index, s32 x, s32 y, s32 z, s32 w) const; - void Uniform1f(int index, float x) const; - void Uniform2f(int index, float x, float y) const; - void Uniform3f(int index, float x, float y, float z) const; - void Uniform4f(int index, float x, float y, float z, float w) const; - void Uniform2uiv(int index, const u32* v) const; - void Uniform3uiv(int index, const u32* v) const; - void Uniform4uiv(int index, const u32* v) const; - void Uniform2iv(int index, const s32* v) const; - void Uniform3iv(int index, const s32* v) const; - void Uniform4iv(int index, const s32* v) const; - void Uniform2fv(int index, const float* v) const; - void Uniform3fv(int index, const float* v) const; - void Uniform4fv(int index, const float* v) const; - - void UniformMatrix2fv(int index, const float* v); - void UniformMatrix3fv(int index, const float* v); - void UniformMatrix4fv(int index, const float* v); - - void BindUniformBlock(const char* name, u32 index); - - void SetName(const std::string_view& name); - void SetFormattedName(const char* format, ...); - - Program& operator=(const Program&) = delete; - Program& operator=(Program&& prog); - - __fi bool operator==(const Program& rhs) const { return m_program_id == rhs.m_program_id; } - __fi bool operator!=(const Program& rhs) const { return m_program_id != rhs.m_program_id; } - - private: - static u32 s_last_program_id; - - GLuint m_program_id = 0; - GLuint m_vertex_shader_id = 0; - GLuint m_fragment_shader_id = 0; - - std::vector m_uniform_locations; - }; -} // namespace GL \ No newline at end of file diff --git a/common/GL/ShaderCache.cpp b/common/GL/ShaderCache.cpp deleted file mode 100644 index 19072272e7..0000000000 --- a/common/GL/ShaderCache.cpp +++ /dev/null @@ -1,547 +0,0 @@ -/* 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/ShaderCache.h" -#include "common/FileSystem.h" -#include "common/Console.h" -#include "common/MD5Digest.h" -#include "common/StringUtil.h" -#include "common/Timer.h" - -namespace GL -{ -#pragma pack(push, 1) - struct CacheIndexEntry - { - u64 vertex_source_hash_low; - u64 vertex_source_hash_high; - u32 vertex_source_length; - u64 fragment_source_hash_low; - u64 fragment_source_hash_high; - u32 fragment_source_length; - u32 file_offset; - u32 blob_size; - u32 blob_format; - }; -#pragma pack(pop) - - ShaderCache::ShaderCache() = default; - - ShaderCache::~ShaderCache() - { - Close(); - } - - bool ShaderCache::CacheIndexKey::operator==(const CacheIndexKey& key) const - { - return ( - vertex_source_hash_low == key.vertex_source_hash_low && vertex_source_hash_high == key.vertex_source_hash_high && - vertex_source_length == key.vertex_source_length && fragment_source_hash_low == key.fragment_source_hash_low && - fragment_source_hash_high == key.fragment_source_hash_high && fragment_source_length == key.fragment_source_length); - } - - bool ShaderCache::CacheIndexKey::operator!=(const CacheIndexKey& key) const - { - return ( - vertex_source_hash_low != key.vertex_source_hash_low || vertex_source_hash_high != key.vertex_source_hash_high || - vertex_source_length != key.vertex_source_length || fragment_source_hash_low != key.fragment_source_hash_low || - fragment_source_hash_high != key.fragment_source_hash_high || fragment_source_length != key.fragment_source_length); - } - - bool ShaderCache::Open(bool is_gles, std::string_view base_path, u32 version) - { - m_base_path = base_path; - m_version = version; - m_program_binary_supported = is_gles || GLAD_GL_ARB_get_program_binary; - if (m_program_binary_supported) - { - // check that there's at least one format and the extension isn't being "faked" - GLint num_formats = 0; - glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &num_formats); - Console.WriteLn("%u program binary formats supported by driver", num_formats); - m_program_binary_supported = (num_formats > 0); - } - - if (!m_program_binary_supported) - { - Console.Warning("Your GL driver does not support program binaries. Hopefully it has a built-in cache."); - return true; - } - - if (!base_path.empty()) - { - const std::string index_filename = GetIndexFileName(); - const std::string blob_filename = GetBlobFileName(); - - if (ReadExisting(index_filename, blob_filename)) - return true; - - return CreateNew(index_filename, blob_filename); - } - - return true; - } - - bool ShaderCache::CreateNew(const std::string& index_filename, const std::string& blob_filename) - { - if (FileSystem::FileExists(index_filename.c_str())) - { - Console.Warning("Removing existing index file '%s'", index_filename.c_str()); - FileSystem::DeleteFilePath(index_filename.c_str()); - } - if (FileSystem::FileExists(blob_filename.c_str())) - { - Console.Warning("Removing existing blob file '%s'", blob_filename.c_str()); - FileSystem::DeleteFilePath(blob_filename.c_str()); - } - - m_index_file = FileSystem::OpenCFile(index_filename.c_str(), "wb"); - if (!m_index_file) - { - Console.Error("Failed to open index file '%s' for writing", index_filename.c_str()); - return false; - } - - const u32 index_version = FILE_VERSION; - if (std::fwrite(&index_version, sizeof(index_version), 1, m_index_file) != 1 || - std::fwrite(&m_version, sizeof(m_version), 1, m_index_file) != 1) - { - Console.Error("Failed to write version to index file '%s'", index_filename.c_str()); - std::fclose(m_index_file); - m_index_file = nullptr; - FileSystem::DeleteFilePath(index_filename.c_str()); - return false; - } - - m_blob_file = FileSystem::OpenCFile(blob_filename.c_str(), "w+b"); - if (!m_blob_file) - { - Console.Error("Failed to open blob file '%s' for writing", blob_filename.c_str()); - std::fclose(m_index_file); - m_index_file = nullptr; - FileSystem::DeleteFilePath(index_filename.c_str()); - return false; - } - - return true; - } - - bool ShaderCache::ReadExisting(const std::string& index_filename, const std::string& blob_filename) - { - m_index_file = FileSystem::OpenCFile(index_filename.c_str(), "r+b"); - if (!m_index_file) - { - // special case here: when there's a sharing violation (i.e. two instances running), - // we don't want to blow away the cache. so just continue without a cache. - if (errno == EACCES) - { - Console.WriteLn("Failed to open shader cache index with EACCES, are you running two instances?"); - return true; - } - - return false; - } - - u32 file_version = 0; - u32 data_version = 0; - if (std::fread(&file_version, sizeof(file_version), 1, m_index_file) != 1 || file_version != FILE_VERSION || - std::fread(&data_version, sizeof(data_version), 1, m_index_file) != 1 || data_version != m_version) - { - Console.Error("Bad file/data version in '%s'", index_filename.c_str()); - std::fclose(m_index_file); - m_index_file = nullptr; - return false; - } - - m_blob_file = FileSystem::OpenCFile(blob_filename.c_str(), "a+b"); - if (!m_blob_file) - { - Console.Error("Blob file '%s' is missing", blob_filename.c_str()); - std::fclose(m_index_file); - m_index_file = nullptr; - return false; - } - - std::fseek(m_blob_file, 0, SEEK_END); - const u32 blob_file_size = static_cast(std::ftell(m_blob_file)); - - for (;;) - { - CacheIndexEntry entry; - if (std::fread(&entry, sizeof(entry), 1, m_index_file) != 1 || - (entry.file_offset + entry.blob_size) > blob_file_size) - { - if (std::feof(m_index_file)) - break; - - Console.Error("Failed to read entry from '%s', corrupt file?", index_filename.c_str()); - m_index.clear(); - std::fclose(m_blob_file); - m_blob_file = nullptr; - std::fclose(m_index_file); - m_index_file = nullptr; - return false; - } - - const CacheIndexKey key{ - entry.vertex_source_hash_low, entry.vertex_source_hash_high, entry.vertex_source_length, - entry.fragment_source_hash_low, entry.fragment_source_hash_high, entry.fragment_source_length}; - const CacheIndexData data{entry.file_offset, entry.blob_size, entry.blob_format}; - m_index.emplace(key, data); - } - - Console.WriteLn("Read %zu entries from '%s'", m_index.size(), index_filename.c_str()); - return true; - } - - void ShaderCache::Close() - { - m_index.clear(); - if (m_index_file) - { - std::fclose(m_index_file); - m_index_file = nullptr; - } - if (m_blob_file) - { - std::fclose(m_blob_file); - m_blob_file = nullptr; - } - - m_base_path = {}; - } - - bool ShaderCache::Recreate() - { - Close(); - - const std::string index_filename = GetIndexFileName(); - const std::string blob_filename = GetBlobFileName(); - - return CreateNew(index_filename, blob_filename); - } - - ShaderCache::CacheIndexKey ShaderCache::GetCacheKey(const std::string_view& vertex_shader, - const std::string_view& fragment_shader) - { - union ShaderHash - { - struct - { - u64 low; - u64 high; - }; - u8 bytes[16]; - }; - - ShaderHash vertex_hash = {}; - ShaderHash fragment_hash = {}; - - MD5Digest digest; - if (!vertex_shader.empty()) - { - digest.Update(vertex_shader.data(), static_cast(vertex_shader.length())); - digest.Final(vertex_hash.bytes); - } - - if (!fragment_shader.empty()) - { - digest.Reset(); - digest.Update(fragment_shader.data(), static_cast(fragment_shader.length())); - digest.Final(fragment_hash.bytes); - } - - return CacheIndexKey{vertex_hash.low, vertex_hash.high, static_cast(vertex_shader.length()), - fragment_hash.low, fragment_hash.high, static_cast(fragment_shader.length())}; - } - - std::string ShaderCache::GetIndexFileName() const - { - return StringUtil::StdStringFromFormat("%s/gl_programs.idx", m_base_path.c_str()); - } - - std::string ShaderCache::GetBlobFileName() const - { - return StringUtil::StdStringFromFormat("%s/gl_programs.bin", m_base_path.c_str()); - } - - std::optional ShaderCache::GetProgram(const std::string_view vertex_shader, - const std::string_view fragment_shader, const PreLinkCallback& callback) - { - if (!m_program_binary_supported || !m_blob_file) - { -#ifdef PCSX2_DEVBUILD - Common::Timer timer; -#endif - - std::optional res = CompileProgram(vertex_shader, fragment_shader, callback, false); - -#ifdef PCSX2_DEVBUILD - Console.WriteLn("Time to compile shader without caching: %.2fms", timer.GetTimeMilliseconds()); -#endif - return res; - } - - const auto key = GetCacheKey(vertex_shader, fragment_shader); - auto iter = m_index.find(key); - if (iter == m_index.end()) - return CompileAndAddProgram(key, vertex_shader, fragment_shader, callback); - - std::vector data(iter->second.blob_size); - if (std::fseek(m_blob_file, iter->second.file_offset, SEEK_SET) != 0 || - std::fread(data.data(), 1, iter->second.blob_size, m_blob_file) != iter->second.blob_size) - { - Console.Error("Read blob from file failed"); - return {}; - } - -#ifdef PCSX2_DEVBUILD - Common::Timer timer; -#endif - - Program prog; - if (prog.CreateFromBinary(data.data(), static_cast(data.size()), iter->second.blob_format)) - { -#ifdef PCSX2_DEVBUILD - Console.WriteLn("Time to create program from binary: %.2fms", timer.GetTimeMilliseconds()); -#endif - - return std::optional(std::move(prog)); - } - - Console.Warning( - "Failed to create program from binary, this may be due to a driver or GPU Change. Recreating cache."); - if (!Recreate()) - return CompileProgram(vertex_shader, fragment_shader, callback, false); - else - return CompileAndAddProgram(key, vertex_shader, fragment_shader, callback); - } - - bool ShaderCache::GetProgram(Program* out_program, const std::string_view vertex_shader, - const std::string_view fragment_shader, const PreLinkCallback& callback /* = */) - { - auto prog = GetProgram(vertex_shader, fragment_shader, callback); - if (!prog) - return false; - - *out_program = std::move(*prog); - return true; - } - - bool ShaderCache::WriteToBlobFile(const CacheIndexKey& key, const std::vector& prog_data, u32 prog_format) - { - if (!m_blob_file || std::fseek(m_blob_file, 0, SEEK_END) != 0) - return false; - - CacheIndexData data; - data.file_offset = static_cast(std::ftell(m_blob_file)); - data.blob_size = static_cast(prog_data.size()); - data.blob_format = prog_format; - - CacheIndexEntry entry = {}; - entry.vertex_source_hash_low = key.vertex_source_hash_low; - entry.vertex_source_hash_high = key.vertex_source_hash_high; - entry.vertex_source_length = key.vertex_source_length; - entry.fragment_source_hash_low = key.fragment_source_hash_low; - entry.fragment_source_hash_high = key.fragment_source_hash_high; - entry.fragment_source_length = key.fragment_source_length; - entry.file_offset = data.file_offset; - entry.blob_size = data.blob_size; - entry.blob_format = data.blob_format; - - if (std::fwrite(prog_data.data(), 1, entry.blob_size, m_blob_file) != entry.blob_size || - std::fflush(m_blob_file) != 0 || std::fwrite(&entry, sizeof(entry), 1, m_index_file) != 1 || - std::fflush(m_index_file) != 0) - { - Console.Error("Failed to write shader blob to file"); - return false; - } - - m_index.emplace(key, data); - return true; - } - - std::optional ShaderCache::CompileProgram(const std::string_view& vertex_shader, - const std::string_view& fragment_shader, const PreLinkCallback& callback, bool set_retrievable) - { - Program prog; - if (!prog.Compile(vertex_shader, fragment_shader)) - return std::nullopt; - - if (callback) - callback(prog); - - if (set_retrievable) - prog.SetBinaryRetrievableHint(); - - if (!prog.Link()) - return std::nullopt; - - return std::optional(std::move(prog)); - } - - std::optional ShaderCache::CompileComputeProgram(const std::string_view& glsl, - const PreLinkCallback& callback, bool set_retrievable) - { - Program prog; - if (!prog.CompileCompute(glsl)) - return std::nullopt; - - if (callback) - callback(prog); - - if (set_retrievable) - prog.SetBinaryRetrievableHint(); - - if (!prog.Link()) - return std::nullopt; - - return std::optional(std::move(prog)); - } - - std::optional ShaderCache::CompileAndAddProgram(const CacheIndexKey& key, - const std::string_view& vertex_shader, const std::string_view& fragment_shader, - const PreLinkCallback& callback) - { -#ifdef PCSX2_DEVBUILD - Common::Timer timer; -#endif - - std::optional prog = CompileProgram(vertex_shader, fragment_shader, callback, true); - if (!prog) - return std::nullopt; - -#ifdef PCSX2_DEVBUILD - const float compile_time = timer.GetTimeMilliseconds(); - timer.Reset(); -#endif - - std::vector prog_data; - u32 prog_format = 0; - if (!prog->GetBinary(&prog_data, &prog_format)) - return std::nullopt; - -#ifdef PCSX2_DEVBUILD - const float binary_time = timer.GetTimeMilliseconds(); - timer.Reset(); -#endif - - WriteToBlobFile(key, prog_data, prog_format); - -#ifdef PCSX2_DEVBUILD - const float write_time = timer.GetTimeMilliseconds(); - Console.WriteLn("Compiled and cached shader: Compile: %.2fms, Binary: %.2fms, Write: %.2fms", compile_time, binary_time, write_time); -#endif - - return prog; - } - - std::optional ShaderCache::GetComputeProgram(const std::string_view glsl, const PreLinkCallback& callback) - { - if (!m_program_binary_supported || !m_blob_file) - { -#ifdef PCSX2_DEVBUILD - Common::Timer timer; -#endif - - std::optional res = CompileComputeProgram(glsl, callback, false); - -#ifdef PCSX2_DEVBUILD - Console.WriteLn("Time to compile shader without caching: %.2fms", timer.GetTimeMilliseconds()); -#endif - return res; - } - - const auto key = GetCacheKey(glsl, std::string_view()); - auto iter = m_index.find(key); - if (iter == m_index.end()) - return CompileAndAddComputeProgram(key, glsl, callback); - - std::vector data(iter->second.blob_size); - if (std::fseek(m_blob_file, iter->second.file_offset, SEEK_SET) != 0 || - std::fread(data.data(), 1, iter->second.blob_size, m_blob_file) != iter->second.blob_size) - { - Console.Error("Read blob from file failed"); - return {}; - } - -#ifdef PCSX2_DEVBUILD - Common::Timer timer; -#endif - - Program prog; - if (prog.CreateFromBinary(data.data(), static_cast(data.size()), iter->second.blob_format)) - { -#ifdef PCSX2_DEVBUILD - Console.WriteLn("Time to create program from binary: %.2fms", timer.GetTimeMilliseconds()); -#endif - - return std::optional(std::move(prog)); - } - - Console.Warning( - "Failed to create program from binary, this may be due to a driver or GPU Change. Recreating cache."); - if (!Recreate()) - return CompileComputeProgram(glsl, callback, false); - else - return CompileAndAddComputeProgram(key, glsl, callback); - } - - bool ShaderCache::GetComputeProgram(Program* out_program, const std::string_view glsl, const PreLinkCallback& callback) - { - auto prog = GetComputeProgram(glsl, callback); - if (!prog) - return false; - - *out_program = std::move(*prog); - return true; - } - - std::optional ShaderCache::CompileAndAddComputeProgram( - const CacheIndexKey& key, const std::string_view& glsl, const PreLinkCallback& callback) - { -#ifdef PCSX2_DEVBUILD - Common::Timer timer; -#endif - - std::optional prog = CompileComputeProgram(glsl, callback, true); - if (!prog) - return std::nullopt; - -#ifdef PCSX2_DEVBUILD - const float compile_time = timer.GetTimeMilliseconds(); - timer.Reset(); -#endif - - std::vector prog_data; - u32 prog_format = 0; - if (!prog->GetBinary(&prog_data, &prog_format)) - return std::nullopt; - -#ifdef PCSX2_DEVBUILD - const float binary_time = timer.GetTimeMilliseconds(); - timer.Reset(); -#endif - - WriteToBlobFile(key, prog_data, prog_format); - -#ifdef PCSX2_DEVBUILD - const float write_time = timer.GetTimeMilliseconds(); - Console.WriteLn("Compiled and cached compute shader: Compile: %.2fms, Binary: %.2fms, Write: %.2fms", compile_time, binary_time, write_time); -#endif - - return prog; - } -} // namespace GL diff --git a/common/GL/ShaderCache.h b/common/GL/ShaderCache.h deleted file mode 100644 index 22894b046c..0000000000 --- a/common/GL/ShaderCache.h +++ /dev/null @@ -1,112 +0,0 @@ -/* 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 . - */ - -#pragma once -#include "common/Pcsx2Defs.h" -#include "common/HashCombine.h" -#include "common/GL/Program.h" -#include -#include -#include -#include -#include -#include -#include - -namespace GL -{ - class ShaderCache - { - public: - using PreLinkCallback = std::function; - - ShaderCache(); - ~ShaderCache(); - - bool Open(bool is_gles, std::string_view base_path, u32 version); - void Close(); - - std::optional GetProgram(const std::string_view vertex_shader, const std::string_view fragment_shader, const PreLinkCallback& callback = {}); - bool GetProgram(Program* out_program, const std::string_view vertex_shader, const std::string_view fragment_shader, const PreLinkCallback& callback = {}); - - std::optional GetComputeProgram(const std::string_view glsl, const PreLinkCallback& callback = {}); - bool GetComputeProgram(Program* out_program, const std::string_view glsl, const PreLinkCallback& callback = {}); - - private: - static constexpr u32 FILE_VERSION = 1; - - struct CacheIndexKey - { - u64 vertex_source_hash_low; - u64 vertex_source_hash_high; - u32 vertex_source_length; - u64 fragment_source_hash_low; - u64 fragment_source_hash_high; - u32 fragment_source_length; - - bool operator==(const CacheIndexKey& key) const; - bool operator!=(const CacheIndexKey& key) const; - }; - - struct CacheIndexEntryHasher - { - std::size_t operator()(const CacheIndexKey& e) const noexcept - { - std::size_t h = 0; - HashCombine(h, - e.vertex_source_hash_low, e.vertex_source_hash_high, e.vertex_source_length, - e.fragment_source_hash_low, e.fragment_source_hash_high, e.fragment_source_length); - return h; - } - }; - - struct CacheIndexData - { - u32 file_offset; - u32 blob_size; - u32 blob_format; - }; - - using CacheIndex = std::unordered_map; - - static CacheIndexKey GetCacheKey(const std::string_view& vertex_shader, const std::string_view& fragment_shader); - - std::string GetIndexFileName() const; - std::string GetBlobFileName() const; - - bool CreateNew(const std::string& index_filename, const std::string& blob_filename); - bool ReadExisting(const std::string& index_filename, const std::string& blob_filename); - bool Recreate(); - - bool WriteToBlobFile(const CacheIndexKey& key, const std::vector& prog_data, u32 prog_format); - - std::optional CompileProgram(const std::string_view& vertex_shader, - const std::string_view& fragment_shader, const PreLinkCallback& callback, - bool set_retrievable); - std::optional CompileAndAddProgram(const CacheIndexKey& key, const std::string_view& vertex_shader, - const std::string_view& fragment_shader, const PreLinkCallback& callback); - - std::optional CompileComputeProgram(const std::string_view& glsl, const PreLinkCallback& callback, bool set_retrievable); - std::optional CompileAndAddComputeProgram(const CacheIndexKey& key, const std::string_view& glsl, const PreLinkCallback& callback); - - std::string m_base_path; - std::FILE* m_index_file = nullptr; - std::FILE* m_blob_file = nullptr; - - CacheIndex m_index; - u32 m_version = 0; - bool m_program_binary_supported = false; - }; -} // namespace GL diff --git a/common/GL/StreamBuffer.cpp b/common/GL/StreamBuffer.cpp deleted file mode 100644 index 85e01f9fae..0000000000 --- a/common/GL/StreamBuffer.cpp +++ /dev/null @@ -1,357 +0,0 @@ -/* 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 "StreamBuffer.h" -#include "common/Align.h" -#include "common/AlignedMalloc.h" -#include "common/Assertions.h" -#include -#include - -namespace GL -{ - StreamBuffer::StreamBuffer(GLenum target, GLuint buffer_id, u32 size) - : m_target(target) - , m_buffer_id(buffer_id) - , m_size(size) - { - } - - StreamBuffer::~StreamBuffer() - { - glDeleteBuffers(1, &m_buffer_id); - } - - void StreamBuffer::Bind() - { - glBindBuffer(m_target, m_buffer_id); - } - - void StreamBuffer::Unbind() - { - glBindBuffer(m_target, 0); - } - - namespace detail - { - // Uses glBufferSubData() to update. Preferred for drivers which don't support {ARB,EXT}_buffer_storage. - class BufferSubDataStreamBuffer final : public StreamBuffer - { - public: - ~BufferSubDataStreamBuffer() override - { - _aligned_free(m_cpu_buffer); - } - - MappingResult Map(u32 alignment, u32 min_size) override - { - return MappingResult{static_cast(m_cpu_buffer), 0, 0, m_size / alignment}; - } - - void Unmap(u32 used_size) override - { - if (used_size == 0) - return; - - glBindBuffer(m_target, m_buffer_id); - glBufferSubData(m_target, 0, used_size, m_cpu_buffer); - } - - u32 GetChunkSize() const override - { - return m_size; - } - - static std::unique_ptr Create(GLenum target, u32 size) - { - glGetError(); - - GLuint buffer_id; - glGenBuffers(1, &buffer_id); - glBindBuffer(target, buffer_id); - glBufferData(target, size, nullptr, GL_STREAM_DRAW); - - GLenum err = glGetError(); - if (err != GL_NO_ERROR) - { - glBindBuffer(target, 0); - glDeleteBuffers(1, &buffer_id); - return {}; - } - - return std::unique_ptr(new BufferSubDataStreamBuffer(target, buffer_id, size)); - } - - private: - BufferSubDataStreamBuffer(GLenum target, GLuint buffer_id, u32 size) - : StreamBuffer(target, buffer_id, size) - { - m_cpu_buffer = static_cast(_aligned_malloc(size, 32)); - if (!m_cpu_buffer) - pxFailRel("Failed to allocate CPU storage for GL buffer"); - } - - u8* m_cpu_buffer; - }; - - // Uses BufferData() to orphan the buffer after every update. Used on Mali where BufferSubData forces a sync. - class BufferDataStreamBuffer final : public StreamBuffer - { - public: - ~BufferDataStreamBuffer() override - { - _aligned_free(m_cpu_buffer); - } - - MappingResult Map(u32 alignment, u32 min_size) override - { - return MappingResult{static_cast(m_cpu_buffer), 0, 0, m_size / alignment}; - } - - void Unmap(u32 used_size) override - { - if (used_size == 0) - return; - - glBindBuffer(m_target, m_buffer_id); - glBufferData(m_target, used_size, m_cpu_buffer, GL_STREAM_DRAW); - } - - u32 GetChunkSize() const override - { - return m_size; - } - - static std::unique_ptr Create(GLenum target, u32 size) - { - glGetError(); - - GLuint buffer_id; - glGenBuffers(1, &buffer_id); - glBindBuffer(target, buffer_id); - glBufferData(target, size, nullptr, GL_STREAM_DRAW); - - GLenum err = glGetError(); - if (err != GL_NO_ERROR) - { - glBindBuffer(target, 0); - glDeleteBuffers(1, &buffer_id); - return {}; - } - - return std::unique_ptr(new BufferDataStreamBuffer(target, buffer_id, size)); - } - - private: - BufferDataStreamBuffer(GLenum target, GLuint buffer_id, u32 size) - : StreamBuffer(target, buffer_id, size) - { - m_cpu_buffer = static_cast(_aligned_malloc(size, 32)); - if (!m_cpu_buffer) - pxFailRel("Failed to allocate CPU storage for GL buffer"); - } - - u8* m_cpu_buffer; - }; - - // Base class for implementations which require syncing. - class SyncingStreamBuffer : public StreamBuffer - { - public: - enum : u32 - { - NUM_SYNC_POINTS = 16 - }; - - virtual ~SyncingStreamBuffer() override - { - for (u32 i = m_available_block_index; i <= m_used_block_index; i++) - { - pxAssert(m_sync_objects[i]); - glDeleteSync(m_sync_objects[i]); - } - } - - protected: - SyncingStreamBuffer(GLenum target, GLuint buffer_id, u32 size) - : StreamBuffer(target, buffer_id, size) - , m_bytes_per_block((size + (NUM_SYNC_POINTS)-1) / NUM_SYNC_POINTS) - { - } - - __fi u32 GetSyncIndexForOffset(u32 offset) { return offset / m_bytes_per_block; } - - __fi void AddSyncsForOffset(u32 offset) - { - const u32 end = GetSyncIndexForOffset(offset); - for (; m_used_block_index < end; m_used_block_index++) - { - pxAssert(!m_sync_objects[m_used_block_index]); - m_sync_objects[m_used_block_index] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - } - } - - __fi void WaitForSync(GLsync& sync) - { - glClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED); - glDeleteSync(sync); - sync = nullptr; - } - - __fi void EnsureSyncsWaitedForOffset(u32 offset) - { - const u32 end = std::min(GetSyncIndexForOffset(offset) + 1, NUM_SYNC_POINTS); - for (; m_available_block_index < end; m_available_block_index++) - { - pxAssert(m_sync_objects[m_available_block_index]); - WaitForSync(m_sync_objects[m_available_block_index]); - } - } - - void AllocateSpace(u32 size) - { - // add sync objects for writes since the last allocation - AddSyncsForOffset(m_position); - - // wait for sync objects for the space we want to use - EnsureSyncsWaitedForOffset(m_position + size); - - // wrap-around? - if ((m_position + size) > m_size) - { - // current position ... buffer end - AddSyncsForOffset(m_size); - - // rewind, and try again - m_position = 0; - - // wait for the sync at the start of the buffer - WaitForSync(m_sync_objects[0]); - m_available_block_index = 1; - - // and however much more we need to satisfy the allocation - EnsureSyncsWaitedForOffset(size); - m_used_block_index = 0; - } - } - - u32 GetChunkSize() const override - { - return m_size / NUM_SYNC_POINTS; - } - - u32 m_position = 0; - u32 m_used_block_index = 0; - u32 m_available_block_index = NUM_SYNC_POINTS; - u32 m_bytes_per_block; - std::array m_sync_objects{}; - }; - - class BufferStorageStreamBuffer : public SyncingStreamBuffer - { - public: - ~BufferStorageStreamBuffer() override - { - glBindBuffer(m_target, m_buffer_id); - glUnmapBuffer(m_target); - glBindBuffer(m_target, 0); - } - - MappingResult Map(u32 alignment, u32 min_size) override - { - if (m_position > 0) - m_position = Common::AlignUp(m_position, alignment); - - AllocateSpace(min_size); - pxAssert((m_position + min_size) <= (m_available_block_index * m_bytes_per_block)); - - const u32 free_space_in_block = ((m_available_block_index * m_bytes_per_block) - m_position); - return MappingResult{static_cast(m_mapped_ptr + m_position), m_position, m_position / alignment, - free_space_in_block / alignment}; - } - - void Unmap(u32 used_size) override - { - pxAssert((m_position + used_size) <= m_size); - if (!m_coherent) - { - Bind(); - glFlushMappedBufferRange(m_target, m_position, used_size); - } - - m_position += used_size; - } - - static std::unique_ptr Create(GLenum target, u32 size, bool coherent = true) - { - glGetError(); - - GLuint buffer_id; - glGenBuffers(1, &buffer_id); - glBindBuffer(target, buffer_id); - - const u32 flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | (coherent ? GL_MAP_COHERENT_BIT : 0); - const u32 map_flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | (coherent ? 0 : GL_MAP_FLUSH_EXPLICIT_BIT); - if (GLAD_GL_VERSION_4_4 || GLAD_GL_ARB_buffer_storage) - glBufferStorage(target, size, nullptr, flags); - else if (GLAD_GL_EXT_buffer_storage) - glBufferStorageEXT(target, size, nullptr, flags); - - GLenum err = glGetError(); - if (err != GL_NO_ERROR) - { - glBindBuffer(target, 0); - glDeleteBuffers(1, &buffer_id); - return {}; - } - - u8* mapped_ptr = static_cast(glMapBufferRange(target, 0, size, map_flags)); - pxAssertRel(mapped_ptr, "Persistent buffer was mapped"); - - return std::unique_ptr(new BufferStorageStreamBuffer(target, buffer_id, size, mapped_ptr, coherent)); - } - - private: - BufferStorageStreamBuffer(GLenum target, GLuint buffer_id, u32 size, u8* mapped_ptr, bool coherent) - : SyncingStreamBuffer(target, buffer_id, size) - , m_mapped_ptr(mapped_ptr) - , m_coherent(coherent) - { - } - - u8* m_mapped_ptr; - bool m_coherent; - }; - - } // namespace detail - - std::unique_ptr StreamBuffer::Create(GLenum target, u32 size) - { - std::unique_ptr buf; - if (GLAD_GL_VERSION_4_4 || GLAD_GL_ARB_buffer_storage || GLAD_GL_EXT_buffer_storage) - { - buf = detail::BufferStorageStreamBuffer::Create(target, size); - if (buf) - return buf; - } - - // BufferSubData is slower on all drivers except NVIDIA... - const char* vendor = reinterpret_cast(glGetString(GL_VENDOR)); - if (std::strstr(vendor, "NVIDIA")) - return detail::BufferSubDataStreamBuffer::Create(target, size); - else - return detail::BufferDataStreamBuffer::Create(target, size); - } -} // namespace GL diff --git a/common/GL/StreamBuffer.h b/common/GL/StreamBuffer.h deleted file mode 100644 index 75a16ed696..0000000000 --- a/common/GL/StreamBuffer.h +++ /dev/null @@ -1,61 +0,0 @@ -/* 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 . - */ - -#pragma once -#include "common/Pcsx2Defs.h" -#include "glad.h" -#include -#include -#include - -namespace GL -{ - /// Provides a buffer for streaming data to the GPU, ideally in write-combined memory. - class StreamBuffer - { - public: - virtual ~StreamBuffer(); - - __fi GLuint GetGLBufferId() const { return m_buffer_id; } - __fi GLenum GetGLTarget() const { return m_target; } - __fi u32 GetSize() const { return m_size; } - - void Bind(); - void Unbind(); - - struct MappingResult - { - void* pointer; - u32 buffer_offset; - u32 index_aligned; // offset / alignment, suitable for base vertex - u32 space_aligned; // remaining space / alignment - }; - - virtual MappingResult Map(u32 alignment, u32 min_size) = 0; - virtual void Unmap(u32 used_size) = 0; - - /// Returns the minimum granularity of blocks which sync objects will be created around. - virtual u32 GetChunkSize() const = 0; - - static std::unique_ptr Create(GLenum target, u32 size); - - protected: - StreamBuffer(GLenum target, GLuint buffer_id, u32 size); - - GLenum m_target; - GLuint m_buffer_id; - u32 m_size; - }; -} // namespace GL \ No newline at end of file diff --git a/common/common.vcxproj b/common/common.vcxproj index de13b778c1..edde698724 100644 --- a/common/common.vcxproj +++ b/common/common.vcxproj @@ -61,13 +61,8 @@ true - - - - - - + @@ -133,11 +128,6 @@ - - - - - diff --git a/common/common.vcxproj.filters b/common/common.vcxproj.filters index 120c0d0f22..aa9f9de387 100644 --- a/common/common.vcxproj.filters +++ b/common/common.vcxproj.filters @@ -94,18 +94,6 @@ Source Files - - Source Files\GL - - - Source Files\GL - - - Source Files\GL - - - Source Files\GL - Source Files @@ -145,12 +133,6 @@ Source Files - - Source Files\GL - - - Source Files\GL - Source Files\Vulkan @@ -181,7 +163,12 @@ Source Files - + + Source Files + + + Source Files + @@ -325,18 +312,6 @@ Header Files - - Header Files\GL - - - Header Files\GL - - - Header Files\GL - - - Header Files\GL - Header Files @@ -388,12 +363,6 @@ Header Files - - Header Files\GL - - - Header Files\GL - Header Files @@ -439,6 +408,9 @@ Header Files + + Header Files + @@ -447,12 +419,6 @@ {eef579af-e6a8-4d3b-a88e-c0e4cad9e5d8} - - {92d1bb2d-d172-4356-b9f4-a8fcc8e7fcce} - - - {5e76b340-cb0e-4946-83ec-7d72e397cac8} - {94154238-8b02-44f8-a7b8-3612e7bfa33c} diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index fb8eaf925d..b2152717fc 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -589,18 +589,51 @@ set(pcsx2GSHeaders if(USE_OPENGL) list(APPEND pcsx2GSSources + GS/Renderers/OpenGL/GLContext.cpp GS/Renderers/OpenGL/GLLoader.cpp + GS/Renderers/OpenGL/GLProgram.cpp + GS/Renderers/OpenGL/GLShaderCache.cpp GS/Renderers/OpenGL/GLState.cpp + GS/Renderers/OpenGL/GLStreamBuffer.cpp GS/Renderers/OpenGL/GSDeviceOGL.cpp GS/Renderers/OpenGL/GSTextureOGL.cpp ) list(APPEND pcsx2GSHeaders + GS/Renderers/OpenGL/GLContext.h GS/Renderers/OpenGL/GLLoader.h + GS/Renderers/OpenGL/GLProgram.h + GS/Renderers/OpenGL/GLShaderCache.h GS/Renderers/OpenGL/GLState.h + GS/Renderers/OpenGL/GLStreamBuffer.h GS/Renderers/OpenGL/GSDeviceOGL.h GS/Renderers/OpenGL/GSTextureOGL.h ) target_link_libraries(PCSX2_FLAGS INTERFACE glad) + + if(WIN32) + list(APPEND pcsx2GSSources GS/Renderers/OpenGL/GLContextWGL.cpp) + list(APPEND pcsx2GSHeaders GS/Renderers/OpenGL/GLContextWGL.h) + target_link_libraries(PCSX2_FLAGS INTERFACE opengl32.lib) + elseif(APPLE) + list(APPEND pcsx2GSSources GS/Renderers/OpenGL/GLContextAGL.cpp) + list(APPEND pcsx2GSHeaders GS/Renderers/OpenGL/GLContextAGL.h) + else() + if(X11_API OR WAYLAND_API) + list(APPEND pcsx2GSSources GS/Renderers/OpenGL/GLContextEGL.cpp) + list(APPEND pcsx2GSHeaders GS/Renderers/OpenGL/GLContextEGL.h) + target_link_libraries(PCSX2_FLAGS INTERFACE PkgConfig::EGL) + endif() + + if(X11_API) + list(APPEND pcsx2GSSources GS/Renderers/OpenGL/GLContextEGLX11.cpp) + list(APPEND pcsx2GSHeaders GS/Renderers/OpenGL/GLContextEGLX11.h) + endif() + + if(WAYLAND_API) + list(APPEND pcsx2GSSources GS/Renderers/OpenGL/GLContextEGLWayland.cpp) + list(APPEND pcsx2GSHeaders GS/Renderers/OpenGL/GLContextEGLWayland.h) + endif() + endif() endif() if(USE_VULKAN) diff --git a/pcsx2/GS/Renderers/OpenGL/GLContext.cpp b/pcsx2/GS/Renderers/OpenGL/GLContext.cpp new file mode 100644 index 0000000000..e0b2ab1646 --- /dev/null +++ b/pcsx2/GS/Renderers/OpenGL/GLContext.cpp @@ -0,0 +1,92 @@ +/* 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 "PrecompiledHeader.h" + +#include "GS/Renderers/OpenGL/GLContext.h" + +#if defined(_WIN32) +#include "GS/Renderers/OpenGL/GLContextWGL.h" +#elif defined(__APPLE__) +#include "GS/Renderers/OpenGL/GLContextAGL.h" +#else // Linux +#ifdef X11_API +#include "GS/Renderers/OpenGL/GLContextEGLX11.h" +#endif +#ifdef WAYLAND_API +#include "GS/Renderers/OpenGL/GLContextEGLWayland.h" +#endif +#endif + +#include "common/Console.h" + +#include "glad.h" + +GLContext::GLContext(const WindowInfo& wi) + : m_wi(wi) +{ +} + +GLContext::~GLContext() = default; + +std::unique_ptr GLContext::Create(const WindowInfo& wi) +{ + // We need at least GL3.3. + static constexpr Version vlist[] = { + {4, 6}, + {4, 5}, + {4, 4}, + {4, 3}, + {4, 2}, + {4, 1}, + {4, 0}, + {3, 3}, + }; + + std::unique_ptr context; +#if defined(_WIN32) + context = GLContextWGL::Create(wi, vlist); +#elif defined(__APPLE__) + context = GLContextAGL::Create(wi, vlist); +#else // Linux +#if defined(X11_API) + if (wi.type == WindowInfo::Type::X11) + context = GLContextEGLX11::Create(wi, vlist); +#endif + +#if defined(WAYLAND_API) + if (wi.type == WindowInfo::Type::Wayland) + context = GLContextEGLWayland::Create(wi, vlist); +#endif +#endif + + if (!context) + return nullptr; + + // NOTE: Not thread-safe. But this is okay, since we're not going to be creating more than one context at a time. + static GLContext* context_being_created; + context_being_created = context.get(); + + // load up glad + if (!gladLoadGLLoader([](const char* name) { return context_being_created->GetProcAddress(name); })) + { + Console.Error("Failed to load GL functions for GLAD"); + return nullptr; + } + + context_being_created = nullptr; + + return context; +} diff --git a/pcsx2/GS/Renderers/OpenGL/GLContext.h b/pcsx2/GS/Renderers/OpenGL/GLContext.h new file mode 100644 index 0000000000..15787644a8 --- /dev/null +++ b/pcsx2/GS/Renderers/OpenGL/GLContext.h @@ -0,0 +1,55 @@ +/* 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 . + */ + +#pragma once + +#include "common/Pcsx2Defs.h" +#include "common/WindowInfo.h" + +#include +#include +#include + +class GLContext +{ +public: + GLContext(const WindowInfo& wi); + virtual ~GLContext(); + + struct Version + { + int major_version; + int minor_version; + }; + + __fi const WindowInfo& GetWindowInfo() const { return m_wi; } + __fi u32 GetSurfaceWidth() const { return m_wi.surface_width; } + __fi u32 GetSurfaceHeight() const { return m_wi.surface_height; } + + virtual void* GetProcAddress(const char* name) = 0; + virtual bool ChangeSurface(const WindowInfo& new_wi) = 0; + virtual void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) = 0; + virtual bool SwapBuffers() = 0; + virtual bool MakeCurrent() = 0; + virtual bool DoneCurrent() = 0; + virtual bool SetSwapInterval(s32 interval) = 0; + virtual std::unique_ptr CreateSharedContext(const WindowInfo& wi) = 0; + + static std::unique_ptr Create(const WindowInfo& wi); + +protected: + WindowInfo m_wi; + Version m_version = {}; +}; diff --git a/pcsx2/GS/Renderers/OpenGL/GLContextAGL.h b/pcsx2/GS/Renderers/OpenGL/GLContextAGL.h new file mode 100644 index 0000000000..a86f1cd4fd --- /dev/null +++ b/pcsx2/GS/Renderers/OpenGL/GLContextAGL.h @@ -0,0 +1,62 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2023 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 . + */ + +#pragma once + +#include "GS/Renderers/OpenGL/GLContext.h" + +#include "glad.h" + +#include + +#if defined(__APPLE__) && defined(__OBJC__) +#import +#else +struct NSView; +struct NSOpenGLContext; +struct NSOpenGLPixelFormat; +#endif + +class GLContextAGL final : public GLContext +{ +public: + GLContextAGL(const WindowInfo& wi); + ~GLContextAGL() override; + + static std::unique_ptr Create(const WindowInfo& wi, gsl::span versions_to_try); + + void* GetProcAddress(const char* name) override; + bool ChangeSurface(const WindowInfo& new_wi) override; + void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override; + bool SwapBuffers() override; + bool MakeCurrent() override; + bool DoneCurrent() override; + bool SetSwapInterval(s32 interval) override; + std::unique_ptr CreateSharedContext(const WindowInfo& wi) override; + +private: + bool Initialize(gsl::span versions_to_try); + bool CreateContext(NSOpenGLContext* share_context, int profile, bool make_current); + void BindContextToView(); + void CleanupView(); + + // returns true if dimensions have changed + bool UpdateDimensions(); + + NSView* m_view = nullptr; + NSOpenGLContext* m_context = nullptr; + NSOpenGLPixelFormat* m_pixel_format = nullptr; + void* m_opengl_module_handle = nullptr; +}; diff --git a/pcsx2/GS/Renderers/OpenGL/GLContextAGL.mm b/pcsx2/GS/Renderers/OpenGL/GLContextAGL.mm new file mode 100644 index 0000000000..0427b9b535 --- /dev/null +++ b/pcsx2/GS/Renderers/OpenGL/GLContextAGL.mm @@ -0,0 +1,236 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2023 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 "PrecompiledHeader.h" + +#include "GS/Renderers/OpenGL/GLContextAGL.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 + +GLContextAGL::GLContextAGL(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"); +} + +GLContextAGL::~GLContextAGL() +{ + if ([NSOpenGLContext currentContext] == m_context) + [NSOpenGLContext clearCurrentContext]; + + CleanupView(); + + if (m_opengl_module_handle) + dlclose(m_opengl_module_handle); +} + +std::unique_ptr GLContextAGL::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 GLContextAGL::Initialize(gsl::span versions_to_try) +{ + for (const Version& cv : versions_to_try) + { + 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* GLContextAGL::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 GLContextAGL::ChangeSurface(const WindowInfo& new_wi) +{ + m_wi = new_wi; + BindContextToView(); + return true; +} + +void GLContextAGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/) +{ + UpdateDimensions(); +} + +bool GLContextAGL::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 GLContextAGL::SwapBuffers() +{ + [m_context flushBuffer]; + return true; +} + +bool GLContextAGL::MakeCurrent() +{ + [m_context makeCurrentContext]; + return true; +} + +bool GLContextAGL::DoneCurrent() +{ + [NSOpenGLContext clearCurrentContext]; + return true; +} + +bool GLContextAGL::SetSwapInterval(s32 interval) +{ + GLint gl_interval = static_cast(interval); + [m_context setValues:&gl_interval forParameter:NSOpenGLCPSwapInterval]; + return true; +} + +std::unique_ptr GLContextAGL::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 GLContextAGL::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("(GLContextAGL) 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 GLContextAGL::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 GLContextAGL::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; +} diff --git a/pcsx2/GS/Renderers/OpenGL/GLContextEGL.cpp b/pcsx2/GS/Renderers/OpenGL/GLContextEGL.cpp new file mode 100644 index 0000000000..f4d3190800 --- /dev/null +++ b/pcsx2/GS/Renderers/OpenGL/GLContextEGL.cpp @@ -0,0 +1,366 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2023 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 "PrecompiledHeader.h" + +#include "GS/Renderers/OpenGL/GLContextEGL.h" + +#include "common/Console.h" + +#include +#include +#include +#include + +GLContextEGL::GLContextEGL(const WindowInfo& wi) + : GLContext(wi) +{ +} + +GLContextEGL::~GLContextEGL() +{ + DestroySurface(); + DestroyContext(); +} + +std::unique_ptr GLContextEGL::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 GLContextEGL::Initialize(gsl::span versions_to_try) +{ + if (!gladLoadEGL()) + { + Console.Error("Loading GLAD EGL functions failed"); + return false; + } + + if (!SetDisplay()) + return false; + + int egl_major, egl_minor; + if (!eglInitialize(m_display, &egl_major, &egl_minor)) + { + Console.Error("eglInitialize() failed: %d", eglGetError()); + return false; + } + Console.WriteLn("EGL Version: %d.%d", egl_major, egl_minor); + + const char* extensions = eglQueryString(m_display, EGL_EXTENSIONS); + if (extensions) + m_supports_surfaceless = std::strstr(extensions, "EGL_KHR_surfaceless_context") != nullptr; + if (!m_supports_surfaceless) + Console.Warning("EGL implementation does not support surfaceless contexts, emulating with pbuffers"); + + for (const Version& version : versions_to_try) + { + if (CreateContextAndSurface(version, nullptr, true)) + return true; + } + + return false; +} + +bool GLContextEGL::SetDisplay() +{ + m_display = eglGetDisplay(static_cast(m_wi.display_connection)); + if (!m_display) + { + Console.Error("eglGetDisplay() failed: %d", eglGetError()); + return false; + } + + return true; +} + +void* GLContextEGL::GetProcAddress(const char* name) +{ + return reinterpret_cast(eglGetProcAddress(name)); +} + +bool GLContextEGL::ChangeSurface(const WindowInfo& new_wi) +{ + const bool was_current = (eglGetCurrentContext() == m_context); + if (was_current) + eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + if (m_surface != EGL_NO_SURFACE) + { + eglDestroySurface(m_display, m_surface); + m_surface = EGL_NO_SURFACE; + } + + m_wi = new_wi; + if (!CreateSurface()) + return false; + + if (was_current && !eglMakeCurrent(m_display, m_surface, m_surface, m_context)) + { + Console.Error("Failed to make context current again after surface change"); + return false; + } + + return true; +} + +void GLContextEGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/) +{ + if (new_surface_width == 0 && new_surface_height == 0) + { + EGLint surface_width, surface_height; + if (eglQuerySurface(m_display, m_surface, EGL_WIDTH, &surface_width) && + eglQuerySurface(m_display, m_surface, EGL_HEIGHT, &surface_height)) + { + m_wi.surface_width = static_cast(surface_width); + m_wi.surface_height = static_cast(surface_height); + return; + } + else + { + Console.Error("eglQuerySurface() failed: %d", eglGetError()); + } + } + + m_wi.surface_width = new_surface_width; + m_wi.surface_height = new_surface_height; +} + +bool GLContextEGL::SwapBuffers() +{ + return eglSwapBuffers(m_display, m_surface); +} + +bool GLContextEGL::MakeCurrent() +{ + if (!eglMakeCurrent(m_display, m_surface, m_surface, m_context)) + { + Console.Error("eglMakeCurrent() failed: %d", eglGetError()); + return false; + } + + return true; +} + +bool GLContextEGL::DoneCurrent() +{ + return eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); +} + +bool GLContextEGL::SetSwapInterval(s32 interval) +{ + return eglSwapInterval(m_display, interval); +} + +std::unique_ptr GLContextEGL::CreateSharedContext(const WindowInfo& wi) +{ + std::unique_ptr context = std::make_unique(wi); + context->m_display = m_display; + context->m_supports_surfaceless = m_supports_surfaceless; + + if (!context->CreateContextAndSurface(m_version, m_context, false)) + return nullptr; + + return context; +} + +EGLNativeWindowType GLContextEGL::GetNativeWindow(EGLConfig config) +{ + return {}; +} + +bool GLContextEGL::CreateSurface() +{ + if (m_wi.type == WindowInfo::Type::Surfaceless) + { + if (m_supports_surfaceless) + return true; + else + return CreatePBufferSurface(); + } + + EGLNativeWindowType native_window = GetNativeWindow(m_config); + m_surface = eglCreateWindowSurface(m_display, m_config, native_window, nullptr); + if (!m_surface) + { + Console.Error("eglCreateWindowSurface() failed: %d", eglGetError()); + return false; + } + + // Some implementations may require the size to be queried at runtime. + EGLint surface_width, surface_height; + if (eglQuerySurface(m_display, m_surface, EGL_WIDTH, &surface_width) && + eglQuerySurface(m_display, m_surface, EGL_HEIGHT, &surface_height)) + { + m_wi.surface_width = static_cast(surface_width); + m_wi.surface_height = static_cast(surface_height); + } + else + { + Console.Error("eglQuerySurface() failed: %d", eglGetError()); + } + + return true; +} + +bool GLContextEGL::CreatePBufferSurface() +{ + const u32 width = std::max(m_wi.surface_width, 1); + const u32 height = std::max(m_wi.surface_height, 1); + + EGLint attrib_list[] = { + EGL_WIDTH, + static_cast(width), + EGL_HEIGHT, + static_cast(height), + EGL_NONE, + }; + + m_surface = eglCreatePbufferSurface(m_display, m_config, attrib_list); + if (!m_surface) + { + Console.Error("eglCreatePbufferSurface() failed: %d", eglGetError()); + return false; + } + + Console.WriteLn("Created %ux%u pbuffer surface", width, height); + return true; +} + +bool GLContextEGL::CheckConfigSurfaceFormat(EGLConfig config) const +{ + int red_size, green_size, blue_size; + if (!eglGetConfigAttrib(m_display, config, EGL_RED_SIZE, &red_size) || + !eglGetConfigAttrib(m_display, config, EGL_GREEN_SIZE, &green_size) || + !eglGetConfigAttrib(m_display, config, EGL_BLUE_SIZE, &blue_size)) + { + return false; + } + + return (red_size == 8 && green_size == 8 && blue_size == 8); +} + +void GLContextEGL::DestroyContext() +{ + if (eglGetCurrentContext() == m_context) + eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + if (m_context != EGL_NO_CONTEXT) + { + eglDestroyContext(m_display, m_context); + m_context = EGL_NO_CONTEXT; + } +} + +void GLContextEGL::DestroySurface() +{ + if (eglGetCurrentSurface(EGL_DRAW) == m_surface) + eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + if (m_surface != EGL_NO_SURFACE) + { + eglDestroySurface(m_display, m_surface); + m_surface = EGL_NO_SURFACE; + } +} + +bool GLContextEGL::CreateContext(const Version& version, EGLContext share_context) +{ + const int surface_attribs[] = {EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_SURFACE_TYPE, + (m_wi.type != WindowInfo::Type::Surfaceless) ? EGL_WINDOW_BIT : 0, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, EGL_NONE}; + + EGLint num_configs; + if (!eglChooseConfig(m_display, surface_attribs, nullptr, 0, &num_configs) || num_configs == 0) + { + Console.Error("eglChooseConfig() failed: %d", eglGetError()); + return false; + } + + std::vector configs(static_cast(num_configs)); + if (!eglChooseConfig(m_display, surface_attribs, configs.data(), num_configs, &num_configs)) + { + Console.Error("eglChooseConfig() failed: %d", eglGetError()); + return false; + } + configs.resize(static_cast(num_configs)); + + m_config = [this, &configs]() { + const auto found_config = std::find_if(std::begin(configs), std::end(configs), + [&](const auto& check_config) { return CheckConfigSurfaceFormat(check_config); }); + if (found_config == std::end(configs)) + { + Console.Warning("No EGL configs matched exactly, using first."); + return configs.front(); + } + else + { + return *found_config; + } + }(); + + const std::array attribs = { + {EGL_CONTEXT_MAJOR_VERSION, version.major_version, EGL_CONTEXT_MINOR_VERSION, version.minor_version, EGL_NONE}}; + + if (!eglBindAPI(EGL_OPENGL_API)) + { + Console.Error("eglBindAPI failed"); + return false; + } + + m_context = eglCreateContext(m_display, m_config, share_context, attribs.data()); + if (!m_context) + { + Console.Error("eglCreateContext() failed: %d", eglGetError()); + return false; + } + + Console.WriteLn("eglCreateContext() succeeded for version %u.%u", version.major_version, version.minor_version); + m_version = version; + return true; +} + +bool GLContextEGL::CreateContextAndSurface(const Version& version, EGLContext share_context, bool make_current) +{ + if (!CreateContext(version, share_context)) + return false; + + if (!CreateSurface()) + { + Console.Error("Failed to create surface for context"); + eglDestroyContext(m_display, m_context); + m_context = EGL_NO_CONTEXT; + return false; + } + + if (make_current && !eglMakeCurrent(m_display, m_surface, m_surface, m_context)) + { + Console.Error("eglMakeCurrent() failed: %d", eglGetError()); + if (m_surface != EGL_NO_SURFACE) + { + eglDestroySurface(m_display, m_surface); + m_surface = EGL_NO_SURFACE; + } + eglDestroyContext(m_display, m_context); + m_context = EGL_NO_CONTEXT; + return false; + } + + return true; +} diff --git a/pcsx2/GS/Renderers/OpenGL/GLContextEGL.h b/pcsx2/GS/Renderers/OpenGL/GLContextEGL.h new file mode 100644 index 0000000000..4b20fdccbf --- /dev/null +++ b/pcsx2/GS/Renderers/OpenGL/GLContextEGL.h @@ -0,0 +1,62 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2023 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 . + */ + +#pragma once + +#include "GS/Renderers/OpenGL/GLContext.h" + +#include "glad_egl.h" + +#include + +class GLContextEGL : public GLContext +{ +public: + GLContextEGL(const WindowInfo& wi); + ~GLContextEGL() override; + + static std::unique_ptr Create(const WindowInfo& wi, gsl::span versions_to_try); + + void* GetProcAddress(const char* name) override; + virtual bool ChangeSurface(const WindowInfo& new_wi) override; + virtual void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override; + bool SwapBuffers() override; + bool MakeCurrent() override; + bool DoneCurrent() override; + bool SetSwapInterval(s32 interval) override; + virtual std::unique_ptr CreateSharedContext(const WindowInfo& wi) override; + +protected: + virtual bool SetDisplay(); + virtual EGLNativeWindowType GetNativeWindow(EGLConfig config); + + bool Initialize(gsl::span versions_to_try); + bool CreateDisplay(); + bool CreateContext(const Version& version, EGLContext share_context); + bool CreateContextAndSurface(const Version& version, EGLContext share_context, bool make_current); + bool CreateSurface(); + bool CreatePBufferSurface(); + bool CheckConfigSurfaceFormat(EGLConfig config) const; + void DestroyContext(); + void DestroySurface(); + + EGLDisplay m_display = EGL_NO_DISPLAY; + EGLSurface m_surface = EGL_NO_SURFACE; + EGLContext m_context = EGL_NO_CONTEXT; + + EGLConfig m_config = {}; + + bool m_supports_surfaceless = false; +}; diff --git a/pcsx2/GS/Renderers/OpenGL/GLContextEGLWayland.cpp b/pcsx2/GS/Renderers/OpenGL/GLContextEGLWayland.cpp new file mode 100644 index 0000000000..704257469f --- /dev/null +++ b/pcsx2/GS/Renderers/OpenGL/GLContextEGLWayland.cpp @@ -0,0 +1,105 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2023 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 "PrecompiledHeader.h" + +#include "GS/Renderers/OpenGL/GLContextEGLWayland.h" + +#include "common/Console.h" + +#include + +static const char* WAYLAND_EGL_MODNAME = "libwayland-egl.so.1"; + +GLContextEGLWayland::GLContextEGLWayland(const WindowInfo& wi) + : GLContextEGL(wi) +{ +} + +GLContextEGLWayland::~GLContextEGLWayland() +{ + if (m_wl_window) + m_wl_egl_window_destroy(m_wl_window); + if (m_wl_module) + dlclose(m_wl_module); +} + +std::unique_ptr GLContextEGLWayland::Create(const WindowInfo& wi, gsl::span versions_to_try) +{ + std::unique_ptr context = std::make_unique(wi); + if (!context->LoadModule() || !context->Initialize(versions_to_try)) + return nullptr; + + return context; +} + +std::unique_ptr GLContextEGLWayland::CreateSharedContext(const WindowInfo& wi) +{ + std::unique_ptr context = std::make_unique(wi); + context->m_display = m_display; + + if (!context->LoadModule() || !context->CreateContextAndSurface(m_version, m_context, false)) + return nullptr; + + return context; +} + +void GLContextEGLWayland::ResizeSurface(u32 new_surface_width, u32 new_surface_height) +{ + if (m_wl_window) + m_wl_egl_window_resize(m_wl_window, new_surface_width, new_surface_height, 0, 0); + + GLContextEGL::ResizeSurface(new_surface_width, new_surface_height); +} + +EGLNativeWindowType GLContextEGLWayland::GetNativeWindow(EGLConfig config) +{ + if (m_wl_window) + { + m_wl_egl_window_destroy(m_wl_window); + m_wl_window = nullptr; + } + + m_wl_window = + m_wl_egl_window_create(static_cast(m_wi.window_handle), m_wi.surface_width, m_wi.surface_height); + if (!m_wl_window) + return {}; + + return reinterpret_cast(m_wl_window); +} + +bool GLContextEGLWayland::LoadModule() +{ + m_wl_module = dlopen(WAYLAND_EGL_MODNAME, RTLD_NOW | RTLD_GLOBAL); + if (!m_wl_module) + { + Console.Error("Failed to load %s.", WAYLAND_EGL_MODNAME); + return false; + } + + m_wl_egl_window_create = + reinterpret_cast(dlsym(m_wl_module, "wl_egl_window_create")); + m_wl_egl_window_destroy = + reinterpret_cast(dlsym(m_wl_module, "wl_egl_window_destroy")); + m_wl_egl_window_resize = + reinterpret_cast(dlsym(m_wl_module, "wl_egl_window_resize")); + if (!m_wl_egl_window_create || !m_wl_egl_window_destroy || !m_wl_egl_window_resize) + { + Console.Error("Failed to load one or more functions from %s.", WAYLAND_EGL_MODNAME); + return false; + } + + return true; +} diff --git a/pcsx2/GS/Renderers/OpenGL/GLContextEGLWayland.h b/pcsx2/GS/Renderers/OpenGL/GLContextEGLWayland.h new file mode 100644 index 0000000000..34eaa2c916 --- /dev/null +++ b/pcsx2/GS/Renderers/OpenGL/GLContextEGLWayland.h @@ -0,0 +1,46 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2023 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 . + */ + +#pragma once + +#include "GS/Renderers/OpenGL/GLContextEGL.h" + +struct wl_egl_window; +struct wl_surface; + +class GLContextEGLWayland final : public GLContextEGL +{ +public: + GLContextEGLWayland(const WindowInfo& wi); + ~GLContextEGLWayland() override; + + static std::unique_ptr Create(const WindowInfo& wi, gsl::span versions_to_try); + + std::unique_ptr CreateSharedContext(const WindowInfo& wi) override; + void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override; + +protected: + EGLNativeWindowType GetNativeWindow(EGLConfig config) override; + +private: + bool LoadModule(); + + wl_egl_window* m_wl_window = nullptr; + + void* m_wl_module = nullptr; + wl_egl_window* (*m_wl_egl_window_create)(struct wl_surface* surface, int width, int height); + void (*m_wl_egl_window_destroy)(struct wl_egl_window* egl_window); + void (*m_wl_egl_window_resize)(struct wl_egl_window* egl_window, int width, int height, int dx, int dy); +}; diff --git a/pcsx2/GS/Renderers/OpenGL/GLContextEGLX11.cpp b/pcsx2/GS/Renderers/OpenGL/GLContextEGLX11.cpp new file mode 100644 index 0000000000..6943d49fea --- /dev/null +++ b/pcsx2/GS/Renderers/OpenGL/GLContextEGLX11.cpp @@ -0,0 +1,56 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2023 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 "PrecompiledHeader.h" + +#include "GS/Renderers/OpenGL/GLContextEGLX11.h" + +#include + +GLContextEGLX11::GLContextEGLX11(const WindowInfo& wi) + : GLContextEGL(wi) +{ +} +GLContextEGLX11::~GLContextEGLX11() = default; + +std::unique_ptr GLContextEGLX11::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; +} + +std::unique_ptr GLContextEGLX11::CreateSharedContext(const WindowInfo& wi) +{ + std::unique_ptr context = std::make_unique(wi); + context->m_display = m_display; + + if (!context->CreateContextAndSurface(m_version, m_context, false)) + return nullptr; + + return context; +} + +void GLContextEGLX11::ResizeSurface(u32 new_surface_width, u32 new_surface_height) +{ + GLContextEGL::ResizeSurface(new_surface_width, new_surface_height); +} + +EGLNativeWindowType GLContextEGLX11::GetNativeWindow(EGLConfig config) +{ + return (EGLNativeWindowType) reinterpret_cast(m_wi.window_handle); +} diff --git a/common/GL/ContextEGLX11.h b/pcsx2/GS/Renderers/OpenGL/GLContextEGLX11.h similarity index 54% rename from common/GL/ContextEGLX11.h rename to pcsx2/GS/Renderers/OpenGL/GLContextEGLX11.h index e1ee5263a6..9d537cc09a 100644 --- a/common/GL/ContextEGLX11.h +++ b/pcsx2/GS/Renderers/OpenGL/GLContextEGLX11.h @@ -1,5 +1,5 @@ /* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2021 PCSX2 Dev Team + * Copyright (C) 2002-2023 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- @@ -15,22 +15,19 @@ #pragma once -#include "common/GL/ContextEGL.h" +#include "GS/Renderers/OpenGL/GLContextEGL.h" -namespace GL +class GLContextEGLX11 final : public GLContextEGL { - class ContextEGLX11 final : public ContextEGL - { - public: - ContextEGLX11(const WindowInfo& wi); - ~ContextEGLX11() override; +public: + GLContextEGLX11(const WindowInfo& wi); + ~GLContextEGLX11() override; - static std::unique_ptr Create(const WindowInfo& wi, gsl::span versions_to_try); + static std::unique_ptr Create(const WindowInfo& wi, gsl::span versions_to_try); - std::unique_ptr CreateSharedContext(const WindowInfo& wi) override; - void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override; + std::unique_ptr CreateSharedContext(const WindowInfo& wi) override; + void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override; - protected: - EGLNativeWindowType GetNativeWindow(EGLConfig config) override; - }; -} // namespace GL +protected: + EGLNativeWindowType GetNativeWindow(EGLConfig config) override; +}; diff --git a/pcsx2/GS/Renderers/OpenGL/GLContextWGL.cpp b/pcsx2/GS/Renderers/OpenGL/GLContextWGL.cpp new file mode 100644 index 0000000000..8535f202aa --- /dev/null +++ b/pcsx2/GS/Renderers/OpenGL/GLContextWGL.cpp @@ -0,0 +1,452 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2023 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 "PrecompiledHeader.h" + +#include "GS/Renderers/OpenGL/GLContextWGL.h" + +#include "common/Assertions.h" +#include "common/Console.h" +#include "common/ScopedGuard.h" + +static void* GetProcAddressCallback(const char* name) +{ + void* addr = reinterpret_cast(wglGetProcAddress(name)); + if (addr) + return addr; + + // try opengl32.dll + return reinterpret_cast(::GetProcAddress(GetModuleHandleA("opengl32.dll"), name)); +} + +static bool ReloadWGL(HDC dc) +{ + if (!gladLoadWGLLoader( + [](const char* name) -> void* { return reinterpret_cast(wglGetProcAddress(name)); }, dc)) + { + Console.Error("Loading GLAD WGL functions failed"); + return false; + } + + return true; +} + +GLContextWGL::GLContextWGL(const WindowInfo& wi) + : GLContext(wi) +{ +} + +GLContextWGL::~GLContextWGL() +{ + if (wglGetCurrentContext() == m_rc) + wglMakeCurrent(m_dc, nullptr); + + if (m_rc) + wglDeleteContext(m_rc); + + ReleaseDC(); +} + +std::unique_ptr GLContextWGL::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 GLContextWGL::Initialize(gsl::span versions_to_try) +{ + if (m_wi.type == WindowInfo::Type::Win32) + { + if (!InitializeDC()) + return false; + } + else + { + if (!CreatePBuffer()) + return false; + } + + // Everything including core/ES requires a dummy profile to load the WGL extensions. + if (!CreateAnyContext(nullptr, true)) + return false; + + for (const Version& cv : versions_to_try) + { + if (CreateVersionContext(cv, nullptr, true)) + { + m_version = cv; + return true; + } + } + + return false; +} + +void* GLContextWGL::GetProcAddress(const char* name) +{ + return GetProcAddressCallback(name); +} + +bool GLContextWGL::ChangeSurface(const WindowInfo& new_wi) +{ + const bool was_current = (wglGetCurrentContext() == m_rc); + + ReleaseDC(); + + m_wi = new_wi; + if (!InitializeDC()) + return false; + + if (was_current && !wglMakeCurrent(m_dc, m_rc)) + { + Console.Error("Failed to make context current again after surface change: 0x%08X", GetLastError()); + return false; + } + + return true; +} + +void GLContextWGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/) +{ + RECT client_rc = {}; + GetClientRect(GetHWND(), &client_rc); + m_wi.surface_width = static_cast(client_rc.right - client_rc.left); + m_wi.surface_height = static_cast(client_rc.bottom - client_rc.top); +} + +bool GLContextWGL::SwapBuffers() +{ + return ::SwapBuffers(m_dc); +} + +bool GLContextWGL::MakeCurrent() +{ + if (!wglMakeCurrent(m_dc, m_rc)) + { + Console.Error("wglMakeCurrent() failed: 0x%08X", GetLastError()); + return false; + } + + return true; +} + +bool GLContextWGL::DoneCurrent() +{ + return wglMakeCurrent(m_dc, nullptr); +} + +bool GLContextWGL::SetSwapInterval(s32 interval) +{ + if (!GLAD_WGL_EXT_swap_control) + return false; + + return wglSwapIntervalEXT(interval); +} + +std::unique_ptr GLContextWGL::CreateSharedContext(const WindowInfo& wi) +{ + std::unique_ptr context = std::make_unique(wi); + if (wi.type == WindowInfo::Type::Win32) + { + if (!context->InitializeDC()) + return nullptr; + } + else + { + if (!context->CreatePBuffer()) + return nullptr; + } + + if (!context->CreateVersionContext(m_version, m_rc, false)) + return nullptr; + + context->m_version = m_version; + return context; +} + +HDC GLContextWGL::GetDCAndSetPixelFormat(HWND hwnd) +{ + PIXELFORMATDESCRIPTOR pfd = {}; + pfd.nSize = sizeof(pfd); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.dwLayerMask = PFD_MAIN_PLANE; + pfd.cRedBits = 8; + pfd.cGreenBits = 8; + pfd.cBlueBits = 8; + pfd.cColorBits = 24; + + HDC hDC = ::GetDC(hwnd); + if (!hDC) + { + Console.Error("GetDC() failed: 0x%08X", GetLastError()); + return {}; + } + + if (!m_pixel_format.has_value()) + { + const int pf = ChoosePixelFormat(hDC, &pfd); + if (pf == 0) + { + Console.Error("ChoosePixelFormat() failed: 0x%08X", GetLastError()); + ::ReleaseDC(hwnd, hDC); + return {}; + } + + m_pixel_format = pf; + } + + if (!SetPixelFormat(hDC, m_pixel_format.value(), &pfd)) + { + Console.Error("SetPixelFormat() failed: 0x%08X", GetLastError()); + ::ReleaseDC(hwnd, hDC); + return {}; + } + + return hDC; +} + +bool GLContextWGL::InitializeDC() +{ + if (m_wi.type == WindowInfo::Type::Win32) + { + m_dc = GetDCAndSetPixelFormat(GetHWND()); + if (!m_dc) + { + Console.Error("Failed to get DC for window"); + return false; + } + + return true; + } + else if (m_wi.type == WindowInfo::Type::Surfaceless) + { + return CreatePBuffer(); + } + else + { + Console.Error("Unknown window info type %u", static_cast(m_wi.type)); + return false; + } +} + +void GLContextWGL::ReleaseDC() +{ + if (m_pbuffer) + { + wglReleasePbufferDCARB(m_pbuffer, m_dc); + m_dc = {}; + + wglDestroyPbufferARB(m_pbuffer); + m_pbuffer = {}; + + ::ReleaseDC(m_dummy_window, m_dummy_dc); + m_dummy_dc = {}; + + DestroyWindow(m_dummy_window); + m_dummy_window = {}; + } + else if (m_dc) + { + ::ReleaseDC(GetHWND(), m_dc); + m_dc = {}; + } +} + +bool GLContextWGL::CreatePBuffer() +{ + static bool window_class_registered = false; + static const wchar_t* window_class_name = L"ContextWGLPBuffer"; + + if (!window_class_registered) + { + WNDCLASSEXW wc = {}; + wc.cbSize = sizeof(WNDCLASSEXW); + wc.style = 0; + wc.lpfnWndProc = DefWindowProcW; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = GetModuleHandle(nullptr); + wc.hIcon = NULL; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wc.lpszMenuName = NULL; + wc.lpszClassName = window_class_name; + wc.hIconSm = NULL; + + if (!RegisterClassExW(&wc)) + { + Console.Error("(ContextWGL::CreatePBuffer) RegisterClassExW() failed"); + return false; + } + + window_class_registered = true; + } + + HWND hwnd = CreateWindowExW(0, window_class_name, window_class_name, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL); + if (!hwnd) + { + Console.Error("(ContextWGL::CreatePBuffer) CreateWindowEx() failed"); + return false; + } + + ScopedGuard hwnd_guard([hwnd]() { DestroyWindow(hwnd); }); + + HDC hdc = GetDCAndSetPixelFormat(hwnd); + if (!hdc) + return false; + + ScopedGuard hdc_guard([hdc, hwnd]() { ::ReleaseDC(hwnd, hdc); }); + + static constexpr const int pb_attribs[] = {0, 0}; + + HGLRC temp_rc = nullptr; + ScopedGuard temp_rc_guard([&temp_rc, hdc]() { + if (temp_rc) + { + wglMakeCurrent(hdc, nullptr); + wglDeleteContext(temp_rc); + } + }); + + if (!GLAD_WGL_ARB_pbuffer) + { + // we're probably running completely surfaceless... need a temporary context. + temp_rc = wglCreateContext(hdc); + if (!temp_rc || !wglMakeCurrent(hdc, temp_rc)) + { + Console.Error("Failed to create temporary context to load WGL for pbuffer."); + return false; + } + + if (!ReloadWGL(hdc) || !GLAD_WGL_ARB_pbuffer) + { + Console.Error("Missing WGL_ARB_pbuffer"); + return false; + } + } + + pxAssertRel(m_pixel_format.has_value(), "Has pixel format for pbuffer"); + HPBUFFERARB pbuffer = wglCreatePbufferARB(hdc, m_pixel_format.value(), 1, 1, pb_attribs); + if (!pbuffer) + { + Console.Error("(ContextWGL::CreatePBuffer) wglCreatePbufferARB() failed"); + return false; + } + + ScopedGuard pbuffer_guard([pbuffer]() { wglDestroyPbufferARB(pbuffer); }); + + m_dc = wglGetPbufferDCARB(pbuffer); + if (!m_dc) + { + Console.Error("(ContextWGL::CreatePbuffer) wglGetPbufferDCARB() failed"); + return false; + } + + m_dummy_window = hwnd; + m_dummy_dc = hdc; + m_pbuffer = pbuffer; + + temp_rc_guard.Run(); + pbuffer_guard.Cancel(); + hdc_guard.Cancel(); + hwnd_guard.Cancel(); + return true; +} + +bool GLContextWGL::CreateAnyContext(HGLRC share_context, bool make_current) +{ + m_rc = wglCreateContext(m_dc); + if (!m_rc) + { + Console.Error("wglCreateContext() failed: 0x%08X", GetLastError()); + return false; + } + + if (make_current) + { + if (!wglMakeCurrent(m_dc, m_rc)) + { + Console.Error("wglMakeCurrent() failed: 0x%08X", GetLastError()); + return false; + } + + // re-init glad-wgl + if (!gladLoadWGLLoader( + [](const char* name) -> void* { return reinterpret_cast(wglGetProcAddress(name)); }, m_dc)) + { + Console.Error("Loading GLAD WGL functions failed"); + return false; + } + } + + if (share_context && !wglShareLists(share_context, m_rc)) + { + Console.Error("wglShareLists() failed: 0x%08X", GetLastError()); + return false; + } + + return true; +} + +bool GLContextWGL::CreateVersionContext(const Version& version, HGLRC share_context, bool make_current) +{ + // we need create context attribs + if (!GLAD_WGL_ARB_create_context) + { + Console.Error("Missing GLAD_WGL_ARB_create_context."); + return false; + } + + HGLRC new_rc; + + const int attribs[] = {WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + WGL_CONTEXT_MAJOR_VERSION_ARB, version.major_version, WGL_CONTEXT_MINOR_VERSION_ARB, version.minor_version, +#ifdef _DEBUG + WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB | WGL_CONTEXT_DEBUG_BIT_ARB, +#else + WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, +#endif + 0, 0}; + + new_rc = wglCreateContextAttribsARB(m_dc, share_context, attribs); + + if (!new_rc) + return false; + + // destroy and swap contexts + if (m_rc) + { + if (!wglMakeCurrent(m_dc, make_current ? new_rc : nullptr)) + { + Console.Error("wglMakeCurrent() failed: 0x%08X", GetLastError()); + wglDeleteContext(new_rc); + return false; + } + + // re-init glad-wgl + if (make_current && !ReloadWGL(m_dc)) + return false; + + wglDeleteContext(m_rc); + } + + m_rc = new_rc; + return true; +} diff --git a/pcsx2/GS/Renderers/OpenGL/GLContextWGL.h b/pcsx2/GS/Renderers/OpenGL/GLContextWGL.h new file mode 100644 index 0000000000..7b7b682417 --- /dev/null +++ b/pcsx2/GS/Renderers/OpenGL/GLContextWGL.h @@ -0,0 +1,67 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2023 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 . + */ + +#pragma once + +#include "GS/Renderers/OpenGL/GLContext.h" + +#include "common/RedtapeWindows.h" + +#include "glad_wgl.h" +#include "glad.h" + +#include +#include + +class GLContextWGL final : public GLContext +{ +public: + GLContextWGL(const WindowInfo& wi); + ~GLContextWGL() override; + + static std::unique_ptr Create(const WindowInfo& wi, gsl::span versions_to_try); + + void* GetProcAddress(const char* name) override; + bool ChangeSurface(const WindowInfo& new_wi) override; + void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override; + bool SwapBuffers() override; + bool MakeCurrent() override; + bool DoneCurrent() override; + bool SetSwapInterval(s32 interval) override; + std::unique_ptr CreateSharedContext(const WindowInfo& wi) override; + +private: + __fi HWND GetHWND() const { return static_cast(m_wi.window_handle); } + + HDC GetDCAndSetPixelFormat(HWND hwnd); + + bool Initialize(gsl::span versions_to_try); + bool InitializeDC(); + void ReleaseDC(); + bool CreatePBuffer(); + bool CreateAnyContext(HGLRC share_context, bool make_current); + bool CreateVersionContext(const Version& version, HGLRC share_context, bool make_current); + + HDC m_dc = {}; + HGLRC m_rc = {}; + + // Can't change pixel format once it's set for a RC. + std::optional m_pixel_format; + + // Dummy window for creating a PBuffer off when we're surfaceless. + HWND m_dummy_window = {}; + HDC m_dummy_dc = {}; + HPBUFFERARB m_pbuffer = {}; +}; diff --git a/pcsx2/GS/Renderers/OpenGL/GLLoader.cpp b/pcsx2/GS/Renderers/OpenGL/GLLoader.cpp index 7d9a68d7dc..d49f2923f4 100644 --- a/pcsx2/GS/Renderers/OpenGL/GLLoader.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GLLoader.cpp @@ -19,6 +19,8 @@ #include "Host.h" #include "HostSettings.h" +#include "glad.h" + namespace ReplaceGL { void APIENTRY ScissorIndexed(GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height) diff --git a/pcsx2/GS/Renderers/OpenGL/GLLoader.h b/pcsx2/GS/Renderers/OpenGL/GLLoader.h index 0b28aff398..2fd515a426 100644 --- a/pcsx2/GS/Renderers/OpenGL/GLLoader.h +++ b/pcsx2/GS/Renderers/OpenGL/GLLoader.h @@ -15,13 +15,6 @@ #pragma once -#define GL_TEX_LEVEL_0 (0) -#define GL_TEX_LEVEL_1 (1) -#define GL_FB_DEFAULT (0) -#define GL_BUFFER_0 (0) - -#include "glad.h" - namespace GLLoader { bool check_gl_requirements(); diff --git a/pcsx2/GS/Renderers/OpenGL/GLProgram.cpp b/pcsx2/GS/Renderers/OpenGL/GLProgram.cpp new file mode 100644 index 0000000000..457953de6d --- /dev/null +++ b/pcsx2/GS/Renderers/OpenGL/GLProgram.cpp @@ -0,0 +1,538 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2023 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 "PrecompiledHeader.h" + +#include "Config.h" +#include "GS/Renderers/OpenGL/GLProgram.h" + +#include "common/Assertions.h" +#include "common/Console.h" +#include "common/Path.h" +#include "common/StringUtil.h" + +#include "fmt/format.h" + +#include +#include + +static u32 s_last_program_id = 0; +static GLuint s_next_bad_shader_id = 1; + +GLProgram::GLProgram() = default; + +GLProgram::GLProgram(GLProgram&& prog) +{ + m_program_id = prog.m_program_id; + prog.m_program_id = 0; + m_vertex_shader_id = prog.m_vertex_shader_id; + prog.m_vertex_shader_id = 0; + m_fragment_shader_id = prog.m_fragment_shader_id; + prog.m_fragment_shader_id = 0; + m_uniform_locations = std::move(prog.m_uniform_locations); +} + +GLProgram::~GLProgram() +{ + Destroy(); +} + +GLuint GLProgram::CompileShader(GLenum type, const std::string_view source) +{ + GLuint id = glCreateShader(type); + + std::array sources = {{source.data()}}; + std::array source_lengths = {{static_cast(source.size())}}; + glShaderSource(id, static_cast(sources.size()), sources.data(), source_lengths.data()); + glCompileShader(id); + + GLint status = GL_FALSE; + glGetShaderiv(id, GL_COMPILE_STATUS, &status); + + GLint info_log_length = 0; + glGetShaderiv(id, GL_INFO_LOG_LENGTH, &info_log_length); + + // Log will create a new line when there are no warnings so let's set a minimum log length of 1. + constexpr int info_log_min_length = 1; + + if (status == GL_FALSE || info_log_length > info_log_min_length) + { + std::string info_log; + info_log.resize(info_log_length + 1); + glGetShaderInfoLog(id, info_log_length, &info_log_length, &info_log[0]); + + if (status == GL_TRUE) + { + Console.Warning("Shader compiled with warnings:\n%s", info_log.c_str()); + } + else + { + Console.Error("Shader failed to compile:\n%s", info_log.c_str()); + + std::ofstream ofs( + Path::Combine(EmuFolders::Logs, fmt::format("pcsx2_bad_shader_{}.txt", s_next_bad_shader_id++)), + std::ofstream::out | std::ofstream::binary); + if (ofs.is_open()) + { + ofs.write(sources[0], source_lengths[0]); + ofs << "\n\nCompile failed, info log:\n"; + ofs << info_log; + ofs.close(); + } + + glDeleteShader(id); + return 0; + } + } + + return id; +} + +void GLProgram::ResetLastProgram() +{ + s_last_program_id = 0; +} + +bool GLProgram::Compile(const std::string_view vertex_shader, const std::string_view fragment_shader) +{ + if (!vertex_shader.empty()) + { + m_vertex_shader_id = CompileShader(GL_VERTEX_SHADER, vertex_shader); + if (m_vertex_shader_id == 0) + return false; + } + + if (!fragment_shader.empty()) + { + m_fragment_shader_id = CompileShader(GL_FRAGMENT_SHADER, fragment_shader); + if (m_fragment_shader_id == 0) + return false; + } + + m_program_id = glCreateProgram(); + if (m_vertex_shader_id != 0) + glAttachShader(m_program_id, m_vertex_shader_id); + if (m_fragment_shader_id != 0) + glAttachShader(m_program_id, m_fragment_shader_id); + return true; +} + +bool GLProgram::CompileCompute(const std::string_view glsl) +{ + GLuint id = CompileShader(GL_COMPUTE_SHADER, glsl); + if (id == 0) + return false; + + m_program_id = glCreateProgram(); + glAttachShader(m_program_id, id); + return true; +} + +bool GLProgram::CreateFromBinary(const void* data, u32 data_length, u32 data_format) +{ + GLuint prog = glCreateProgram(); + glProgramBinary(prog, static_cast(data_format), data, data_length); + + GLint link_status; + glGetProgramiv(prog, GL_LINK_STATUS, &link_status); + if (link_status != GL_TRUE) + { + Console.Error("Failed to create GL program from binary: status %d", link_status); + glDeleteProgram(prog); + return false; + } + + m_program_id = prog; + return true; +} + +bool GLProgram::GetBinary(std::vector* out_data, u32* out_data_format) +{ + GLint binary_size = 0; + glGetProgramiv(m_program_id, GL_PROGRAM_BINARY_LENGTH, &binary_size); + if (binary_size == 0) + { + Console.Warning("glGetProgramiv(GL_PROGRAM_BINARY_LENGTH) returned 0"); + return false; + } + + GLenum format = 0; + out_data->resize(static_cast(binary_size)); + glGetProgramBinary(m_program_id, binary_size, &binary_size, &format, out_data->data()); + if (binary_size == 0) + { + Console.Warning("glGetProgramBinary() failed"); + return false; + } + else if (static_cast(binary_size) != out_data->size()) + { + Console.Warning("Size changed from %zu to %d after glGetProgramBinary()", out_data->size(), binary_size); + out_data->resize(static_cast(binary_size)); + } + + *out_data_format = static_cast(format); + DevCon.WriteLn("Program binary retrieved, %zu bytes, format %u", out_data->size(), *out_data_format); + return true; +} + +void GLProgram::SetBinaryRetrievableHint() +{ + glProgramParameteri(m_program_id, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE); +} + +void GLProgram::BindAttribute(GLuint index, const char* name) +{ + glBindAttribLocation(m_program_id, index, name); +} + +void GLProgram::BindDefaultAttributes() +{ + BindAttribute(0, "a_position"); + BindAttribute(1, "a_texcoord"); + BindAttribute(2, "a_color"); +} + +void GLProgram::BindFragData(GLuint index /*= 0*/, const char* name /*= "o_col0"*/) +{ + glBindFragDataLocation(m_program_id, index, name); +} + +void GLProgram::BindFragDataIndexed(GLuint color_number /*= 0*/, const char* name /*= "o_col0"*/) +{ + if (GLAD_GL_VERSION_3_3 || GLAD_GL_ARB_blend_func_extended) + { + glBindFragDataLocationIndexed(m_program_id, color_number, 0, name); + return; + } + else if (GLAD_GL_EXT_blend_func_extended) + { + glBindFragDataLocationIndexedEXT(m_program_id, color_number, 0, name); + return; + } + + Console.Error("BindFragDataIndexed() called without ARB or EXT extension, we'll probably crash."); + glBindFragDataLocationIndexed(m_program_id, color_number, 0, name); +} + +bool GLProgram::Link() +{ + glLinkProgram(m_program_id); + + if (m_vertex_shader_id != 0) + glDeleteShader(m_vertex_shader_id); + m_vertex_shader_id = 0; + if (m_fragment_shader_id != 0) + glDeleteShader(m_fragment_shader_id); + m_fragment_shader_id = 0; + + GLint status = GL_FALSE; + glGetProgramiv(m_program_id, GL_LINK_STATUS, &status); + + GLint info_log_length = 0; + + // Log will create a new line when there are no warnings so let's set a minimum log length of 1. + constexpr int info_log_min_length = 1; + + glGetProgramiv(m_program_id, GL_INFO_LOG_LENGTH, &info_log_length); + + if (status == GL_FALSE || info_log_length > info_log_min_length) + { + std::string info_log; + info_log.resize(info_log_length + 1); + glGetProgramInfoLog(m_program_id, info_log_length, &info_log_length, &info_log[0]); + + if (status == GL_TRUE) + { + Console.Error("Program linked with warnings:\n%s", info_log.c_str()); + } + else + { + Console.Error("Program failed to link:\n%s", info_log.c_str()); + glDeleteProgram(m_program_id); + m_program_id = 0; + return false; + } + } + + return true; +} + +void GLProgram::Bind() const +{ + if (s_last_program_id == m_program_id) + return; + + glUseProgram(m_program_id); + s_last_program_id = m_program_id; +} + +void GLProgram::Destroy() +{ + if (m_vertex_shader_id != 0) + { + glDeleteShader(m_vertex_shader_id); + m_vertex_shader_id = 0; + } + if (m_fragment_shader_id != 0) + { + glDeleteShader(m_fragment_shader_id); + m_fragment_shader_id = 0; + } + if (m_program_id != 0) + { + glDeleteProgram(m_program_id); + m_program_id = 0; + } + + m_uniform_locations.clear(); +} + +int GLProgram::RegisterUniform(const char* name) +{ + int id = static_cast(m_uniform_locations.size()); + m_uniform_locations.push_back(glGetUniformLocation(m_program_id, name)); + return id; +} + +void GLProgram::Uniform1ui(int index, u32 x) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform1ui(location, x); +} + +void GLProgram::Uniform2ui(int index, u32 x, u32 y) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform2ui(location, x, y); +} + +void GLProgram::Uniform3ui(int index, u32 x, u32 y, u32 z) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform3ui(location, x, y, z); +} + +void GLProgram::Uniform4ui(int index, u32 x, u32 y, u32 z, u32 w) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform4ui(location, x, y, z, w); +} + +void GLProgram::Uniform1i(int index, s32 x) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform1i(location, x); +} + +void GLProgram::Uniform2i(int index, s32 x, s32 y) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform2i(location, x, y); +} + +void GLProgram::Uniform3i(int index, s32 x, s32 y, s32 z) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform3i(location, x, y, z); +} + +void GLProgram::Uniform4i(int index, s32 x, s32 y, s32 z, s32 w) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform4i(location, x, y, z, w); +} + +void GLProgram::Uniform1f(int index, float x) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform1f(location, x); +} + +void GLProgram::Uniform2f(int index, float x, float y) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform2f(location, x, y); +} + +void GLProgram::Uniform3f(int index, float x, float y, float z) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform3f(location, x, y, z); +} + +void GLProgram::Uniform4f(int index, float x, float y, float z, float w) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform4f(location, x, y, z, w); +} + +void GLProgram::Uniform2uiv(int index, const u32* v) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform2uiv(location, 1, v); +} + +void GLProgram::Uniform3uiv(int index, const u32* v) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform3uiv(location, 1, v); +} + +void GLProgram::Uniform4uiv(int index, const u32* v) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform4uiv(location, 1, v); +} + +void GLProgram::Uniform2iv(int index, const s32* v) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform2iv(location, 1, v); +} + +void GLProgram::Uniform3iv(int index, const s32* v) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform3iv(location, 1, v); +} + +void GLProgram::Uniform4iv(int index, const s32* v) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform4iv(location, 1, v); +} + +void GLProgram::Uniform2fv(int index, const float* v) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform2fv(location, 1, v); +} + +void GLProgram::Uniform3fv(int index, const float* v) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform3fv(location, 1, v); +} + +void GLProgram::Uniform4fv(int index, const float* v) const +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniform4fv(location, 1, v); +} + +void GLProgram::UniformMatrix2fv(int index, const float* v) +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniformMatrix2fv(location, 1, GL_FALSE, v); +} + +void GLProgram::UniformMatrix3fv(int index, const float* v) +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniformMatrix3fv(location, 1, GL_FALSE, v); +} + +void GLProgram::UniformMatrix4fv(int index, const float* v) +{ + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; + if (location >= 0) + glUniformMatrix4fv(location, 1, GL_FALSE, v); +} + +void GLProgram::BindUniformBlock(const char* name, u32 index) +{ + const GLint location = glGetUniformBlockIndex(m_program_id, name); + if (location >= 0) + glUniformBlockBinding(m_program_id, location, index); +} + +void GLProgram::SetName(const std::string_view& name) +{ + if (name.empty()) + return; + +#ifdef _DEBUG + glObjectLabel(GL_PROGRAM, m_program_id, name.length(), name.data()); +#endif +} + +void GLProgram::SetFormattedName(const char* format, ...) +{ + va_list ap; + va_start(ap, format); + std::string n = StringUtil::StdStringFromFormatV(format, ap); + va_end(ap); + SetName(n); +} + +GLProgram& GLProgram::operator=(GLProgram&& prog) +{ + Destroy(); + m_program_id = prog.m_program_id; + prog.m_program_id = 0; + m_vertex_shader_id = prog.m_vertex_shader_id; + prog.m_vertex_shader_id = 0; + m_fragment_shader_id = prog.m_fragment_shader_id; + prog.m_fragment_shader_id = 0; + m_uniform_locations = std::move(prog.m_uniform_locations); + return *this; +} diff --git a/pcsx2/GS/Renderers/OpenGL/GLProgram.h b/pcsx2/GS/Renderers/OpenGL/GLProgram.h new file mode 100644 index 0000000000..80f31608f1 --- /dev/null +++ b/pcsx2/GS/Renderers/OpenGL/GLProgram.h @@ -0,0 +1,103 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2023 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 . + */ + +#pragma once + +#include "common/Pcsx2Defs.h" + +#include "glad.h" + +#include +#include + +class GLProgram +{ +public: + GLProgram(); + GLProgram(const GLProgram&) = delete; + GLProgram(GLProgram&& prog); + ~GLProgram(); + + static GLuint CompileShader(GLenum type, const std::string_view source); + static void ResetLastProgram(); + + bool IsValid() const { return m_program_id != 0; } + + bool Compile(const std::string_view vertex_shader, const std::string_view fragment_shader); + + bool CompileCompute(const std::string_view glsl); + + bool CreateFromBinary(const void* data, u32 data_length, u32 data_format); + + bool GetBinary(std::vector* out_data, u32* out_data_format); + void SetBinaryRetrievableHint(); + + void BindAttribute(GLuint index, const char* name); + void BindDefaultAttributes(); + + void BindFragData(GLuint index = 0, const char* name = "o_col0"); + void BindFragDataIndexed(GLuint color_number = 0, const char* name = "o_col0"); + + bool Link(); + + void Bind() const; + + void Destroy(); + + int RegisterUniform(const char* name); + void Uniform1ui(int index, u32 x) const; + void Uniform2ui(int index, u32 x, u32 y) const; + void Uniform3ui(int index, u32 x, u32 y, u32 z) const; + void Uniform4ui(int index, u32 x, u32 y, u32 z, u32 w) const; + void Uniform1i(int index, s32 x) const; + void Uniform2i(int index, s32 x, s32 y) const; + void Uniform3i(int index, s32 x, s32 y, s32 z) const; + void Uniform4i(int index, s32 x, s32 y, s32 z, s32 w) const; + void Uniform1f(int index, float x) const; + void Uniform2f(int index, float x, float y) const; + void Uniform3f(int index, float x, float y, float z) const; + void Uniform4f(int index, float x, float y, float z, float w) const; + void Uniform2uiv(int index, const u32* v) const; + void Uniform3uiv(int index, const u32* v) const; + void Uniform4uiv(int index, const u32* v) const; + void Uniform2iv(int index, const s32* v) const; + void Uniform3iv(int index, const s32* v) const; + void Uniform4iv(int index, const s32* v) const; + void Uniform2fv(int index, const float* v) const; + void Uniform3fv(int index, const float* v) const; + void Uniform4fv(int index, const float* v) const; + + void UniformMatrix2fv(int index, const float* v); + void UniformMatrix3fv(int index, const float* v); + void UniformMatrix4fv(int index, const float* v); + + void BindUniformBlock(const char* name, u32 index); + + void SetName(const std::string_view& name); + void SetFormattedName(const char* format, ...); + + GLProgram& operator=(const GLProgram&) = delete; + GLProgram& operator=(GLProgram&& prog); + + __fi bool operator==(const GLProgram& rhs) const { return m_program_id == rhs.m_program_id; } + __fi bool operator!=(const GLProgram& rhs) const { return m_program_id != rhs.m_program_id; } + +private: + GLuint m_program_id = 0; + GLuint m_vertex_shader_id = 0; + GLuint m_fragment_shader_id = 0; + + std::vector m_uniform_locations; +}; diff --git a/pcsx2/GS/Renderers/OpenGL/GLShaderCache.cpp b/pcsx2/GS/Renderers/OpenGL/GLShaderCache.cpp new file mode 100644 index 0000000000..9f6b49ead9 --- /dev/null +++ b/pcsx2/GS/Renderers/OpenGL/GLShaderCache.cpp @@ -0,0 +1,550 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2023 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 "PrecompiledHeader.h" + +#include "GS/Renderers/OpenGL/GLShaderCache.h" +#include "GS/GS.h" + +#include "Config.h" +#include "ShaderCacheVersion.h" + +#include "common/FileSystem.h" +#include "common/Console.h" +#include "common/MD5Digest.h" +#include "common/Path.h" +#include "common/StringUtil.h" +#include "common/Timer.h" + +#pragma pack(push, 1) +struct CacheIndexEntry +{ + u64 vertex_source_hash_low; + u64 vertex_source_hash_high; + u32 vertex_source_length; + u64 fragment_source_hash_low; + u64 fragment_source_hash_high; + u32 fragment_source_length; + u32 file_offset; + u32 blob_size; + u32 blob_format; +}; +#pragma pack(pop) + +GLShaderCache::GLShaderCache() = default; + +GLShaderCache::~GLShaderCache() +{ + Close(); +} + +bool GLShaderCache::CacheIndexKey::operator==(const CacheIndexKey& key) const +{ + return (vertex_source_hash_low == key.vertex_source_hash_low && + vertex_source_hash_high == key.vertex_source_hash_high && + vertex_source_length == key.vertex_source_length && + fragment_source_hash_low == key.fragment_source_hash_low && + fragment_source_hash_high == key.fragment_source_hash_high && + fragment_source_length == key.fragment_source_length); +} + +bool GLShaderCache::CacheIndexKey::operator!=(const CacheIndexKey& key) const +{ + return (vertex_source_hash_low != key.vertex_source_hash_low || + vertex_source_hash_high != key.vertex_source_hash_high || + vertex_source_length != key.vertex_source_length || + fragment_source_hash_low != key.fragment_source_hash_low || + fragment_source_hash_high != key.fragment_source_hash_high || + fragment_source_length != key.fragment_source_length); +} + +bool GLShaderCache::Open() +{ + m_program_binary_supported = GLAD_GL_ARB_get_program_binary; + if (m_program_binary_supported) + { + // check that there's at least one format and the extension isn't being "faked" + GLint num_formats = 0; + glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &num_formats); + Console.WriteLn("%u program binary formats supported by driver", num_formats); + m_program_binary_supported = (num_formats > 0); + } + + if (!m_program_binary_supported) + { + Console.Warning("Your GL driver does not support program binaries. Hopefully it has a built-in cache."); + return true; + } + + if (!GSConfig.DisableShaderCache) + { + const std::string index_filename = GetIndexFileName(); + const std::string blob_filename = GetBlobFileName(); + + if (ReadExisting(index_filename, blob_filename)) + return true; + + return CreateNew(index_filename, blob_filename); + } + + return true; +} + +bool GLShaderCache::CreateNew(const std::string& index_filename, const std::string& blob_filename) +{ + if (FileSystem::FileExists(index_filename.c_str())) + { + Console.Warning("Removing existing index file '%s'", index_filename.c_str()); + FileSystem::DeleteFilePath(index_filename.c_str()); + } + if (FileSystem::FileExists(blob_filename.c_str())) + { + Console.Warning("Removing existing blob file '%s'", blob_filename.c_str()); + FileSystem::DeleteFilePath(blob_filename.c_str()); + } + + m_index_file = FileSystem::OpenCFile(index_filename.c_str(), "wb"); + if (!m_index_file) + { + Console.Error("Failed to open index file '%s' for writing", index_filename.c_str()); + return false; + } + + const u32 file_version = SHADER_CACHE_VERSION; + if (std::fwrite(&file_version, sizeof(file_version), 1, m_index_file) != 1) + { + Console.Error("Failed to write version to index file '%s'", index_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + FileSystem::DeleteFilePath(index_filename.c_str()); + return false; + } + + m_blob_file = FileSystem::OpenCFile(blob_filename.c_str(), "w+b"); + if (!m_blob_file) + { + Console.Error("Failed to open blob file '%s' for writing", blob_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + FileSystem::DeleteFilePath(index_filename.c_str()); + return false; + } + + return true; +} + +bool GLShaderCache::ReadExisting(const std::string& index_filename, const std::string& blob_filename) +{ + m_index_file = FileSystem::OpenCFile(index_filename.c_str(), "r+b"); + if (!m_index_file) + { + // special case here: when there's a sharing violation (i.e. two instances running), + // we don't want to blow away the cache. so just continue without a cache. + if (errno == EACCES) + { + Console.WriteLn("Failed to open shader cache index with EACCES, are you running two instances?"); + return true; + } + + return false; + } + + u32 file_version = 0; + if (std::fread(&file_version, sizeof(file_version), 1, m_index_file) != 1 || file_version != SHADER_CACHE_VERSION) + { + Console.Error("Bad file/data version in '%s'", index_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + return false; + } + + m_blob_file = FileSystem::OpenCFile(blob_filename.c_str(), "a+b"); + if (!m_blob_file) + { + Console.Error("Blob file '%s' is missing", blob_filename.c_str()); + std::fclose(m_index_file); + m_index_file = nullptr; + return false; + } + + std::fseek(m_blob_file, 0, SEEK_END); + const u32 blob_file_size = static_cast(std::ftell(m_blob_file)); + + for (;;) + { + CacheIndexEntry entry; + if (std::fread(&entry, sizeof(entry), 1, m_index_file) != 1 || + (entry.file_offset + entry.blob_size) > blob_file_size) + { + if (std::feof(m_index_file)) + break; + + Console.Error("Failed to read entry from '%s', corrupt file?", index_filename.c_str()); + m_index.clear(); + std::fclose(m_blob_file); + m_blob_file = nullptr; + std::fclose(m_index_file); + m_index_file = nullptr; + return false; + } + + const CacheIndexKey key{entry.vertex_source_hash_low, entry.vertex_source_hash_high, entry.vertex_source_length, + entry.fragment_source_hash_low, entry.fragment_source_hash_high, entry.fragment_source_length}; + const CacheIndexData data{entry.file_offset, entry.blob_size, entry.blob_format}; + m_index.emplace(key, data); + } + + Console.WriteLn("Read %zu entries from '%s'", m_index.size(), index_filename.c_str()); + return true; +} + +void GLShaderCache::Close() +{ + m_index.clear(); + if (m_index_file) + { + std::fclose(m_index_file); + m_index_file = nullptr; + } + if (m_blob_file) + { + std::fclose(m_blob_file); + m_blob_file = nullptr; + } +} + +bool GLShaderCache::Recreate() +{ + Close(); + + const std::string index_filename = GetIndexFileName(); + const std::string blob_filename = GetBlobFileName(); + + return CreateNew(index_filename, blob_filename); +} + +GLShaderCache::CacheIndexKey GLShaderCache::GetCacheKey( + const std::string_view& vertex_shader, const std::string_view& fragment_shader) +{ + union ShaderHash + { + struct + { + u64 low; + u64 high; + }; + u8 bytes[16]; + }; + + ShaderHash vertex_hash = {}; + ShaderHash fragment_hash = {}; + + MD5Digest digest; + if (!vertex_shader.empty()) + { + digest.Update(vertex_shader.data(), static_cast(vertex_shader.length())); + digest.Final(vertex_hash.bytes); + } + + if (!fragment_shader.empty()) + { + digest.Reset(); + digest.Update(fragment_shader.data(), static_cast(fragment_shader.length())); + digest.Final(fragment_hash.bytes); + } + + return CacheIndexKey{vertex_hash.low, vertex_hash.high, static_cast(vertex_shader.length()), fragment_hash.low, + fragment_hash.high, static_cast(fragment_shader.length())}; +} + +std::string GLShaderCache::GetIndexFileName() const +{ + return Path::Combine(EmuFolders::Cache, "gl_programs.idx"); +} + +std::string GLShaderCache::GetBlobFileName() const +{ + return Path::Combine(EmuFolders::Cache, "gl_programs.bin"); +} + +std::optional GLShaderCache::GetProgram( + const std::string_view vertex_shader, const std::string_view fragment_shader, const PreLinkCallback& callback) +{ + if (!m_program_binary_supported || !m_blob_file) + { +#ifdef PCSX2_DEVBUILD + Common::Timer timer; +#endif + + std::optional res = CompileProgram(vertex_shader, fragment_shader, callback, false); + +#ifdef PCSX2_DEVBUILD + Console.WriteLn("Time to compile shader without caching: %.2fms", timer.GetTimeMilliseconds()); +#endif + return res; + } + + const auto key = GetCacheKey(vertex_shader, fragment_shader); + auto iter = m_index.find(key); + if (iter == m_index.end()) + return CompileAndAddProgram(key, vertex_shader, fragment_shader, callback); + + std::vector data(iter->second.blob_size); + if (std::fseek(m_blob_file, iter->second.file_offset, SEEK_SET) != 0 || + std::fread(data.data(), 1, iter->second.blob_size, m_blob_file) != iter->second.blob_size) + { + Console.Error("Read blob from file failed"); + return {}; + } + +#ifdef PCSX2_DEVBUILD + Common::Timer timer; +#endif + + GLProgram prog; + if (prog.CreateFromBinary(data.data(), static_cast(data.size()), iter->second.blob_format)) + { +#ifdef PCSX2_DEVBUILD + Console.WriteLn("Time to create program from binary: %.2fms", timer.GetTimeMilliseconds()); +#endif + + return std::optional(std::move(prog)); + } + + Console.Warning( + "Failed to create program from binary, this may be due to a driver or GPU Change. Recreating cache."); + if (!Recreate()) + return CompileProgram(vertex_shader, fragment_shader, callback, false); + else + return CompileAndAddProgram(key, vertex_shader, fragment_shader, callback); +} + +bool GLShaderCache::GetProgram(GLProgram* out_program, const std::string_view vertex_shader, + const std::string_view fragment_shader, const PreLinkCallback& callback /* = */) +{ + auto prog = GetProgram(vertex_shader, fragment_shader, callback); + if (!prog) + return false; + + *out_program = std::move(*prog); + return true; +} + +bool GLShaderCache::WriteToBlobFile(const CacheIndexKey& key, const std::vector& prog_data, u32 prog_format) +{ + if (!m_blob_file || std::fseek(m_blob_file, 0, SEEK_END) != 0) + return false; + + CacheIndexData data; + data.file_offset = static_cast(std::ftell(m_blob_file)); + data.blob_size = static_cast(prog_data.size()); + data.blob_format = prog_format; + + CacheIndexEntry entry = {}; + entry.vertex_source_hash_low = key.vertex_source_hash_low; + entry.vertex_source_hash_high = key.vertex_source_hash_high; + entry.vertex_source_length = key.vertex_source_length; + entry.fragment_source_hash_low = key.fragment_source_hash_low; + entry.fragment_source_hash_high = key.fragment_source_hash_high; + entry.fragment_source_length = key.fragment_source_length; + entry.file_offset = data.file_offset; + entry.blob_size = data.blob_size; + entry.blob_format = data.blob_format; + + if (std::fwrite(prog_data.data(), 1, entry.blob_size, m_blob_file) != entry.blob_size || + std::fflush(m_blob_file) != 0 || std::fwrite(&entry, sizeof(entry), 1, m_index_file) != 1 || + std::fflush(m_index_file) != 0) + { + Console.Error("Failed to write shader blob to file"); + return false; + } + + m_index.emplace(key, data); + return true; +} + +std::optional GLShaderCache::CompileProgram(const std::string_view& vertex_shader, + const std::string_view& fragment_shader, const PreLinkCallback& callback, bool set_retrievable) +{ + GLProgram prog; + if (!prog.Compile(vertex_shader, fragment_shader)) + return std::nullopt; + + if (callback) + callback(prog); + + if (set_retrievable) + prog.SetBinaryRetrievableHint(); + + if (!prog.Link()) + return std::nullopt; + + return std::optional(std::move(prog)); +} + +std::optional GLShaderCache::CompileComputeProgram( + const std::string_view& glsl, const PreLinkCallback& callback, bool set_retrievable) +{ + GLProgram prog; + if (!prog.CompileCompute(glsl)) + return std::nullopt; + + if (callback) + callback(prog); + + if (set_retrievable) + prog.SetBinaryRetrievableHint(); + + if (!prog.Link()) + return std::nullopt; + + return std::optional(std::move(prog)); +} + +std::optional GLShaderCache::CompileAndAddProgram(const CacheIndexKey& key, + const std::string_view& vertex_shader, const std::string_view& fragment_shader, const PreLinkCallback& callback) +{ +#ifdef PCSX2_DEVBUILD + Common::Timer timer; +#endif + + std::optional prog = CompileProgram(vertex_shader, fragment_shader, callback, true); + if (!prog) + return std::nullopt; + +#ifdef PCSX2_DEVBUILD + const float compile_time = timer.GetTimeMilliseconds(); + timer.Reset(); +#endif + + std::vector prog_data; + u32 prog_format = 0; + if (!prog->GetBinary(&prog_data, &prog_format)) + return std::nullopt; + +#ifdef PCSX2_DEVBUILD + const float binary_time = timer.GetTimeMilliseconds(); + timer.Reset(); +#endif + + WriteToBlobFile(key, prog_data, prog_format); + +#ifdef PCSX2_DEVBUILD + const float write_time = timer.GetTimeMilliseconds(); + Console.WriteLn("Compiled and cached shader: Compile: %.2fms, Binary: %.2fms, Write: %.2fms", compile_time, + binary_time, write_time); +#endif + + return prog; +} + +std::optional GLShaderCache::GetComputeProgram(const std::string_view glsl, const PreLinkCallback& callback) +{ + if (!m_program_binary_supported || !m_blob_file) + { +#ifdef PCSX2_DEVBUILD + Common::Timer timer; +#endif + + std::optional res = CompileComputeProgram(glsl, callback, false); + +#ifdef PCSX2_DEVBUILD + Console.WriteLn("Time to compile shader without caching: %.2fms", timer.GetTimeMilliseconds()); +#endif + return res; + } + + const auto key = GetCacheKey(glsl, std::string_view()); + auto iter = m_index.find(key); + if (iter == m_index.end()) + return CompileAndAddComputeProgram(key, glsl, callback); + + std::vector data(iter->second.blob_size); + if (std::fseek(m_blob_file, iter->second.file_offset, SEEK_SET) != 0 || + std::fread(data.data(), 1, iter->second.blob_size, m_blob_file) != iter->second.blob_size) + { + Console.Error("Read blob from file failed"); + return {}; + } + +#ifdef PCSX2_DEVBUILD + Common::Timer timer; +#endif + + GLProgram prog; + if (prog.CreateFromBinary(data.data(), static_cast(data.size()), iter->second.blob_format)) + { +#ifdef PCSX2_DEVBUILD + Console.WriteLn("Time to create program from binary: %.2fms", timer.GetTimeMilliseconds()); +#endif + + return std::optional(std::move(prog)); + } + + Console.Warning( + "Failed to create program from binary, this may be due to a driver or GPU Change. Recreating cache."); + if (!Recreate()) + return CompileComputeProgram(glsl, callback, false); + else + return CompileAndAddComputeProgram(key, glsl, callback); +} + +bool GLShaderCache::GetComputeProgram( + GLProgram* out_program, const std::string_view glsl, const PreLinkCallback& callback) +{ + auto prog = GetComputeProgram(glsl, callback); + if (!prog) + return false; + + *out_program = std::move(*prog); + return true; +} + +std::optional GLShaderCache::CompileAndAddComputeProgram( + const CacheIndexKey& key, const std::string_view& glsl, const PreLinkCallback& callback) +{ +#ifdef PCSX2_DEVBUILD + Common::Timer timer; +#endif + + std::optional prog = CompileComputeProgram(glsl, callback, true); + if (!prog) + return std::nullopt; + +#ifdef PCSX2_DEVBUILD + const float compile_time = timer.GetTimeMilliseconds(); + timer.Reset(); +#endif + + std::vector prog_data; + u32 prog_format = 0; + if (!prog->GetBinary(&prog_data, &prog_format)) + return std::nullopt; + +#ifdef PCSX2_DEVBUILD + const float binary_time = timer.GetTimeMilliseconds(); + timer.Reset(); +#endif + + WriteToBlobFile(key, prog_data, prog_format); + +#ifdef PCSX2_DEVBUILD + const float write_time = timer.GetTimeMilliseconds(); + Console.WriteLn("Compiled and cached compute shader: Compile: %.2fms, Binary: %.2fms, Write: %.2fms", compile_time, + binary_time, write_time); +#endif + + return prog; +} diff --git a/pcsx2/GS/Renderers/OpenGL/GLShaderCache.h b/pcsx2/GS/Renderers/OpenGL/GLShaderCache.h new file mode 100644 index 0000000000..cbee361e46 --- /dev/null +++ b/pcsx2/GS/Renderers/OpenGL/GLShaderCache.h @@ -0,0 +1,108 @@ +/* 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 . + */ + +#pragma once +#include "GS/Renderers/OpenGL/GLProgram.h" + +#include "common/HashCombine.h" + +#include +#include +#include +#include +#include +#include +#include + +class GLShaderCache +{ +public: + using PreLinkCallback = std::function; + + GLShaderCache(); + ~GLShaderCache(); + + bool Open(); + void Close(); + + std::optional GetProgram(const std::string_view vertex_shader, const std::string_view fragment_shader, + const PreLinkCallback& callback = {}); + bool GetProgram(GLProgram* out_program, const std::string_view vertex_shader, + const std::string_view fragment_shader, const PreLinkCallback& callback = {}); + + std::optional GetComputeProgram(const std::string_view glsl, const PreLinkCallback& callback = {}); + bool GetComputeProgram(GLProgram* out_program, const std::string_view glsl, const PreLinkCallback& callback = {}); + +private: + struct CacheIndexKey + { + u64 vertex_source_hash_low; + u64 vertex_source_hash_high; + u32 vertex_source_length; + u64 fragment_source_hash_low; + u64 fragment_source_hash_high; + u32 fragment_source_length; + + bool operator==(const CacheIndexKey& key) const; + bool operator!=(const CacheIndexKey& key) const; + }; + + struct CacheIndexEntryHasher + { + std::size_t operator()(const CacheIndexKey& e) const noexcept + { + std::size_t h = 0; + HashCombine(h, e.vertex_source_hash_low, e.vertex_source_hash_high, e.vertex_source_length, + e.fragment_source_hash_low, e.fragment_source_hash_high, e.fragment_source_length); + return h; + } + }; + + struct CacheIndexData + { + u32 file_offset; + u32 blob_size; + u32 blob_format; + }; + + using CacheIndex = std::unordered_map; + + static CacheIndexKey GetCacheKey(const std::string_view& vertex_shader, const std::string_view& fragment_shader); + + std::string GetIndexFileName() const; + std::string GetBlobFileName() const; + + bool CreateNew(const std::string& index_filename, const std::string& blob_filename); + bool ReadExisting(const std::string& index_filename, const std::string& blob_filename); + bool Recreate(); + + bool WriteToBlobFile(const CacheIndexKey& key, const std::vector& prog_data, u32 prog_format); + + std::optional CompileProgram(const std::string_view& vertex_shader, + const std::string_view& fragment_shader, const PreLinkCallback& callback, bool set_retrievable); + std::optional CompileAndAddProgram(const CacheIndexKey& key, const std::string_view& vertex_shader, + const std::string_view& fragment_shader, const PreLinkCallback& callback); + + std::optional CompileComputeProgram( + const std::string_view& glsl, const PreLinkCallback& callback, bool set_retrievable); + std::optional CompileAndAddComputeProgram( + const CacheIndexKey& key, const std::string_view& glsl, const PreLinkCallback& callback); + + std::FILE* m_index_file = nullptr; + std::FILE* m_blob_file = nullptr; + + CacheIndex m_index; + bool m_program_binary_supported = false; +}; diff --git a/pcsx2/GS/Renderers/OpenGL/GLState.h b/pcsx2/GS/Renderers/OpenGL/GLState.h index b7aeb7cc67..bc757b0070 100644 --- a/pcsx2/GS/Renderers/OpenGL/GLState.h +++ b/pcsx2/GS/Renderers/OpenGL/GLState.h @@ -18,6 +18,8 @@ #include "GS/Renderers/OpenGL/GLLoader.h" #include "GS/GSVector.h" +#include "glad.h" + class GSTextureOGL; namespace GLState diff --git a/pcsx2/GS/Renderers/OpenGL/GLStreamBuffer.cpp b/pcsx2/GS/Renderers/OpenGL/GLStreamBuffer.cpp new file mode 100644 index 0000000000..acbe8e0805 --- /dev/null +++ b/pcsx2/GS/Renderers/OpenGL/GLStreamBuffer.cpp @@ -0,0 +1,343 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2023 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 "PrecompiledHeader.h" + +#include "GS/Renderers/OpenGL/GLStreamBuffer.h" + +#include "common/Align.h" +#include "common/AlignedMalloc.h" +#include "common/Assertions.h" + +#include +#include + +GLStreamBuffer::GLStreamBuffer(GLenum target, GLuint buffer_id, u32 size) + : m_target(target) + , m_buffer_id(buffer_id) + , m_size(size) +{ +} + +GLStreamBuffer::~GLStreamBuffer() +{ + glDeleteBuffers(1, &m_buffer_id); +} + +void GLStreamBuffer::Bind() +{ + glBindBuffer(m_target, m_buffer_id); +} + +void GLStreamBuffer::Unbind() +{ + glBindBuffer(m_target, 0); +} + +namespace +{ + // Uses glBufferSubData() to update. Preferred for drivers which don't support {ARB,EXT}_buffer_storage. + class BufferSubDataStreamBuffer final : public GLStreamBuffer + { + public: + ~BufferSubDataStreamBuffer() override { _aligned_free(m_cpu_buffer); } + + MappingResult Map(u32 alignment, u32 min_size) override + { + return MappingResult{static_cast(m_cpu_buffer), 0, 0, m_size / alignment}; + } + + void Unmap(u32 used_size) override + { + if (used_size == 0) + return; + + glBindBuffer(m_target, m_buffer_id); + glBufferSubData(m_target, 0, used_size, m_cpu_buffer); + } + + u32 GetChunkSize() const override { return m_size; } + + static std::unique_ptr Create(GLenum target, u32 size) + { + glGetError(); + + GLuint buffer_id; + glGenBuffers(1, &buffer_id); + glBindBuffer(target, buffer_id); + glBufferData(target, size, nullptr, GL_STREAM_DRAW); + + GLenum err = glGetError(); + if (err != GL_NO_ERROR) + { + glBindBuffer(target, 0); + glDeleteBuffers(1, &buffer_id); + return {}; + } + + return std::unique_ptr(new BufferSubDataStreamBuffer(target, buffer_id, size)); + } + + private: + BufferSubDataStreamBuffer(GLenum target, GLuint buffer_id, u32 size) + : GLStreamBuffer(target, buffer_id, size) + { + m_cpu_buffer = static_cast(_aligned_malloc(size, 32)); + if (!m_cpu_buffer) + pxFailRel("Failed to allocate CPU storage for GL buffer"); + } + + u8* m_cpu_buffer; + }; + + // Uses BufferData() to orphan the buffer after every update. Used on Mali where BufferSubData forces a sync. + class BufferDataStreamBuffer final : public GLStreamBuffer + { + public: + ~BufferDataStreamBuffer() override { _aligned_free(m_cpu_buffer); } + + MappingResult Map(u32 alignment, u32 min_size) override + { + return MappingResult{static_cast(m_cpu_buffer), 0, 0, m_size / alignment}; + } + + void Unmap(u32 used_size) override + { + if (used_size == 0) + return; + + glBindBuffer(m_target, m_buffer_id); + glBufferData(m_target, used_size, m_cpu_buffer, GL_STREAM_DRAW); + } + + u32 GetChunkSize() const override { return m_size; } + + static std::unique_ptr Create(GLenum target, u32 size) + { + glGetError(); + + GLuint buffer_id; + glGenBuffers(1, &buffer_id); + glBindBuffer(target, buffer_id); + glBufferData(target, size, nullptr, GL_STREAM_DRAW); + + GLenum err = glGetError(); + if (err != GL_NO_ERROR) + { + glBindBuffer(target, 0); + glDeleteBuffers(1, &buffer_id); + return {}; + } + + return std::unique_ptr(new BufferDataStreamBuffer(target, buffer_id, size)); + } + + private: + BufferDataStreamBuffer(GLenum target, GLuint buffer_id, u32 size) + : GLStreamBuffer(target, buffer_id, size) + { + m_cpu_buffer = static_cast(_aligned_malloc(size, 32)); + if (!m_cpu_buffer) + pxFailRel("Failed to allocate CPU storage for GL buffer"); + } + + u8* m_cpu_buffer; + }; + + // Base class for implementations which require syncing. + class SyncingStreamBuffer : public GLStreamBuffer + { + public: + enum : u32 + { + NUM_SYNC_POINTS = 16 + }; + + virtual ~SyncingStreamBuffer() override + { + for (u32 i = m_available_block_index; i <= m_used_block_index; i++) + { + pxAssert(m_sync_objects[i]); + glDeleteSync(m_sync_objects[i]); + } + } + + protected: + SyncingStreamBuffer(GLenum target, GLuint buffer_id, u32 size) + : GLStreamBuffer(target, buffer_id, size) + , m_bytes_per_block((size + (NUM_SYNC_POINTS)-1) / NUM_SYNC_POINTS) + { + } + + __fi u32 GetSyncIndexForOffset(u32 offset) { return offset / m_bytes_per_block; } + + __fi void AddSyncsForOffset(u32 offset) + { + const u32 end = GetSyncIndexForOffset(offset); + for (; m_used_block_index < end; m_used_block_index++) + { + pxAssert(!m_sync_objects[m_used_block_index]); + m_sync_objects[m_used_block_index] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + } + } + + __fi void WaitForSync(GLsync& sync) + { + glClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED); + glDeleteSync(sync); + sync = nullptr; + } + + __fi void EnsureSyncsWaitedForOffset(u32 offset) + { + const u32 end = std::min(GetSyncIndexForOffset(offset) + 1, NUM_SYNC_POINTS); + for (; m_available_block_index < end; m_available_block_index++) + { + pxAssert(m_sync_objects[m_available_block_index]); + WaitForSync(m_sync_objects[m_available_block_index]); + } + } + + void AllocateSpace(u32 size) + { + // add sync objects for writes since the last allocation + AddSyncsForOffset(m_position); + + // wait for sync objects for the space we want to use + EnsureSyncsWaitedForOffset(m_position + size); + + // wrap-around? + if ((m_position + size) > m_size) + { + // current position ... buffer end + AddSyncsForOffset(m_size); + + // rewind, and try again + m_position = 0; + + // wait for the sync at the start of the buffer + WaitForSync(m_sync_objects[0]); + m_available_block_index = 1; + + // and however much more we need to satisfy the allocation + EnsureSyncsWaitedForOffset(size); + m_used_block_index = 0; + } + } + + u32 GetChunkSize() const override { return m_size / NUM_SYNC_POINTS; } + + u32 m_position = 0; + u32 m_used_block_index = 0; + u32 m_available_block_index = NUM_SYNC_POINTS; + u32 m_bytes_per_block; + std::array m_sync_objects{}; + }; + + class BufferStorageStreamBuffer : public SyncingStreamBuffer + { + public: + ~BufferStorageStreamBuffer() override + { + glBindBuffer(m_target, m_buffer_id); + glUnmapBuffer(m_target); + glBindBuffer(m_target, 0); + } + + MappingResult Map(u32 alignment, u32 min_size) override + { + if (m_position > 0) + m_position = Common::AlignUp(m_position, alignment); + + AllocateSpace(min_size); + pxAssert((m_position + min_size) <= (m_available_block_index * m_bytes_per_block)); + + const u32 free_space_in_block = ((m_available_block_index * m_bytes_per_block) - m_position); + return MappingResult{static_cast(m_mapped_ptr + m_position), m_position, m_position / alignment, + free_space_in_block / alignment}; + } + + void Unmap(u32 used_size) override + { + pxAssert((m_position + used_size) <= m_size); + if (!m_coherent) + { + Bind(); + glFlushMappedBufferRange(m_target, m_position, used_size); + } + + m_position += used_size; + } + + static std::unique_ptr Create(GLenum target, u32 size, bool coherent = true) + { + glGetError(); + + GLuint buffer_id; + glGenBuffers(1, &buffer_id); + glBindBuffer(target, buffer_id); + + const u32 flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | (coherent ? GL_MAP_COHERENT_BIT : 0); + const u32 map_flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | (coherent ? 0 : GL_MAP_FLUSH_EXPLICIT_BIT); + if (GLAD_GL_VERSION_4_4 || GLAD_GL_ARB_buffer_storage) + glBufferStorage(target, size, nullptr, flags); + else if (GLAD_GL_EXT_buffer_storage) + glBufferStorageEXT(target, size, nullptr, flags); + + GLenum err = glGetError(); + if (err != GL_NO_ERROR) + { + glBindBuffer(target, 0); + glDeleteBuffers(1, &buffer_id); + return {}; + } + + u8* mapped_ptr = static_cast(glMapBufferRange(target, 0, size, map_flags)); + pxAssertRel(mapped_ptr, "Persistent buffer was mapped"); + + return std::unique_ptr( + new BufferStorageStreamBuffer(target, buffer_id, size, mapped_ptr, coherent)); + } + + private: + BufferStorageStreamBuffer(GLenum target, GLuint buffer_id, u32 size, u8* mapped_ptr, bool coherent) + : SyncingStreamBuffer(target, buffer_id, size) + , m_mapped_ptr(mapped_ptr) + , m_coherent(coherent) + { + } + + u8* m_mapped_ptr; + bool m_coherent; + }; +} // namespace + +std::unique_ptr GLStreamBuffer::Create(GLenum target, u32 size) +{ + std::unique_ptr buf; + if (GLAD_GL_VERSION_4_4 || GLAD_GL_ARB_buffer_storage || GLAD_GL_EXT_buffer_storage) + { + buf = BufferStorageStreamBuffer::Create(target, size); + if (buf) + return buf; + } + + // BufferSubData is slower on all drivers except NVIDIA... + const char* vendor = reinterpret_cast(glGetString(GL_VENDOR)); + if (std::strstr(vendor, "NVIDIA")) + return BufferSubDataStreamBuffer::Create(target, size); + else + return BufferDataStreamBuffer::Create(target, size); +} diff --git a/pcsx2/GS/Renderers/OpenGL/GLStreamBuffer.h b/pcsx2/GS/Renderers/OpenGL/GLStreamBuffer.h new file mode 100644 index 0000000000..a3e4b34493 --- /dev/null +++ b/pcsx2/GS/Renderers/OpenGL/GLStreamBuffer.h @@ -0,0 +1,61 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2023 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 . + */ + +#pragma once + +#include "common/Pcsx2Defs.h" + +#include "glad.h" + +#include +#include +#include + +/// Provides a buffer for streaming data to the GPU, ideally in write-combined memory. +class GLStreamBuffer +{ +public: + virtual ~GLStreamBuffer(); + + __fi GLuint GetGLBufferId() const { return m_buffer_id; } + __fi GLenum GetGLTarget() const { return m_target; } + __fi u32 GetSize() const { return m_size; } + + void Bind(); + void Unbind(); + + struct MappingResult + { + void* pointer; + u32 buffer_offset; + u32 index_aligned; // offset / alignment, suitable for base vertex + u32 space_aligned; // remaining space / alignment + }; + + virtual MappingResult Map(u32 alignment, u32 min_size) = 0; + virtual void Unmap(u32 used_size) = 0; + + /// Returns the minimum granularity of blocks which sync objects will be created around. + virtual u32 GetChunkSize() const = 0; + + static std::unique_ptr Create(GLenum target, u32 size); + +protected: + GLStreamBuffer(GLenum target, GLuint buffer_id, u32 size); + + GLenum m_target; + GLuint m_buffer_id; + u32 m_size; +}; diff --git a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp index 427f02b3c0..ec12c2cb6d 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp @@ -15,13 +15,13 @@ #include "PrecompiledHeader.h" +#include "GS/Renderers/OpenGL/GLContext.h" +#include "GS/Renderers/OpenGL/GSDeviceOGL.h" +#include "GS/Renderers/OpenGL/GLState.h" #include "GS/GSState.h" #include "GS/GSGL.h" #include "GS/GSUtil.h" -#include "GS/Renderers/OpenGL/GSDeviceOGL.h" -#include "GS/Renderers/OpenGL/GLState.h" #include "Host.h" -#include "ShaderCacheVersion.h" #include "common/StringUtil.h" @@ -32,8 +32,6 @@ #include #include -//#define ONLY_LINES - static constexpr u32 g_vs_cb_index = 1; static constexpr u32 g_ps_cb_index = 0; @@ -43,7 +41,7 @@ static constexpr u32 VERTEX_UNIFORM_BUFFER_SIZE = 8 * 1024 * 1024; static constexpr u32 FRAGMENT_UNIFORM_BUFFER_SIZE = 8 * 1024 * 1024; static constexpr u32 TEXTURE_UPLOAD_BUFFER_SIZE = 128 * 1024 * 1024; -static std::unique_ptr s_texture_upload_buffer; +static std::unique_ptr s_texture_upload_buffer; GSDeviceOGL::GSDeviceOGL() = default; @@ -94,12 +92,7 @@ bool GSDeviceOGL::Create() if (!AcquireWindow(true)) return false; - // We need at least GL3.3. - static constexpr const GL::Context::Version version_list[] = {{GL::Context::Profile::Core, 4, 6}, - {GL::Context::Profile::Core, 4, 5}, {GL::Context::Profile::Core, 4, 4}, {GL::Context::Profile::Core, 4, 3}, - {GL::Context::Profile::Core, 4, 2}, {GL::Context::Profile::Core, 4, 1}, {GL::Context::Profile::Core, 4, 0}, - {GL::Context::Profile::Core, 3, 3}}; - m_gl_context = GL::Context::Create(m_window_info, version_list); + m_gl_context = GLContext::Create(m_window_info); if (!m_gl_context) { Console.Error("Failed to create any GL context"); @@ -124,7 +117,7 @@ bool GSDeviceOGL::Create() if (!GSConfig.DisableShaderCache) { - if (!m_shader_cache.Open(false, EmuFolders::Cache, SHADER_CACHE_VERSION)) + if (!m_shader_cache.Open()) Console.Warning("Shader cache failed to open."); } else @@ -240,10 +233,10 @@ bool GSDeviceOGL::Create() glGenVertexArrays(1, &m_vao); IASetVAO(m_vao); - m_vertex_stream_buffer = GL::StreamBuffer::Create(GL_ARRAY_BUFFER, VERTEX_BUFFER_SIZE); - m_index_stream_buffer = GL::StreamBuffer::Create(GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE); - m_vertex_uniform_stream_buffer = GL::StreamBuffer::Create(GL_UNIFORM_BUFFER, VERTEX_UNIFORM_BUFFER_SIZE); - m_fragment_uniform_stream_buffer = GL::StreamBuffer::Create(GL_UNIFORM_BUFFER, FRAGMENT_UNIFORM_BUFFER_SIZE); + m_vertex_stream_buffer = GLStreamBuffer::Create(GL_ARRAY_BUFFER, VERTEX_BUFFER_SIZE); + m_index_stream_buffer = GLStreamBuffer::Create(GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE); + m_vertex_uniform_stream_buffer = GLStreamBuffer::Create(GL_UNIFORM_BUFFER, VERTEX_UNIFORM_BUFFER_SIZE); + m_fragment_uniform_stream_buffer = GLStreamBuffer::Create(GL_UNIFORM_BUFFER, FRAGMENT_UNIFORM_BUFFER_SIZE); glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &m_uniform_buffer_alignment); if (!m_vertex_stream_buffer || !m_index_stream_buffer || !m_vertex_uniform_stream_buffer || !m_fragment_uniform_stream_buffer) { @@ -513,7 +506,7 @@ bool GSDeviceOGL::Create() // **************************************************************** if (!GLLoader::buggy_pbo) { - s_texture_upload_buffer = GL::StreamBuffer::Create(GL_PIXEL_UNPACK_BUFFER, TEXTURE_UPLOAD_BUFFER_SIZE); + s_texture_upload_buffer = GLStreamBuffer::Create(GL_PIXEL_UNPACK_BUFFER, TEXTURE_UPLOAD_BUFFER_SIZE); if (s_texture_upload_buffer) { // Don't keep it bound, we'll re-bind when we need it. @@ -582,7 +575,7 @@ bool GSDeviceOGL::CreateTextureFX() m_om_dss[key] = CreateDepthStencil(OMDepthStencilSelector(key)); } - GL::Program::ResetLastProgram(); + GLProgram::ResetLastProgram(); return true; } @@ -616,24 +609,24 @@ void GSDeviceOGL::DestroyResources() m_shadeboost.ps.Destroy(); - for (GL::Program& prog : m_date.primid_ps) + for (GLProgram& prog : m_date.primid_ps) prog.Destroy(); delete m_date.dss; m_fxaa.ps.Destroy(); - for (GL::Program& prog : m_present) + for (GLProgram& prog : m_present) prog.Destroy(); - for (GL::Program& prog : m_convert.ps) + for (GLProgram& prog : m_convert.ps) prog.Destroy(); delete m_convert.dss; delete m_convert.dss_write; - for (GL::Program& prog : m_interlace.ps) + for (GLProgram& prog : m_interlace.ps) prog.Destroy(); - for (GL::Program& prog : m_merge_obj.ps) + for (GLProgram& prog : m_merge_obj.ps) prog.Destroy(); m_fragment_uniform_stream_buffer.reset(); @@ -718,8 +711,9 @@ std::string GSDeviceOGL::GetDriverInfo() const const char* gl_vendor = reinterpret_cast(glGetString(GL_VENDOR)); const char* gl_renderer = reinterpret_cast(glGetString(GL_RENDERER)); const char* gl_version = reinterpret_cast(glGetString(GL_VERSION)); - return StringUtil::StdStringFromFormat( - "%s Context:\n%s\n%s %s", m_gl_context->IsGLES() ? "OpenGL ES" : "OpenGL", gl_version, gl_vendor, gl_renderer); + const char* gl_shading_language_version = reinterpret_cast(glGetString(GL_SHADING_LANGUAGE_VERSION)); + return fmt::format( + "OpenGL Context:\n{}\n{} {}\nGLSL: {}", gl_version, gl_vendor, gl_renderer, gl_shading_language_version); } GSDevice::PresentResult GSDeviceOGL::BeginPresent(bool frame_skip) @@ -757,10 +751,7 @@ void GSDeviceOGL::EndPresent() void GSDeviceOGL::CreateTimestampQueries() { - const bool gles = m_gl_context->IsGLES(); - const auto GenQueries = gles ? glGenQueriesEXT : glGenQueries; - - GenQueries(static_cast(m_timestamp_queries.size()), m_timestamp_queries.data()); + glGenQueries(static_cast(m_timestamp_queries.size()), m_timestamp_queries.data()); KickTimestampQuery(); } @@ -769,16 +760,10 @@ void GSDeviceOGL::DestroyTimestampQueries() if (m_timestamp_queries[0] == 0) return; - const bool gles = m_gl_context->IsGLES(); - const auto DeleteQueries = gles ? glDeleteQueriesEXT : glDeleteQueries; - if (m_timestamp_query_started) - { - const auto EndQuery = gles ? glEndQueryEXT : glEndQuery; - EndQuery(GL_TIME_ELAPSED); - } + glEndQuery(GL_TIME_ELAPSED); - DeleteQueries(static_cast(m_timestamp_queries.size()), m_timestamp_queries.data()); + glDeleteQueries(static_cast(m_timestamp_queries.size()), m_timestamp_queries.data()); m_timestamp_queries.fill(0); m_read_timestamp_query = 0; m_write_timestamp_query = 0; @@ -788,38 +773,16 @@ void GSDeviceOGL::DestroyTimestampQueries() void GSDeviceOGL::PopTimestampQuery() { - const bool gles = m_gl_context->IsGLES(); - - if (gles) - { - GLint disjoint = 0; - glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjoint); - if (disjoint) - { - DevCon.WriteLn("GPU timing disjoint, resetting."); - if (m_timestamp_query_started) - glEndQueryEXT(GL_TIME_ELAPSED); - - m_read_timestamp_query = 0; - m_write_timestamp_query = 0; - m_waiting_timestamp_queries = 0; - m_timestamp_query_started = false; - } - } - while (m_waiting_timestamp_queries > 0) { - const auto GetQueryObjectiv = gles ? glGetQueryObjectivEXT : glGetQueryObjectiv; - const auto GetQueryObjectui64v = gles ? glGetQueryObjectui64vEXT : glGetQueryObjectui64v; - GLint available = 0; - GetQueryObjectiv(m_timestamp_queries[m_read_timestamp_query], GL_QUERY_RESULT_AVAILABLE, &available); + glGetQueryObjectiv(m_timestamp_queries[m_read_timestamp_query], GL_QUERY_RESULT_AVAILABLE, &available); if (!available) break; u64 result = 0; - GetQueryObjectui64v(m_timestamp_queries[m_read_timestamp_query], GL_QUERY_RESULT, &result); + glGetQueryObjectui64v(m_timestamp_queries[m_read_timestamp_query], GL_QUERY_RESULT, &result); m_accumulated_gpu_time += static_cast(static_cast(result) / 1000000.0); m_read_timestamp_query = (m_read_timestamp_query + 1) % NUM_TIMESTAMP_QUERIES; m_waiting_timestamp_queries--; @@ -827,8 +790,7 @@ void GSDeviceOGL::PopTimestampQuery() if (m_timestamp_query_started) { - const auto EndQuery = gles ? glEndQueryEXT : glEndQuery; - EndQuery(GL_TIME_ELAPSED); + glEndQuery(GL_TIME_ELAPSED); m_write_timestamp_query = (m_write_timestamp_query + 1) % NUM_TIMESTAMP_QUERIES; m_timestamp_query_started = false; @@ -841,10 +803,7 @@ void GSDeviceOGL::KickTimestampQuery() if (m_timestamp_query_started || m_waiting_timestamp_queries == NUM_TIMESTAMP_QUERIES) return; - const bool gles = m_gl_context->IsGLES(); - const auto BeginQuery = gles ? glBeginQueryEXT : glBeginQuery; - - BeginQuery(GL_TIME_ELAPSED, m_timestamp_queries[m_write_timestamp_query]); + glBeginQuery(GL_TIME_ELAPSED, m_timestamp_queries[m_write_timestamp_query]); m_timestamp_query_started = true; } @@ -853,9 +812,6 @@ bool GSDeviceOGL::SetGPUTimingEnabled(bool enabled) if (m_gpu_timing_enabled == enabled) return true; - if (enabled && m_gl_context->IsGLES() && !GLAD_GL_EXT_disjoint_timer_query) - return false; - m_gpu_timing_enabled = enabled; if (m_gpu_timing_enabled) CreateTimestampQueries(); @@ -1343,7 +1299,7 @@ void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture StretchRect(sTex, sRect, dTex, dRect, m_convert.ps[(int)shader], linear); } -void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, const GL::Program& ps, bool linear) +void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, const GLProgram& ps, bool linear) { StretchRect(sTex, sRect, dTex, dRect, ps, false, OMColorMaskSelector(), linear); } @@ -1360,7 +1316,7 @@ void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture StretchRect(sTex, sRect, dTex, dRect, m_convert.ps[(int)ShaderConvert::COPY], false, cms, false); } -void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, const GL::Program& ps, bool alpha_blend, OMColorMaskSelector cms, bool linear) +void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, const GLProgram& ps, bool alpha_blend, OMColorMaskSelector cms, bool linear) { ASSERT(sTex); @@ -1413,7 +1369,7 @@ void GSDeviceOGL::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture cb.SetTarget(dRect, ds); cb.SetTime(shaderTime); - GL::Program& prog = m_present[static_cast(shader)]; + GLProgram& prog = m_present[static_cast(shader)]; prog.Bind(); prog.Uniform4fv(0, cb.SourceRect.F32); prog.Uniform4fv(1, cb.TargetRect.F32); @@ -1444,7 +1400,7 @@ void GSDeviceOGL::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture void GSDeviceOGL::UpdateCLUTTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, GSTexture* dTex, u32 dOffset, u32 dSize) { const ShaderConvert shader = (dSize == 16) ? ShaderConvert::CLUT_4 : ShaderConvert::CLUT_8; - GL::Program& prog = m_convert.ps[static_cast(shader)]; + GLProgram& prog = m_convert.ps[static_cast(shader)]; prog.Bind(); prog.Uniform3ui(0, offsetX, offsetY, dOffset); prog.Uniform1f(1, sScale); @@ -1464,7 +1420,7 @@ void GSDeviceOGL::UpdateCLUTTexture(GSTexture* sTex, float sScale, u32 offsetX, void GSDeviceOGL::ConvertToIndexedTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, u32 SBW, u32 SPSM, GSTexture* dTex, u32 DBW, u32 DPSM) { const ShaderConvert shader = ShaderConvert::RGBA_TO_8I; - GL::Program& prog = m_convert.ps[static_cast(shader)]; + GLProgram& prog = m_convert.ps[static_cast(shader)]; prog.Bind(); prog.Uniform1ui(0, SBW); prog.Uniform1ui(1, DBW); @@ -1692,7 +1648,7 @@ bool GSDeviceOGL::CompileFXAAProgram() } const std::string ps(GetShaderSource("ps_main", GL_FRAGMENT_SHADER, shader->c_str(), fxaa_macro)); - std::optional prog = m_shader_cache.GetProgram(m_convert.vs, ps); + std::optional prog = m_shader_cache.GetProgram(m_convert.vs, ps); if (!prog.has_value()) { Console.Error("Failed to compile FXAA fragment shader"); @@ -1890,7 +1846,7 @@ bool GSDeviceOGL::CreateCASPrograms() return false; } - const auto link_uniforms = [](GL::Program& prog) { + const auto link_uniforms = [](GLProgram& prog) { prog.RegisterUniform("const0"); prog.RegisterUniform("const1"); prog.RegisterUniform("srcOffset"); @@ -1903,7 +1859,7 @@ bool GSDeviceOGL::CreateCASPrograms() bool GSDeviceOGL::DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array& constants) { - const GL::Program& prog = sharpen_only ? m_cas.sharpen_ps : m_cas.upscale_ps; + const GLProgram& prog = sharpen_only ? m_cas.sharpen_ps : m_cas.upscale_ps; prog.Bind(); prog.Uniform4uiv(0, &constants[0]); prog.Uniform4uiv(1, &constants[4]); @@ -1929,7 +1885,7 @@ bool GSDeviceOGL::CreateImGuiProgram() return false; } - std::optional prog = m_shader_cache.GetProgram( + std::optional prog = m_shader_cache.GetProgram( GetShaderSource("vs_main", GL_VERTEX_SHADER, glsl.value()), GetShaderSource("ps_main", GL_FRAGMENT_SHADER, glsl.value())); if (!prog.has_value()) @@ -2216,7 +2172,7 @@ void GSDeviceOGL::SetScissor(const GSVector4i& scissor) } } -__fi static void WriteToStreamBuffer(GL::StreamBuffer* sb, u32 index, u32 align, const void* data, u32 size) +__fi static void WriteToStreamBuffer(GLStreamBuffer* sb, u32 index, u32 align, const void* data, u32 size) { const auto res = sb->Map(align, size); std::memcpy(res.pointer, data, size); @@ -2237,7 +2193,7 @@ void GSDeviceOGL::SetupPipeline(const ProgramSelector& psel) const std::string vs(GetVSSource(psel.vs)); const std::string ps(GetPSSource(psel.ps)); - GL::Program prog; + GLProgram prog; m_shader_cache.GetProgram(&prog, vs, ps); it = m_programs.emplace(psel, std::move(prog)).first; it->second.Bind(); @@ -2636,7 +2592,7 @@ void GSDeviceOGL::DebugMessageCallback(GLenum gl_source, GLenum gl_type, GLuint } } -GL::StreamBuffer* GSDeviceOGL::GetTextureUploadBuffer() +GLStreamBuffer* GSDeviceOGL::GetTextureUploadBuffer() { return s_texture_upload_buffer.get(); } diff --git a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h index d7a92c1242..b71d7bb52e 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h +++ b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h @@ -1,5 +1,5 @@ /* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2021 PCSX2 Dev Team + * Copyright (C) 2002-2023 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- @@ -15,17 +15,19 @@ #pragma once -#include "common/GL/Context.h" -#include "common/GL/StreamBuffer.h" -#include "common/GL/Program.h" -#include "common/GL/ShaderCache.h" -#include "common/HashCombine.h" #include "GS/Renderers/Common/GSDevice.h" -#include "GSTextureOGL.h" -#include "GLState.h" -#include "GLLoader.h" +#include "GS/Renderers/OpenGL/GLLoader.h" +#include "GS/Renderers/OpenGL/GLProgram.h" +#include "GS/Renderers/OpenGL/GLShaderCache.h" +#include "GS/Renderers/OpenGL/GLState.h" +#include "GS/Renderers/OpenGL/GLStreamBuffer.h" +#include "GS/Renderers/OpenGL/GSTextureOGL.h" #include "GS/GS.h" +#include "common/HashCombine.h" + +class GLContext; + class GSDepthStencilOGL { bool m_depth_enable; @@ -150,77 +152,77 @@ public: private: static constexpr u8 NUM_TIMESTAMP_QUERIES = 5; - std::unique_ptr m_gl_context; + std::unique_ptr m_gl_context; GLuint m_fbo = 0; // frame buffer container GLuint m_fbo_read = 0; // frame buffer container only for reading GLuint m_fbo_write = 0; // frame buffer container only for writing - std::unique_ptr m_vertex_stream_buffer; - std::unique_ptr m_index_stream_buffer; + std::unique_ptr m_vertex_stream_buffer; + std::unique_ptr m_index_stream_buffer; GLuint m_expand_ibo = 0; GLuint m_vao = 0; GLuint m_expand_vao = 0; GLenum m_draw_topology = 0; - std::unique_ptr m_vertex_uniform_stream_buffer; - std::unique_ptr m_fragment_uniform_stream_buffer; + std::unique_ptr m_vertex_uniform_stream_buffer; + std::unique_ptr m_fragment_uniform_stream_buffer; GLint m_uniform_buffer_alignment = 0; struct { - GL::Program ps[2]; // program object + GLProgram ps[2]; // program object } m_merge_obj; struct { - GL::Program ps[NUM_INTERLACE_SHADERS]; // program object + GLProgram ps[NUM_INTERLACE_SHADERS]; // program object } m_interlace; struct { std::string vs; - GL::Program ps[static_cast(ShaderConvert::Count)]; // program object + GLProgram ps[static_cast(ShaderConvert::Count)]; // program object GLuint ln = 0; // sampler object GLuint pt = 0; // sampler object GSDepthStencilOGL* dss = nullptr; GSDepthStencilOGL* dss_write = nullptr; } m_convert; - GL::Program m_present[static_cast(PresentShader::Count)]; + GLProgram m_present[static_cast(PresentShader::Count)]; struct { - GL::Program ps; + GLProgram ps; } m_fxaa; struct { GSDepthStencilOGL* dss = nullptr; - GL::Program primid_ps[2]; + GLProgram primid_ps[2]; } m_date; struct { - GL::Program ps; + GLProgram ps; } m_shadeboost; struct { - GL::Program upscale_ps; - GL::Program sharpen_ps; + GLProgram upscale_ps; + GLProgram sharpen_ps; } m_cas; struct { - GL::Program ps; + GLProgram ps; GLuint vao = 0; } m_imgui; GLuint m_ps_ss[1 << 8]; GSDepthStencilOGL* m_om_dss[1 << 5] = {}; - std::unordered_map m_programs; - GL::ShaderCache m_shader_cache; + std::unordered_map m_programs; + GLShaderCache m_shader_cache; GLuint m_palette_ss = 0; @@ -279,7 +281,7 @@ public: // Used by OpenGL, so the same calling convention is required. static void APIENTRY DebugMessageCallback(GLenum gl_source, GLenum gl_type, GLuint id, GLenum gl_severity, GLsizei gl_length, const GLchar* gl_message, const void* userParam); - static GL::StreamBuffer* GetTextureUploadBuffer(); + static GLStreamBuffer* GetTextureUploadBuffer(); __fi u32 GetFBORead() const { return m_fbo_read; } __fi u32 GetFBOWrite() const { return m_fbo_write; } @@ -328,9 +330,9 @@ public: void BlitRect(GSTexture* sTex, const GSVector4i& r, const GSVector2i& dsize, bool at_origin, bool linear); void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader = ShaderConvert::COPY, bool linear = true) override; - void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, const GL::Program& ps, bool linear = true); + void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, const GLProgram& ps, bool linear = true); void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, bool red, bool green, bool blue, bool alpha) override; - void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, const GL::Program& ps, bool alpha_blend, OMColorMaskSelector cms, bool linear = true); + void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, const GLProgram& ps, bool alpha_blend, OMColorMaskSelector cms, bool linear = true); void PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, PresentShader shader, float shaderTime, bool linear) override; void UpdateCLUTTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, GSTexture* dTex, u32 dOffset, u32 dSize) override; void ConvertToIndexedTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, u32 SBW, u32 SPSM, GSTexture* dTex, u32 DBW, u32 DPSM) override; diff --git a/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.cpp b/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.cpp index 0111ae9a40..88cf978b69 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.cpp @@ -185,12 +185,12 @@ void* GSTextureOGL::GetNativeHandle() const void GSTextureOGL::Clear(const void* data) { - glClearTexImage(m_texture_id, GL_TEX_LEVEL_0, m_int_format, m_int_type, data); + glClearTexImage(m_texture_id, 0, m_int_format, m_int_type, data); } void GSTextureOGL::Clear(const void* data, const GSVector4i& area) { - glClearTexSubImage(m_texture_id, GL_TEX_LEVEL_0, area.x, area.y, 0, area.width(), area.height(), 1, m_int_format, m_int_type, data); + glClearTexSubImage(m_texture_id, 0, area.x, area.y, 0, area.width(), area.height(), 1, m_int_format, m_int_type, data); } bool GSTextureOGL::Update(const GSVector4i& r, const void* data, int pitch, int layer) @@ -241,7 +241,7 @@ bool GSTextureOGL::Update(const GSVector4i& r, const void* data, int pitch, int } else { - GL::StreamBuffer* const sb = GSDeviceOGL::GetTextureUploadBuffer(); + GLStreamBuffer* const sb = GSDeviceOGL::GetTextureUploadBuffer(); const auto map = sb->Map(TEXTURE_UPLOAD_ALIGNMENT, map_size); StringUtil::StrideMemCpy(map.pointer, preferred_pitch, data, pitch, r.width() << m_int_shift, r.height()); @@ -311,7 +311,7 @@ void GSTextureOGL::Unmap() { const u32 pitch = Common::AlignUpPow2(m_r_w << m_int_shift, TEXTURE_UPLOAD_PITCH_ALIGNMENT); const u32 upload_size = pitch * m_r_h; - GL::StreamBuffer* sb = GSDeviceOGL::GetTextureUploadBuffer(); + GLStreamBuffer* sb = GSDeviceOGL::GetTextureUploadBuffer(); sb->Unmap(upload_size); sb->Bind(); diff --git a/pcsx2/pcsx2core.vcxproj b/pcsx2/pcsx2core.vcxproj index 5b9b880e26..f522e191e2 100644 --- a/pcsx2/pcsx2core.vcxproj +++ b/pcsx2/pcsx2core.vcxproj @@ -206,6 +206,11 @@ + + + + + @@ -553,6 +558,11 @@ + + + + + diff --git a/pcsx2/pcsx2core.vcxproj.filters b/pcsx2/pcsx2core.vcxproj.filters index d9af7c0a05..2dd56c4a8f 100644 --- a/pcsx2/pcsx2core.vcxproj.filters +++ b/pcsx2/pcsx2core.vcxproj.filters @@ -1376,6 +1376,21 @@ System\Ps2\GS\Renderers\Direct3D12 + + System\Ps2\GS\Renderers\OpenGL + + + System\Ps2\GS\Renderers\OpenGL + + + System\Ps2\GS\Renderers\OpenGL + + + System\Ps2\GS\Renderers\OpenGL + + + System\Ps2\GS\Renderers\OpenGL + @@ -2297,6 +2312,21 @@ System\Ps2\GS\Renderers\Direct3D12 + + System\Ps2\GS\Renderers\OpenGL + + + System\Ps2\GS\Renderers\OpenGL + + + System\Ps2\GS\Renderers\OpenGL + + + System\Ps2\GS\Renderers\OpenGL + + + System\Ps2\GS\Renderers\OpenGL +