diff --git a/include/PICA/gpu.hpp b/include/PICA/gpu.hpp index e84c259c..f35b1254 100644 --- a/include/PICA/gpu.hpp +++ b/include/PICA/gpu.hpp @@ -86,10 +86,15 @@ class GPU { bool lightingLUTDirty = false; GPU(Memory& mem, EmulatorConfig& config); - void initGraphicsContext(SDL_Window* window) { renderer->initGraphicsContext(window); } void display() { renderer->display(); } void screenshot(const std::string& name) { renderer->screenshot(name); } +#if defined(PANDA3DS_FRONTEND_SDL) + void initGraphicsContext(SDL_Window* window) { renderer->initGraphicsContext(window); } +#elif defined(PANDA3DS_FRONTEND_QT) + void initGraphicsContext(GL::Context* context) { renderer->initGraphicsContext(context); } +#endif + void fireDMA(u32 dest, u32 source, u32 size); void reset(); diff --git a/include/emulator.hpp b/include/emulator.hpp index a00c7fe1..91fa02a0 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -20,6 +20,10 @@ #include "http_server.hpp" #endif +#ifdef PANDA3DS_FRONTEND_QT +#include "gl/context.h" +#endif + enum class ROMType { None, ELF, @@ -37,10 +41,12 @@ class Emulator { Crypto::AESEngine aesEngine; Cheats cheats; +#ifdef PANDA3DS_FRONTEND_SDL SDL_Window* window; #ifdef PANDA3DS_ENABLE_OPENGL SDL_GLContext glContext; +#endif #endif SDL_GameController* gameController = nullptr; @@ -106,5 +112,13 @@ class Emulator { bool load3DSX(const std::filesystem::path& path); bool loadELF(const std::filesystem::path& path); bool loadELF(std::ifstream& file); - void initGraphicsContext(); + +#ifdef PANDA3DS_FRONTEND_QT + // For passing the GL context from Qt to the renderer + void initGraphicsContext(GL::Context* glContext) { gpu.initGraphicsContext(nullptr); } +#else + void initGraphicsContext() { gpu.initGraphicsContext(window); } +#endif + + EmulatorConfig& getConfig() { return config; } }; diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index 2612f04a..65a668ad 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -5,7 +5,9 @@ #include #include #include +#include +#include "emulator.hpp" #include "panda_qt/screen.hpp" class MainWindow : public QMainWindow { @@ -18,6 +20,10 @@ class MainWindow : public QMainWindow { Dark = 2, }; + // This would normally be an std::unique_ptr but it's shared between threads so definitely not + Emulator* emu = nullptr; + std::thread emuThread; + QComboBox* themeSelect = nullptr; QMenuBar* menuBar = nullptr; ScreenWidget screen; diff --git a/include/panda_qt/screen.hpp b/include/panda_qt/screen.hpp index 1555e463..8f04c0e9 100644 --- a/include/panda_qt/screen.hpp +++ b/include/panda_qt/screen.hpp @@ -11,6 +11,7 @@ class ScreenWidget : public QWidget { public: ScreenWidget(QWidget* parent = nullptr); + GL::Context* getGLContext() { return glContext.get(); } private: std::unique_ptr glContext = nullptr; diff --git a/include/renderer.hpp b/include/renderer.hpp index c189da7f..1f3d63d3 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -7,6 +7,10 @@ #include "PICA/regs.hpp" #include "helpers.hpp" +#ifdef PANDA3DS_FRONTEND_QT +#include "gl/context.h" +#endif + enum class RendererType : s8 { // Todo: Auto = -1, Null = 0, @@ -50,10 +54,15 @@ class Renderer { virtual void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) = 0; // Clear a GPU buffer in VRAM virtual void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) = 0; // Perform display transfer virtual void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) = 0; - virtual void drawVertices(PICA::PrimType primType, std::span vertices) = 0; // Draw the given vertices + virtual void drawVertices(PICA::PrimType primType, std::span vertices) = 0; // Draw the given vertices virtual void screenshot(const std::string& name) = 0; + // Functions for initializing the graphics context for the Qt frontend, where we don't have the convenience of SDL_Window +#ifdef PANDA3DS_FRONTEND_QT + virtual void initGraphicsContext(GL::Context* context) { Helpers::panic("Tried to initialize incompatible renderer with GL context"); } +#endif + void setFBSize(u32 width, u32 height) { fbSize[0] = width; fbSize[1] = height; diff --git a/include/renderer_gl/renderer_gl.hpp b/include/renderer_gl/renderer_gl.hpp index dab33ae8..56324b0e 100644 --- a/include/renderer_gl/renderer_gl.hpp +++ b/include/renderer_gl/renderer_gl.hpp @@ -67,6 +67,7 @@ class RendererGL final : public Renderer { void setupTextureEnvState(); void bindTexturesToSlots(); void updateLightingLUT(); + void initGraphicsContextInternal(); public: RendererGL(GPU& gpu, const std::array& internalRegs, const std::array& externalRegs) @@ -83,6 +84,10 @@ class RendererGL final : public Renderer { std::optional getColourBuffer(u32 addr, PICA::ColorFmt format, u32 width, u32 height, bool createIfnotFound = true); +#ifdef PANDA3DS_FRONTEND_QT + virtual void initGraphicsContext([[maybe_unused]] GL::Context* context) override { initGraphicsContextInternal(); } +#endif + // Take a screenshot of the screen and store it in a file void screenshot(const std::string& name) override; }; diff --git a/include/renderer_null/renderer_null.hpp b/include/renderer_null/renderer_null.hpp index 231ed41d..79d60ba6 100644 --- a/include/renderer_null/renderer_null.hpp +++ b/include/renderer_null/renderer_null.hpp @@ -15,4 +15,8 @@ class RendererNull final : public Renderer { void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override; void drawVertices(PICA::PrimType primType, std::span vertices) override; void screenshot(const std::string& name) override; + +#ifdef PANDA3DS_FRONTEND_QT + virtual void initGraphicsContext([[maybe_unused]] GL::Context* context) override {} +#endif }; diff --git a/include/renderer_sw/renderer_sw.hpp b/include/renderer_sw/renderer_sw.hpp index 9e68b00f..e944c16c 100644 --- a/include/renderer_sw/renderer_sw.hpp +++ b/include/renderer_sw/renderer_sw.hpp @@ -15,4 +15,8 @@ class RendererSw final : public Renderer { void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override; void drawVertices(PICA::PrimType primType, std::span vertices) override; void screenshot(const std::string& name) override; + +#ifdef PANDA3DS_FRONTEND_QT + virtual void initGraphicsContext([[maybe_unused]] GL::Context* context) override {} +#endif }; diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index 50b26cc7..14989c13 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -46,7 +46,7 @@ void RendererGL::reset() { } } -void RendererGL::initGraphicsContext(SDL_Window* window) { +void RendererGL::initGraphicsContextInternal() { gl.reset(); auto gl_resources = cmrc::RendererGL::get_filesystem(); @@ -168,6 +168,10 @@ void RendererGL::initGraphicsContext(SDL_Window* window) { reset(); } +// The OpenGL renderer doesn't need to do anything with the GL context (For Qt frontend) or the SDL window (For SDL frontend) +// So we just call initGraphicsContextInternal for both +void RendererGL::initGraphicsContext([[maybe_unused]] SDL_Window* window) { initGraphicsContextInternal(); } + // Set up the OpenGL blending context to match the emulated PICA void RendererGL::setupBlending() { // Map of PICA blending equations to OpenGL blending equations. The unused blending equations are equivalent to equation 0 (add) diff --git a/src/emulator.cpp b/src/emulator.cpp index e51ca47b..387239fa 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -41,6 +41,8 @@ Emulator::Emulator() } #endif + // Only create SDL Window for SDL frontend +#ifdef PANDA3DS_FRONTEND_SDL if (needOpenGL) { // Demand 3.3 core for software renderer, or 4.1 core for OpenGL renderer (max available on MacOS) // MacOS gets mad if we don't explicitly demand a core profile @@ -71,6 +73,7 @@ Emulator::Emulator() Helpers::warn("Window creation failed: %s", SDL_GetError()); } } +#endif #endif if (SDL_WasInit(SDL_INIT_GAMECONTROLLER)) { @@ -126,6 +129,8 @@ void Emulator::reset(ReloadOption reload) { void Emulator::step() {} void Emulator::render() {} +// Main loop for the SDL frontend. TODO: Move it to a dedicated file +#ifdef PANDA3DS_FRONTEND_SDL void Emulator::run() { programRunning = true; @@ -403,6 +408,7 @@ void Emulator::run() { SDL_GL_SwapWindow(window); } } +#endif // Only resume if a ROM is properly loaded void Emulator::resume() { running = (romType != ROMType::None); } @@ -562,9 +568,6 @@ bool Emulator::loadELF(std::ifstream& file) { return true; } -// Reset our graphics context and initialize the GPU's graphics context -void Emulator::initGraphicsContext() { gpu.initGraphicsContext(window); } - #ifdef PANDA3DS_ENABLE_DISCORD_RPC void Emulator::updateDiscord() { if (config.discordRpcEnabled) { diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index a27c3319..0a201c55 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -4,7 +4,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) setWindowTitle("Alber"); // Enable drop events for loading ROMs setAcceptDrops(true); - resize(320, 240); + resize(400, 240 * 2); screen.show(); // Set our menu bar up @@ -25,9 +25,42 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) themeSelect->setGeometry(40, 40, 100, 50); themeSelect->show(); connect(themeSelect, &QComboBox::currentIndexChanged, this, [&](int index) { setTheme(static_cast(index)); }); + + emu = new Emulator(); + + // The emulator graphics context for the thread should be initialized in the emulator thread due to how GL contexts work + emuThread = std::thread([&]() { + const RendererType rendererType = emu->getConfig().rendererType; + + if (rendererType == RendererType::OpenGL || rendererType == RendererType::Software || rendererType == RendererType::Null) { + // Make GL context current for this thread, enable VSync + GL::Context* glContext = screen.getGLContext(); + glContext->MakeCurrent(); + glContext->SetSwapInterval(1); + + emu->initGraphicsContext(glContext); + } else { + Helpers::panic("Unsupported renderer type for the Qt backend! Vulkan on Qt is currently WIP, try the SDL frontend instead!"); + } + + bool success = emu->loadROM("OoT.3ds"); + if (!success) { + Helpers::panic("Failed to load ROM"); + } + + while (true) { + emu->runFrame(); + screen.getGLContext()->SwapBuffers(); + } + }); } -MainWindow::~MainWindow() { delete menuBar; } +// Cleanup when the main window closes +MainWindow::~MainWindow() { + delete emu; + delete menuBar; + delete themeSelect; +} void MainWindow::setTheme(Theme theme) { currentTheme = theme; diff --git a/src/panda_qt/screen.cpp b/src/panda_qt/screen.cpp index 496edb47..d01682a8 100644 --- a/src/panda_qt/screen.cpp +++ b/src/panda_qt/screen.cpp @@ -20,7 +20,7 @@ #ifdef PANDA3DS_ENABLE_OPENGL ScreenWidget::ScreenWidget(QWidget* parent) : QWidget(parent) { // Create a native window for use with our graphics API of choice - resize(320, 240); + resize(400, 240 * 2); setAutoFillBackground(false); setAttribute(Qt::WA_NativeWindow, true); @@ -33,18 +33,6 @@ ScreenWidget::ScreenWidget(QWidget* parent) : QWidget(parent) { if (!createGLContext()) { Helpers::panic("Failed to create GL context for display"); } - - // Make our context current to use it - glContext->MakeCurrent(); - // Enable VSync for now - glContext->SetSwapInterval(1); - - OpenGL::setViewport(320, 240); - OpenGL::setClearColor(1.0, 0.0, 0.0, 1.0); - OpenGL::clearColor(); - - // Swap buffers to display our red as a test - glContext->SwapBuffers(); } bool ScreenWidget::createGLContext() {