Qt: Initial support for Metal renderer

This commit is contained in:
wheremyfoodat 2025-07-26 03:14:59 +03:00
commit 864472ebb2
18 changed files with 124 additions and 42 deletions

View file

@ -675,7 +675,7 @@ if(ENABLE_METAL AND APPLE)
target_sources(AlberCore PRIVATE ${RENDERER_MTL_SOURCE_FILES}) target_sources(AlberCore PRIVATE ${RENDERER_MTL_SOURCE_FILES})
target_compile_definitions(AlberCore PUBLIC "PANDA3DS_ENABLE_METAL=1") 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 # TODO: check if all of them are needed
target_link_libraries(AlberCore PUBLIC "-framework Metal" "-framework Foundation" "-framework QuartzCore" resources_renderer_mtl) target_link_libraries(AlberCore PUBLIC "-framework Metal" "-framework Foundation" "-framework QuartzCore" resources_renderer_mtl)
endif() 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/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/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 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/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/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 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("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES})
source_group("Header Files\\Qt" FILES ${FRONTEND_HEADER_FILES}) source_group("Header Files\\Qt" FILES ${FRONTEND_HEADER_FILES})
include_directories(${Qt6Gui_PRIVATE_INCLUDE_DIRS}) include_directories(${Qt6Gui_PRIVATE_INCLUDE_DIRS})

View file

@ -109,11 +109,8 @@ class GPU {
void screenshot(const std::string& name) { renderer->screenshot(name); } void screenshot(const std::string& name) { renderer->screenshot(name); }
void deinitGraphicsContext() { renderer->deinitGraphicsContext(); } void deinitGraphicsContext() { renderer->deinitGraphicsContext(); }
#if defined(PANDA3DS_FRONTEND_SDL) void initGraphicsContext(void* window) { renderer->initGraphicsContext(window); }
void initGraphicsContext(SDL_Window* window) { renderer->initGraphicsContext(window); }
#elif defined(PANDA3DS_FRONTEND_QT)
void initGraphicsContext(GL::Context* context) { renderer->initGraphicsContext(context); } 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();

View file

@ -108,9 +108,8 @@ class Emulator {
#ifdef PANDA3DS_FRONTEND_QT #ifdef PANDA3DS_FRONTEND_QT
// For passing the GL context from Qt to the renderer // For passing the GL context from Qt to the renderer
void initGraphicsContext(GL::Context* glContext) { gpu.initGraphicsContext(nullptr); } void initGraphicsContext(GL::Context* glContext) { gpu.initGraphicsContext(glContext); }
#else void initGraphicsContext(void* window) { gpu.initGraphicsContext(window); }
void initGraphicsContext(SDL_Window* window) { gpu.initGraphicsContext(window); }
#endif #endif
RomFS::DumpingResult dumpRomFS(const std::filesystem::path& path); RomFS::DumpingResult dumpRomFS(const std::filesystem::path& path);

View file

@ -9,6 +9,8 @@
// OpenGL widget for drawing the 3DS screen // OpenGL widget for drawing the 3DS screen
class ScreenWidget : public QWidget { class ScreenWidget : public QWidget {
enum class API { OpenGL, Metal, Vulkan };
Q_OBJECT Q_OBJECT
public: public:
@ -20,6 +22,7 @@ class ScreenWidget : public QWidget {
void resizeSurface(u32 width, u32 height); void resizeSurface(u32 width, u32 height);
GL::Context* getGLContext() { return glContext.get(); } GL::Context* getGLContext() { return glContext.get(); }
void* getMTKLayer() { return mtkLayer; }
// Dimensions of our output surface // Dimensions of our output surface
u32 surfaceWidth = 0; u32 surfaceWidth = 0;
@ -30,6 +33,8 @@ class ScreenWidget : public QWidget {
u32 previousWidth = 0; u32 previousWidth = 0;
u32 previousHeight = 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 // Coordinates (x/y/width/height) for the two screens in window space, used for properly handling touchscreen regardless
// of layout or resizing // of layout or resizing
ScreenLayout::WindowCoordinates screenCoordinates; ScreenLayout::WindowCoordinates screenCoordinates;
@ -40,10 +45,18 @@ class ScreenWidget : public QWidget {
void reloadScreenLayout(ScreenLayout::Layout newLayout, float newTopScreenSize); void reloadScreenLayout(ScreenLayout::Layout newLayout, float newTopScreenSize);
private: private:
// GL context for GL-based renderers
std::unique_ptr<GL::Context> glContext = nullptr; std::unique_ptr<GL::Context> glContext = nullptr;
// CA::MetalLayer for the Metal renderer
void* mtkLayer = nullptr;
ResizeCallback resizeCallback; ResizeCallback resizeCallback;
bool createGLContext(); bool createGLContext();
bool createMetalContext();
void resizeMetalView();
qreal devicePixelRatioFromScreen() const; qreal devicePixelRatioFromScreen() const;
int scaledWindowWidth() const; int scaledWindowWidth() const;

View file

@ -69,7 +69,7 @@ class Renderer {
virtual void reset() = 0; virtual void reset() = 0;
virtual void display() = 0; // Display the 3DS screen contents to the window 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 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 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 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 // Called to notify the core to use OpenGL ES and not desktop GL
virtual void setupGLES() {} virtual void setupGLES() {}
// Only relevant for Metal renderer on iOS // Used for Metal renderer on Qt and iOS
// Passes a SwiftUI MTKView's layer (CAMetalLayer) to the renderer // Passes an NSView's backing layer (CAMetalLayer) to the renderer
virtual void setMTKLayer(void* layer) {}; 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. // 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 // It is responsible for things like looking up which vertex/fragment shaders to use, recompiling them if they don't exist, choosing between

View file

@ -191,7 +191,7 @@ class RendererGL final : public Renderer {
void reset() override; void reset() override;
void display() override; // Display the 3DS screen contents to the window 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 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 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; void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override;

View file

@ -13,7 +13,6 @@
#include "mtl_vertex_buffer_cache.hpp" #include "mtl_vertex_buffer_cache.hpp"
#include "renderer.hpp" #include "renderer.hpp"
// HACK: use the OpenGL cache // HACK: use the OpenGL cache
#include "../renderer_gl/surface_cache.hpp" #include "../renderer_gl/surface_cache.hpp"
@ -30,7 +29,7 @@ class RendererMTL final : public Renderer {
void reset() override; void reset() override;
void display() 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 clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override;
void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) 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 textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override;

View file

@ -9,7 +9,7 @@ class RendererNull final : public Renderer {
void reset() override; void reset() override;
void display() 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 clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override;
void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) 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 textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override;

View file

@ -9,7 +9,7 @@ class RendererSw final : public Renderer {
void reset() override; void reset() override;
void display() 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 clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override;
void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) 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 textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override;

View file

@ -113,7 +113,7 @@ class RendererVK final : public Renderer {
void reset() override; void reset() override;
void display() 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 clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override;
void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) 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 textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override;

View file

@ -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) // 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 // 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 // Set up the OpenGL blending context to match the emulated PICA
void RendererGL::setupBlending() { void RendererGL::setupBlending() {

View file

@ -10,8 +10,8 @@
#include "PICA/gpu.hpp" #include "PICA/gpu.hpp"
#include "PICA/pica_hash.hpp" #include "PICA/pica_hash.hpp"
#include "screen_layout.hpp"
#include "SDL_metal.h" #include "SDL_metal.h"
#include "screen_layout.hpp"
using namespace PICA; using namespace PICA;
@ -59,7 +59,7 @@ void RendererMTL::reset() {
void RendererMTL::setMTKLayer(void* layer) { void RendererMTL::setMTKLayer(void* layer) {
metalLayer = (CA::MetalLayer*)layer; metalLayer = (CA::MetalLayer*)layer;
} void RendererMTL::setMTKLayer(void* layer) { metalLayer = (CA::MetalLayer*)layer; }
void RendererMTL::display() { void RendererMTL::display() {
CA::MetalDrawable* drawable = metalLayer->nextDrawable(); CA::MetalDrawable* drawable = metalLayer->nextDrawable();
@ -151,13 +151,13 @@ void RendererMTL::display() {
drawable->release(); drawable->release();
} }
void RendererMTL::initGraphicsContext(SDL_Window* window) { void RendererMTL::initGraphicsContext(void* window) {
// On iOS, the SwiftUI side handles the MetalLayer // On Qt and IOS, the frontend handles the MetalLayer
#ifdef PANDA3DS_IOS #if defined(PANDA3DS_FRONTEND_QT) || defined(PANDA3DS_IOS)
device = MTL::CreateSystemDefaultDevice(); device = MTL::CreateSystemDefaultDevice();
#else #else
// TODO: what should be the type of the view? // 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); metalLayer = (CA::MetalLayer*)SDL_Metal_GetLayer(view);
device = MTL::CreateSystemDefaultDevice(); device = MTL::CreateSystemDefaultDevice();
metalLayer->setDevice(device); metalLayer->setDevice(device);

View file

@ -6,7 +6,7 @@ RendererNull::~RendererNull() {}
void RendererNull::reset() {} void RendererNull::reset() {}
void RendererNull::display() {} 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::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {}
void RendererNull::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) {} 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) {} void RendererNull::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) {}

View file

@ -7,7 +7,7 @@ RendererSw::~RendererSw() {}
void RendererSw::reset() { printf("RendererSW: Unimplemented reset call\n"); } void RendererSw::reset() { printf("RendererSW: Unimplemented reset call\n"); }
void RendererSw::display() { printf("RendererSW: Unimplemented display 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::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) { void RendererSw::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) {

View file

@ -173,7 +173,8 @@ std::tuple<vk::UniquePipeline, vk::UniquePipelineLayout> createGraphicsPipeline(
vk::PipelineDynamicStateCreateInfo dynamicState = {}; vk::PipelineDynamicStateCreateInfo dynamicState = {};
static vk::DynamicState dynamicStates[] = {// The viewport and scissor of the framebuffer will be dynamic at static vk::DynamicState dynamicStates[] = {// The viewport and scissor of the framebuffer will be dynamic at
// run-time // run-time
vk::DynamicState::eViewport, vk::DynamicState::eScissor}; vk::DynamicState::eViewport, vk::DynamicState::eScissor
};
dynamicState.dynamicStateCount = std::size(dynamicStates); dynamicState.dynamicStateCount = std::size(dynamicStates);
dynamicState.pDynamicStates = dynamicStates; dynamicState.pDynamicStates = dynamicStates;
@ -469,7 +470,8 @@ vk::RenderPass RendererVK::getRenderPass(vk::Format colorFormat, std::optional<v
vk::SubpassDependency( vk::SubpassDependency(
0, VK_SUBPASS_EXTERNAL, vk::PipelineStageFlagBits::eAllGraphics, vk::PipelineStageFlagBits::eAllGraphics, 0, VK_SUBPASS_EXTERNAL, vk::PipelineStageFlagBits::eAllGraphics, vk::PipelineStageFlagBits::eAllGraphics,
vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eColorAttachmentWrite, vk::DependencyFlagBits::eByRegion vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eColorAttachmentWrite, vk::DependencyFlagBits::eByRegion
)}; )
};
renderPassInfo.setDependencies(subpassDependencies); renderPassInfo.setDependencies(subpassDependencies);
@ -892,8 +894,8 @@ using VulkanDynamicLoader = vk::detail::DynamicLoader;
using VulkanDynamicLoader = vk::DynamicLoader; using VulkanDynamicLoader = vk::DynamicLoader;
#endif #endif
void RendererVK::initGraphicsContext(SDL_Window* window) { void RendererVK::initGraphicsContext(void* windowPointer) {
targetWindow = window; targetWindow = (SDL_Window*)windowPointer;
// Resolve all instance function pointers // Resolve all instance function pointers
static VulkanDynamicLoader dl; static VulkanDynamicLoader dl;
VULKAN_HPP_DEFAULT_DISPATCHER.init(dl.getProcAddress<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr")); VULKAN_HPP_DEFAULT_DISPATCHER.init(dl.getProcAddress<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr"));
@ -978,8 +980,8 @@ void RendererVK::initGraphicsContext(SDL_Window* window) {
} }
// Create surface // Create surface
if (window) { if (targetWindow) {
if (VkSurfaceKHR newSurface; SDL_Vulkan_CreateSurface(window, instance.get(), &newSurface)) { if (VkSurfaceKHR newSurface; SDL_Vulkan_CreateSurface(targetWindow, instance.get(), &newSurface)) {
swapchainSurface = newSurface; swapchainSurface = newSurface;
} else { } else {
Helpers::warn("Error creating Vulkan surface"); Helpers::warn("Error creating Vulkan surface");
@ -1127,7 +1129,7 @@ void RendererVK::initGraphicsContext(SDL_Window* window) {
vk::Extent2D swapchainExtent; vk::Extent2D swapchainExtent;
{ {
int windowWidth, windowHeight; int windowWidth, windowHeight;
SDL_Vulkan_GetDrawableSize(window, &windowWidth, &windowHeight); SDL_Vulkan_GetDrawableSize(targetWindow, &windowWidth, &windowHeight);
swapchainExtent.width = windowWidth; swapchainExtent.width = windowWidth;
swapchainExtent.height = windowHeight; swapchainExtent.height = windowHeight;
} }
@ -1275,7 +1277,8 @@ void RendererVK::initGraphicsContext(SDL_Window* window) {
static vk::DescriptorSetLayoutBinding displayShaderLayout[] = { static vk::DescriptorSetLayoutBinding displayShaderLayout[] = {
{// Just a singular texture slot {// 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()) { 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] = { static vk::ImageSubresourceRange depthStencilRanges[2] = {
vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1), 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 // Clear RenderTarget
getCurrentCommandBuffer().clearDepthStencilImage( getCurrentCommandBuffer().clearDepthStencilImage(

View file

@ -168,7 +168,8 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
} else if (usingVk) { } else if (usingVk) {
Helpers::panic("Vulkan on Qt is currently WIP, try the SDL frontend instead!"); Helpers::panic("Vulkan on Qt is currently WIP, try the SDL frontend instead!");
} else if (usingMtl) { } 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 { } else {
Helpers::panic("Unsupported graphics backend for Qt frontend!"); Helpers::panic("Unsupported graphics backend for Qt frontend!");
} }
@ -213,6 +214,8 @@ void MainWindow::emuThreadMainLoop() {
void MainWindow::swapEmuBuffer() { void MainWindow::swapEmuBuffer() {
if (usingGL) { if (usingGL) {
screen->getGLContext()->SwapBuffers(); screen->getGLContext()->SwapBuffers();
} else if (usingMtl) {
// The renderer itself calls presentDrawable to swap buffers on Metal
} else { } else {
Helpers::panic("[Qt] Don't know how to swap buffers for the current rendering backend :("); Helpers::panic("[Qt] Don't know how to swap buffers for the current rendering backend :(");
} }

View file

@ -0,0 +1,51 @@
#import <AppKit/AppKit.h>
#import <QuartzCore/CAMetalLayer.h>
#import <Metal/Metal.h>
#import <Metal/Metal.hpp>
#import <QuartzCore/QuartzCore.hpp>
#import <QWindow>
#import "panda_qt/screen.hpp"
bool ScreenWidget::createMetalContext() {
NSView* nativeView = (NSView*)this->winId();
CAMetalLayer* metalLayer = [CAMetalLayer layer];
if (!metalLayer) {
return false;
}
id<MTLDevice> 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<void*>(cppLayer);
return true;
}
void ScreenWidget::resizeMetalView() {
NSView* view = (NSView*)this->windowHandle()->winId();
CAMetalLayer* metalLayer = (CAMetalLayer*)[view layer];
if (metalLayer) {
metalLayer.drawableSize = CGSizeMake(surfaceWidth, surfaceHeight);
}
}

View file

@ -31,9 +31,17 @@ ScreenWidget::ScreenWidget(ResizeCallback resizeCallback, QWidget* parent) : QWi
setMouseTracking(true); setMouseTracking(true);
show(); show();
if (api == API::OpenGL) {
if (!createGLContext()) { if (!createGLContext()) {
Helpers::panic("Failed to create GL context for display"); 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");
}
} }
void ScreenWidget::resizeEvent(QResizeEvent* event) { void ScreenWidget::resizeEvent(QResizeEvent* event) {
@ -47,15 +55,18 @@ void ScreenWidget::resizeEvent(QResizeEvent* event) {
this->windowInfo = *windowInfo; 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); resizeCallback(surfaceWidth, surfaceHeight);
} }
// Note: This will run on the emulator thread, we don't want any Qt calls happening there. // Note: This will run on the emulator thread, we don't want any Qt calls happening there.
void ScreenWidget::resizeSurface(u32 width, u32 height) { void ScreenWidget::resizeSurface(u32 width, u32 height) {
if (previousWidth != width || previousHeight != height) { if (api == API::OpenGL && (previousWidth != width || previousHeight != height)) {
if (glContext) { if (glContext) {
glContext->ResizeSurface(width, height); glContext->ResizeSurface(width, height);
} }