mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-08 07:05:40 +12:00
Merge pull request #303 from wheremyfoodat/rhappy
Get emulator rendering working with Qt
This commit is contained in:
commit
844803089b
12 changed files with 98 additions and 22 deletions
|
@ -86,10 +86,15 @@ class GPU {
|
||||||
bool lightingLUTDirty = false;
|
bool lightingLUTDirty = false;
|
||||||
|
|
||||||
GPU(Memory& mem, EmulatorConfig& config);
|
GPU(Memory& mem, EmulatorConfig& config);
|
||||||
void initGraphicsContext(SDL_Window* window) { renderer->initGraphicsContext(window); }
|
|
||||||
void display() { renderer->display(); }
|
void display() { renderer->display(); }
|
||||||
void screenshot(const std::string& name) { renderer->screenshot(name); }
|
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 fireDMA(u32 dest, u32 source, u32 size);
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,10 @@
|
||||||
#include "http_server.hpp"
|
#include "http_server.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef PANDA3DS_FRONTEND_QT
|
||||||
|
#include "gl/context.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
enum class ROMType {
|
enum class ROMType {
|
||||||
None,
|
None,
|
||||||
ELF,
|
ELF,
|
||||||
|
@ -37,10 +41,12 @@ class Emulator {
|
||||||
Crypto::AESEngine aesEngine;
|
Crypto::AESEngine aesEngine;
|
||||||
Cheats cheats;
|
Cheats cheats;
|
||||||
|
|
||||||
|
#ifdef PANDA3DS_FRONTEND_SDL
|
||||||
SDL_Window* window;
|
SDL_Window* window;
|
||||||
|
|
||||||
#ifdef PANDA3DS_ENABLE_OPENGL
|
#ifdef PANDA3DS_ENABLE_OPENGL
|
||||||
SDL_GLContext glContext;
|
SDL_GLContext glContext;
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
SDL_GameController* gameController = nullptr;
|
SDL_GameController* gameController = nullptr;
|
||||||
|
@ -106,5 +112,13 @@ class Emulator {
|
||||||
bool load3DSX(const std::filesystem::path& path);
|
bool load3DSX(const std::filesystem::path& path);
|
||||||
bool loadELF(const std::filesystem::path& path);
|
bool loadELF(const std::filesystem::path& path);
|
||||||
bool loadELF(std::ifstream& file);
|
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; }
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
#include <QMenuBar>
|
#include <QMenuBar>
|
||||||
#include <QPalette>
|
#include <QPalette>
|
||||||
#include <QtWidgets>
|
#include <QtWidgets>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "emulator.hpp"
|
||||||
#include "panda_qt/screen.hpp"
|
#include "panda_qt/screen.hpp"
|
||||||
|
|
||||||
class MainWindow : public QMainWindow {
|
class MainWindow : public QMainWindow {
|
||||||
|
@ -18,6 +20,10 @@ class MainWindow : public QMainWindow {
|
||||||
Dark = 2,
|
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;
|
QComboBox* themeSelect = nullptr;
|
||||||
QMenuBar* menuBar = nullptr;
|
QMenuBar* menuBar = nullptr;
|
||||||
ScreenWidget screen;
|
ScreenWidget screen;
|
||||||
|
|
|
@ -11,6 +11,7 @@ class ScreenWidget : public QWidget {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ScreenWidget(QWidget* parent = nullptr);
|
ScreenWidget(QWidget* parent = nullptr);
|
||||||
|
GL::Context* getGLContext() { return glContext.get(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<GL::Context> glContext = nullptr;
|
std::unique_ptr<GL::Context> glContext = nullptr;
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
#include "PICA/regs.hpp"
|
#include "PICA/regs.hpp"
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
|
|
||||||
|
#ifdef PANDA3DS_FRONTEND_QT
|
||||||
|
#include "gl/context.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
enum class RendererType : s8 {
|
enum class RendererType : s8 {
|
||||||
// Todo: Auto = -1,
|
// Todo: Auto = -1,
|
||||||
Null = 0,
|
Null = 0,
|
||||||
|
@ -54,6 +58,11 @@ class Renderer {
|
||||||
|
|
||||||
virtual void screenshot(const std::string& name) = 0;
|
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) {
|
void setFBSize(u32 width, u32 height) {
|
||||||
fbSize[0] = width;
|
fbSize[0] = width;
|
||||||
fbSize[1] = height;
|
fbSize[1] = height;
|
||||||
|
|
|
@ -67,6 +67,7 @@ class RendererGL final : public Renderer {
|
||||||
void setupTextureEnvState();
|
void setupTextureEnvState();
|
||||||
void bindTexturesToSlots();
|
void bindTexturesToSlots();
|
||||||
void updateLightingLUT();
|
void updateLightingLUT();
|
||||||
|
void initGraphicsContextInternal();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RendererGL(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs)
|
RendererGL(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs)
|
||||||
|
@ -83,6 +84,10 @@ class RendererGL final : public Renderer {
|
||||||
|
|
||||||
std::optional<ColourBuffer> getColourBuffer(u32 addr, PICA::ColorFmt format, u32 width, u32 height, bool createIfnotFound = true);
|
std::optional<ColourBuffer> 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
|
// Take a screenshot of the screen and store it in a file
|
||||||
void screenshot(const std::string& name) override;
|
void screenshot(const std::string& name) override;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override;
|
||||||
void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) override;
|
void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) override;
|
||||||
void screenshot(const std::string& name) override;
|
void screenshot(const std::string& name) override;
|
||||||
|
|
||||||
|
#ifdef PANDA3DS_FRONTEND_QT
|
||||||
|
virtual void initGraphicsContext([[maybe_unused]] GL::Context* context) override {}
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override;
|
||||||
void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) override;
|
void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) override;
|
||||||
void screenshot(const std::string& name) override;
|
void screenshot(const std::string& name) override;
|
||||||
|
|
||||||
|
#ifdef PANDA3DS_FRONTEND_QT
|
||||||
|
virtual void initGraphicsContext([[maybe_unused]] GL::Context* context) override {}
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
|
@ -46,7 +46,7 @@ void RendererGL::reset() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererGL::initGraphicsContext(SDL_Window* window) {
|
void RendererGL::initGraphicsContextInternal() {
|
||||||
gl.reset();
|
gl.reset();
|
||||||
|
|
||||||
auto gl_resources = cmrc::RendererGL::get_filesystem();
|
auto gl_resources = cmrc::RendererGL::get_filesystem();
|
||||||
|
@ -168,6 +168,10 @@ void RendererGL::initGraphicsContext(SDL_Window* window) {
|
||||||
reset();
|
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
|
// Set up the OpenGL blending context to match the emulated PICA
|
||||||
void RendererGL::setupBlending() {
|
void RendererGL::setupBlending() {
|
||||||
// Map of PICA blending equations to OpenGL blending equations. The unused blending equations are equivalent to equation 0 (add)
|
// Map of PICA blending equations to OpenGL blending equations. The unused blending equations are equivalent to equation 0 (add)
|
||||||
|
|
|
@ -41,6 +41,8 @@ Emulator::Emulator()
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Only create SDL Window for SDL frontend
|
||||||
|
#ifdef PANDA3DS_FRONTEND_SDL
|
||||||
if (needOpenGL) {
|
if (needOpenGL) {
|
||||||
// Demand 3.3 core for software renderer, or 4.1 core for OpenGL renderer (max available on MacOS)
|
// 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
|
// 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());
|
Helpers::warn("Window creation failed: %s", SDL_GetError());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (SDL_WasInit(SDL_INIT_GAMECONTROLLER)) {
|
if (SDL_WasInit(SDL_INIT_GAMECONTROLLER)) {
|
||||||
|
@ -126,6 +129,8 @@ void Emulator::reset(ReloadOption reload) {
|
||||||
void Emulator::step() {}
|
void Emulator::step() {}
|
||||||
void Emulator::render() {}
|
void Emulator::render() {}
|
||||||
|
|
||||||
|
// Main loop for the SDL frontend. TODO: Move it to a dedicated file
|
||||||
|
#ifdef PANDA3DS_FRONTEND_SDL
|
||||||
void Emulator::run() {
|
void Emulator::run() {
|
||||||
programRunning = true;
|
programRunning = true;
|
||||||
|
|
||||||
|
@ -403,6 +408,7 @@ void Emulator::run() {
|
||||||
SDL_GL_SwapWindow(window);
|
SDL_GL_SwapWindow(window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Only resume if a ROM is properly loaded
|
// Only resume if a ROM is properly loaded
|
||||||
void Emulator::resume() { running = (romType != ROMType::None); }
|
void Emulator::resume() { running = (romType != ROMType::None); }
|
||||||
|
@ -562,9 +568,6 @@ bool Emulator::loadELF(std::ifstream& file) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset our graphics context and initialize the GPU's graphics context
|
|
||||||
void Emulator::initGraphicsContext() { gpu.initGraphicsContext(window); }
|
|
||||||
|
|
||||||
#ifdef PANDA3DS_ENABLE_DISCORD_RPC
|
#ifdef PANDA3DS_ENABLE_DISCORD_RPC
|
||||||
void Emulator::updateDiscord() {
|
void Emulator::updateDiscord() {
|
||||||
if (config.discordRpcEnabled) {
|
if (config.discordRpcEnabled) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
||||||
setWindowTitle("Alber");
|
setWindowTitle("Alber");
|
||||||
// Enable drop events for loading ROMs
|
// Enable drop events for loading ROMs
|
||||||
setAcceptDrops(true);
|
setAcceptDrops(true);
|
||||||
resize(320, 240);
|
resize(400, 240 * 2);
|
||||||
screen.show();
|
screen.show();
|
||||||
|
|
||||||
// Set our menu bar up
|
// Set our menu bar up
|
||||||
|
@ -25,9 +25,42 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
||||||
themeSelect->setGeometry(40, 40, 100, 50);
|
themeSelect->setGeometry(40, 40, 100, 50);
|
||||||
themeSelect->show();
|
themeSelect->show();
|
||||||
connect(themeSelect, &QComboBox::currentIndexChanged, this, [&](int index) { setTheme(static_cast<Theme>(index)); });
|
connect(themeSelect, &QComboBox::currentIndexChanged, this, [&](int index) { setTheme(static_cast<Theme>(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) {
|
void MainWindow::setTheme(Theme theme) {
|
||||||
currentTheme = theme;
|
currentTheme = theme;
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
#ifdef PANDA3DS_ENABLE_OPENGL
|
#ifdef PANDA3DS_ENABLE_OPENGL
|
||||||
ScreenWidget::ScreenWidget(QWidget* parent) : QWidget(parent) {
|
ScreenWidget::ScreenWidget(QWidget* parent) : QWidget(parent) {
|
||||||
// Create a native window for use with our graphics API of choice
|
// Create a native window for use with our graphics API of choice
|
||||||
resize(320, 240);
|
resize(400, 240 * 2);
|
||||||
|
|
||||||
setAutoFillBackground(false);
|
setAutoFillBackground(false);
|
||||||
setAttribute(Qt::WA_NativeWindow, true);
|
setAttribute(Qt::WA_NativeWindow, true);
|
||||||
|
@ -33,18 +33,6 @@ ScreenWidget::ScreenWidget(QWidget* parent) : QWidget(parent) {
|
||||||
if (!createGLContext()) {
|
if (!createGLContext()) {
|
||||||
Helpers::panic("Failed to create GL context for display");
|
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() {
|
bool ScreenWidget::createGLContext() {
|
||||||
|
|
Loading…
Add table
Reference in a new issue