mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-08-12 11:08:46 +00:00
Add Metal support to Qt frontend and clean up renderer creation code (#795)
Some checks are pending
Android Build / x64 (release) (push) Waiting to run
Android Build / arm64 (release) (push) Waiting to run
HTTP Server Build / build (push) Waiting to run
Hydra Core Build / Windows (push) Waiting to run
Hydra Core Build / MacOS (push) Waiting to run
Hydra Core Build / Linux (push) Waiting to run
Hydra Core Build / Android-x64 (push) Waiting to run
Hydra Core Build / ARM-Libretro (push) Waiting to run
Linux AppImage Build / build (push) Waiting to run
Linux Build / build (push) Waiting to run
MacOS Build / MacOS-arm64 (push) Waiting to run
MacOS Build / MacOS-x86_64 (push) Waiting to run
MacOS Build / MacOS-Universal (push) Blocked by required conditions
Qt Build / Windows (push) Waiting to run
Qt Build / MacOS-arm64 (push) Waiting to run
Qt Build / MacOS-x86_64 (push) Waiting to run
Qt Build / MacOS-Universal (push) Blocked by required conditions
Qt Build / Linux (push) Waiting to run
Hardware Test Build / build (push) Waiting to run
Windows Build / build (push) Waiting to run
iOS Simulator Build / build (push) Waiting to run
Some checks are pending
Android Build / x64 (release) (push) Waiting to run
Android Build / arm64 (release) (push) Waiting to run
HTTP Server Build / build (push) Waiting to run
Hydra Core Build / Windows (push) Waiting to run
Hydra Core Build / MacOS (push) Waiting to run
Hydra Core Build / Linux (push) Waiting to run
Hydra Core Build / Android-x64 (push) Waiting to run
Hydra Core Build / ARM-Libretro (push) Waiting to run
Linux AppImage Build / build (push) Waiting to run
Linux Build / build (push) Waiting to run
MacOS Build / MacOS-arm64 (push) Waiting to run
MacOS Build / MacOS-x86_64 (push) Waiting to run
MacOS Build / MacOS-Universal (push) Blocked by required conditions
Qt Build / Windows (push) Waiting to run
Qt Build / MacOS-arm64 (push) Waiting to run
Qt Build / MacOS-x86_64 (push) Waiting to run
Qt Build / MacOS-Universal (push) Blocked by required conditions
Qt Build / Linux (push) Waiting to run
Hardware Test Build / build (push) Waiting to run
Windows Build / build (push) Waiting to run
iOS Simulator Build / build (push) Waiting to run
* Qt: Initial support for Metal renderer * Clean up graphics context code * Nits * More nits * Qt: Move screen-related stuff to own folder * Qt: Make screen widget polymorphic * Qt: Re-add Metal * Add factory for screen widget * Qt: Support compilation without Metal * Qt: Fix build without Metal * Oops * oops
This commit is contained in:
parent
8b0b1939cf
commit
0446bcdaa1
23 changed files with 331 additions and 146 deletions
|
@ -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()
|
||||||
|
@ -730,18 +730,25 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE)
|
||||||
option(GENERATE_QT_TRANSLATION "Generate Qt translation file" OFF)
|
option(GENERATE_QT_TRANSLATION "Generate Qt translation file" OFF)
|
||||||
set(QT_LANGUAGES docs/translations)
|
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/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/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
|
||||||
|
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/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
|
||||||
include/panda_qt/thread_debugger.hpp include/panda_qt/cpu_debugger.hpp include/panda_qt/dsp_debugger.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("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})
|
||||||
|
|
|
@ -109,11 +109,7 @@ 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* context) { renderer->initGraphicsContext(context); }
|
||||||
void initGraphicsContext(SDL_Window* window) { renderer->initGraphicsContext(window); }
|
|
||||||
#elif defined(PANDA3DS_FRONTEND_QT)
|
|
||||||
void initGraphicsContext(GL::Context* context) { renderer->initGraphicsContext(context); }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void fireDMA(u32 dest, u32 source, u32 size);
|
void fireDMA(u32 dest, u32 source, u32 size);
|
||||||
void reset();
|
void reset();
|
||||||
|
|
|
@ -106,12 +106,8 @@ class Emulator {
|
||||||
bool loadELF(const std::filesystem::path& path);
|
bool loadELF(const std::filesystem::path& path);
|
||||||
bool loadELF(std::ifstream& file);
|
bool loadELF(std::ifstream& file);
|
||||||
|
|
||||||
#ifdef PANDA3DS_FRONTEND_QT
|
// For passing the SDL Window, GL context, etc from the frontend to the renderer
|
||||||
// For passing the GL context from Qt to the renderer
|
void initGraphicsContext(void* context) { gpu.initGraphicsContext(context); }
|
||||||
void initGraphicsContext(GL::Context* glContext) { gpu.initGraphicsContext(nullptr); }
|
|
||||||
#else
|
|
||||||
void initGraphicsContext(SDL_Window* window) { gpu.initGraphicsContext(window); }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
RomFS::DumpingResult dumpRomFS(const std::filesystem::path& path);
|
RomFS::DumpingResult dumpRomFS(const std::filesystem::path& path);
|
||||||
void setOutputSize(u32 width, u32 height) { gpu.setOutputSize(width, height); }
|
void setOutputSize(u32 width, u32 height) { gpu.setOutputSize(width, height); }
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
#include "panda_qt/cpu_debugger.hpp"
|
#include "panda_qt/cpu_debugger.hpp"
|
||||||
#include "panda_qt/dsp_debugger.hpp"
|
#include "panda_qt/dsp_debugger.hpp"
|
||||||
#include "panda_qt/patch_window.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/shader_editor.hpp"
|
||||||
#include "panda_qt/text_editor.hpp"
|
#include "panda_qt/text_editor.hpp"
|
||||||
#include "panda_qt/thread_debugger.hpp"
|
#include "panda_qt/thread_debugger.hpp"
|
||||||
|
@ -136,7 +136,7 @@ class MainWindow : public QMainWindow {
|
||||||
void loadKeybindings();
|
void loadKeybindings();
|
||||||
void saveKeybindings();
|
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 usingGL = false;
|
||||||
bool usingVk = false;
|
bool usingVk = false;
|
||||||
bool usingMtl = false;
|
bool usingMtl = false;
|
||||||
|
|
|
@ -1,25 +1,27 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "gl/context.h"
|
#include "gl/context.h"
|
||||||
#include "screen_layout.hpp"
|
#include "screen_layout.hpp"
|
||||||
#include "window_info.h"
|
#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 {
|
class ScreenWidget : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using ResizeCallback = std::function<void(u32, u32)>;
|
using ResizeCallback = std::function<void(u32, u32)>;
|
||||||
|
|
||||||
ScreenWidget(ResizeCallback resizeCallback, QWidget* parent = nullptr);
|
enum class API { OpenGL, Metal, Vulkan };
|
||||||
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);
|
|
||||||
|
|
||||||
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
|
// Dimensions of our output surface
|
||||||
u32 surfaceWidth = 0;
|
u32 surfaceWidth = 0;
|
||||||
|
@ -30,8 +32,9 @@ class ScreenWidget : public QWidget {
|
||||||
u32 previousWidth = 0;
|
u32 previousWidth = 0;
|
||||||
u32 previousHeight = 0;
|
u32 previousHeight = 0;
|
||||||
|
|
||||||
// Coordinates (x/y/width/height) for the two screens in window space, used for properly handling touchscreen regardless
|
API api = API::OpenGL;
|
||||||
// of layout or resizing
|
|
||||||
|
// Coordinates (x/y/width/height) for the two screens in window space, used for properly handling touchscreen
|
||||||
ScreenLayout::WindowCoordinates screenCoordinates;
|
ScreenLayout::WindowCoordinates screenCoordinates;
|
||||||
// Screen layouts and sizes
|
// Screen layouts and sizes
|
||||||
ScreenLayout::Layout screenLayout = ScreenLayout::Layout::Default;
|
ScreenLayout::Layout screenLayout = ScreenLayout::Layout::Default;
|
||||||
|
@ -39,16 +42,23 @@ class ScreenWidget : public QWidget {
|
||||||
|
|
||||||
void reloadScreenLayout(ScreenLayout::Layout newLayout, float newTopScreenSize);
|
void reloadScreenLayout(ScreenLayout::Layout newLayout, float newTopScreenSize);
|
||||||
|
|
||||||
private:
|
// Creates a screen widget depending on the graphics API we're using
|
||||||
std::unique_ptr<GL::Context> glContext = nullptr;
|
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;
|
ResizeCallback resizeCallback;
|
||||||
|
|
||||||
bool createGLContext();
|
virtual bool createContext() = 0;
|
||||||
|
virtual void resizeDisplay() = 0;
|
||||||
|
std::optional<WindowInfo> getWindowInfo();
|
||||||
|
|
||||||
|
private:
|
||||||
qreal devicePixelRatioFromScreen() const;
|
qreal devicePixelRatioFromScreen() const;
|
||||||
int scaledWindowWidth() const;
|
int scaledWindowWidth() const;
|
||||||
int scaledWindowHeight() const;
|
int scaledWindowHeight() const;
|
||||||
std::optional<WindowInfo> getWindowInfo();
|
|
||||||
|
|
||||||
void reloadScreenCoordinates();
|
void reloadScreenCoordinates();
|
||||||
};
|
};
|
18
include/panda_qt/screen/screen_gl.hpp
Normal file
18
include/panda_qt/screen/screen_gl.hpp
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#pragma once
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "gl/context.h"
|
||||||
|
#include "panda_qt/screen/screen.hpp"
|
||||||
|
|
||||||
|
class ScreenWidgetGL : public ScreenWidget {
|
||||||
|
std::unique_ptr<GL::Context> 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;
|
||||||
|
};
|
18
include/panda_qt/screen/screen_mtl.hpp
Normal file
18
include/panda_qt/screen/screen_mtl.hpp
Normal file
|
@ -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;
|
||||||
|
};
|
|
@ -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* 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 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
|
||||||
|
@ -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
|
// 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; }
|
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) {
|
void setFBSize(u32 width, u32 height) {
|
||||||
fbSize[0] = width;
|
fbSize[0] = width;
|
||||||
fbSize[1] = height;
|
fbSize[1] = height;
|
||||||
|
|
|
@ -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* context) 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;
|
||||||
|
@ -211,10 +211,6 @@ class RendererGL final : public Renderer {
|
||||||
void resetStateManager() { gl.reset(); }
|
void resetStateManager() { gl.reset(); }
|
||||||
void initUbershader(OpenGL::Program& program);
|
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
|
// Take a screenshot of the screen and store it in a file
|
||||||
void screenshot(const std::string& name) override;
|
void screenshot(const std::string& name) override;
|
||||||
};
|
};
|
|
@ -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* context) 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;
|
||||||
|
@ -38,10 +37,6 @@ class RendererMTL final : public Renderer {
|
||||||
void screenshot(const std::string& name) override;
|
void screenshot(const std::string& name) override;
|
||||||
void deinitGraphicsContext() override;
|
void deinitGraphicsContext() override;
|
||||||
|
|
||||||
#ifdef PANDA3DS_FRONTEND_QT
|
|
||||||
virtual void initGraphicsContext([[maybe_unused]] GL::Context* context) override {}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
virtual void setMTKLayer(void* layer) override;
|
virtual void setMTKLayer(void* layer) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -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* context) 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;
|
||||||
|
@ -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.
|
// 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.
|
// 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; };
|
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
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,15 +9,11 @@ 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* context) 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;
|
||||||
void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) override;
|
void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) override;
|
||||||
void screenshot(const std::string& name) override;
|
void screenshot(const std::string& name) override;
|
||||||
void deinitGraphicsContext() override;
|
void deinitGraphicsContext() override;
|
||||||
|
|
||||||
#ifdef PANDA3DS_FRONTEND_QT
|
|
||||||
virtual void initGraphicsContext([[maybe_unused]] GL::Context* context) override {}
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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* context) 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;
|
||||||
|
|
|
@ -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* context) { 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() {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
@ -57,9 +57,7 @@ void RendererMTL::reset() {
|
||||||
colorRenderTargetCache.reset();
|
colorRenderTargetCache.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererMTL::setMTKLayer(void* layer) {
|
void RendererMTL::setMTKLayer(void* layer) { metalLayer = (CA::MetalLayer*)layer; }
|
||||||
metalLayer = (CA::MetalLayer*)layer;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RendererMTL::display() {
|
void RendererMTL::display() {
|
||||||
CA::MetalDrawable* drawable = metalLayer->nextDrawable();
|
CA::MetalDrawable* drawable = metalLayer->nextDrawable();
|
||||||
|
@ -151,13 +149,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);
|
||||||
|
|
|
@ -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* context) {}
|
||||||
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) {}
|
||||||
|
|
|
@ -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* 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::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) {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "cheats.hpp"
|
#include "cheats.hpp"
|
||||||
#include "input_mappings.hpp"
|
#include "input_mappings.hpp"
|
||||||
#include "panda_qt/dsp_debugger.hpp"
|
#include "panda_qt/dsp_debugger.hpp"
|
||||||
|
#include "panda_qt/screen/screen.hpp"
|
||||||
#include "sdl_sensors.hpp"
|
#include "sdl_sensors.hpp"
|
||||||
#include "services/dsp.hpp"
|
#include "services/dsp.hpp"
|
||||||
#include "version.hpp"
|
#include "version.hpp"
|
||||||
|
@ -25,8 +26,19 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
||||||
resize(800, 240 * 4);
|
resize(800, 240 * 4);
|
||||||
show();
|
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
|
// 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);
|
setCentralWidget(screen);
|
||||||
|
|
||||||
appRunning = true;
|
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
|
// The emulator graphics context for the thread should be initialized in the emulator thread due to how GL contexts work
|
||||||
emuThread = std::thread([this]() {
|
emuThread = std::thread([this]() {
|
||||||
const RendererType rendererType = emu->getConfig().rendererType;
|
switch (screen->api) {
|
||||||
usingGL = (rendererType == RendererType::OpenGL || rendererType == RendererType::Software || rendererType == RendererType::Null);
|
case ScreenWidget::API::OpenGL: {
|
||||||
usingVk = (rendererType == RendererType::Vulkan);
|
// Make GL context current for this thread, enable VSync
|
||||||
usingMtl = (rendererType == RendererType::Metal);
|
GL::Context* glContext = screen->getGLContext();
|
||||||
|
glContext->MakeCurrent();
|
||||||
|
glContext->SetSwapInterval(emu->getConfig().vsyncEnabled ? 1 : 0);
|
||||||
|
|
||||||
if (usingGL) {
|
if (glContext->IsGLES()) {
|
||||||
// Make GL context current for this thread, enable VSync
|
emu->getRenderer()->setupGLES();
|
||||||
GL::Context* glContext = screen->getGLContext();
|
}
|
||||||
glContext->MakeCurrent();
|
|
||||||
glContext->SetSwapInterval(emu->getConfig().vsyncEnabled ? 1 : 0);
|
|
||||||
|
|
||||||
if (glContext->IsGLES()) {
|
emu->initGraphicsContext(glContext);
|
||||||
emu->getRenderer()->setupGLES();
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
emu->initGraphicsContext(glContext);
|
case ScreenWidget::API::Metal: {
|
||||||
} else if (usingVk) {
|
emu->initGraphicsContext(nullptr);
|
||||||
Helpers::panic("Vulkan on Qt is currently WIP, try the SDL frontend instead!");
|
emu->getRenderer()->setMTKLayer(screen->getMTKLayer());
|
||||||
} else if (usingMtl) {
|
break;
|
||||||
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::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
|
// We have to initialize controllers on the same thread they'll be polled in
|
||||||
|
@ -213,6 +226,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 :(");
|
||||||
}
|
}
|
||||||
|
@ -290,6 +305,7 @@ MainWindow::~MainWindow() {
|
||||||
delete aboutWindow;
|
delete aboutWindow;
|
||||||
delete configWindow;
|
delete configWindow;
|
||||||
delete cheatsEditor;
|
delete cheatsEditor;
|
||||||
|
delete screen;
|
||||||
delete luaEditor;
|
delete luaEditor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
71
src/panda_qt/screen/metal_context.mm
Normal file
71
src/panda_qt/screen/metal_context.mm
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
#import <AppKit/AppKit.h>
|
||||||
|
#import <Metal/Metal.h>
|
||||||
|
#import <QuartzCore/CAMetalLayer.h>
|
||||||
|
#import <Metal/Metal.hpp>
|
||||||
|
#import <QWindow>
|
||||||
|
#import <QuartzCore/QuartzCore.hpp>
|
||||||
|
|
||||||
|
#import "panda_qt/screen/screen_mtl.hpp"
|
||||||
|
|
||||||
|
id<MTLDevice> 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<void*>(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<CA::MetalLayer*>(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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
|
#ifdef PANDA3DS_ENABLE_OPENGL
|
||||||
#include "opengl.hpp"
|
#include "opengl.hpp"
|
||||||
|
#endif
|
||||||
// opengl.hpp must be included at the very top. This comment exists to make clang-format not reorder it :p
|
// opengl.hpp must be included at the very top. This comment exists to make clang-format not reorder it :p
|
||||||
|
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
#include <QWindow>
|
#include <QWindow>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
|
@ -12,16 +14,16 @@
|
||||||
#include <qpa/qplatformnativeinterface.h>
|
#include <qpa/qplatformnativeinterface.h>
|
||||||
#endif
|
#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
|
// and https://github.com/melonDS-emu/melonDS/blob/master/src/frontend/qt_sdl/main.cpp
|
||||||
|
|
||||||
#ifdef PANDA3DS_ENABLE_OPENGL
|
#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
|
// Create a native window for use with our graphics API of choice
|
||||||
resize(800, 240 * 4);
|
|
||||||
|
|
||||||
setAutoFillBackground(false);
|
setAutoFillBackground(false);
|
||||||
setAttribute(Qt::WA_NativeWindow, true);
|
setAttribute(Qt::WA_NativeWindow, true);
|
||||||
setAttribute(Qt::WA_NoSystemBackground, true);
|
setAttribute(Qt::WA_NoSystemBackground, true);
|
||||||
|
@ -29,11 +31,8 @@ ScreenWidget::ScreenWidget(ResizeCallback resizeCallback, QWidget* parent) : QWi
|
||||||
setAttribute(Qt::WA_KeyCompression, false);
|
setAttribute(Qt::WA_KeyCompression, false);
|
||||||
setFocusPolicy(Qt::StrongFocus);
|
setFocusPolicy(Qt::StrongFocus);
|
||||||
setMouseTracking(true);
|
setMouseTracking(true);
|
||||||
show();
|
|
||||||
|
|
||||||
if (!createGLContext()) {
|
// The graphics context, as well as resizing and showing the widget, is handled by the screen backend
|
||||||
Helpers::panic("Failed to create GL context for display");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScreenWidget::resizeEvent(QResizeEvent* event) {
|
void ScreenWidget::resizeEvent(QResizeEvent* event) {
|
||||||
|
@ -48,18 +47,7 @@ void ScreenWidget::resizeEvent(QResizeEvent* event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadScreenCoordinates();
|
reloadScreenCoordinates();
|
||||||
|
resizeDisplay();
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScreenWidget::reloadScreenCoordinates() {
|
void ScreenWidget::reloadScreenCoordinates() {
|
||||||
|
@ -73,30 +61,6 @@ void ScreenWidget::reloadScreenLayout(ScreenLayout::Layout newLayout, float newT
|
||||||
reloadScreenCoordinates();
|
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<GL::Context::Version, 8> 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> 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 {
|
qreal ScreenWidget::devicePixelRatioFromScreen() const {
|
||||||
const QScreen* screenForRatio = windowHandle()->screen();
|
const QScreen* screenForRatio = windowHandle()->screen();
|
||||||
if (!screenForRatio) {
|
if (!screenForRatio) {
|
||||||
|
@ -156,3 +120,15 @@ std::optional<WindowInfo> ScreenWidget::getWindowInfo() {
|
||||||
return wi;
|
return wi;
|
||||||
}
|
}
|
||||||
#endif
|
#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");
|
||||||
|
}
|
||||||
|
}
|
64
src/panda_qt/screen/screen_gl.cpp
Normal file
64
src/panda_qt/screen/screen_gl.cpp
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
#include "panda_qt/screen/screen_gl.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#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<GL::Context::Version, 8> 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> 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
|
33
src/panda_qt/screen/screen_mtl.cpp
Normal file
33
src/panda_qt/screen/screen_mtl.cpp
Normal file
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue