mirror of https://github.com/PCSX2/pcsx2.git
GS: Move OpenGL helpers from common to GS
This commit is contained in:
parent
083969a3d2
commit
0ed418834a
|
@ -128,22 +128,6 @@ target_sources(common PRIVATE
|
||||||
emitter/x86types.h
|
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)
|
if(USE_VULKAN)
|
||||||
target_link_libraries(common PUBLIC
|
target_link_libraries(common PUBLIC
|
||||||
Vulkan-Headers glslang
|
Vulkan-Headers glslang
|
||||||
|
@ -208,45 +192,9 @@ if(DBUS_API)
|
||||||
target_link_libraries(common PRIVATE ${DBUS_LINK_LIBRARIES})
|
target_link_libraries(common PRIVATE ${DBUS_LINK_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_OPENGL)
|
if(X11_API AND TARGET PkgConfig::XRANDR)
|
||||||
if(WIN32)
|
target_link_libraries(common PRIVATE PkgConfig::XRANDR)
|
||||||
target_sources(common PRIVATE
|
target_compile_definitions(common PRIVATE "HAS_XRANDR=1")
|
||||||
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()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_VULKAN)
|
if(USE_VULKAN)
|
||||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "common/PrecompiledHeader.h"
|
|
||||||
|
|
||||||
#include "common/Console.h"
|
|
||||||
#include "common/GL/Context.h"
|
|
||||||
#include "glad.h"
|
|
||||||
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
|
||||||
#ifdef __APPLE__
|
|
||||||
#include <stdlib.h>
|
|
||||||
#else
|
|
||||||
#include <malloc.h>
|
|
||||||
#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::FullscreenModeInfo> Context::EnumerateFullscreenModes()
|
|
||||||
{
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<GL::Context> Context::Create(const WindowInfo& wi, gsl::span<const Version> versions_to_try)
|
|
||||||
{
|
|
||||||
if (ShouldPreferESContext())
|
|
||||||
{
|
|
||||||
// move ES versions to the front
|
|
||||||
Version* new_versions_to_try = static_cast<Version*>(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<const Version>(new_versions_to_try, versions_to_try.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Context> 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<const char*>(glGetString(GL_VENDOR));
|
|
||||||
const char* gl_renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
|
|
||||||
const char* gl_version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
|
|
||||||
const char* gl_shading_language_version = reinterpret_cast<const char*>(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<const Context::Version> 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
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "common/Pcsx2Defs.h"
|
|
||||||
#include "common/WindowInfo.h"
|
|
||||||
|
|
||||||
#include <gsl/span>
|
|
||||||
#include <array>
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
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<Context> CreateSharedContext(const WindowInfo& wi) = 0;
|
|
||||||
|
|
||||||
virtual std::vector<FullscreenModeInfo> EnumerateFullscreenModes();
|
|
||||||
|
|
||||||
static std::unique_ptr<Context> Create(const WindowInfo& wi, gsl::span<const Version> versions_to_try);
|
|
||||||
|
|
||||||
static std::unique_ptr<Context> Create(const WindowInfo& wi) { return Create(wi, GetAllVersionsList()); }
|
|
||||||
|
|
||||||
static gsl::span<const Version> GetAllVersionsList();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
WindowInfo m_wi;
|
|
||||||
Version m_version = {};
|
|
||||||
};
|
|
||||||
} // namespace GL
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "common/GL/Context.h"
|
|
||||||
#include "glad.h"
|
|
||||||
|
|
||||||
#if defined(__APPLE__) && defined(__OBJC__)
|
|
||||||
#import <AppKit/AppKit.h>
|
|
||||||
#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<Context> Create(const WindowInfo& wi, gsl::span<const Version> 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<Context> CreateSharedContext(const WindowInfo& wi) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool Initialize(gsl::span<const Version> 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
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "common/GL/ContextAGL.h"
|
|
||||||
#include "common/Assertions.h"
|
|
||||||
#include "common/Console.h"
|
|
||||||
#include "glad.h"
|
|
||||||
#include <dlfcn.h>
|
|
||||||
|
|
||||||
#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<Context> ContextAGL::Create(const WindowInfo& wi, gsl::span<const Version> versions_to_try)
|
|
||||||
{
|
|
||||||
std::unique_ptr<ContextAGL> context = std::make_unique<ContextAGL>(wi);
|
|
||||||
if (!context->Initialize(versions_to_try))
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ContextAGL::Initialize(gsl::span<const Version> 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<int>(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<u32>(window_size.width * window_scale);
|
|
||||||
const u32 new_height = static_cast<u32>(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<GLint>(interval);
|
|
||||||
[m_context setValues:&gl_interval forParameter:NSOpenGLCPSwapInterval];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Context> ContextAGL::CreateSharedContext(const WindowInfo& wi)
|
|
||||||
{
|
|
||||||
std::unique_ptr<ContextAGL> context = std::make_unique<ContextAGL>(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<NSOpenGLPixelFormatAttribute>(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
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "common/PrecompiledHeader.h"
|
|
||||||
|
|
||||||
#include "common/Console.h"
|
|
||||||
#include "ContextEGL.h"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstring>
|
|
||||||
#include <optional>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace GL
|
|
||||||
{
|
|
||||||
ContextEGL::ContextEGL(const WindowInfo& wi)
|
|
||||||
: Context(wi)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ContextEGL::~ContextEGL()
|
|
||||||
{
|
|
||||||
DestroySurface();
|
|
||||||
DestroyContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Context> ContextEGL::Create(const WindowInfo& wi, gsl::span<const Version> versions_to_try)
|
|
||||||
{
|
|
||||||
std::unique_ptr<ContextEGL> context = std::make_unique<ContextEGL>(wi);
|
|
||||||
if (!context->Initialize(versions_to_try))
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ContextEGL::Initialize(gsl::span<const Version> 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<EGLNativeDisplayType>(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<void*>(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<u32>(surface_width);
|
|
||||||
m_wi.surface_height = static_cast<u32>(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<Context> ContextEGL::CreateSharedContext(const WindowInfo& wi)
|
|
||||||
{
|
|
||||||
std::unique_ptr<ContextEGL> context = std::make_unique<ContextEGL>(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<u32>(surface_width);
|
|
||||||
m_wi.surface_height = static_cast<u32>(surface_height);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.Error("eglQuerySurface() failed: %d", eglGetError());
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ContextEGL::CreatePBufferSurface()
|
|
||||||
{
|
|
||||||
const u32 width = std::max<u32>(m_wi.surface_width, 1);
|
|
||||||
const u32 height = std::max<u32>(m_wi.surface_height, 1);
|
|
||||||
|
|
||||||
EGLint attrib_list[] = {
|
|
||||||
EGL_WIDTH, static_cast<EGLint>(width),
|
|
||||||
EGL_HEIGHT, static_cast<EGLint>(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<EGLConfig> configs(static_cast<u32>(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<u32>(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<int, 8> {
|
|
||||||
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
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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<Context> Create(const WindowInfo& wi, gsl::span<const Version> 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<Context> CreateSharedContext(const WindowInfo& wi) override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual bool SetDisplay();
|
|
||||||
virtual EGLNativeWindowType GetNativeWindow(EGLConfig config);
|
|
||||||
|
|
||||||
bool Initialize(gsl::span<const Version> 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
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "common/PrecompiledHeader.h"
|
|
||||||
#include "common/Console.h"
|
|
||||||
|
|
||||||
#include "ContextEGLWayland.h"
|
|
||||||
|
|
||||||
#include <dlfcn.h>
|
|
||||||
|
|
||||||
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<Context> ContextEGLWayland::Create(const WindowInfo& wi, gsl::span<const Version> versions_to_try)
|
|
||||||
{
|
|
||||||
std::unique_ptr<ContextEGLWayland> context = std::make_unique<ContextEGLWayland>(wi);
|
|
||||||
if (!context->LoadModule() || !context->Initialize(versions_to_try))
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Context> ContextEGLWayland::CreateSharedContext(const WindowInfo& wi)
|
|
||||||
{
|
|
||||||
std::unique_ptr<ContextEGLWayland> context = std::make_unique<ContextEGLWayland>(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<wl_surface*>(m_wi.window_handle), m_wi.surface_width, m_wi.surface_height);
|
|
||||||
if (!m_wl_window)
|
|
||||||
return {};
|
|
||||||
|
|
||||||
return reinterpret_cast<EGLNativeWindowType>(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<decltype(m_wl_egl_window_create)>(dlsym(m_wl_module, "wl_egl_window_create"));
|
|
||||||
m_wl_egl_window_destroy = reinterpret_cast<decltype(m_wl_egl_window_destroy)>(dlsym(m_wl_module, "wl_egl_window_destroy"));
|
|
||||||
m_wl_egl_window_resize = reinterpret_cast<decltype(m_wl_egl_window_resize)>(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
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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<Context> Create(const WindowInfo& wi, gsl::span<const Version> versions_to_try);
|
|
||||||
|
|
||||||
std::unique_ptr<Context> 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
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "common/PrecompiledHeader.h"
|
|
||||||
|
|
||||||
#include "common/GL/ContextEGLX11.h"
|
|
||||||
|
|
||||||
#include <X11/Xlib.h>
|
|
||||||
|
|
||||||
namespace GL
|
|
||||||
{
|
|
||||||
ContextEGLX11::ContextEGLX11(const WindowInfo& wi)
|
|
||||||
: ContextEGL(wi)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
ContextEGLX11::~ContextEGLX11() = default;
|
|
||||||
|
|
||||||
std::unique_ptr<Context> ContextEGLX11::Create(const WindowInfo& wi, gsl::span<const Version> versions_to_try)
|
|
||||||
{
|
|
||||||
std::unique_ptr<ContextEGLX11> context = std::make_unique<ContextEGLX11>(wi);
|
|
||||||
if (!context->Initialize(versions_to_try))
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Context> ContextEGLX11::CreateSharedContext(const WindowInfo& wi)
|
|
||||||
{
|
|
||||||
std::unique_ptr<ContextEGLX11> context = std::make_unique<ContextEGLX11>(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<Window>(m_wi.window_handle);
|
|
||||||
}
|
|
||||||
} // namespace GL
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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<void*>(wglGetProcAddress(name));
|
|
||||||
if (addr)
|
|
||||||
return addr;
|
|
||||||
|
|
||||||
// try opengl32.dll
|
|
||||||
return reinterpret_cast<void*>(::GetProcAddress(GetModuleHandleA("opengl32.dll"), name));
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool ReloadWGL(HDC dc)
|
|
||||||
{
|
|
||||||
if (!gladLoadWGLLoader([](const char* name) -> void* { return reinterpret_cast<void*>(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<Context> ContextWGL::Create(const WindowInfo& wi, gsl::span<const Version> versions_to_try)
|
|
||||||
{
|
|
||||||
std::unique_ptr<ContextWGL> context = std::make_unique<ContextWGL>(wi);
|
|
||||||
if (!context->Initialize(versions_to_try))
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ContextWGL::Initialize(gsl::span<const Version> 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<u32>(client_rc.right - client_rc.left);
|
|
||||||
m_wi.surface_height = static_cast<u32>(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<Context> ContextWGL::CreateSharedContext(const WindowInfo& wi)
|
|
||||||
{
|
|
||||||
std::unique_ptr<ContextWGL> context = std::make_unique<ContextWGL>(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<unsigned>(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<void*>(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
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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 <windows.h>
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
namespace GL
|
|
||||||
{
|
|
||||||
class ContextWGL final : public Context
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ContextWGL(const WindowInfo& wi);
|
|
||||||
~ContextWGL() override;
|
|
||||||
|
|
||||||
static std::unique_ptr<Context> Create(const WindowInfo& wi, gsl::span<const Version> 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<Context> CreateSharedContext(const WindowInfo& wi) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
__fi HWND GetHWND() const { return static_cast<HWND>(m_wi.window_handle); }
|
|
||||||
|
|
||||||
HDC GetDCAndSetPixelFormat(HWND hwnd);
|
|
||||||
|
|
||||||
bool Initialize(gsl::span<const Version> 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<int> 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
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "common/PrecompiledHeader.h"
|
|
||||||
|
|
||||||
#include "common/GL/Program.h"
|
|
||||||
#include "common/Assertions.h"
|
|
||||||
#include "common/Console.h"
|
|
||||||
#include "common/StringUtil.h"
|
|
||||||
#include <array>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
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<const GLchar*, 1> sources = {{source.data()}};
|
|
||||||
std::array<GLint, 1> source_lengths = {{static_cast<GLint>(source.size())}};
|
|
||||||
glShaderSource(id, static_cast<GLsizei>(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<GLenum>(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<u8>* 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<size_t>(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<size_t>(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<size_t>(binary_size));
|
|
||||||
}
|
|
||||||
|
|
||||||
*out_data_format = static_cast<u32>(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<int>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "../Pcsx2Defs.h"
|
|
||||||
#include "glad.h"
|
|
||||||
#include <string_view>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
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<u8>* 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<GLint> m_uniform_locations;
|
|
||||||
};
|
|
||||||
} // namespace GL
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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<u32>(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<u32>(vertex_shader.length()));
|
|
||||||
digest.Final(vertex_hash.bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fragment_shader.empty())
|
|
||||||
{
|
|
||||||
digest.Reset();
|
|
||||||
digest.Update(fragment_shader.data(), static_cast<u32>(fragment_shader.length()));
|
|
||||||
digest.Final(fragment_hash.bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
return CacheIndexKey{vertex_hash.low, vertex_hash.high, static_cast<u32>(vertex_shader.length()),
|
|
||||||
fragment_hash.low, fragment_hash.high, static_cast<u32>(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<Program> 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<Program> 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<u8> 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<u32>(data.size()), iter->second.blob_format))
|
|
||||||
{
|
|
||||||
#ifdef PCSX2_DEVBUILD
|
|
||||||
Console.WriteLn("Time to create program from binary: %.2fms", timer.GetTimeMilliseconds());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return std::optional<Program>(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<u8>& 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<u32>(std::ftell(m_blob_file));
|
|
||||||
data.blob_size = static_cast<u32>(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<Program> 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<Program>(std::move(prog));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<Program> 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<Program>(std::move(prog));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<Program> 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<Program> 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<u8> 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<Program> 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<Program> 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<u8> 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<u32>(data.size()), iter->second.blob_format))
|
|
||||||
{
|
|
||||||
#ifdef PCSX2_DEVBUILD
|
|
||||||
Console.WriteLn("Time to create program from binary: %.2fms", timer.GetTimeMilliseconds());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return std::optional<Program>(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<Program> ShaderCache::CompileAndAddComputeProgram(
|
|
||||||
const CacheIndexKey& key, const std::string_view& glsl, const PreLinkCallback& callback)
|
|
||||||
{
|
|
||||||
#ifdef PCSX2_DEVBUILD
|
|
||||||
Common::Timer timer;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
std::optional<Program> prog = CompileComputeProgram(glsl, callback, true);
|
|
||||||
if (!prog)
|
|
||||||
return std::nullopt;
|
|
||||||
|
|
||||||
#ifdef PCSX2_DEVBUILD
|
|
||||||
const float compile_time = timer.GetTimeMilliseconds();
|
|
||||||
timer.Reset();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
std::vector<u8> 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
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "common/Pcsx2Defs.h"
|
|
||||||
#include "common/HashCombine.h"
|
|
||||||
#include "common/GL/Program.h"
|
|
||||||
#include <cstdio>
|
|
||||||
#include <functional>
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace GL
|
|
||||||
{
|
|
||||||
class ShaderCache
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using PreLinkCallback = std::function<void(Program&)>;
|
|
||||||
|
|
||||||
ShaderCache();
|
|
||||||
~ShaderCache();
|
|
||||||
|
|
||||||
bool Open(bool is_gles, std::string_view base_path, u32 version);
|
|
||||||
void Close();
|
|
||||||
|
|
||||||
std::optional<Program> 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<Program> 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<CacheIndexKey, CacheIndexData, CacheIndexEntryHasher>;
|
|
||||||
|
|
||||||
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<u8>& prog_data, u32 prog_format);
|
|
||||||
|
|
||||||
std::optional<Program> CompileProgram(const std::string_view& vertex_shader,
|
|
||||||
const std::string_view& fragment_shader, const PreLinkCallback& callback,
|
|
||||||
bool set_retrievable);
|
|
||||||
std::optional<Program> CompileAndAddProgram(const CacheIndexKey& key, const std::string_view& vertex_shader,
|
|
||||||
const std::string_view& fragment_shader, const PreLinkCallback& callback);
|
|
||||||
|
|
||||||
std::optional<Program> CompileComputeProgram(const std::string_view& glsl, const PreLinkCallback& callback, bool set_retrievable);
|
|
||||||
std::optional<Program> 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
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "StreamBuffer.h"
|
|
||||||
#include "common/Align.h"
|
|
||||||
#include "common/AlignedMalloc.h"
|
|
||||||
#include "common/Assertions.h"
|
|
||||||
#include <array>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
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<void*>(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<StreamBuffer> 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<StreamBuffer>(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<u8*>(_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<void*>(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<StreamBuffer> 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<StreamBuffer>(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<u8*>(_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<u32>(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<GLsync, NUM_SYNC_POINTS> 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<void*>(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<StreamBuffer> 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<u8*>(glMapBufferRange(target, 0, size, map_flags));
|
|
||||||
pxAssertRel(mapped_ptr, "Persistent buffer was mapped");
|
|
||||||
|
|
||||||
return std::unique_ptr<StreamBuffer>(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> StreamBuffer::Create(GLenum target, u32 size)
|
|
||||||
{
|
|
||||||
std::unique_ptr<StreamBuffer> 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<const char*>(glGetString(GL_VENDOR));
|
|
||||||
if (std::strstr(vendor, "NVIDIA"))
|
|
||||||
return detail::BufferSubDataStreamBuffer::Create(target, size);
|
|
||||||
else
|
|
||||||
return detail::BufferDataStreamBuffer::Create(target, size);
|
|
||||||
}
|
|
||||||
} // namespace GL
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "common/Pcsx2Defs.h"
|
|
||||||
#include "glad.h"
|
|
||||||
#include <memory>
|
|
||||||
#include <tuple>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
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<StreamBuffer> 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
|
|
|
@ -61,13 +61,8 @@
|
||||||
<ClCompile Include="FastJmp.cpp">
|
<ClCompile Include="FastJmp.cpp">
|
||||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="General.cpp" />
|
|
||||||
<ClCompile Include="GL\Context.cpp" />
|
|
||||||
<ClCompile Include="GL\ContextWGL.cpp" />
|
|
||||||
<ClCompile Include="GL\Program.cpp" />
|
|
||||||
<ClCompile Include="GL\ShaderCache.cpp" />
|
|
||||||
<ClCompile Include="GL\StreamBuffer.cpp" />
|
|
||||||
<ClCompile Include="FileSystem.cpp" />
|
<ClCompile Include="FileSystem.cpp" />
|
||||||
|
<ClCompile Include="General.cpp" />
|
||||||
<ClCompile Include="Image.cpp" />
|
<ClCompile Include="Image.cpp" />
|
||||||
<ClCompile Include="HTTPDownloader.cpp" />
|
<ClCompile Include="HTTPDownloader.cpp" />
|
||||||
<ClCompile Include="HTTPDownloaderCurl.cpp">
|
<ClCompile Include="HTTPDownloaderCurl.cpp">
|
||||||
|
@ -133,11 +128,6 @@
|
||||||
<ClInclude Include="Easing.h" />
|
<ClInclude Include="Easing.h" />
|
||||||
<ClInclude Include="boost_spsc_queue.hpp" />
|
<ClInclude Include="boost_spsc_queue.hpp" />
|
||||||
<ClInclude Include="FastJmp.h" />
|
<ClInclude Include="FastJmp.h" />
|
||||||
<ClInclude Include="GL\Context.h" />
|
|
||||||
<ClInclude Include="GL\ContextWGL.h" />
|
|
||||||
<ClInclude Include="GL\Program.h" />
|
|
||||||
<ClInclude Include="GL\ShaderCache.h" />
|
|
||||||
<ClInclude Include="GL\StreamBuffer.h" />
|
|
||||||
<ClInclude Include="FileSystem.h" />
|
<ClInclude Include="FileSystem.h" />
|
||||||
<ClInclude Include="HashCombine.h" />
|
<ClInclude Include="HashCombine.h" />
|
||||||
<ClInclude Include="HeapArray.h" />
|
<ClInclude Include="HeapArray.h" />
|
||||||
|
|
|
@ -94,18 +94,6 @@
|
||||||
<ClCompile Include="WindowInfo.cpp">
|
<ClCompile Include="WindowInfo.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="GL\ContextWGL.cpp">
|
|
||||||
<Filter>Source Files\GL</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="GL\Context.cpp">
|
|
||||||
<Filter>Source Files\GL</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="GL\StreamBuffer.cpp">
|
|
||||||
<Filter>Source Files\GL</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="FileSystem.cpp">
|
|
||||||
<Filter>Source Files\GL</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="StringUtil.cpp">
|
<ClCompile Include="StringUtil.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
@ -145,12 +133,6 @@
|
||||||
<ClCompile Include="MD5Digest.cpp">
|
<ClCompile Include="MD5Digest.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="GL\Program.cpp">
|
|
||||||
<Filter>Source Files\GL</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="GL\ShaderCache.cpp">
|
|
||||||
<Filter>Source Files\GL</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="Vulkan\vk_mem_alloc.cpp">
|
<ClCompile Include="Vulkan\vk_mem_alloc.cpp">
|
||||||
<Filter>Source Files\Vulkan</Filter>
|
<Filter>Source Files\Vulkan</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
@ -181,7 +163,12 @@
|
||||||
<ClCompile Include="WAVWriter.cpp">
|
<ClCompile Include="WAVWriter.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="General.cpp" />
|
<ClCompile Include="FileSystem.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="General.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="AlignedMalloc.h">
|
<ClInclude Include="AlignedMalloc.h">
|
||||||
|
@ -325,18 +312,6 @@
|
||||||
<ClInclude Include="BitCast.h">
|
<ClInclude Include="BitCast.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="GL\Context.h">
|
|
||||||
<Filter>Header Files\GL</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="GL\ContextWGL.h">
|
|
||||||
<Filter>Header Files\GL</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="GL\StreamBuffer.h">
|
|
||||||
<Filter>Header Files\GL</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="FileSystem.h">
|
|
||||||
<Filter>Header Files\GL</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="StringUtil.h">
|
<ClInclude Include="StringUtil.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
@ -388,12 +363,6 @@
|
||||||
<ClInclude Include="MD5Digest.h">
|
<ClInclude Include="MD5Digest.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="GL\Program.h">
|
|
||||||
<Filter>Header Files\GL</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="GL\ShaderCache.h">
|
|
||||||
<Filter>Header Files\GL</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="ZipHelpers.h">
|
<ClInclude Include="ZipHelpers.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
@ -439,6 +408,9 @@
|
||||||
<ClInclude Include="WAVWriter.h">
|
<ClInclude Include="WAVWriter.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="FileSystem.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Filter Include="Source Files">
|
<Filter Include="Source Files">
|
||||||
|
@ -447,12 +419,6 @@
|
||||||
<Filter Include="Header Files">
|
<Filter Include="Header Files">
|
||||||
<UniqueIdentifier>{eef579af-e6a8-4d3b-a88e-c0e4cad9e5d8}</UniqueIdentifier>
|
<UniqueIdentifier>{eef579af-e6a8-4d3b-a88e-c0e4cad9e5d8}</UniqueIdentifier>
|
||||||
</Filter>
|
</Filter>
|
||||||
<Filter Include="Header Files\GL">
|
|
||||||
<UniqueIdentifier>{92d1bb2d-d172-4356-b9f4-a8fcc8e7fcce}</UniqueIdentifier>
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="Source Files\GL">
|
|
||||||
<UniqueIdentifier>{5e76b340-cb0e-4946-83ec-7d72e397cac8}</UniqueIdentifier>
|
|
||||||
</Filter>
|
|
||||||
<Filter Include="Source Files\Vulkan">
|
<Filter Include="Source Files\Vulkan">
|
||||||
<UniqueIdentifier>{94154238-8b02-44f8-a7b8-3612e7bfa33c}</UniqueIdentifier>
|
<UniqueIdentifier>{94154238-8b02-44f8-a7b8-3612e7bfa33c}</UniqueIdentifier>
|
||||||
</Filter>
|
</Filter>
|
||||||
|
|
|
@ -589,18 +589,51 @@ set(pcsx2GSHeaders
|
||||||
|
|
||||||
if(USE_OPENGL)
|
if(USE_OPENGL)
|
||||||
list(APPEND pcsx2GSSources
|
list(APPEND pcsx2GSSources
|
||||||
|
GS/Renderers/OpenGL/GLContext.cpp
|
||||||
GS/Renderers/OpenGL/GLLoader.cpp
|
GS/Renderers/OpenGL/GLLoader.cpp
|
||||||
|
GS/Renderers/OpenGL/GLProgram.cpp
|
||||||
|
GS/Renderers/OpenGL/GLShaderCache.cpp
|
||||||
GS/Renderers/OpenGL/GLState.cpp
|
GS/Renderers/OpenGL/GLState.cpp
|
||||||
|
GS/Renderers/OpenGL/GLStreamBuffer.cpp
|
||||||
GS/Renderers/OpenGL/GSDeviceOGL.cpp
|
GS/Renderers/OpenGL/GSDeviceOGL.cpp
|
||||||
GS/Renderers/OpenGL/GSTextureOGL.cpp
|
GS/Renderers/OpenGL/GSTextureOGL.cpp
|
||||||
)
|
)
|
||||||
list(APPEND pcsx2GSHeaders
|
list(APPEND pcsx2GSHeaders
|
||||||
|
GS/Renderers/OpenGL/GLContext.h
|
||||||
GS/Renderers/OpenGL/GLLoader.h
|
GS/Renderers/OpenGL/GLLoader.h
|
||||||
|
GS/Renderers/OpenGL/GLProgram.h
|
||||||
|
GS/Renderers/OpenGL/GLShaderCache.h
|
||||||
GS/Renderers/OpenGL/GLState.h
|
GS/Renderers/OpenGL/GLState.h
|
||||||
|
GS/Renderers/OpenGL/GLStreamBuffer.h
|
||||||
GS/Renderers/OpenGL/GSDeviceOGL.h
|
GS/Renderers/OpenGL/GSDeviceOGL.h
|
||||||
GS/Renderers/OpenGL/GSTextureOGL.h
|
GS/Renderers/OpenGL/GSTextureOGL.h
|
||||||
)
|
)
|
||||||
target_link_libraries(PCSX2_FLAGS INTERFACE glad)
|
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()
|
endif()
|
||||||
|
|
||||||
if(USE_VULKAN)
|
if(USE_VULKAN)
|
||||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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> 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<GLContext> 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;
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/Pcsx2Defs.h"
|
||||||
|
#include "common/WindowInfo.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<GLContext> CreateSharedContext(const WindowInfo& wi) = 0;
|
||||||
|
|
||||||
|
static std::unique_ptr<GLContext> Create(const WindowInfo& wi);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
WindowInfo m_wi;
|
||||||
|
Version m_version = {};
|
||||||
|
};
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GS/Renderers/OpenGL/GLContext.h"
|
||||||
|
|
||||||
|
#include "glad.h"
|
||||||
|
|
||||||
|
#include <gsl/span>
|
||||||
|
|
||||||
|
#if defined(__APPLE__) && defined(__OBJC__)
|
||||||
|
#import <AppKit/AppKit.h>
|
||||||
|
#else
|
||||||
|
struct NSView;
|
||||||
|
struct NSOpenGLContext;
|
||||||
|
struct NSOpenGLPixelFormat;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class GLContextAGL final : public GLContext
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GLContextAGL(const WindowInfo& wi);
|
||||||
|
~GLContextAGL() override;
|
||||||
|
|
||||||
|
static std::unique_ptr<GLContext> Create(const WindowInfo& wi, gsl::span<const Version> 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<GLContext> CreateSharedContext(const WindowInfo& wi) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool Initialize(gsl::span<const Version> 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;
|
||||||
|
};
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "PrecompiledHeader.h"
|
||||||
|
|
||||||
|
#include "GS/Renderers/OpenGL/GLContextAGL.h"
|
||||||
|
|
||||||
|
#include "common/Assertions.h"
|
||||||
|
#include "common/Console.h"
|
||||||
|
|
||||||
|
#include "glad.h"
|
||||||
|
|
||||||
|
#include <dlfcn.h>
|
||||||
|
|
||||||
|
#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<Context> GLContextAGL::Create(const WindowInfo& wi, gsl::span<const Version> versions_to_try)
|
||||||
|
{
|
||||||
|
std::unique_ptr<GLContextAGL> context = std::make_unique<GLContextAGL>(wi);
|
||||||
|
if (!context->Initialize(versions_to_try))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLContextAGL::Initialize(gsl::span<const Version> 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<int>(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<u32>(window_size.width * window_scale);
|
||||||
|
const u32 new_height = static_cast<u32>(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<GLint>(interval);
|
||||||
|
[m_context setValues:&gl_interval forParameter:NSOpenGLCPSwapInterval];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Context> GLContextAGL::CreateSharedContext(const WindowInfo& wi)
|
||||||
|
{
|
||||||
|
std::unique_ptr<GLContextAGL> context = std::make_unique<GLContextAGL>(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<NSOpenGLPixelFormatAttribute>(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;
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "PrecompiledHeader.h"
|
||||||
|
|
||||||
|
#include "GS/Renderers/OpenGL/GLContextEGL.h"
|
||||||
|
|
||||||
|
#include "common/Console.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
GLContextEGL::GLContextEGL(const WindowInfo& wi)
|
||||||
|
: GLContext(wi)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
GLContextEGL::~GLContextEGL()
|
||||||
|
{
|
||||||
|
DestroySurface();
|
||||||
|
DestroyContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<GLContext> GLContextEGL::Create(const WindowInfo& wi, gsl::span<const Version> versions_to_try)
|
||||||
|
{
|
||||||
|
std::unique_ptr<GLContextEGL> context = std::make_unique<GLContextEGL>(wi);
|
||||||
|
if (!context->Initialize(versions_to_try))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLContextEGL::Initialize(gsl::span<const Version> 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<EGLNativeDisplayType>(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<void*>(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<u32>(surface_width);
|
||||||
|
m_wi.surface_height = static_cast<u32>(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<GLContext> GLContextEGL::CreateSharedContext(const WindowInfo& wi)
|
||||||
|
{
|
||||||
|
std::unique_ptr<GLContextEGL> context = std::make_unique<GLContextEGL>(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<u32>(surface_width);
|
||||||
|
m_wi.surface_height = static_cast<u32>(surface_height);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.Error("eglQuerySurface() failed: %d", eglGetError());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLContextEGL::CreatePBufferSurface()
|
||||||
|
{
|
||||||
|
const u32 width = std::max<u32>(m_wi.surface_width, 1);
|
||||||
|
const u32 height = std::max<u32>(m_wi.surface_height, 1);
|
||||||
|
|
||||||
|
EGLint attrib_list[] = {
|
||||||
|
EGL_WIDTH,
|
||||||
|
static_cast<EGLint>(width),
|
||||||
|
EGL_HEIGHT,
|
||||||
|
static_cast<EGLint>(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<EGLConfig> configs(static_cast<u32>(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<u32>(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<int, 5> 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;
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GS/Renderers/OpenGL/GLContext.h"
|
||||||
|
|
||||||
|
#include "glad_egl.h"
|
||||||
|
|
||||||
|
#include <gsl/span>
|
||||||
|
|
||||||
|
class GLContextEGL : public GLContext
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GLContextEGL(const WindowInfo& wi);
|
||||||
|
~GLContextEGL() override;
|
||||||
|
|
||||||
|
static std::unique_ptr<GLContext> Create(const WindowInfo& wi, gsl::span<const Version> 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<GLContext> CreateSharedContext(const WindowInfo& wi) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual bool SetDisplay();
|
||||||
|
virtual EGLNativeWindowType GetNativeWindow(EGLConfig config);
|
||||||
|
|
||||||
|
bool Initialize(gsl::span<const Version> 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;
|
||||||
|
};
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "PrecompiledHeader.h"
|
||||||
|
|
||||||
|
#include "GS/Renderers/OpenGL/GLContextEGLWayland.h"
|
||||||
|
|
||||||
|
#include "common/Console.h"
|
||||||
|
|
||||||
|
#include <dlfcn.h>
|
||||||
|
|
||||||
|
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<GLContext> GLContextEGLWayland::Create(const WindowInfo& wi, gsl::span<const Version> versions_to_try)
|
||||||
|
{
|
||||||
|
std::unique_ptr<GLContextEGLWayland> context = std::make_unique<GLContextEGLWayland>(wi);
|
||||||
|
if (!context->LoadModule() || !context->Initialize(versions_to_try))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<GLContext> GLContextEGLWayland::CreateSharedContext(const WindowInfo& wi)
|
||||||
|
{
|
||||||
|
std::unique_ptr<GLContextEGLWayland> context = std::make_unique<GLContextEGLWayland>(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<wl_surface*>(m_wi.window_handle), m_wi.surface_width, m_wi.surface_height);
|
||||||
|
if (!m_wl_window)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return reinterpret_cast<EGLNativeWindowType>(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<decltype(m_wl_egl_window_create)>(dlsym(m_wl_module, "wl_egl_window_create"));
|
||||||
|
m_wl_egl_window_destroy =
|
||||||
|
reinterpret_cast<decltype(m_wl_egl_window_destroy)>(dlsym(m_wl_module, "wl_egl_window_destroy"));
|
||||||
|
m_wl_egl_window_resize =
|
||||||
|
reinterpret_cast<decltype(m_wl_egl_window_resize)>(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;
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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<GLContext> Create(const WindowInfo& wi, gsl::span<const Version> versions_to_try);
|
||||||
|
|
||||||
|
std::unique_ptr<GLContext> 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);
|
||||||
|
};
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "PrecompiledHeader.h"
|
||||||
|
|
||||||
|
#include "GS/Renderers/OpenGL/GLContextEGLX11.h"
|
||||||
|
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
|
||||||
|
GLContextEGLX11::GLContextEGLX11(const WindowInfo& wi)
|
||||||
|
: GLContextEGL(wi)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
GLContextEGLX11::~GLContextEGLX11() = default;
|
||||||
|
|
||||||
|
std::unique_ptr<GLContext> GLContextEGLX11::Create(const WindowInfo& wi, gsl::span<const Version> versions_to_try)
|
||||||
|
{
|
||||||
|
std::unique_ptr<GLContextEGLX11> context = std::make_unique<GLContextEGLX11>(wi);
|
||||||
|
if (!context->Initialize(versions_to_try))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<GLContext> GLContextEGLX11::CreateSharedContext(const WindowInfo& wi)
|
||||||
|
{
|
||||||
|
std::unique_ptr<GLContextEGLX11> context = std::make_unique<GLContextEGLX11>(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<Window>(m_wi.window_handle);
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/* PCSX2 - PS2 Emulator for PCs
|
/* 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
|
* 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-
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||||
|
@ -15,22 +15,19 @@
|
||||||
|
|
||||||
#pragma once
|
#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:
|
||||||
{
|
GLContextEGLX11(const WindowInfo& wi);
|
||||||
public:
|
~GLContextEGLX11() override;
|
||||||
ContextEGLX11(const WindowInfo& wi);
|
|
||||||
~ContextEGLX11() override;
|
|
||||||
|
|
||||||
static std::unique_ptr<Context> Create(const WindowInfo& wi, gsl::span<const Version> versions_to_try);
|
static std::unique_ptr<GLContext> Create(const WindowInfo& wi, gsl::span<const Version> versions_to_try);
|
||||||
|
|
||||||
std::unique_ptr<Context> CreateSharedContext(const WindowInfo& wi) override;
|
std::unique_ptr<GLContext> CreateSharedContext(const WindowInfo& wi) override;
|
||||||
void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override;
|
void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
EGLNativeWindowType GetNativeWindow(EGLConfig config) override;
|
EGLNativeWindowType GetNativeWindow(EGLConfig config) override;
|
||||||
};
|
};
|
||||||
} // namespace GL
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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<void*>(wglGetProcAddress(name));
|
||||||
|
if (addr)
|
||||||
|
return addr;
|
||||||
|
|
||||||
|
// try opengl32.dll
|
||||||
|
return reinterpret_cast<void*>(::GetProcAddress(GetModuleHandleA("opengl32.dll"), name));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ReloadWGL(HDC dc)
|
||||||
|
{
|
||||||
|
if (!gladLoadWGLLoader(
|
||||||
|
[](const char* name) -> void* { return reinterpret_cast<void*>(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<GLContext> GLContextWGL::Create(const WindowInfo& wi, gsl::span<const Version> versions_to_try)
|
||||||
|
{
|
||||||
|
std::unique_ptr<GLContextWGL> context = std::make_unique<GLContextWGL>(wi);
|
||||||
|
if (!context->Initialize(versions_to_try))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLContextWGL::Initialize(gsl::span<const Version> 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<u32>(client_rc.right - client_rc.left);
|
||||||
|
m_wi.surface_height = static_cast<u32>(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<GLContext> GLContextWGL::CreateSharedContext(const WindowInfo& wi)
|
||||||
|
{
|
||||||
|
std::unique_ptr<GLContextWGL> context = std::make_unique<GLContextWGL>(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<unsigned>(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<void*>(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;
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GS/Renderers/OpenGL/GLContext.h"
|
||||||
|
|
||||||
|
#include "common/RedtapeWindows.h"
|
||||||
|
|
||||||
|
#include "glad_wgl.h"
|
||||||
|
#include "glad.h"
|
||||||
|
|
||||||
|
#include <gsl/span>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
class GLContextWGL final : public GLContext
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GLContextWGL(const WindowInfo& wi);
|
||||||
|
~GLContextWGL() override;
|
||||||
|
|
||||||
|
static std::unique_ptr<GLContext> Create(const WindowInfo& wi, gsl::span<const Version> 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<GLContext> CreateSharedContext(const WindowInfo& wi) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
__fi HWND GetHWND() const { return static_cast<HWND>(m_wi.window_handle); }
|
||||||
|
|
||||||
|
HDC GetDCAndSetPixelFormat(HWND hwnd);
|
||||||
|
|
||||||
|
bool Initialize(gsl::span<const Version> 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<int> 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 = {};
|
||||||
|
};
|
|
@ -19,6 +19,8 @@
|
||||||
#include "Host.h"
|
#include "Host.h"
|
||||||
#include "HostSettings.h"
|
#include "HostSettings.h"
|
||||||
|
|
||||||
|
#include "glad.h"
|
||||||
|
|
||||||
namespace ReplaceGL
|
namespace ReplaceGL
|
||||||
{
|
{
|
||||||
void APIENTRY ScissorIndexed(GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height)
|
void APIENTRY ScissorIndexed(GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height)
|
||||||
|
|
|
@ -15,13 +15,6 @@
|
||||||
|
|
||||||
#pragma once
|
#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
|
namespace GLLoader
|
||||||
{
|
{
|
||||||
bool check_gl_requirements();
|
bool check_gl_requirements();
|
||||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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 <array>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
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<const GLchar*, 1> sources = {{source.data()}};
|
||||||
|
std::array<GLint, 1> source_lengths = {{static_cast<GLint>(source.size())}};
|
||||||
|
glShaderSource(id, static_cast<GLsizei>(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<GLenum>(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<u8>* 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<size_t>(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<size_t>(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<size_t>(binary_size));
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_data_format = static_cast<u32>(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<int>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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;
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/Pcsx2Defs.h"
|
||||||
|
|
||||||
|
#include "glad.h"
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<u8>* 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<GLint> m_uniform_locations;
|
||||||
|
};
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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<u32>(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<u32>(vertex_shader.length()));
|
||||||
|
digest.Final(vertex_hash.bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fragment_shader.empty())
|
||||||
|
{
|
||||||
|
digest.Reset();
|
||||||
|
digest.Update(fragment_shader.data(), static_cast<u32>(fragment_shader.length()));
|
||||||
|
digest.Final(fragment_hash.bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CacheIndexKey{vertex_hash.low, vertex_hash.high, static_cast<u32>(vertex_shader.length()), fragment_hash.low,
|
||||||
|
fragment_hash.high, static_cast<u32>(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<GLProgram> 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<GLProgram> 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<u8> 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<u32>(data.size()), iter->second.blob_format))
|
||||||
|
{
|
||||||
|
#ifdef PCSX2_DEVBUILD
|
||||||
|
Console.WriteLn("Time to create program from binary: %.2fms", timer.GetTimeMilliseconds());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return std::optional<GLProgram>(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<u8>& 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<u32>(std::ftell(m_blob_file));
|
||||||
|
data.blob_size = static_cast<u32>(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<GLProgram> 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<GLProgram>(std::move(prog));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<GLProgram> 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<GLProgram>(std::move(prog));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<GLProgram> 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<GLProgram> 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<u8> 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<GLProgram> 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<GLProgram> 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<u8> 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<u32>(data.size()), iter->second.blob_format))
|
||||||
|
{
|
||||||
|
#ifdef PCSX2_DEVBUILD
|
||||||
|
Console.WriteLn("Time to create program from binary: %.2fms", timer.GetTimeMilliseconds());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return std::optional<GLProgram>(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<GLProgram> GLShaderCache::CompileAndAddComputeProgram(
|
||||||
|
const CacheIndexKey& key, const std::string_view& glsl, const PreLinkCallback& callback)
|
||||||
|
{
|
||||||
|
#ifdef PCSX2_DEVBUILD
|
||||||
|
Common::Timer timer;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::optional<GLProgram> prog = CompileComputeProgram(glsl, callback, true);
|
||||||
|
if (!prog)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
#ifdef PCSX2_DEVBUILD
|
||||||
|
const float compile_time = timer.GetTimeMilliseconds();
|
||||||
|
timer.Reset();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::vector<u8> 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;
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "GS/Renderers/OpenGL/GLProgram.h"
|
||||||
|
|
||||||
|
#include "common/HashCombine.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <functional>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class GLShaderCache
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using PreLinkCallback = std::function<void(GLProgram&)>;
|
||||||
|
|
||||||
|
GLShaderCache();
|
||||||
|
~GLShaderCache();
|
||||||
|
|
||||||
|
bool Open();
|
||||||
|
void Close();
|
||||||
|
|
||||||
|
std::optional<GLProgram> 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<GLProgram> 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<CacheIndexKey, CacheIndexData, CacheIndexEntryHasher>;
|
||||||
|
|
||||||
|
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<u8>& prog_data, u32 prog_format);
|
||||||
|
|
||||||
|
std::optional<GLProgram> CompileProgram(const std::string_view& vertex_shader,
|
||||||
|
const std::string_view& fragment_shader, const PreLinkCallback& callback, bool set_retrievable);
|
||||||
|
std::optional<GLProgram> CompileAndAddProgram(const CacheIndexKey& key, const std::string_view& vertex_shader,
|
||||||
|
const std::string_view& fragment_shader, const PreLinkCallback& callback);
|
||||||
|
|
||||||
|
std::optional<GLProgram> CompileComputeProgram(
|
||||||
|
const std::string_view& glsl, const PreLinkCallback& callback, bool set_retrievable);
|
||||||
|
std::optional<GLProgram> 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;
|
||||||
|
};
|
|
@ -18,6 +18,8 @@
|
||||||
#include "GS/Renderers/OpenGL/GLLoader.h"
|
#include "GS/Renderers/OpenGL/GLLoader.h"
|
||||||
#include "GS/GSVector.h"
|
#include "GS/GSVector.h"
|
||||||
|
|
||||||
|
#include "glad.h"
|
||||||
|
|
||||||
class GSTextureOGL;
|
class GSTextureOGL;
|
||||||
|
|
||||||
namespace GLState
|
namespace GLState
|
||||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "PrecompiledHeader.h"
|
||||||
|
|
||||||
|
#include "GS/Renderers/OpenGL/GLStreamBuffer.h"
|
||||||
|
|
||||||
|
#include "common/Align.h"
|
||||||
|
#include "common/AlignedMalloc.h"
|
||||||
|
#include "common/Assertions.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
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<void*>(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<GLStreamBuffer> 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<GLStreamBuffer>(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<u8*>(_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<void*>(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<GLStreamBuffer> 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<GLStreamBuffer>(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<u8*>(_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<u32>(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<GLsync, NUM_SYNC_POINTS> 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<void*>(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<GLStreamBuffer> 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<u8*>(glMapBufferRange(target, 0, size, map_flags));
|
||||||
|
pxAssertRel(mapped_ptr, "Persistent buffer was mapped");
|
||||||
|
|
||||||
|
return std::unique_ptr<GLStreamBuffer>(
|
||||||
|
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> GLStreamBuffer::Create(GLenum target, u32 size)
|
||||||
|
{
|
||||||
|
std::unique_ptr<GLStreamBuffer> 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<const char*>(glGetString(GL_VENDOR));
|
||||||
|
if (std::strstr(vendor, "NVIDIA"))
|
||||||
|
return BufferSubDataStreamBuffer::Create(target, size);
|
||||||
|
else
|
||||||
|
return BufferDataStreamBuffer::Create(target, size);
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/Pcsx2Defs.h"
|
||||||
|
|
||||||
|
#include "glad.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
/// 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<GLStreamBuffer> Create(GLenum target, u32 size);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
GLStreamBuffer(GLenum target, GLuint buffer_id, u32 size);
|
||||||
|
|
||||||
|
GLenum m_target;
|
||||||
|
GLuint m_buffer_id;
|
||||||
|
u32 m_size;
|
||||||
|
};
|
|
@ -15,13 +15,13 @@
|
||||||
|
|
||||||
#include "PrecompiledHeader.h"
|
#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/GSState.h"
|
||||||
#include "GS/GSGL.h"
|
#include "GS/GSGL.h"
|
||||||
#include "GS/GSUtil.h"
|
#include "GS/GSUtil.h"
|
||||||
#include "GS/Renderers/OpenGL/GSDeviceOGL.h"
|
|
||||||
#include "GS/Renderers/OpenGL/GLState.h"
|
|
||||||
#include "Host.h"
|
#include "Host.h"
|
||||||
#include "ShaderCacheVersion.h"
|
|
||||||
|
|
||||||
#include "common/StringUtil.h"
|
#include "common/StringUtil.h"
|
||||||
|
|
||||||
|
@ -32,8 +32,6 @@
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
//#define ONLY_LINES
|
|
||||||
|
|
||||||
static constexpr u32 g_vs_cb_index = 1;
|
static constexpr u32 g_vs_cb_index = 1;
|
||||||
static constexpr u32 g_ps_cb_index = 0;
|
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 FRAGMENT_UNIFORM_BUFFER_SIZE = 8 * 1024 * 1024;
|
||||||
static constexpr u32 TEXTURE_UPLOAD_BUFFER_SIZE = 128 * 1024 * 1024;
|
static constexpr u32 TEXTURE_UPLOAD_BUFFER_SIZE = 128 * 1024 * 1024;
|
||||||
|
|
||||||
static std::unique_ptr<GL::StreamBuffer> s_texture_upload_buffer;
|
static std::unique_ptr<GLStreamBuffer> s_texture_upload_buffer;
|
||||||
|
|
||||||
GSDeviceOGL::GSDeviceOGL() = default;
|
GSDeviceOGL::GSDeviceOGL() = default;
|
||||||
|
|
||||||
|
@ -94,12 +92,7 @@ bool GSDeviceOGL::Create()
|
||||||
if (!AcquireWindow(true))
|
if (!AcquireWindow(true))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// We need at least GL3.3.
|
m_gl_context = GLContext::Create(m_window_info);
|
||||||
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);
|
|
||||||
if (!m_gl_context)
|
if (!m_gl_context)
|
||||||
{
|
{
|
||||||
Console.Error("Failed to create any GL context");
|
Console.Error("Failed to create any GL context");
|
||||||
|
@ -124,7 +117,7 @@ bool GSDeviceOGL::Create()
|
||||||
|
|
||||||
if (!GSConfig.DisableShaderCache)
|
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.");
|
Console.Warning("Shader cache failed to open.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -240,10 +233,10 @@ bool GSDeviceOGL::Create()
|
||||||
glGenVertexArrays(1, &m_vao);
|
glGenVertexArrays(1, &m_vao);
|
||||||
IASetVAO(m_vao);
|
IASetVAO(m_vao);
|
||||||
|
|
||||||
m_vertex_stream_buffer = GL::StreamBuffer::Create(GL_ARRAY_BUFFER, VERTEX_BUFFER_SIZE);
|
m_vertex_stream_buffer = GLStreamBuffer::Create(GL_ARRAY_BUFFER, VERTEX_BUFFER_SIZE);
|
||||||
m_index_stream_buffer = GL::StreamBuffer::Create(GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE);
|
m_index_stream_buffer = GLStreamBuffer::Create(GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE);
|
||||||
m_vertex_uniform_stream_buffer = GL::StreamBuffer::Create(GL_UNIFORM_BUFFER, VERTEX_UNIFORM_BUFFER_SIZE);
|
m_vertex_uniform_stream_buffer = GLStreamBuffer::Create(GL_UNIFORM_BUFFER, VERTEX_UNIFORM_BUFFER_SIZE);
|
||||||
m_fragment_uniform_stream_buffer = GL::StreamBuffer::Create(GL_UNIFORM_BUFFER, FRAGMENT_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);
|
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)
|
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)
|
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)
|
if (s_texture_upload_buffer)
|
||||||
{
|
{
|
||||||
// Don't keep it bound, we'll re-bind when we need it.
|
// 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));
|
m_om_dss[key] = CreateDepthStencil(OMDepthStencilSelector(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
GL::Program::ResetLastProgram();
|
GLProgram::ResetLastProgram();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -616,24 +609,24 @@ void GSDeviceOGL::DestroyResources()
|
||||||
|
|
||||||
m_shadeboost.ps.Destroy();
|
m_shadeboost.ps.Destroy();
|
||||||
|
|
||||||
for (GL::Program& prog : m_date.primid_ps)
|
for (GLProgram& prog : m_date.primid_ps)
|
||||||
prog.Destroy();
|
prog.Destroy();
|
||||||
delete m_date.dss;
|
delete m_date.dss;
|
||||||
|
|
||||||
m_fxaa.ps.Destroy();
|
m_fxaa.ps.Destroy();
|
||||||
|
|
||||||
for (GL::Program& prog : m_present)
|
for (GLProgram& prog : m_present)
|
||||||
prog.Destroy();
|
prog.Destroy();
|
||||||
|
|
||||||
for (GL::Program& prog : m_convert.ps)
|
for (GLProgram& prog : m_convert.ps)
|
||||||
prog.Destroy();
|
prog.Destroy();
|
||||||
delete m_convert.dss;
|
delete m_convert.dss;
|
||||||
delete m_convert.dss_write;
|
delete m_convert.dss_write;
|
||||||
|
|
||||||
for (GL::Program& prog : m_interlace.ps)
|
for (GLProgram& prog : m_interlace.ps)
|
||||||
prog.Destroy();
|
prog.Destroy();
|
||||||
|
|
||||||
for (GL::Program& prog : m_merge_obj.ps)
|
for (GLProgram& prog : m_merge_obj.ps)
|
||||||
prog.Destroy();
|
prog.Destroy();
|
||||||
|
|
||||||
m_fragment_uniform_stream_buffer.reset();
|
m_fragment_uniform_stream_buffer.reset();
|
||||||
|
@ -718,8 +711,9 @@ std::string GSDeviceOGL::GetDriverInfo() const
|
||||||
const char* gl_vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
|
const char* gl_vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
|
||||||
const char* gl_renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
|
const char* gl_renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
|
||||||
const char* gl_version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
|
const char* gl_version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
|
||||||
return StringUtil::StdStringFromFormat(
|
const char* gl_shading_language_version = reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION));
|
||||||
"%s Context:\n%s\n%s %s", m_gl_context->IsGLES() ? "OpenGL ES" : "OpenGL", gl_version, gl_vendor, gl_renderer);
|
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)
|
GSDevice::PresentResult GSDeviceOGL::BeginPresent(bool frame_skip)
|
||||||
|
@ -757,10 +751,7 @@ void GSDeviceOGL::EndPresent()
|
||||||
|
|
||||||
void GSDeviceOGL::CreateTimestampQueries()
|
void GSDeviceOGL::CreateTimestampQueries()
|
||||||
{
|
{
|
||||||
const bool gles = m_gl_context->IsGLES();
|
glGenQueries(static_cast<u32>(m_timestamp_queries.size()), m_timestamp_queries.data());
|
||||||
const auto GenQueries = gles ? glGenQueriesEXT : glGenQueries;
|
|
||||||
|
|
||||||
GenQueries(static_cast<u32>(m_timestamp_queries.size()), m_timestamp_queries.data());
|
|
||||||
KickTimestampQuery();
|
KickTimestampQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -769,16 +760,10 @@ void GSDeviceOGL::DestroyTimestampQueries()
|
||||||
if (m_timestamp_queries[0] == 0)
|
if (m_timestamp_queries[0] == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const bool gles = m_gl_context->IsGLES();
|
|
||||||
const auto DeleteQueries = gles ? glDeleteQueriesEXT : glDeleteQueries;
|
|
||||||
|
|
||||||
if (m_timestamp_query_started)
|
if (m_timestamp_query_started)
|
||||||
{
|
glEndQuery(GL_TIME_ELAPSED);
|
||||||
const auto EndQuery = gles ? glEndQueryEXT : glEndQuery;
|
|
||||||
EndQuery(GL_TIME_ELAPSED);
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteQueries(static_cast<u32>(m_timestamp_queries.size()), m_timestamp_queries.data());
|
glDeleteQueries(static_cast<u32>(m_timestamp_queries.size()), m_timestamp_queries.data());
|
||||||
m_timestamp_queries.fill(0);
|
m_timestamp_queries.fill(0);
|
||||||
m_read_timestamp_query = 0;
|
m_read_timestamp_query = 0;
|
||||||
m_write_timestamp_query = 0;
|
m_write_timestamp_query = 0;
|
||||||
|
@ -788,38 +773,16 @@ void GSDeviceOGL::DestroyTimestampQueries()
|
||||||
|
|
||||||
void GSDeviceOGL::PopTimestampQuery()
|
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)
|
while (m_waiting_timestamp_queries > 0)
|
||||||
{
|
{
|
||||||
const auto GetQueryObjectiv = gles ? glGetQueryObjectivEXT : glGetQueryObjectiv;
|
|
||||||
const auto GetQueryObjectui64v = gles ? glGetQueryObjectui64vEXT : glGetQueryObjectui64v;
|
|
||||||
|
|
||||||
GLint available = 0;
|
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)
|
if (!available)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
u64 result = 0;
|
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<float>(static_cast<double>(result) / 1000000.0);
|
m_accumulated_gpu_time += static_cast<float>(static_cast<double>(result) / 1000000.0);
|
||||||
m_read_timestamp_query = (m_read_timestamp_query + 1) % NUM_TIMESTAMP_QUERIES;
|
m_read_timestamp_query = (m_read_timestamp_query + 1) % NUM_TIMESTAMP_QUERIES;
|
||||||
m_waiting_timestamp_queries--;
|
m_waiting_timestamp_queries--;
|
||||||
|
@ -827,8 +790,7 @@ void GSDeviceOGL::PopTimestampQuery()
|
||||||
|
|
||||||
if (m_timestamp_query_started)
|
if (m_timestamp_query_started)
|
||||||
{
|
{
|
||||||
const auto EndQuery = gles ? glEndQueryEXT : glEndQuery;
|
glEndQuery(GL_TIME_ELAPSED);
|
||||||
EndQuery(GL_TIME_ELAPSED);
|
|
||||||
|
|
||||||
m_write_timestamp_query = (m_write_timestamp_query + 1) % NUM_TIMESTAMP_QUERIES;
|
m_write_timestamp_query = (m_write_timestamp_query + 1) % NUM_TIMESTAMP_QUERIES;
|
||||||
m_timestamp_query_started = false;
|
m_timestamp_query_started = false;
|
||||||
|
@ -841,10 +803,7 @@ void GSDeviceOGL::KickTimestampQuery()
|
||||||
if (m_timestamp_query_started || m_waiting_timestamp_queries == NUM_TIMESTAMP_QUERIES)
|
if (m_timestamp_query_started || m_waiting_timestamp_queries == NUM_TIMESTAMP_QUERIES)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const bool gles = m_gl_context->IsGLES();
|
glBeginQuery(GL_TIME_ELAPSED, m_timestamp_queries[m_write_timestamp_query]);
|
||||||
const auto BeginQuery = gles ? glBeginQueryEXT : glBeginQuery;
|
|
||||||
|
|
||||||
BeginQuery(GL_TIME_ELAPSED, m_timestamp_queries[m_write_timestamp_query]);
|
|
||||||
m_timestamp_query_started = true;
|
m_timestamp_query_started = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -853,9 +812,6 @@ bool GSDeviceOGL::SetGPUTimingEnabled(bool enabled)
|
||||||
if (m_gpu_timing_enabled == enabled)
|
if (m_gpu_timing_enabled == enabled)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (enabled && m_gl_context->IsGLES() && !GLAD_GL_EXT_disjoint_timer_query)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
m_gpu_timing_enabled = enabled;
|
m_gpu_timing_enabled = enabled;
|
||||||
if (m_gpu_timing_enabled)
|
if (m_gpu_timing_enabled)
|
||||||
CreateTimestampQueries();
|
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);
|
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);
|
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);
|
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);
|
ASSERT(sTex);
|
||||||
|
|
||||||
|
@ -1413,7 +1369,7 @@ void GSDeviceOGL::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture
|
||||||
cb.SetTarget(dRect, ds);
|
cb.SetTarget(dRect, ds);
|
||||||
cb.SetTime(shaderTime);
|
cb.SetTime(shaderTime);
|
||||||
|
|
||||||
GL::Program& prog = m_present[static_cast<int>(shader)];
|
GLProgram& prog = m_present[static_cast<int>(shader)];
|
||||||
prog.Bind();
|
prog.Bind();
|
||||||
prog.Uniform4fv(0, cb.SourceRect.F32);
|
prog.Uniform4fv(0, cb.SourceRect.F32);
|
||||||
prog.Uniform4fv(1, cb.TargetRect.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)
|
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;
|
const ShaderConvert shader = (dSize == 16) ? ShaderConvert::CLUT_4 : ShaderConvert::CLUT_8;
|
||||||
GL::Program& prog = m_convert.ps[static_cast<int>(shader)];
|
GLProgram& prog = m_convert.ps[static_cast<int>(shader)];
|
||||||
prog.Bind();
|
prog.Bind();
|
||||||
prog.Uniform3ui(0, offsetX, offsetY, dOffset);
|
prog.Uniform3ui(0, offsetX, offsetY, dOffset);
|
||||||
prog.Uniform1f(1, sScale);
|
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)
|
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;
|
const ShaderConvert shader = ShaderConvert::RGBA_TO_8I;
|
||||||
GL::Program& prog = m_convert.ps[static_cast<int>(shader)];
|
GLProgram& prog = m_convert.ps[static_cast<int>(shader)];
|
||||||
prog.Bind();
|
prog.Bind();
|
||||||
prog.Uniform1ui(0, SBW);
|
prog.Uniform1ui(0, SBW);
|
||||||
prog.Uniform1ui(1, DBW);
|
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));
|
const std::string ps(GetShaderSource("ps_main", GL_FRAGMENT_SHADER, shader->c_str(), fxaa_macro));
|
||||||
std::optional<GL::Program> prog = m_shader_cache.GetProgram(m_convert.vs, ps);
|
std::optional<GLProgram> prog = m_shader_cache.GetProgram(m_convert.vs, ps);
|
||||||
if (!prog.has_value())
|
if (!prog.has_value())
|
||||||
{
|
{
|
||||||
Console.Error("Failed to compile FXAA fragment shader");
|
Console.Error("Failed to compile FXAA fragment shader");
|
||||||
|
@ -1890,7 +1846,7 @@ bool GSDeviceOGL::CreateCASPrograms()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto link_uniforms = [](GL::Program& prog) {
|
const auto link_uniforms = [](GLProgram& prog) {
|
||||||
prog.RegisterUniform("const0");
|
prog.RegisterUniform("const0");
|
||||||
prog.RegisterUniform("const1");
|
prog.RegisterUniform("const1");
|
||||||
prog.RegisterUniform("srcOffset");
|
prog.RegisterUniform("srcOffset");
|
||||||
|
@ -1903,7 +1859,7 @@ bool GSDeviceOGL::CreateCASPrograms()
|
||||||
|
|
||||||
bool GSDeviceOGL::DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array<u32, NUM_CAS_CONSTANTS>& constants)
|
bool GSDeviceOGL::DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array<u32, NUM_CAS_CONSTANTS>& 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.Bind();
|
||||||
prog.Uniform4uiv(0, &constants[0]);
|
prog.Uniform4uiv(0, &constants[0]);
|
||||||
prog.Uniform4uiv(1, &constants[4]);
|
prog.Uniform4uiv(1, &constants[4]);
|
||||||
|
@ -1929,7 +1885,7 @@ bool GSDeviceOGL::CreateImGuiProgram()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<GL::Program> prog = m_shader_cache.GetProgram(
|
std::optional<GLProgram> prog = m_shader_cache.GetProgram(
|
||||||
GetShaderSource("vs_main", GL_VERTEX_SHADER, glsl.value()),
|
GetShaderSource("vs_main", GL_VERTEX_SHADER, glsl.value()),
|
||||||
GetShaderSource("ps_main", GL_FRAGMENT_SHADER, glsl.value()));
|
GetShaderSource("ps_main", GL_FRAGMENT_SHADER, glsl.value()));
|
||||||
if (!prog.has_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);
|
const auto res = sb->Map(align, size);
|
||||||
std::memcpy(res.pointer, data, 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 vs(GetVSSource(psel.vs));
|
||||||
const std::string ps(GetPSSource(psel.ps));
|
const std::string ps(GetPSSource(psel.ps));
|
||||||
|
|
||||||
GL::Program prog;
|
GLProgram prog;
|
||||||
m_shader_cache.GetProgram(&prog, vs, ps);
|
m_shader_cache.GetProgram(&prog, vs, ps);
|
||||||
it = m_programs.emplace(psel, std::move(prog)).first;
|
it = m_programs.emplace(psel, std::move(prog)).first;
|
||||||
it->second.Bind();
|
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();
|
return s_texture_upload_buffer.get();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* PCSX2 - PS2 Emulator for PCs
|
/* 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
|
* 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-
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||||
|
@ -15,17 +15,19 @@
|
||||||
|
|
||||||
#pragma once
|
#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 "GS/Renderers/Common/GSDevice.h"
|
||||||
#include "GSTextureOGL.h"
|
#include "GS/Renderers/OpenGL/GLLoader.h"
|
||||||
#include "GLState.h"
|
#include "GS/Renderers/OpenGL/GLProgram.h"
|
||||||
#include "GLLoader.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 "GS/GS.h"
|
||||||
|
|
||||||
|
#include "common/HashCombine.h"
|
||||||
|
|
||||||
|
class GLContext;
|
||||||
|
|
||||||
class GSDepthStencilOGL
|
class GSDepthStencilOGL
|
||||||
{
|
{
|
||||||
bool m_depth_enable;
|
bool m_depth_enable;
|
||||||
|
@ -150,77 +152,77 @@ public:
|
||||||
private:
|
private:
|
||||||
static constexpr u8 NUM_TIMESTAMP_QUERIES = 5;
|
static constexpr u8 NUM_TIMESTAMP_QUERIES = 5;
|
||||||
|
|
||||||
std::unique_ptr<GL::Context> m_gl_context;
|
std::unique_ptr<GLContext> m_gl_context;
|
||||||
|
|
||||||
GLuint m_fbo = 0; // frame buffer container
|
GLuint m_fbo = 0; // frame buffer container
|
||||||
GLuint m_fbo_read = 0; // frame buffer container only for reading
|
GLuint m_fbo_read = 0; // frame buffer container only for reading
|
||||||
GLuint m_fbo_write = 0; // frame buffer container only for writing
|
GLuint m_fbo_write = 0; // frame buffer container only for writing
|
||||||
|
|
||||||
std::unique_ptr<GL::StreamBuffer> m_vertex_stream_buffer;
|
std::unique_ptr<GLStreamBuffer> m_vertex_stream_buffer;
|
||||||
std::unique_ptr<GL::StreamBuffer> m_index_stream_buffer;
|
std::unique_ptr<GLStreamBuffer> m_index_stream_buffer;
|
||||||
GLuint m_expand_ibo = 0;
|
GLuint m_expand_ibo = 0;
|
||||||
GLuint m_vao = 0;
|
GLuint m_vao = 0;
|
||||||
GLuint m_expand_vao = 0;
|
GLuint m_expand_vao = 0;
|
||||||
GLenum m_draw_topology = 0;
|
GLenum m_draw_topology = 0;
|
||||||
|
|
||||||
std::unique_ptr<GL::StreamBuffer> m_vertex_uniform_stream_buffer;
|
std::unique_ptr<GLStreamBuffer> m_vertex_uniform_stream_buffer;
|
||||||
std::unique_ptr<GL::StreamBuffer> m_fragment_uniform_stream_buffer;
|
std::unique_ptr<GLStreamBuffer> m_fragment_uniform_stream_buffer;
|
||||||
GLint m_uniform_buffer_alignment = 0;
|
GLint m_uniform_buffer_alignment = 0;
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
GL::Program ps[2]; // program object
|
GLProgram ps[2]; // program object
|
||||||
} m_merge_obj;
|
} m_merge_obj;
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
GL::Program ps[NUM_INTERLACE_SHADERS]; // program object
|
GLProgram ps[NUM_INTERLACE_SHADERS]; // program object
|
||||||
} m_interlace;
|
} m_interlace;
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
std::string vs;
|
std::string vs;
|
||||||
GL::Program ps[static_cast<int>(ShaderConvert::Count)]; // program object
|
GLProgram ps[static_cast<int>(ShaderConvert::Count)]; // program object
|
||||||
GLuint ln = 0; // sampler object
|
GLuint ln = 0; // sampler object
|
||||||
GLuint pt = 0; // sampler object
|
GLuint pt = 0; // sampler object
|
||||||
GSDepthStencilOGL* dss = nullptr;
|
GSDepthStencilOGL* dss = nullptr;
|
||||||
GSDepthStencilOGL* dss_write = nullptr;
|
GSDepthStencilOGL* dss_write = nullptr;
|
||||||
} m_convert;
|
} m_convert;
|
||||||
|
|
||||||
GL::Program m_present[static_cast<int>(PresentShader::Count)];
|
GLProgram m_present[static_cast<int>(PresentShader::Count)];
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
GL::Program ps;
|
GLProgram ps;
|
||||||
} m_fxaa;
|
} m_fxaa;
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
GSDepthStencilOGL* dss = nullptr;
|
GSDepthStencilOGL* dss = nullptr;
|
||||||
GL::Program primid_ps[2];
|
GLProgram primid_ps[2];
|
||||||
} m_date;
|
} m_date;
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
GL::Program ps;
|
GLProgram ps;
|
||||||
} m_shadeboost;
|
} m_shadeboost;
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
GL::Program upscale_ps;
|
GLProgram upscale_ps;
|
||||||
GL::Program sharpen_ps;
|
GLProgram sharpen_ps;
|
||||||
} m_cas;
|
} m_cas;
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
GL::Program ps;
|
GLProgram ps;
|
||||||
GLuint vao = 0;
|
GLuint vao = 0;
|
||||||
} m_imgui;
|
} m_imgui;
|
||||||
|
|
||||||
GLuint m_ps_ss[1 << 8];
|
GLuint m_ps_ss[1 << 8];
|
||||||
GSDepthStencilOGL* m_om_dss[1 << 5] = {};
|
GSDepthStencilOGL* m_om_dss[1 << 5] = {};
|
||||||
std::unordered_map<ProgramSelector, GL::Program, ProgramSelectorHash> m_programs;
|
std::unordered_map<ProgramSelector, GLProgram, ProgramSelectorHash> m_programs;
|
||||||
GL::ShaderCache m_shader_cache;
|
GLShaderCache m_shader_cache;
|
||||||
|
|
||||||
GLuint m_palette_ss = 0;
|
GLuint m_palette_ss = 0;
|
||||||
|
|
||||||
|
@ -279,7 +281,7 @@ public:
|
||||||
// Used by OpenGL, so the same calling convention is required.
|
// 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 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 GetFBORead() const { return m_fbo_read; }
|
||||||
__fi u32 GetFBOWrite() const { return m_fbo_write; }
|
__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 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, 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, 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 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 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;
|
void ConvertToIndexedTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, u32 SBW, u32 SPSM, GSTexture* dTex, u32 DBW, u32 DPSM) override;
|
||||||
|
|
|
@ -185,12 +185,12 @@ void* GSTextureOGL::GetNativeHandle() const
|
||||||
|
|
||||||
void GSTextureOGL::Clear(const void* data)
|
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)
|
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)
|
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
|
else
|
||||||
{
|
{
|
||||||
GL::StreamBuffer* const sb = GSDeviceOGL::GetTextureUploadBuffer();
|
GLStreamBuffer* const sb = GSDeviceOGL::GetTextureUploadBuffer();
|
||||||
|
|
||||||
const auto map = sb->Map(TEXTURE_UPLOAD_ALIGNMENT, map_size);
|
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());
|
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 pitch = Common::AlignUpPow2(m_r_w << m_int_shift, TEXTURE_UPLOAD_PITCH_ALIGNMENT);
|
||||||
const u32 upload_size = pitch * m_r_h;
|
const u32 upload_size = pitch * m_r_h;
|
||||||
GL::StreamBuffer* sb = GSDeviceOGL::GetTextureUploadBuffer();
|
GLStreamBuffer* sb = GSDeviceOGL::GetTextureUploadBuffer();
|
||||||
sb->Unmap(upload_size);
|
sb->Unmap(upload_size);
|
||||||
sb->Bind();
|
sb->Bind();
|
||||||
|
|
||||||
|
|
|
@ -206,6 +206,11 @@
|
||||||
<ClCompile Include="GS\Renderers\DX12\D3D12ShaderCache.cpp" />
|
<ClCompile Include="GS\Renderers\DX12\D3D12ShaderCache.cpp" />
|
||||||
<ClCompile Include="GS\Renderers\DX12\D3D12StreamBuffer.cpp" />
|
<ClCompile Include="GS\Renderers\DX12\D3D12StreamBuffer.cpp" />
|
||||||
<ClCompile Include="GS\Renderers\DX12\D3D12Texture.cpp" />
|
<ClCompile Include="GS\Renderers\DX12\D3D12Texture.cpp" />
|
||||||
|
<ClCompile Include="GS\Renderers\OpenGL\GLContext.cpp" />
|
||||||
|
<ClCompile Include="GS\Renderers\OpenGL\GLContextWGL.cpp" />
|
||||||
|
<ClCompile Include="GS\Renderers\OpenGL\GLProgram.cpp" />
|
||||||
|
<ClCompile Include="GS\Renderers\OpenGL\GLShaderCache.cpp" />
|
||||||
|
<ClCompile Include="GS\Renderers\OpenGL\GLStreamBuffer.cpp" />
|
||||||
<ClCompile Include="INISettingsInterface.cpp" />
|
<ClCompile Include="INISettingsInterface.cpp" />
|
||||||
<ClCompile Include="Frontend\InputManager.cpp" />
|
<ClCompile Include="Frontend\InputManager.cpp" />
|
||||||
<ClCompile Include="Frontend\InputSource.cpp" />
|
<ClCompile Include="Frontend\InputSource.cpp" />
|
||||||
|
@ -553,6 +558,11 @@
|
||||||
<ClInclude Include="GS\Renderers\DX12\D3D12StreamBuffer.h" />
|
<ClInclude Include="GS\Renderers\DX12\D3D12StreamBuffer.h" />
|
||||||
<ClInclude Include="GS\Renderers\DX12\D3D12Texture.h" />
|
<ClInclude Include="GS\Renderers\DX12\D3D12Texture.h" />
|
||||||
<ClInclude Include="GS\Renderers\HW\GSHwHack.h" />
|
<ClInclude Include="GS\Renderers\HW\GSHwHack.h" />
|
||||||
|
<ClInclude Include="GS\Renderers\OpenGL\GLContext.h" />
|
||||||
|
<ClInclude Include="GS\Renderers\OpenGL\GLContextWGL.h" />
|
||||||
|
<ClInclude Include="GS\Renderers\OpenGL\GLProgram.h" />
|
||||||
|
<ClInclude Include="GS\Renderers\OpenGL\GLShaderCache.h" />
|
||||||
|
<ClInclude Include="GS\Renderers\OpenGL\GLStreamBuffer.h" />
|
||||||
<ClInclude Include="INISettingsInterface.h" />
|
<ClInclude Include="INISettingsInterface.h" />
|
||||||
<ClInclude Include="Frontend\InputManager.h" />
|
<ClInclude Include="Frontend\InputManager.h" />
|
||||||
<ClInclude Include="Frontend\InputSource.h" />
|
<ClInclude Include="Frontend\InputSource.h" />
|
||||||
|
|
|
@ -1376,6 +1376,21 @@
|
||||||
<ClCompile Include="GS\Renderers\DX12\D3D12Builders.cpp">
|
<ClCompile Include="GS\Renderers\DX12\D3D12Builders.cpp">
|
||||||
<Filter>System\Ps2\GS\Renderers\Direct3D12</Filter>
|
<Filter>System\Ps2\GS\Renderers\Direct3D12</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="GS\Renderers\OpenGL\GLContextWGL.cpp">
|
||||||
|
<Filter>System\Ps2\GS\Renderers\OpenGL</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="GS\Renderers\OpenGL\GLProgram.cpp">
|
||||||
|
<Filter>System\Ps2\GS\Renderers\OpenGL</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="GS\Renderers\OpenGL\GLShaderCache.cpp">
|
||||||
|
<Filter>System\Ps2\GS\Renderers\OpenGL</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="GS\Renderers\OpenGL\GLStreamBuffer.cpp">
|
||||||
|
<Filter>System\Ps2\GS\Renderers\OpenGL</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="GS\Renderers\OpenGL\GLContext.cpp">
|
||||||
|
<Filter>System\Ps2\GS\Renderers\OpenGL</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="Patch.h">
|
<ClInclude Include="Patch.h">
|
||||||
|
@ -2297,6 +2312,21 @@
|
||||||
<ClInclude Include="GS\Renderers\DX12\D3D12Builders.h">
|
<ClInclude Include="GS\Renderers\DX12\D3D12Builders.h">
|
||||||
<Filter>System\Ps2\GS\Renderers\Direct3D12</Filter>
|
<Filter>System\Ps2\GS\Renderers\Direct3D12</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="GS\Renderers\OpenGL\GLContextWGL.h">
|
||||||
|
<Filter>System\Ps2\GS\Renderers\OpenGL</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="GS\Renderers\OpenGL\GLProgram.h">
|
||||||
|
<Filter>System\Ps2\GS\Renderers\OpenGL</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="GS\Renderers\OpenGL\GLShaderCache.h">
|
||||||
|
<Filter>System\Ps2\GS\Renderers\OpenGL</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="GS\Renderers\OpenGL\GLStreamBuffer.h">
|
||||||
|
<Filter>System\Ps2\GS\Renderers\OpenGL</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="GS\Renderers\OpenGL\GLContext.h">
|
||||||
|
<Filter>System\Ps2\GS\Renderers\OpenGL</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<CustomBuildStep Include="rdebug\deci2.h">
|
<CustomBuildStep Include="rdebug\deci2.h">
|
||||||
|
|
Loading…
Reference in New Issue