mirror of
https://github.com/OpenEmu/PCSX2-Core.git
synced 2025-11-01 11:07:36 +00:00
373 lines
9.4 KiB
C++
373 lines
9.4 KiB
C++
/* 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 "OpenGLHostDisplay.h"
|
|
#include "common/Assertions.h"
|
|
#include "common/Console.h"
|
|
#include "common/ScopedGuard.h"
|
|
#include "common/StringUtil.h"
|
|
#include "common/GL/Program.h"
|
|
|
|
#include <array>
|
|
#include <tuple>
|
|
|
|
class OpenGLHostDisplayTexture : public HostDisplayTexture
|
|
{
|
|
public:
|
|
OpenGLHostDisplayTexture(GLuint texture, u32 width, u32 height)
|
|
: m_texture(texture)
|
|
, m_width(width)
|
|
, m_height(height)
|
|
{
|
|
}
|
|
~OpenGLHostDisplayTexture() override = default;
|
|
|
|
void* GetHandle() const override { return reinterpret_cast<void*>(static_cast<uintptr_t>(m_texture)); }
|
|
u32 GetWidth() const override { return m_width; }
|
|
u32 GetHeight() const override { return m_height; }
|
|
|
|
GLuint GetGLID() const { return m_texture; }
|
|
|
|
private:
|
|
GLuint m_texture;
|
|
u32 m_width;
|
|
u32 m_height;
|
|
u32 m_layers;
|
|
u32 m_levels;
|
|
};
|
|
|
|
OpenGLHostDisplay::OpenGLHostDisplay() = default;
|
|
|
|
OpenGLHostDisplay::~OpenGLHostDisplay()
|
|
{
|
|
pxAssertMsg(!m_gl_context, "Context should have been destroyed by now");
|
|
}
|
|
|
|
HostDisplay::RenderAPI OpenGLHostDisplay::GetRenderAPI() const
|
|
{
|
|
return m_gl_context->IsGLES() ? RenderAPI::OpenGLES : RenderAPI::OpenGL;
|
|
}
|
|
|
|
void* OpenGLHostDisplay::GetRenderDevice() const
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
void* OpenGLHostDisplay::GetRenderContext() const
|
|
{
|
|
return m_gl_context.get();
|
|
}
|
|
|
|
void* OpenGLHostDisplay::GetRenderSurface() const
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<HostDisplayTexture> OpenGLHostDisplay::CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, bool dynamic /* = false */)
|
|
{
|
|
// clear error
|
|
glGetError();
|
|
|
|
GLuint id;
|
|
glGenTextures(1, &id);
|
|
glBindTexture(GL_TEXTURE_2D, id);
|
|
|
|
if ((GLAD_GL_ARB_texture_storage || GLAD_GL_ES_VERSION_3_0) && !data)
|
|
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height);
|
|
else
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
|
|
|
|
GLenum error = glGetError();
|
|
if (error != GL_NO_ERROR)
|
|
{
|
|
Console.Error("Failed to create texture: 0x%X", error);
|
|
glDeleteTextures(1, &id);
|
|
return nullptr;
|
|
}
|
|
|
|
return std::make_unique<OpenGLHostDisplayTexture>(id, width, height);
|
|
}
|
|
|
|
void OpenGLHostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* texture_data, u32 texture_data_stride)
|
|
{
|
|
OpenGLHostDisplayTexture* tex = static_cast<OpenGLHostDisplayTexture*>(texture);
|
|
|
|
GLint alignment;
|
|
if (texture_data_stride & 1)
|
|
alignment = 1;
|
|
else if (texture_data_stride & 2)
|
|
alignment = 2;
|
|
else
|
|
alignment = 4;
|
|
|
|
GLint old_texture_binding = 0, old_alignment = 0, old_row_length = 0;
|
|
glGetIntegerv(GL_TEXTURE_BINDING_2D, &old_texture_binding);
|
|
glBindTexture(GL_TEXTURE_2D, tex->GetGLID());
|
|
|
|
glGetIntegerv(GL_UNPACK_ALIGNMENT, &old_alignment);
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);
|
|
|
|
glGetIntegerv(GL_UNPACK_ROW_LENGTH, &old_row_length);
|
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture_data_stride / sizeof(u32));
|
|
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RGBA8, GL_UNSIGNED_BYTE, texture_data);
|
|
|
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, old_row_length);
|
|
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, old_alignment);
|
|
glBindTexture(GL_TEXTURE_2D, old_texture_binding);
|
|
}
|
|
|
|
void OpenGLHostDisplay::SetVSync(VsyncMode mode)
|
|
{
|
|
if (m_gl_context->GetWindowInfo().type == WindowInfo::Type::Surfaceless)
|
|
return;
|
|
|
|
// Window framebuffer has to be bound to call SetSwapInterval.
|
|
GLint current_fbo = 0;
|
|
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo);
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
|
|
|
if (mode != VsyncMode::Adaptive || !m_gl_context->SetSwapInterval(-1))
|
|
m_gl_context->SetSwapInterval(static_cast<s32>(mode != VsyncMode::Off));
|
|
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo);
|
|
}
|
|
|
|
const char* OpenGLHostDisplay::GetGLSLVersionString() const
|
|
{
|
|
if (GetRenderAPI() == RenderAPI::OpenGLES)
|
|
{
|
|
if (GLAD_GL_ES_VERSION_3_0)
|
|
return "#version 300 es";
|
|
else
|
|
return "#version 100";
|
|
}
|
|
else
|
|
{
|
|
if (GLAD_GL_VERSION_3_3)
|
|
return "#version 330";
|
|
else
|
|
return "#version 130";
|
|
}
|
|
}
|
|
|
|
std::string OpenGLHostDisplay::GetGLSLVersionHeader() const
|
|
{
|
|
std::string header = GetGLSLVersionString();
|
|
header += "\n\n";
|
|
if (GetRenderAPI() == RenderAPI::OpenGLES)
|
|
{
|
|
header += "precision highp float;\n";
|
|
header += "precision highp int;\n\n";
|
|
}
|
|
|
|
return header;
|
|
}
|
|
|
|
bool OpenGLHostDisplay::HasRenderDevice() const
|
|
{
|
|
return static_cast<bool>(m_gl_context);
|
|
}
|
|
|
|
bool OpenGLHostDisplay::HasRenderSurface() const
|
|
{
|
|
return m_window_info.type != WindowInfo::Type::Surfaceless;
|
|
}
|
|
|
|
bool OpenGLHostDisplay::CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, VsyncMode vsync, bool threaded_presentation, bool debug_device)
|
|
{
|
|
m_gl_context = GL::Context::Create(wi);
|
|
if (!m_gl_context)
|
|
{
|
|
Console.Error("Failed to create any GL context");
|
|
m_gl_context.reset();
|
|
return false;
|
|
}
|
|
|
|
m_window_info = m_gl_context->GetWindowInfo();
|
|
m_vsync_mode = vsync;
|
|
return true;
|
|
}
|
|
|
|
bool OpenGLHostDisplay::InitializeRenderDevice(std::string_view shader_cache_directory, bool debug_device)
|
|
{
|
|
SetSwapInterval();
|
|
GL::Program::ResetLastProgram();
|
|
return true;
|
|
}
|
|
|
|
void OpenGLHostDisplay::SetSwapInterval()
|
|
{
|
|
const int interval = ((m_vsync_mode == VsyncMode::Adaptive) ? -1 : ((m_vsync_mode == VsyncMode::On) ? 1 : 0));
|
|
m_gl_context->SetSwapInterval(interval);
|
|
}
|
|
|
|
bool OpenGLHostDisplay::MakeRenderContextCurrent()
|
|
{
|
|
if (!m_gl_context->MakeCurrent())
|
|
{
|
|
Console.Error("Failed to make GL context current");
|
|
return false;
|
|
}
|
|
|
|
SetSwapInterval();
|
|
return true;
|
|
}
|
|
|
|
bool OpenGLHostDisplay::DoneRenderContextCurrent()
|
|
{
|
|
return m_gl_context->DoneCurrent();
|
|
}
|
|
|
|
void OpenGLHostDisplay::DestroyRenderDevice()
|
|
{
|
|
if (!m_gl_context)
|
|
return;
|
|
|
|
m_gl_context->DoneCurrent();
|
|
m_gl_context.reset();
|
|
}
|
|
|
|
bool OpenGLHostDisplay::ChangeRenderWindow(const WindowInfo& new_wi)
|
|
{
|
|
pxAssert(m_gl_context);
|
|
|
|
if (!m_gl_context->ChangeSurface(new_wi))
|
|
{
|
|
Console.Error("Failed to change surface");
|
|
return false;
|
|
}
|
|
|
|
m_window_info = m_gl_context->GetWindowInfo();
|
|
|
|
if (new_wi.type != WindowInfo::Type::Surfaceless)
|
|
{
|
|
// reset vsync rate, since it (usually) gets lost
|
|
if (m_vsync_mode != VsyncMode::Adaptive || !m_gl_context->SetSwapInterval(-1))
|
|
m_gl_context->SetSwapInterval(static_cast<s32>(m_vsync_mode != VsyncMode::Off));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void OpenGLHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_height, float new_window_scale)
|
|
{
|
|
if (!m_gl_context)
|
|
return;
|
|
|
|
m_window_info.surface_scale = new_window_scale;
|
|
if (m_window_info.surface_width == static_cast<u32>(new_window_width) &&
|
|
m_window_info.surface_height == static_cast<u32>(new_window_height))
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_gl_context->ResizeSurface(static_cast<u32>(new_window_width), static_cast<u32>(new_window_height));
|
|
m_window_info = m_gl_context->GetWindowInfo();
|
|
}
|
|
|
|
bool OpenGLHostDisplay::SupportsFullscreen() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool OpenGLHostDisplay::IsFullscreen()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool OpenGLHostDisplay::SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
HostDisplay::AdapterAndModeList OpenGLHostDisplay::GetAdapterAndModeList()
|
|
{
|
|
AdapterAndModeList aml;
|
|
|
|
if (m_gl_context)
|
|
{
|
|
for (const GL::Context::FullscreenModeInfo& fmi : m_gl_context->EnumerateFullscreenModes())
|
|
aml.fullscreen_modes.push_back(GetFullscreenModeString(fmi.width, fmi.height, fmi.refresh_rate));
|
|
}
|
|
|
|
return aml;
|
|
}
|
|
|
|
void OpenGLHostDisplay::DestroyRenderSurface()
|
|
{
|
|
if (!m_gl_context)
|
|
return;
|
|
|
|
m_window_info = {};
|
|
if (!m_gl_context->ChangeSurface(m_window_info))
|
|
Console.Error("Failed to switch to surfaceless");
|
|
}
|
|
|
|
std::string OpenGLHostDisplay::GetDriverInfo() const
|
|
{
|
|
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));
|
|
return StringUtil::StdStringFromFormat(
|
|
"%s Context:\n%s\n%s %s", m_gl_context->IsGLES() ? "OpenGL ES" : "OpenGL", gl_version, gl_vendor, gl_renderer);
|
|
}
|
|
|
|
bool OpenGLHostDisplay::CreateImGuiContext()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void OpenGLHostDisplay::DestroyImGuiContext()
|
|
{
|
|
}
|
|
|
|
bool OpenGLHostDisplay::UpdateImGuiFontTexture()
|
|
{
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool OpenGLHostDisplay::BeginPresent(bool frame_skip)
|
|
{
|
|
if (frame_skip || m_window_info.type == WindowInfo::Type::Surfaceless)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
glDisable(GL_SCISSOR_TEST);
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 1);
|
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
glViewport(0, 0, m_window_info.surface_width, m_window_info.surface_height);
|
|
return true;
|
|
}
|
|
|
|
void OpenGLHostDisplay::EndPresent()
|
|
{
|
|
// clear out pipeline bindings, since imgui doesn't use them
|
|
glBindProgramPipeline(0);
|
|
glDisable(GL_SCISSOR_TEST);
|
|
glDisable(GL_STENCIL_TEST);
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
GL::Program::ResetLastProgram();
|
|
|
|
m_gl_context->SwapBuffers();
|
|
}
|