diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e522bdb..8e8e9428 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -675,7 +675,7 @@ if(ENABLE_METAL AND APPLE) target_sources(AlberCore PRIVATE ${RENDERER_MTL_SOURCE_FILES}) target_compile_definitions(AlberCore PUBLIC "PANDA3DS_ENABLE_METAL=1") - target_include_directories(AlberCore PRIVATE third_party/metal-cpp) + target_include_directories(AlberCore PUBLIC third_party/metal-cpp) # TODO: check if all of them are needed target_link_libraries(AlberCore PUBLIC "-framework Metal" "-framework Foundation" "-framework QuartzCore" resources_renderer_mtl) endif() @@ -730,18 +730,25 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE) option(GENERATE_QT_TRANSLATION "Generate Qt translation file" OFF) set(QT_LANGUAGES docs/translations) - set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp src/panda_qt/about_window.cpp + set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/main_window.cpp src/panda_qt/about_window.cpp src/panda_qt/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp src/panda_qt/cheats_window.cpp src/panda_qt/mappings.cpp src/panda_qt/patch_window.cpp src/panda_qt/elided_label.cpp src/panda_qt/shader_editor.cpp src/panda_qt/translations.cpp src/panda_qt/thread_debugger.cpp src/panda_qt/cpu_debugger.cpp src/panda_qt/dsp_debugger.cpp src/panda_qt/input_window.cpp + src/panda_qt/screen/screen.cpp src/panda_qt/screen/screen_gl.cpp src/panda_qt/screen/screen_mtl.cpp ) - set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp + + set(FRONTEND_HEADER_FILES include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp include/panda_qt/config_window.hpp include/panda_qt/text_editor.hpp include/panda_qt/cheats_window.hpp include/panda_qt/patch_window.hpp include/panda_qt/elided_label.hpp include/panda_qt/shader_editor.hpp include/panda_qt/thread_debugger.hpp include/panda_qt/cpu_debugger.hpp include/panda_qt/dsp_debugger.hpp - include/panda_qt/disabled_widget_overlay.hpp include/panda_qt/input_window.hpp + include/panda_qt/disabled_widget_overlay.hpp include/panda_qt/input_window.hpp include/panda_qt/screen/screen.hpp + include/panda_qt/screen/screen_gl.hpp include/panda_qt/screen/screen_mtl.hpp ) + if (APPLE AND ENABLE_METAL) + set(FRONTEND_SOURCE_FILES ${FRONTEND_SOURCE_FILES} src/panda_qt/screen/metal_context.mm) + endif() + source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES}) source_group("Header Files\\Qt" FILES ${FRONTEND_HEADER_FILES}) include_directories(${Qt6Gui_PRIVATE_INCLUDE_DIRS}) diff --git a/include/PICA/gpu.hpp b/include/PICA/gpu.hpp index 77bfb8ed..e62f41b3 100644 --- a/include/PICA/gpu.hpp +++ b/include/PICA/gpu.hpp @@ -109,11 +109,7 @@ class GPU { void screenshot(const std::string& name) { renderer->screenshot(name); } void deinitGraphicsContext() { renderer->deinitGraphicsContext(); } -#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 initGraphicsContext(void* context) { renderer->initGraphicsContext(context); } void fireDMA(u32 dest, u32 source, u32 size); void reset(); diff --git a/include/emulator.hpp b/include/emulator.hpp index bed01937..366f86f2 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -106,12 +106,8 @@ class Emulator { bool loadELF(const std::filesystem::path& path); bool loadELF(std::ifstream& file); -#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(SDL_Window* window) { gpu.initGraphicsContext(window); } -#endif + // For passing the SDL Window, GL context, etc from the frontend to the renderer + void initGraphicsContext(void* context) { gpu.initGraphicsContext(context); } RomFS::DumpingResult dumpRomFS(const std::filesystem::path& path); void setOutputSize(u32 width, u32 height) { gpu.setOutputSize(width, height); } diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index 7bdf6b96..b259a1bc 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -20,7 +20,7 @@ #include "panda_qt/cpu_debugger.hpp" #include "panda_qt/dsp_debugger.hpp" #include "panda_qt/patch_window.hpp" -#include "panda_qt/screen.hpp" +#include "panda_qt/screen/screen.hpp" #include "panda_qt/shader_editor.hpp" #include "panda_qt/text_editor.hpp" #include "panda_qt/thread_debugger.hpp" @@ -136,7 +136,7 @@ class MainWindow : public QMainWindow { void loadKeybindings(); void saveKeybindings(); - // Tracks whether we are using an OpenGL-backed renderer or a Vulkan-backed renderer + // Tracks what graphics API is backing our renderer bool usingGL = false; bool usingVk = false; bool usingMtl = false; diff --git a/include/panda_qt/screen.hpp b/include/panda_qt/screen/screen.hpp similarity index 54% rename from include/panda_qt/screen.hpp rename to include/panda_qt/screen/screen.hpp index 270bf10f..4908b096 100644 --- a/include/panda_qt/screen.hpp +++ b/include/panda_qt/screen/screen.hpp @@ -1,25 +1,27 @@ #pragma once #include #include -#include #include "gl/context.h" #include "screen_layout.hpp" #include "window_info.h" -// OpenGL widget for drawing the 3DS screen +// Abstract screen widget for drawing the 3DS screen. We've got a child class for each graphics API (ScreenWidgetGL, ScreenWidgetMTL, ...) class ScreenWidget : public QWidget { Q_OBJECT public: using ResizeCallback = std::function; - ScreenWidget(ResizeCallback resizeCallback, QWidget* parent = nullptr); - void resizeEvent(QResizeEvent* event) override; - // Called by the emulator thread for resizing the actual GL surface, since the emulator thread owns the GL context - void resizeSurface(u32 width, u32 height); + enum class API { OpenGL, Metal, Vulkan }; - GL::Context* getGLContext() { return glContext.get(); } + ScreenWidget(API api, ResizeCallback resizeCallback, QWidget* parent = nullptr); + virtual ~ScreenWidget() {} + + void resizeEvent(QResizeEvent* event) override; + + virtual GL::Context* getGLContext() { return nullptr; } + virtual void* getMTKLayer() { return nullptr; } // Dimensions of our output surface u32 surfaceWidth = 0; @@ -30,8 +32,9 @@ class ScreenWidget : public QWidget { u32 previousWidth = 0; u32 previousHeight = 0; - // Coordinates (x/y/width/height) for the two screens in window space, used for properly handling touchscreen regardless - // of layout or resizing + API api = API::OpenGL; + + // Coordinates (x/y/width/height) for the two screens in window space, used for properly handling touchscreen ScreenLayout::WindowCoordinates screenCoordinates; // Screen layouts and sizes ScreenLayout::Layout screenLayout = ScreenLayout::Layout::Default; @@ -39,16 +42,23 @@ class ScreenWidget : public QWidget { void reloadScreenLayout(ScreenLayout::Layout newLayout, float newTopScreenSize); - private: - std::unique_ptr glContext = nullptr; + // Creates a screen widget depending on the graphics API we're using + static ScreenWidget* getWidget(API api, ResizeCallback resizeCallback, QWidget* parent = nullptr); + + // Called by the emulator thread on OpenGL for resizing the actual GL surface, since the emulator thread owns the GL context + virtual void resizeSurface(u32 width, u32 height) {}; + + protected: ResizeCallback resizeCallback; - bool createGLContext(); + virtual bool createContext() = 0; + virtual void resizeDisplay() = 0; + std::optional getWindowInfo(); + private: qreal devicePixelRatioFromScreen() const; int scaledWindowWidth() const; int scaledWindowHeight() const; - std::optional getWindowInfo(); void reloadScreenCoordinates(); }; diff --git a/include/panda_qt/screen/screen_gl.hpp b/include/panda_qt/screen/screen_gl.hpp new file mode 100644 index 00000000..04a2c11b --- /dev/null +++ b/include/panda_qt/screen/screen_gl.hpp @@ -0,0 +1,18 @@ +#pragma once +#include + +#include "gl/context.h" +#include "panda_qt/screen/screen.hpp" + +class ScreenWidgetGL : public ScreenWidget { + std::unique_ptr glContext = nullptr; + + public: + ScreenWidgetGL(API api, ResizeCallback resizeCallback, QWidget* parent = nullptr); + + virtual GL::Context* getGLContext() override; + virtual bool createContext() override; + + virtual void resizeDisplay() override; + virtual void resizeSurface(u32 width, u32 height) override; +}; \ No newline at end of file diff --git a/include/panda_qt/screen/screen_mtl.hpp b/include/panda_qt/screen/screen_mtl.hpp new file mode 100644 index 00000000..cdd240e6 --- /dev/null +++ b/include/panda_qt/screen/screen_mtl.hpp @@ -0,0 +1,18 @@ +#pragma once +#include "panda_qt/screen/screen.hpp" + +class ScreenWidgetMTL : public ScreenWidget { + void* mtkLayer = nullptr; + + // Objective-C++ functions for handling the Metal context + bool createMetalContext(); + void resizeMetalView(); + + public: + ScreenWidgetMTL(API api, ResizeCallback resizeCallback, QWidget* parent = nullptr); + ~ScreenWidgetMTL() override; + + virtual void* getMTKLayer() override; + virtual bool createContext() override; + virtual void resizeDisplay() override; +}; \ No newline at end of file diff --git a/include/renderer.hpp b/include/renderer.hpp index 40f244db..b3f81faf 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -69,7 +69,7 @@ class Renderer { virtual void reset() = 0; virtual void display() = 0; // Display the 3DS screen contents to the window - virtual void initGraphicsContext(SDL_Window* window) = 0; // Initialize graphics context + virtual void initGraphicsContext(void* context) = 0; // Initialize graphics context 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; @@ -91,9 +91,9 @@ class Renderer { // Called to notify the core to use OpenGL ES and not desktop GL virtual void setupGLES() {} - // Only relevant for Metal renderer on iOS - // Passes a SwiftUI MTKView's layer (CAMetalLayer) to the renderer - virtual void setMTKLayer(void* layer) {}; + // Used for Metal renderer on Qt and iOS + // Passes an NSView's backing layer (CAMetalLayer) to the renderer + virtual void setMTKLayer(void* layer) { Helpers::panic("Renderer doesn't support MTK Layer"); }; // This function is called on every draw call before parsing vertex data. // It is responsible for things like looking up which vertex/fragment shaders to use, recompiling them if they don't exist, choosing between @@ -101,11 +101,6 @@ class Renderer { // Returns whether this draw is eligible for using hardware-accelerated shaders or if shaders should run on the CPU virtual bool prepareForDraw(ShaderUnit& shaderUnit, PICA::DrawAcceleration* accel) { return false; } - // 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 4708fdee..2023966f 100644 --- a/include/renderer_gl/renderer_gl.hpp +++ b/include/renderer_gl/renderer_gl.hpp @@ -191,7 +191,7 @@ class RendererGL final : public Renderer { void reset() override; void display() override; // Display the 3DS screen contents to the window - void initGraphicsContext(SDL_Window* window) override; // Initialize graphics context + void initGraphicsContext(void* context) override; // Initialize graphics context void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override; // Clear a GPU buffer in VRAM void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) override; // Perform display transfer void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override; @@ -211,10 +211,6 @@ class RendererGL final : public Renderer { void resetStateManager() { gl.reset(); } void initUbershader(OpenGL::Program& program); -#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; }; \ No newline at end of file diff --git a/include/renderer_mtl/renderer_mtl.hpp b/include/renderer_mtl/renderer_mtl.hpp index c9b4b9b6..29eb02d4 100644 --- a/include/renderer_mtl/renderer_mtl.hpp +++ b/include/renderer_mtl/renderer_mtl.hpp @@ -13,7 +13,6 @@ #include "mtl_vertex_buffer_cache.hpp" #include "renderer.hpp" - // HACK: use the OpenGL cache #include "../renderer_gl/surface_cache.hpp" @@ -30,7 +29,7 @@ class RendererMTL final : public Renderer { void reset() override; void display() override; - void initGraphicsContext(SDL_Window* window) override; + void initGraphicsContext(void* context) 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 textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override; @@ -38,10 +37,6 @@ class RendererMTL final : public Renderer { void screenshot(const std::string& name) override; void deinitGraphicsContext() override; -#ifdef PANDA3DS_FRONTEND_QT - virtual void initGraphicsContext([[maybe_unused]] GL::Context* context) override {} -#endif - virtual void setMTKLayer(void* layer) override; private: diff --git a/include/renderer_null/renderer_null.hpp b/include/renderer_null/renderer_null.hpp index 50a724d8..28ab41af 100644 --- a/include/renderer_null/renderer_null.hpp +++ b/include/renderer_null/renderer_null.hpp @@ -9,7 +9,7 @@ class RendererNull final : public Renderer { void reset() override; void display() override; - void initGraphicsContext(SDL_Window* window) override; + void initGraphicsContext(void* context) 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 textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override; @@ -20,8 +20,4 @@ class RendererNull final : public Renderer { // Tell the GPU core that we'll handle vertex fetch & shader execution in the renderer in order to speed up execution. // Of course, we don't do this and geometry is never actually processed, since this is the null renderer. virtual bool prepareForDraw(ShaderUnit& shaderUnit, PICA::DrawAcceleration* accel) override { return true; }; - -#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 dd12bf0a..e2dd90cb 100644 --- a/include/renderer_sw/renderer_sw.hpp +++ b/include/renderer_sw/renderer_sw.hpp @@ -9,15 +9,11 @@ class RendererSw final : public Renderer { void reset() override; void display() override; - void initGraphicsContext(SDL_Window* window) override; + void initGraphicsContext(void* context) 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 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; void deinitGraphicsContext() override; - -#ifdef PANDA3DS_FRONTEND_QT - virtual void initGraphicsContext([[maybe_unused]] GL::Context* context) override {} -#endif }; diff --git a/include/renderer_vk/renderer_vk.hpp b/include/renderer_vk/renderer_vk.hpp index 25cc26f8..a274e1c7 100644 --- a/include/renderer_vk/renderer_vk.hpp +++ b/include/renderer_vk/renderer_vk.hpp @@ -113,7 +113,7 @@ class RendererVK final : public Renderer { void reset() override; void display() override; - void initGraphicsContext(SDL_Window* window) override; + void initGraphicsContext(void* context) 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 textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override; diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index 8ab7f1df..659a1e1d 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -191,7 +191,7 @@ void RendererGL::initGraphicsContextInternal() { // 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(); } +void RendererGL::initGraphicsContext([[maybe_unused]] void* context) { initGraphicsContextInternal(); } // Set up the OpenGL blending context to match the emulated PICA void RendererGL::setupBlending() { diff --git a/src/core/renderer_mtl/renderer_mtl.cpp b/src/core/renderer_mtl/renderer_mtl.cpp index 71cdd616..8f924d43 100644 --- a/src/core/renderer_mtl/renderer_mtl.cpp +++ b/src/core/renderer_mtl/renderer_mtl.cpp @@ -10,8 +10,8 @@ #include "PICA/gpu.hpp" #include "PICA/pica_hash.hpp" -#include "screen_layout.hpp" #include "SDL_metal.h" +#include "screen_layout.hpp" using namespace PICA; @@ -57,9 +57,7 @@ void RendererMTL::reset() { colorRenderTargetCache.reset(); } -void RendererMTL::setMTKLayer(void* layer) { - metalLayer = (CA::MetalLayer*)layer; -} +void RendererMTL::setMTKLayer(void* layer) { metalLayer = (CA::MetalLayer*)layer; } void RendererMTL::display() { CA::MetalDrawable* drawable = metalLayer->nextDrawable(); @@ -151,13 +149,13 @@ void RendererMTL::display() { drawable->release(); } -void RendererMTL::initGraphicsContext(SDL_Window* window) { - // On iOS, the SwiftUI side handles the MetalLayer -#ifdef PANDA3DS_IOS +void RendererMTL::initGraphicsContext(void* window) { + // On Qt and IOS, the frontend handles the MetalLayer +#if defined(PANDA3DS_FRONTEND_QT) || defined(PANDA3DS_IOS) device = MTL::CreateSystemDefaultDevice(); #else // TODO: what should be the type of the view? - void* view = SDL_Metal_CreateView(window); + void* view = SDL_Metal_CreateView((SDL_Window*)window); metalLayer = (CA::MetalLayer*)SDL_Metal_GetLayer(view); device = MTL::CreateSystemDefaultDevice(); metalLayer->setDevice(device); diff --git a/src/core/renderer_null/renderer_null.cpp b/src/core/renderer_null/renderer_null.cpp index 4be9d089..4db2696f 100644 --- a/src/core/renderer_null/renderer_null.cpp +++ b/src/core/renderer_null/renderer_null.cpp @@ -6,7 +6,7 @@ RendererNull::~RendererNull() {} void RendererNull::reset() {} void RendererNull::display() {} -void RendererNull::initGraphicsContext(SDL_Window* window) {} +void RendererNull::initGraphicsContext(void* context) {} 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::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) {} diff --git a/src/core/renderer_sw/renderer_sw.cpp b/src/core/renderer_sw/renderer_sw.cpp index 86b6032f..a117e373 100644 --- a/src/core/renderer_sw/renderer_sw.cpp +++ b/src/core/renderer_sw/renderer_sw.cpp @@ -7,7 +7,7 @@ RendererSw::~RendererSw() {} void RendererSw::reset() { printf("RendererSW: Unimplemented reset call\n"); } void RendererSw::display() { printf("RendererSW: Unimplemented display call\n"); } -void RendererSw::initGraphicsContext(SDL_Window* window) { printf("RendererSW: Unimplemented initGraphicsContext call\n"); } +void RendererSw::initGraphicsContext(void* context) { printf("RendererSW: Unimplemented initGraphicsContext call\n"); } void RendererSw::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) { printf("RendererSW: Unimplemented clearBuffer call\n"); } void RendererSw::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) { diff --git a/src/core/renderer_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp index 57533bde..0bf9fae5 100644 --- a/src/core/renderer_vk/renderer_vk.cpp +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -173,7 +173,8 @@ std::tuple createGraphicsPipeline( vk::PipelineDynamicStateCreateInfo dynamicState = {}; static vk::DynamicState dynamicStates[] = {// The viewport and scissor of the framebuffer will be dynamic at // run-time - vk::DynamicState::eViewport, vk::DynamicState::eScissor}; + vk::DynamicState::eViewport, vk::DynamicState::eScissor + }; dynamicState.dynamicStateCount = std::size(dynamicStates); dynamicState.pDynamicStates = dynamicStates; @@ -469,7 +470,8 @@ vk::RenderPass RendererVK::getRenderPass(vk::Format colorFormat, std::optional("vkGetInstanceProcAddr")); @@ -978,8 +980,8 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { } // Create surface - if (window) { - if (VkSurfaceKHR newSurface; SDL_Vulkan_CreateSurface(window, instance.get(), &newSurface)) { + if (targetWindow) { + if (VkSurfaceKHR newSurface; SDL_Vulkan_CreateSurface(targetWindow, instance.get(), &newSurface)) { swapchainSurface = newSurface; } else { Helpers::warn("Error creating Vulkan surface"); @@ -1127,7 +1129,7 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { vk::Extent2D swapchainExtent; { int windowWidth, windowHeight; - SDL_Vulkan_GetDrawableSize(window, &windowWidth, &windowHeight); + SDL_Vulkan_GetDrawableSize(targetWindow, &windowWidth, &windowHeight); swapchainExtent.width = windowWidth; swapchainExtent.height = windowHeight; } @@ -1275,7 +1277,8 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { static vk::DescriptorSetLayoutBinding displayShaderLayout[] = { {// Just a singular texture slot - 0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, + 0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment + }, }; if (auto createResult = Vulkan::DescriptorUpdateBatch::create(device.get()); createResult.has_value()) { @@ -1407,7 +1410,8 @@ void RendererVK::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 co static vk::ImageSubresourceRange depthStencilRanges[2] = { vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1), - vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eStencil, 0, 1, 0, 1)}; + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eStencil, 0, 1, 0, 1) + }; // Clear RenderTarget getCurrentCommandBuffer().clearDepthStencilImage( diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index b592226b..f74f2061 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -10,6 +10,7 @@ #include "cheats.hpp" #include "input_mappings.hpp" #include "panda_qt/dsp_debugger.hpp" +#include "panda_qt/screen/screen.hpp" #include "sdl_sensors.hpp" #include "services/dsp.hpp" #include "version.hpp" @@ -25,8 +26,19 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) resize(800, 240 * 4); show(); + const RendererType rendererType = emu->getConfig().rendererType; + usingGL = (rendererType == RendererType::OpenGL || rendererType == RendererType::Software || rendererType == RendererType::Null); + usingVk = (rendererType == RendererType::Vulkan); + usingMtl = (rendererType == RendererType::Metal); + + ScreenWidget::API api = ScreenWidget::API::OpenGL; + if (usingVk) + api = ScreenWidget::API::Vulkan; + else if (usingMtl) + api = ScreenWidget::API::Metal; + // We pass a callback to the screen widget that will be triggered every time we resize the screen - screen = new ScreenWidget([this](u32 width, u32 height) { handleScreenResize(width, height); }, this); + screen = ScreenWidget::getWidget(api, [this](u32 width, u32 height) { handleScreenResize(width, height); }, this); setCentralWidget(screen); appRunning = true; @@ -149,28 +161,29 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) // The emulator graphics context for the thread should be initialized in the emulator thread due to how GL contexts work emuThread = std::thread([this]() { - const RendererType rendererType = emu->getConfig().rendererType; - usingGL = (rendererType == RendererType::OpenGL || rendererType == RendererType::Software || rendererType == RendererType::Null); - usingVk = (rendererType == RendererType::Vulkan); - usingMtl = (rendererType == RendererType::Metal); + switch (screen->api) { + case ScreenWidget::API::OpenGL: { + // Make GL context current for this thread, enable VSync + GL::Context* glContext = screen->getGLContext(); + glContext->MakeCurrent(); + glContext->SetSwapInterval(emu->getConfig().vsyncEnabled ? 1 : 0); - if (usingGL) { - // Make GL context current for this thread, enable VSync - GL::Context* glContext = screen->getGLContext(); - glContext->MakeCurrent(); - glContext->SetSwapInterval(emu->getConfig().vsyncEnabled ? 1 : 0); + if (glContext->IsGLES()) { + emu->getRenderer()->setupGLES(); + } - if (glContext->IsGLES()) { - emu->getRenderer()->setupGLES(); + emu->initGraphicsContext(glContext); + break; } - emu->initGraphicsContext(glContext); - } else if (usingVk) { - Helpers::panic("Vulkan on Qt is currently WIP, try the SDL frontend instead!"); - } else if (usingMtl) { - Helpers::panic("Metal on Qt currently doesn't work, try the SDL frontend instead!"); - } else { - Helpers::panic("Unsupported graphics backend for Qt frontend!"); + case ScreenWidget::API::Metal: { + emu->initGraphicsContext(nullptr); + emu->getRenderer()->setMTKLayer(screen->getMTKLayer()); + break; + } + + case ScreenWidget::API::Vulkan: Helpers::panic("Vulkan on Qt is currently WIP, try the SDL frontend instead!"); break; + default: Helpers::panic("Unsupported graphics backend for Qt frontend!"); break; } // We have to initialize controllers on the same thread they'll be polled in @@ -213,6 +226,8 @@ void MainWindow::emuThreadMainLoop() { void MainWindow::swapEmuBuffer() { if (usingGL) { screen->getGLContext()->SwapBuffers(); + } else if (usingMtl) { + // The renderer itself calls presentDrawable to swap buffers on Metal } else { Helpers::panic("[Qt] Don't know how to swap buffers for the current rendering backend :("); } @@ -290,6 +305,7 @@ MainWindow::~MainWindow() { delete aboutWindow; delete configWindow; delete cheatsEditor; + delete screen; delete luaEditor; } diff --git a/src/panda_qt/screen/metal_context.mm b/src/panda_qt/screen/metal_context.mm new file mode 100644 index 00000000..e7a508e4 --- /dev/null +++ b/src/panda_qt/screen/metal_context.mm @@ -0,0 +1,71 @@ +#import +#import +#import +#import +#import +#import + +#import "panda_qt/screen/screen_mtl.hpp" + +id metalDevice = nil; + +bool ScreenWidgetMTL::createMetalContext() { + NSView* nativeView = (NSView*)this->winId(); + // Retain the layer so that we can manually memory manage it. + CAMetalLayer* metalLayer = [[CAMetalLayer layer] retain]; + + if (!metalLayer) { + return false; + } + + metalDevice = MTLCreateSystemDefaultDevice(); + + if (!metalDevice) { + NSLog(@"Failed to create metal device"); + return false; + } + + metalLayer.device = metalDevice; + metalLayer.framebufferOnly = NO; + metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm; + + CGFloat scale = [nativeView window].backingScaleFactor; + CGSize pointSize = nativeView.bounds.size; + + metalLayer.contentsScale = scale; + metalLayer.drawableSize = CGSizeMake(pointSize.width * scale, pointSize.height * scale); + + [nativeView setLayer:metalLayer]; + [nativeView setWantsLayer:YES]; + + CA::MetalLayer* cppLayer = (CA::MetalLayer*)metalLayer; + mtkLayer = static_cast(cppLayer); + + return true; +} + +void ScreenWidgetMTL::resizeMetalView() { + NSView* view = (NSView*)this->windowHandle()->winId(); + CAMetalLayer* metalLayer = (CAMetalLayer*)[view layer]; + + if (metalLayer) { + metalLayer.drawableSize = CGSizeMake(surfaceWidth, surfaceHeight); + } +} + +ScreenWidgetMTL::~ScreenWidgetMTL() { + if (mtkLayer) { + CAMetalLayer* metalLayer = (__bridge CAMetalLayer*)static_cast(mtkLayer); + + NSView* view = (NSView*)this->winId(); + [view setLayer:nil]; + [view setWantsLayer:NO]; + + // Release Metal device and layer + metalLayer.device = nil; + [metalLayer release]; + [metalDevice release]; + + mtkLayer = nullptr; + } +} \ No newline at end of file diff --git a/src/panda_qt/screen.cpp b/src/panda_qt/screen/screen.cpp similarity index 66% rename from src/panda_qt/screen.cpp rename to src/panda_qt/screen/screen.cpp index 0876bb71..94835284 100644 --- a/src/panda_qt/screen.cpp +++ b/src/panda_qt/screen/screen.cpp @@ -1,10 +1,12 @@ +#ifdef PANDA3DS_ENABLE_OPENGL #include "opengl.hpp" +#endif // opengl.hpp must be included at the very top. This comment exists to make clang-format not reorder it :p + #include #include #include #include -#include #include #include @@ -12,16 +14,16 @@ #include #endif -#include "panda_qt/screen.hpp" +#include "panda_qt/screen/screen.hpp" +#include "panda_qt/screen/screen_gl.hpp" +#include "panda_qt/screen/screen_mtl.hpp" -// OpenGL screen widget, based on https://github.com/stenzek/duckstation/blob/master/src/duckstation-qt/displaywidget.cpp +// Screen widget, based on https://github.com/stenzek/duckstation/blob/master/src/duckstation-qt/displaywidget.cpp // and https://github.com/melonDS-emu/melonDS/blob/master/src/frontend/qt_sdl/main.cpp #ifdef PANDA3DS_ENABLE_OPENGL -ScreenWidget::ScreenWidget(ResizeCallback resizeCallback, QWidget* parent) : QWidget(parent), resizeCallback(resizeCallback) { +ScreenWidget::ScreenWidget(API api, ResizeCallback resizeCallback, QWidget* parent) : api(api), QWidget(parent), resizeCallback(resizeCallback) { // Create a native window for use with our graphics API of choice - resize(800, 240 * 4); - setAutoFillBackground(false); setAttribute(Qt::WA_NativeWindow, true); setAttribute(Qt::WA_NoSystemBackground, true); @@ -29,11 +31,8 @@ ScreenWidget::ScreenWidget(ResizeCallback resizeCallback, QWidget* parent) : QWi setAttribute(Qt::WA_KeyCompression, false); setFocusPolicy(Qt::StrongFocus); setMouseTracking(true); - show(); - if (!createGLContext()) { - Helpers::panic("Failed to create GL context for display"); - } + // The graphics context, as well as resizing and showing the widget, is handled by the screen backend } void ScreenWidget::resizeEvent(QResizeEvent* event) { @@ -48,18 +47,7 @@ void ScreenWidget::resizeEvent(QResizeEvent* event) { } reloadScreenCoordinates(); - - // This will call take care of calling resizeSurface from the emulator thread - resizeCallback(surfaceWidth, surfaceHeight); -} - -// Note: This will run on the emulator thread, we don't want any Qt calls happening there. -void ScreenWidget::resizeSurface(u32 width, u32 height) { - if (previousWidth != width || previousHeight != height) { - if (glContext) { - glContext->ResizeSurface(width, height); - } - } + resizeDisplay(); } void ScreenWidget::reloadScreenCoordinates() { @@ -73,30 +61,6 @@ void ScreenWidget::reloadScreenLayout(ScreenLayout::Layout newLayout, float newT reloadScreenCoordinates(); } -bool ScreenWidget::createGLContext() { - // List of GL context versions we will try. Anything 4.1+ is good for desktop OpenGL, and 3.1+ for OpenGL ES - static constexpr std::array versionsToTry = { - GL::Context::Version{GL::Context::Profile::Core, 4, 6}, GL::Context::Version{GL::Context::Profile::Core, 4, 5}, - GL::Context::Version{GL::Context::Profile::Core, 4, 4}, GL::Context::Version{GL::Context::Profile::Core, 4, 3}, - GL::Context::Version{GL::Context::Profile::Core, 4, 2}, GL::Context::Version{GL::Context::Profile::Core, 4, 1}, - GL::Context::Version{GL::Context::Profile::ES, 3, 2}, GL::Context::Version{GL::Context::Profile::ES, 3, 1}, - }; - - std::optional windowInfo = getWindowInfo(); - if (windowInfo.has_value()) { - this->windowInfo = *windowInfo; - - glContext = GL::Context::Create(*getWindowInfo(), versionsToTry); - if (glContext == nullptr) { - return false; - } - - glContext->DoneCurrent(); - } - - return glContext != nullptr; -} - qreal ScreenWidget::devicePixelRatioFromScreen() const { const QScreen* screenForRatio = windowHandle()->screen(); if (!screenForRatio) { @@ -156,3 +120,15 @@ std::optional ScreenWidget::getWindowInfo() { return wi; } #endif + +ScreenWidget* ScreenWidget::getWidget(API api, ResizeCallback resizeCallback, QWidget* parent) { + if (api == API::OpenGL) { + return new ScreenWidgetGL(api, resizeCallback, parent); + } else if (api == API::Metal) { + return new ScreenWidgetMTL(api, resizeCallback, parent); + } else if (api == API::Vulkan) { + Helpers::panic("Vulkan is not yet supported on Panda3DS-Qt. Try SDL instead"); + } else { + Helpers::panic("ScreenWidget::getWidget: Unimplemented graphics API"); + } +} \ No newline at end of file diff --git a/src/panda_qt/screen/screen_gl.cpp b/src/panda_qt/screen/screen_gl.cpp new file mode 100644 index 00000000..0d737f94 --- /dev/null +++ b/src/panda_qt/screen/screen_gl.cpp @@ -0,0 +1,64 @@ +#include "panda_qt/screen/screen_gl.hpp" + +#include + +#ifdef PANDA3DS_ENABLE_OPENGL +ScreenWidgetGL::ScreenWidgetGL(API api, ResizeCallback resizeCallback, QWidget* parent) : ScreenWidget(api, resizeCallback, parent) { + // On Wayland + OpenGL, we have to show the window before we can create a graphics context. + resize(800, 240 * 4); + show(); + + if (!createContext()) { + Helpers::panic("Failed to create GL context for display"); + } +} + +bool ScreenWidgetGL::createContext() { + // List of GL context versions we will try. Anything 4.1+ is good for desktop OpenGL, and 3.1+ for OpenGL ES + static constexpr std::array versionsToTry = { + GL::Context::Version{GL::Context::Profile::Core, 4, 6}, GL::Context::Version{GL::Context::Profile::Core, 4, 5}, + GL::Context::Version{GL::Context::Profile::Core, 4, 4}, GL::Context::Version{GL::Context::Profile::Core, 4, 3}, + GL::Context::Version{GL::Context::Profile::Core, 4, 2}, GL::Context::Version{GL::Context::Profile::Core, 4, 1}, + GL::Context::Version{GL::Context::Profile::ES, 3, 2}, GL::Context::Version{GL::Context::Profile::ES, 3, 1}, + }; + + std::optional windowInfo = getWindowInfo(); + if (windowInfo.has_value()) { + this->windowInfo = *windowInfo; + + glContext = GL::Context::Create(*getWindowInfo(), versionsToTry); + if (glContext == nullptr) { + return false; + } + + glContext->DoneCurrent(); + } + + return glContext != nullptr; +} + +void ScreenWidgetGL::resizeDisplay() { + // This will call take care of calling resizeSurface from the emulator thread, as the GL renderer must resize from the emu thread + resizeCallback(surfaceWidth, surfaceHeight); +} + +// Note: This will run on the emulator thread, we don't want any Qt calls happening there. +void ScreenWidgetGL::resizeSurface(u32 width, u32 height) { + if (previousWidth != width || previousHeight != height) { + if (glContext) { + glContext->ResizeSurface(width, height); + } + } +} + +GL::Context* ScreenWidgetGL::getGLContext() { return glContext.get(); } +#else +ScreenWidgetGL::ScreenWidgetGL(API api, ResizeCallback resizeCallback, QWidget* parent) : ScreenWidget(api, resizeCallback, parent) { + Helpers::panic("OpenGL renderer not supported. Make sure you've compiled with OpenGL support and that you're on a compatible platform"); +} + +GL::Context* ScreenWidgetGL::getGLContext() { nullptr; } +bool ScreenWidgetGL::createContext() { return false; } +void ScreenWidgetGL::resizeDisplay() {} +void ScreenWidgetGL::resizeSurface(u32 width, u32 height) {} +#endif \ No newline at end of file diff --git a/src/panda_qt/screen/screen_mtl.cpp b/src/panda_qt/screen/screen_mtl.cpp new file mode 100644 index 00000000..472b166b --- /dev/null +++ b/src/panda_qt/screen/screen_mtl.cpp @@ -0,0 +1,33 @@ +#include "panda_qt/screen/screen_mtl.hpp" + +#ifdef PANDA3DS_ENABLE_METAL +ScreenWidgetMTL::ScreenWidgetMTL(API api, ResizeCallback resizeCallback, QWidget* parent) : ScreenWidget(api, resizeCallback, parent) { + if (!createContext()) { + Helpers::panic("Failed to create Metal context for display"); + } + + resize(800, 240 * 4); + show(); +} + +void ScreenWidgetMTL::resizeDisplay() { + resizeMetalView(); + resizeCallback(surfaceWidth, surfaceHeight); +} + +bool ScreenWidgetMTL::createContext() { return createMetalContext(); } +void* ScreenWidgetMTL::getMTKLayer() { return mtkLayer; } + +#else +ScreenWidgetMTL::ScreenWidgetMTL(API api, ResizeCallback resizeCallback, QWidget* parent) : ScreenWidget(api, resizeCallback, parent) { + Helpers::panic("Metal renderer not supported. Make sure you've compiled with Metal support and that you're on a compatible platform"); +} + +ScreenWidgetMTL::~ScreenWidgetMTL() {} +bool ScreenWidgetMTL::createContext() { return false; } +bool ScreenWidgetMTL::createMetalContext() { return false; } +void* ScreenWidgetMTL::getMTKLayer() { return nullptr; } + +void ScreenWidgetMTL::resizeDisplay() {} +void ScreenWidgetMTL::resizeMetalView() {} +#endif \ No newline at end of file