Prepare frontend for multiple graphics APIs (#6347)

* externals: Update dynarmic

* settings: Introduce GraphicsAPI enum

* For now it's OpenGL only but will be expanded upon later

* citra_qt: Introduce backend agnostic context management

* Mostly a direct port from yuzu

* core: Simplify context acquire

* settings: Add option to create debug contexts

* renderer_opengl: Abstract initialization to Driver

* This commit also updates glad and adds some useful extensions which we will use in part 2

* Rasterizer construction is moved to the specific renderer instead of RendererBase.
  Software rendering has been disable to achieve this but will be brought back in the next commit.

* video_core: Remove Init/Shutdown methods from renderer

* The constructor and destructor can do the same job

* In addition move opengl function loading to Qt since SDL already does this. Also remove ErrorVideoCore which is never reached

* citra_qt: Decouple software renderer from opengl part 1

* citra: Decouple software renderer from opengl part 2

* android: Decouple software renderer from opengl part 3

* swrasterizer: Decouple software renderer from opengl part 4

* This commit simply enforces the renderer naming conventions in the software renderer

* video_core: Move RendererBase to VideoCore

* video_core: De-globalize screenshot state

* video_core: Pass system to the renderers

* video_core: Commonize shader uniform data

* video_core: Abstract backend agnostic rasterizer operations

* bootmanager: Remove references to OpenGL for macOS

OpenGL macOS headers definitions clash heavily with each other

* citra_qt: Proper title for api settings

* video_core: Reduce boost usage

* bootmanager: Fix hide mouse option

Remove event handlers from RenderWidget for events that are
already handled by the parent GRenderWindow.
Also enable mouse tracking on the RenderWidget.

* android: Remove software from graphics api list

* code: Address review comments

* citra: Port per-game settings read

* Having to update the default value for all backends is a pain so lets centralize it

* android: Rename to OpenGLES

---------

Co-authored-by: MerryMage <MerryMage@users.noreply.github.com>
Co-authored-by: Vitor Kiguchi <vitor-kiguchi@hotmail.com>
This commit is contained in:
GPUCode 2023-03-27 14:29:17 +03:00 committed by GitHub
parent 9ef42040af
commit b5d6f645bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
99 changed files with 3165 additions and 4501 deletions

View file

@ -7,40 +7,14 @@
#include <string>
#define SDL_MAIN_HANDLED
#include <SDL.h>
#include <fmt/format.h>
#include <glad/glad.h>
#include "citra/emu_window/emu_window_sdl2.h"
#include "common/logging/log.h"
#include "common/scm_rev.h"
#include "common/settings.h"
#include "core/3ds.h"
#include "core/core.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "input_common/motion_emu.h"
#include "input_common/sdl/sdl.h"
#include "network/network.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
SharedContext_SDL2::SharedContext_SDL2() {
window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,
SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
context = SDL_GL_CreateContext(window);
}
SharedContext_SDL2::~SharedContext_SDL2() {
SDL_GL_DeleteContext(context);
SDL_DestroyWindow(window);
}
void SharedContext_SDL2::MakeCurrent() {
SDL_GL_MakeCurrent(window, context);
}
void SharedContext_SDL2::DoneCurrent() {
SDL_GL_MakeCurrent(window, nullptr);
}
void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
@ -135,78 +109,9 @@ void EmuWindow_SDL2::Fullscreen() {
SDL_MaximizeWindow(render_window);
}
EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen, bool is_secondary) : EmuWindow(is_secondary) {
// Initialize the window
if (Settings::values.use_gles) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
} else {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
}
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
// Enable context sharing for the shared context
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
// Enable vsync
SDL_GL_SetSwapInterval(1);
std::string window_title = fmt::format("Citra {} | {}-{}", Common::g_build_fullname,
Common::g_scm_branch, Common::g_scm_desc);
render_window =
SDL_CreateWindow(window_title.c_str(),
SDL_WINDOWPOS_UNDEFINED, // x position
SDL_WINDOWPOS_UNDEFINED, // y position
Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
if (render_window == nullptr) {
LOG_CRITICAL(Frontend, "Failed to create SDL2 window: {}", SDL_GetError());
exit(1);
}
dummy_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,
SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
if (fullscreen) {
Fullscreen();
}
window_context = SDL_GL_CreateContext(render_window);
core_context = CreateSharedContext();
last_saved_context = nullptr;
if (window_context == nullptr) {
LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context: {}", SDL_GetError());
exit(1);
}
if (core_context == nullptr) {
LOG_CRITICAL(Frontend, "Failed to create shared SDL2 GL context: {}", SDL_GetError());
exit(1);
}
render_window_id = SDL_GetWindowID(render_window);
auto gl_load_func = Settings::values.use_gles ? gladLoadGLES2Loader : gladLoadGLLoader;
if (!gl_load_func(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
LOG_CRITICAL(Frontend, "Failed to initialize GL functions: {}", SDL_GetError());
exit(1);
}
OnResize();
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
SDL_PumpEvents();
}
EmuWindow_SDL2::EmuWindow_SDL2(bool is_secondary) : EmuWindow(is_secondary) {}
EmuWindow_SDL2::~EmuWindow_SDL2() {
core_context.reset();
SDL_GL_DeleteContext(window_context);
SDL_Quit();
}
@ -222,28 +127,6 @@ void EmuWindow_SDL2::InitializeSDL2() {
SDL_SetMainReady();
}
std::unique_ptr<Frontend::GraphicsContext> EmuWindow_SDL2::CreateSharedContext() const {
return std::make_unique<SharedContext_SDL2>();
}
void EmuWindow_SDL2::SaveContext() {
last_saved_context = SDL_GL_GetCurrentContext();
}
void EmuWindow_SDL2::RestoreContext() {
SDL_GL_MakeCurrent(render_window, last_saved_context);
}
void EmuWindow_SDL2::Present() {
SDL_GL_MakeCurrent(render_window, window_context);
SDL_GL_SetSwapInterval(1);
while (IsOpen()) {
VideoCore::g_renderer->TryPresent(100, is_secondary);
SDL_GL_SwapWindow(render_window);
}
SDL_GL_MakeCurrent(render_window, nullptr);
}
void EmuWindow_SDL2::PollEvents() {
SDL_Event event;
std::vector<SDL_Event> other_window_events;
@ -312,14 +195,6 @@ void EmuWindow_SDL2::PollEvents() {
}
}
void EmuWindow_SDL2::MakeCurrent() {
core_context->MakeCurrent();
}
void EmuWindow_SDL2::DoneCurrent() {
core_context->DoneCurrent();
}
void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);
}

View file

@ -10,56 +10,27 @@
struct SDL_Window;
class SharedContext_SDL2 : public Frontend::GraphicsContext {
public:
using SDL_GLContext = void*;
SharedContext_SDL2();
~SharedContext_SDL2() override;
void MakeCurrent() override;
void DoneCurrent() override;
private:
SDL_GLContext context;
SDL_Window* window;
};
class EmuWindow_SDL2 : public Frontend::EmuWindow {
public:
explicit EmuWindow_SDL2(bool fullscreen, bool is_secondary);
explicit EmuWindow_SDL2(bool is_secondary);
~EmuWindow_SDL2();
/// Initializes SDL2
static void InitializeSDL2();
void Present();
/// Presents the most recent frame from the video backend
virtual void Present() {}
/// Polls window events
void PollEvents() override;
/// Makes the graphics context current for the caller thread
void MakeCurrent() override;
/// Releases the GL context from the caller thread
void DoneCurrent() override;
/// Whether the window is still open, and a close request hasn't yet been sent
bool IsOpen() const;
/// Close the window.
void RequestClose();
/// Creates a new context that is shared with the current context
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
/// Saves the current context, for the purpose of e.g. creating new shared contexts
void SaveContext() override;
/// Restores the context previously saved
void RestoreContext() override;
private:
protected:
/// Called by PollEvents when a key is pressed or released.
void OnKeyEvent(int key, u8 state);
@ -105,17 +76,6 @@ private:
/// Fake hidden window for the core context
SDL_Window* dummy_window;
using SDL_GLContext = void*;
/// The OpenGL context associated with the window
SDL_GLContext window_context;
/// Used by SaveContext and RestoreContext
SDL_GLContext last_saved_context;
/// The OpenGL context associated with the core
std::unique_ptr<Frontend::GraphicsContext> core_context;
/// Keeps track of how often to update the title bar during gameplay
u32 last_time = 0;
};

View file

@ -0,0 +1,152 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <cstdlib>
#include <string>
#define SDL_MAIN_HANDLED
#include <SDL.h>
#include <glad/glad.h>
#include "citra/emu_window/emu_window_sdl2_gl.h"
#include "common/scm_rev.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
class SDLGLContext : public Frontend::GraphicsContext {
public:
using SDL_GLContext = void*;
SDLGLContext() {
window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,
SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
context = SDL_GL_CreateContext(window);
}
~SDLGLContext() override {
SDL_GL_DeleteContext(context);
SDL_DestroyWindow(window);
}
void MakeCurrent() override {
SDL_GL_MakeCurrent(window, context);
}
void DoneCurrent() override {
SDL_GL_MakeCurrent(window, nullptr);
}
private:
SDL_Window* window;
SDL_GLContext context;
};
EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(bool fullscreen, bool is_secondary)
: EmuWindow_SDL2{is_secondary} {
// Initialize the window
if (Settings::values.use_gles) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
} else {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
}
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
// Enable context sharing for the shared context
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
// Enable vsync
SDL_GL_SetSwapInterval(1);
// Enable debug context
if (Settings::values.renderer_debug) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
}
std::string window_title = fmt::format("Citra {} | {}-{}", Common::g_build_fullname,
Common::g_scm_branch, Common::g_scm_desc);
render_window =
SDL_CreateWindow(window_title.c_str(),
SDL_WINDOWPOS_UNDEFINED, // x position
SDL_WINDOWPOS_UNDEFINED, // y position
Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
if (render_window == nullptr) {
LOG_CRITICAL(Frontend, "Failed to create SDL2 window: {}", SDL_GetError());
exit(1);
}
dummy_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,
SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
if (fullscreen) {
Fullscreen();
}
window_context = SDL_GL_CreateContext(render_window);
core_context = CreateSharedContext();
last_saved_context = nullptr;
if (window_context == nullptr) {
LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context: {}", SDL_GetError());
exit(1);
}
if (core_context == nullptr) {
LOG_CRITICAL(Frontend, "Failed to create shared SDL2 GL context: {}", SDL_GetError());
exit(1);
}
render_window_id = SDL_GetWindowID(render_window);
auto gl_load_func = Settings::values.use_gles ? gladLoadGLES2Loader : gladLoadGLLoader;
if (!gl_load_func(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
LOG_CRITICAL(Frontend, "Failed to initialize GL functions: {}", SDL_GetError());
exit(1);
}
OnResize();
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
SDL_PumpEvents();
}
EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() {
core_context.reset();
SDL_DestroyWindow(render_window);
SDL_GL_DeleteContext(window_context);
}
std::unique_ptr<Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const {
return std::make_unique<SDLGLContext>();
}
void EmuWindow_SDL2_GL::MakeCurrent() {
core_context->MakeCurrent();
}
void EmuWindow_SDL2_GL::DoneCurrent() {
core_context->DoneCurrent();
}
void EmuWindow_SDL2_GL::SaveContext() {
last_saved_context = SDL_GL_GetCurrentContext();
}
void EmuWindow_SDL2_GL::RestoreContext() {
SDL_GL_MakeCurrent(render_window, last_saved_context);
}
void EmuWindow_SDL2_GL::Present() {
SDL_GL_MakeCurrent(render_window, window_context);
SDL_GL_SetSwapInterval(1);
while (IsOpen()) {
VideoCore::g_renderer->TryPresent(100, is_secondary);
SDL_GL_SwapWindow(render_window);
}
SDL_GL_MakeCurrent(render_window, nullptr);
}

View file

@ -0,0 +1,35 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "citra/emu_window/emu_window_sdl2.h"
struct SDL_Window;
class EmuWindow_SDL2_GL : public EmuWindow_SDL2 {
public:
explicit EmuWindow_SDL2_GL(bool fullscreen, bool is_secondary);
~EmuWindow_SDL2_GL();
void Present() override;
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
void MakeCurrent() override;
void DoneCurrent() override;
void SaveContext() override;
void RestoreContext() override;
private:
using SDL_GLContext = void*;
/// The OpenGL context associated with the window
SDL_GLContext window_context;
/// Used by SaveContext and RestoreContext
SDL_GLContext last_saved_context;
/// The OpenGL context associated with the core
std::unique_ptr<Frontend::GraphicsContext> core_context;
};

View file

@ -0,0 +1,127 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <cstdlib>
#include <string>
#define SDL_MAIN_HANDLED
#include <SDL.h>
#include <SDL_rect.h>
#include "citra/emu_window/emu_window_sdl2_sw.h"
#include "common/color.h"
#include "common/scm_rev.h"
#include "core/frontend/emu_window.h"
#include "core/hw/gpu.h"
#include "core/memory.h"
#include "video_core/video_core.h"
class DummyContext : public Frontend::GraphicsContext {};
EmuWindow_SDL2_SW::EmuWindow_SDL2_SW(bool fullscreen, bool is_secondary)
: EmuWindow_SDL2{is_secondary} {
std::string window_title = fmt::format("Citra {} | {}-{}", Common::g_build_fullname,
Common::g_scm_branch, Common::g_scm_desc);
render_window =
SDL_CreateWindow(window_title.c_str(),
SDL_WINDOWPOS_UNDEFINED, // x position
SDL_WINDOWPOS_UNDEFINED, // y position
Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight,
SDL_WINDOW_SHOWN);
if (render_window == nullptr) {
LOG_CRITICAL(Frontend, "Failed to create SDL2 window: {}", SDL_GetError());
exit(1);
}
window_surface = SDL_GetWindowSurface(render_window);
renderer = SDL_CreateSoftwareRenderer(window_surface);
if (fullscreen) {
Fullscreen();
}
render_window_id = SDL_GetWindowID(render_window);
OnResize();
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
SDL_PumpEvents();
}
EmuWindow_SDL2_SW::~EmuWindow_SDL2_SW() {
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(render_window);
}
std::unique_ptr<Frontend::GraphicsContext> EmuWindow_SDL2_SW::CreateSharedContext() const {
return std::make_unique<DummyContext>();
}
void EmuWindow_SDL2_SW::Present() {
const auto layout{Layout::DefaultFrameLayout(
Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight, false, false)};
while (IsOpen()) {
SDL_SetRenderDrawColor(renderer, Settings::values.bg_red.GetValue() * 255,
Settings::values.bg_green.GetValue() * 255,
Settings::values.bg_blue.GetValue() * 255, 0xFF);
SDL_RenderClear(renderer);
const auto draw_screen = [&](int fb_id) {
const auto dst_rect = fb_id == 0 ? layout.top_screen : layout.bottom_screen;
SDL_Rect sdl_rect{static_cast<int>(dst_rect.left), static_cast<int>(dst_rect.top),
static_cast<int>(dst_rect.GetWidth()),
static_cast<int>(dst_rect.GetHeight())};
SDL_Surface* screen = LoadFramebuffer(fb_id);
SDL_BlitSurface(screen, nullptr, window_surface, &sdl_rect);
SDL_FreeSurface(screen);
};
draw_screen(0);
draw_screen(1);
SDL_RenderPresent(renderer);
SDL_UpdateWindowSurface(render_window);
}
}
SDL_Surface* EmuWindow_SDL2_SW::LoadFramebuffer(int fb_id) {
const auto& framebuffer = GPU::g_regs.framebuffer_config[fb_id];
const PAddr framebuffer_addr =
framebuffer.active_fb == 0 ? framebuffer.address_left1 : framebuffer.address_left2;
Memory::RasterizerFlushRegion(framebuffer_addr, framebuffer.stride * framebuffer.height);
const u8* framebuffer_data = VideoCore::g_memory->GetPhysicalPointer(framebuffer_addr);
const int width = framebuffer.height;
const int height = framebuffer.width;
const int bpp = GPU::Regs::BytesPerPixel(framebuffer.color_format);
SDL_Surface* surface =
SDL_CreateRGBSurfaceWithFormat(0, width, height, 0, SDL_PIXELFORMAT_ABGR8888);
SDL_LockSurface(surface);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
const u8* pixel = framebuffer_data + (x * height + height - y) * bpp;
const Common::Vec4 color = [&] {
switch (framebuffer.color_format) {
case GPU::Regs::PixelFormat::RGBA8:
return Common::Color::DecodeRGBA8(pixel);
case GPU::Regs::PixelFormat::RGB8:
return Common::Color::DecodeRGB8(pixel);
case GPU::Regs::PixelFormat::RGB565:
return Common::Color::DecodeRGB565(pixel);
case GPU::Regs::PixelFormat::RGB5A1:
return Common::Color::DecodeRGB5A1(pixel);
case GPU::Regs::PixelFormat::RGBA4:
return Common::Color::DecodeRGBA4(pixel);
}
}();
u8* dst_pixel = reinterpret_cast<u8*>(surface->pixels) + (y * width + x) * 4;
std::memcpy(dst_pixel, color.AsArray(), sizeof(color));
}
}
SDL_UnlockSurface(surface);
return surface;
}

View file

@ -0,0 +1,32 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "citra/emu_window/emu_window_sdl2.h"
struct SDL_Renderer;
struct SDL_Surface;
class EmuWindow_SDL2_SW : public EmuWindow_SDL2 {
public:
explicit EmuWindow_SDL2_SW(bool fullscreen, bool is_secondary);
~EmuWindow_SDL2_SW();
void Present() override;
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
void MakeCurrent() override {}
void DoneCurrent() override {}
private:
/// Loads a framebuffer to an SDL surface
SDL_Surface* LoadFramebuffer(int fb_id);
/// The SDL software renderer
SDL_Renderer* renderer;
/// The window surface
SDL_Surface* window_surface;
};