diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e522bdb..a6754814 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() @@ -735,6 +735,7 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE) 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 ) + set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp 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 @@ -742,6 +743,10 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE) include/panda_qt/disabled_widget_overlay.hpp include/panda_qt/input_window.hpp ) + if (APPLE AND ENABLE_METAL) + set(FRONTEND_SOURCE_FILES ${FRONTEND_SOURCE_FILES} src/panda_qt/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..4a3580b0 100644 --- a/include/PICA/gpu.hpp +++ b/include/PICA/gpu.hpp @@ -109,11 +109,8 @@ 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(void* window) { renderer->initGraphicsContext(window); } 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 bed01937..be770d91 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -108,9 +108,8 @@ class Emulator { #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); } + void initGraphicsContext(GL::Context* glContext) { gpu.initGraphicsContext(glContext); } + void initGraphicsContext(void* window) { gpu.initGraphicsContext(window); } #endif RomFS::DumpingResult dumpRomFS(const std::filesystem::path& path); diff --git a/include/panda_qt/screen.hpp b/include/panda_qt/screen.hpp index 270bf10f..a461fb5d 100644 --- a/include/panda_qt/screen.hpp +++ b/include/panda_qt/screen.hpp @@ -9,6 +9,8 @@ // OpenGL widget for drawing the 3DS screen class ScreenWidget : public QWidget { + enum class API { OpenGL, Metal, Vulkan }; + Q_OBJECT public: @@ -20,6 +22,7 @@ class ScreenWidget : public QWidget { void resizeSurface(u32 width, u32 height); GL::Context* getGLContext() { return glContext.get(); } + void* getMTKLayer() { return mtkLayer; } // Dimensions of our output surface u32 surfaceWidth = 0; @@ -30,6 +33,8 @@ class ScreenWidget : public QWidget { u32 previousWidth = 0; u32 previousHeight = 0; + API api = API::OpenGL; + // Coordinates (x/y/width/height) for the two screens in window space, used for properly handling touchscreen regardless // of layout or resizing ScreenLayout::WindowCoordinates screenCoordinates; @@ -40,10 +45,18 @@ class ScreenWidget : public QWidget { void reloadScreenLayout(ScreenLayout::Layout newLayout, float newTopScreenSize); private: + // GL context for GL-based renderers std::unique_ptr glContext = nullptr; + + // CA::MetalLayer for the Metal renderer + void* mtkLayer = nullptr; + ResizeCallback resizeCallback; bool createGLContext(); + bool createMetalContext(); + + void resizeMetalView(); qreal devicePixelRatioFromScreen() const; int scaledWindowWidth() const; diff --git a/include/renderer.hpp b/include/renderer.hpp index 40f244db..2803e821 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* window) = 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 diff --git a/include/renderer_gl/renderer_gl.hpp b/include/renderer_gl/renderer_gl.hpp index 4708fdee..ac7fc2fa 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* window) 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; diff --git a/include/renderer_mtl/renderer_mtl.hpp b/include/renderer_mtl/renderer_mtl.hpp index c9b4b9b6..3d197aa6 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* window) 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/include/renderer_null/renderer_null.hpp b/include/renderer_null/renderer_null.hpp index 50a724d8..56fb3652 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* window) 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/include/renderer_sw/renderer_sw.hpp b/include/renderer_sw/renderer_sw.hpp index dd12bf0a..b520eb6d 100644 --- a/include/renderer_sw/renderer_sw.hpp +++ b/include/renderer_sw/renderer_sw.hpp @@ -9,7 +9,7 @@ class RendererSw final : public Renderer { void reset() override; void display() override; - void initGraphicsContext(SDL_Window* window) override; + void initGraphicsContext(void* window) 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/include/renderer_vk/renderer_vk.hpp b/include/renderer_vk/renderer_vk.hpp index 25cc26f8..00bfd9da 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* window) 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..9ece493c 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* window) { 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..12f35aa2 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; @@ -59,7 +59,7 @@ void RendererMTL::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 +151,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..08f005a4 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* window) {} 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..1a59f38d 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* window) { 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..00f0040e 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -168,7 +168,8 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) } 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!"); + emu->initGraphicsContext((void*)nullptr); + emu->getRenderer()->setMTKLayer(screen->getMTKLayer()); } else { Helpers::panic("Unsupported graphics backend for Qt frontend!"); } @@ -213,6 +214,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 :("); } diff --git a/src/panda_qt/metal_context.mm b/src/panda_qt/metal_context.mm new file mode 100644 index 00000000..1aa242f8 --- /dev/null +++ b/src/panda_qt/metal_context.mm @@ -0,0 +1,51 @@ +#import +#import +#import +#import +#import +#import + +#import "panda_qt/screen.hpp" + +bool ScreenWidget::createMetalContext() { + NSView* nativeView = (NSView*)this->winId(); + CAMetalLayer* metalLayer = [CAMetalLayer layer]; + + if (!metalLayer) { + return false; + } + + id 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 ScreenWidget::resizeMetalView() { + NSView* view = (NSView*)this->windowHandle()->winId(); + CAMetalLayer* metalLayer = (CAMetalLayer*)[view layer]; + + if (metalLayer) { + metalLayer.drawableSize = CGSizeMake(surfaceWidth, surfaceHeight); + } +} \ No newline at end of file diff --git a/src/panda_qt/screen.cpp b/src/panda_qt/screen.cpp index 0876bb71..9f125f88 100644 --- a/src/panda_qt/screen.cpp +++ b/src/panda_qt/screen.cpp @@ -31,8 +31,16 @@ ScreenWidget::ScreenWidget(ResizeCallback resizeCallback, QWidget* parent) : QWi setMouseTracking(true); show(); - if (!createGLContext()) { - Helpers::panic("Failed to create GL context for display"); + if (api == API::OpenGL) { + if (!createGLContext()) { + Helpers::panic("Failed to create GL context for display"); + } + } else if (api == API::Metal) { + if (!createMetalContext()) { + Helpers::panic("Failed to create Metal context for display"); + } + } else { + Helpers::panic("Unspported api for Qt screen widget"); } } @@ -47,15 +55,18 @@ void ScreenWidget::resizeEvent(QResizeEvent* event) { this->windowInfo = *windowInfo; } - reloadScreenCoordinates(); + if (api == API::Metal) { + resizeMetalView(); + } - // This will call take care of calling resizeSurface from the emulator thread + reloadScreenCoordinates(); + // 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 ScreenWidget::resizeSurface(u32 width, u32 height) { - if (previousWidth != width || previousHeight != height) { + if (api == API::OpenGL && (previousWidth != width || previousHeight != height)) { if (glContext) { glContext->ResizeSurface(width, height); }