diff --git a/CMakeLists.txt b/CMakeLists.txt index ef98e1af..620a1c27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,7 +95,8 @@ endif() set(SOURCE_FILES src/main.cpp src/emulator.cpp src/io_file.cpp src/config.cpp src/core/CPU/cpu_dynarmic.cpp src/core/CPU/dynarmic_cycles.cpp - src/core/memory.cpp src/renderer.cpp src/httpserver.cpp src/stb_image_write.c + src/core/memory.cpp src/renderer.cpp src/core/renderer_null/renderer_null.cpp + src/httpserver.cpp src/stb_image_write.c ) set(CRYPTO_SOURCE_FILES src/core/crypto/aes_engine.cpp) set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limits.cpp @@ -130,7 +131,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp include/dynarmic_cp15.hpp include/kernel/resource_limits.hpp include/kernel/kernel_types.hpp include/kernel/config_mem.hpp include/services/service_manager.hpp include/services/apt.hpp include/kernel/handles.hpp include/services/hid.hpp include/services/fs.hpp - include/services/gsp_gpu.hpp include/services/gsp_lcd.hpp include/arm_defs.hpp + include/services/gsp_gpu.hpp include/services/gsp_lcd.hpp include/arm_defs.hpp include/renderer_null/renderer_null.hpp include/PICA/gpu.hpp include/PICA/regs.hpp include/services/ndm.hpp include/PICA/shader.hpp include/PICA/shader_unit.hpp include/PICA/float_types.hpp include/logger.hpp include/loader/ncch.hpp include/loader/ncsd.hpp include/io_file.hpp diff --git a/include/config.hpp b/include/config.hpp index bdb697bf..6bccdad6 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -1,10 +1,14 @@ #pragma once #include +#include "renderer.hpp" + // Remember to initialize every field here to its default value otherwise bad things will happen struct EmulatorConfig { bool shaderJitEnabled = false; + RendererType rendererType = RendererType::OpenGL; + EmulatorConfig(const std::filesystem::path& path); void load(const std::filesystem::path& path); void save(const std::filesystem::path& path); }; \ No newline at end of file diff --git a/include/emulator.hpp b/include/emulator.hpp index f27cd990..d99eff1d 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -25,13 +25,13 @@ enum class ROMType { }; class Emulator { + EmulatorConfig config; CPU cpu; GPU gpu; Memory memory; Kernel kernel; Crypto::AESEngine aesEngine; - EmulatorConfig config; SDL_Window* window; #ifdef PANDA3DS_ENABLE_OPENGL @@ -70,8 +70,8 @@ class Emulator { public: // Decides whether to reload or not reload the ROM when resetting. We use enum class over a plain bool for clarity. // If NoReload is selected, the emulator will not reload its selected ROM. This is useful for things like booting up the emulator, or resetting to - // change ROMs. If Reload is selected, the emulator will reload its selected ROM. This is useful for eg a "reset" button that keeps the current ROM - // and just resets the emu + // change ROMs. If Reload is selected, the emulator will reload its selected ROM. This is useful for eg a "reset" button that keeps the current + // ROM and just resets the emu enum class ReloadOption { NoReload, Reload }; Emulator(); diff --git a/include/renderer.hpp b/include/renderer.hpp index 5a2b40b4..e14afcea 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -1,11 +1,19 @@ #pragma once #include #include +#include #include "PICA/pica_vertex.hpp" #include "PICA/regs.hpp" #include "helpers.hpp" +enum class RendererType : s8 { + // Todo: Auto = -1, + Null = 0, + OpenGL = 1, + Vulkan = 2, +}; + class GPU; class Renderer { @@ -28,6 +36,8 @@ class Renderer { virtual ~Renderer(); static constexpr u32 vertexBufferSize = 0x10000; + static std::optional typeFromString(std::string inString); + static const char* typeToString(RendererType rendererType); virtual void reset() = 0; virtual void display() = 0; // Display the 3DS screen contents to the window diff --git a/include/renderer_gl/renderer_gl.hpp b/include/renderer_gl/renderer_gl.hpp index 0e7f7bcb..15d12ade 100644 --- a/include/renderer_gl/renderer_gl.hpp +++ b/include/renderer_gl/renderer_gl.hpp @@ -68,6 +68,7 @@ class RendererGL final : public Renderer { public: RendererGL(GPU& gpu, const std::array& internalRegs) : Renderer(gpu, internalRegs) {} + ~RendererGL() override; void reset() override; void display() override; // Display the 3DS screen contents to the window diff --git a/include/renderer_null/renderer_null.hpp b/include/renderer_null/renderer_null.hpp new file mode 100644 index 00000000..29080786 --- /dev/null +++ b/include/renderer_null/renderer_null.hpp @@ -0,0 +1,17 @@ +#include "renderer.hpp" + +class GPU; + +class RendererNull final : public Renderer { + public: + RendererNull(GPU& gpu, const std::array& internalRegs); + ~RendererNull() override; + + void reset() override; + void display() override; + void initGraphicsContext() override; + void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override; + void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) override; + void drawVertices(PICA::PrimType primType, std::span vertices) override; + void screenshot(const std::string& name) override; +}; \ No newline at end of file diff --git a/src/config.cpp b/src/config.cpp index 6c9a8450..a5e9330c 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1,6 +1,7 @@ #include "config.hpp" #include +#include #include "helpers.hpp" #include "toml.hpp" @@ -9,6 +10,8 @@ // We are legally allowed, as per the author's wish, to use the above code without any licensing restrictions // However we still want to follow the license as closely as possible and offer the proper attributions. +EmulatorConfig::EmulatorConfig(const std::filesystem::path& path) { load(path); } + void EmulatorConfig::load(const std::filesystem::path& path) { // If the configuration file does not exist, create it and return std::error_code error; @@ -31,6 +34,17 @@ void EmulatorConfig::load(const std::filesystem::path& path) { if (gpuResult.is_ok()) { auto gpu = gpuResult.unwrap(); + // Get renderer + auto rendererName = toml::find_or(gpu, "Renderer", "OpenGL"); + auto configRendererType = Renderer::typeFromString(rendererName); + + if (configRendererType.has_value()) { + rendererType = configRendererType.value(); + } else { + Helpers::warn("Invalid renderer specified: %s\n", rendererName.c_str()); + rendererType = RendererType::OpenGL; + } + shaderJitEnabled = toml::find_or(gpu, "EnableShaderJIT", false); } } @@ -43,7 +57,7 @@ void EmulatorConfig::save(const std::filesystem::path& path) { if (std::filesystem::exists(path, error)) { try { data = toml::parse(path); - } catch (std::exception& ex) { + } catch (const std::exception& ex) { Helpers::warn("Exception trying to parse config file. Exception: %s\n", ex.what()); return; } @@ -55,6 +69,7 @@ void EmulatorConfig::save(const std::filesystem::path& path) { } data["GPU"]["EnableShaderJIT"] = shaderJitEnabled; + data["GPU"]["Renderer"] = std::string(Renderer::typeToString(rendererType)); std::ofstream file(path, std::ios::out); file << data; diff --git a/src/core/PICA/gpu.cpp b/src/core/PICA/gpu.cpp index 15c99c42..d75b0ae5 100644 --- a/src/core/PICA/gpu.cpp +++ b/src/core/PICA/gpu.cpp @@ -7,7 +7,7 @@ #include "PICA/float_types.hpp" #include "PICA/regs.hpp" - +#include "renderer_null/renderer_null.hpp" #ifdef PANDA3DS_ENABLE_OPENGL #include "renderer_gl/renderer_gl.hpp" #endif @@ -20,10 +20,27 @@ GPU::GPU(Memory& mem, EmulatorConfig& config) : mem(mem), config(config) { vram = new u8[vramSize]; mem.setVRAM(vram); // Give the bus a pointer to our VRAM - // TODO: Configurable backend + switch (config.rendererType) { + case RendererType::Null: { + renderer.reset(new RendererNull(*this, regs)); + break; + } #ifdef PANDA3DS_ENABLE_OPENGL - renderer.reset(new RendererGL(*this, regs)); + case RendererType::OpenGL: { + renderer.reset(new RendererGL(*this, regs)); + break; + } #endif + + case RendererType::Vulkan: { + Helpers::panic("Vulkan is not supported yet, please pick another renderer"); + } + + default: { + Helpers::panic("Rendering backend not supported: %s", Renderer::typeToString(config.rendererType)); + break; + } + } } void GPU::reset() { diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index 94639f51..4d948444 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -14,6 +14,8 @@ using namespace Floats; using namespace Helpers; using namespace PICA; +RendererGL::~RendererGL() {} + void RendererGL::reset() { depthBufferCache.reset(); colourBufferCache.reset(); diff --git a/src/core/renderer_null/renderer_null.cpp b/src/core/renderer_null/renderer_null.cpp new file mode 100644 index 00000000..9df2ddeb --- /dev/null +++ b/src/core/renderer_null/renderer_null.cpp @@ -0,0 +1,12 @@ +#include "renderer_null/renderer_null.hpp" + +RendererNull::RendererNull(GPU& gpu, const std::array& internalRegs) : Renderer(gpu, internalRegs) {} +RendererNull::~RendererNull() {} + +void RendererNull::reset() {} +void RendererNull::display() {} +void RendererNull::initGraphicsContext() {} +void RendererNull::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {} +void RendererNull::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) {} +void RendererNull::drawVertices(PICA::PrimType primType, std::span vertices) {} +void RendererNull::screenshot(const std::string& name) {} \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index 23baa258..8ab6e06e 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -14,7 +14,9 @@ __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1; } #endif -Emulator::Emulator() : kernel(cpu, memory, gpu), cpu(memory, kernel), gpu(memory, config), memory(cpu.getTicksRef()) { +Emulator::Emulator() + : config(std::filesystem::current_path() / "config.toml"), kernel(cpu, memory, gpu), cpu(memory, kernel), gpu(memory, config), + memory(cpu.getTicksRef()) { if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) { Helpers::panic("Failed to initialize SDL2"); } @@ -26,24 +28,26 @@ Emulator::Emulator() : kernel(cpu, memory, gpu), cpu(memory, kernel), gpu(memory } #ifdef PANDA3DS_ENABLE_OPENGL - // Request OpenGL 4.1 Core (Max available on MacOS) - // MacOS gets mad if we don't explicitly demand a core profile - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); - window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_OPENGL); + if (config.rendererType == RendererType::OpenGL) { + // Request OpenGL 4.1 Core (Max available on MacOS) + // MacOS gets mad if we don't explicitly demand a core profile + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_OPENGL); - if (window == nullptr) { - Helpers::panic("Window creation failed: %s", SDL_GetError()); - } + if (window == nullptr) { + Helpers::panic("Window creation failed: %s", SDL_GetError()); + } - glContext = SDL_GL_CreateContext(window); - if (glContext == nullptr) { - Helpers::panic("OpenGL context creation failed: %s", SDL_GetError()); - } + glContext = SDL_GL_CreateContext(window); + if (glContext == nullptr) { + Helpers::panic("OpenGL context creation failed: %s", SDL_GetError()); + } - if (!gladLoadGL(reinterpret_cast(SDL_GL_GetProcAddress))) { - Helpers::panic("OpenGL init failed: %s", SDL_GetError()); + if (!gladLoadGL(reinterpret_cast(SDL_GL_GetProcAddress))) { + Helpers::panic("OpenGL init failed: %s", SDL_GetError()); + } } #endif @@ -56,7 +60,6 @@ Emulator::Emulator() : kernel(cpu, memory, gpu), cpu(memory, kernel), gpu(memory } } - config.load(std::filesystem::current_path() / "config.toml"); reset(ReloadOption::NoReload); } diff --git a/src/main.cpp b/src/main.cpp index 1559565a..66a04b9e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,9 +1,9 @@ #include "emulator.hpp" -int main (int argc, char *argv[]) { - Emulator emu; +int main(int argc, char *argv[]) { + Emulator emu; - emu.initGraphicsContext(); + emu.initGraphicsContext(); if (argc > 1) { auto romPath = std::filesystem::current_path() / argv[1]; diff --git a/src/renderer.cpp b/src/renderer.cpp index b3da0501..3ba29aea 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -1,4 +1,35 @@ #include "renderer.hpp" +#include +#include + Renderer::Renderer(GPU& gpu, const std::array& internalRegs) : gpu(gpu), regs(internalRegs) {} -Renderer::~Renderer() {} \ No newline at end of file +Renderer::~Renderer() {} + +std::optional Renderer::typeFromString(std::string inString) { + // Transform to lower-case to make the setting case-insensitive + std::transform(inString.begin(), inString.end(), inString.begin(), [](unsigned char c) { return std::tolower(c); }); + + // Huge table of possible names and misspellings + // Please stop misspelling Vulkan as Vulcan + static const std::unordered_map map = { + {"null", RendererType::Null}, {"nil", RendererType::Null}, {"none", RendererType::Null}, + {"gl", RendererType::OpenGL}, {"ogl", RendererType::OpenGL}, {"opengl", RendererType::OpenGL}, + {"vk", RendererType::Vulkan}, {"vulkan", RendererType::Vulkan}, {"vulcan", RendererType::Vulkan}, + }; + + if (auto search = map.find(inString); search != map.end()) { + return search->second; + } + + return std::nullopt; +} + +const char* Renderer::typeToString(RendererType rendererType) { + switch (rendererType) { + case RendererType::Null: return "null"; + case RendererType::OpenGL: return "opengl"; + case RendererType::Vulkan: return "vulkan"; + default: return "Invalid"; + } +} \ No newline at end of file