diff --git a/CMakeLists.txt b/CMakeLists.txt index 42e8615e57..36e1ba30bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ cmake_minimum_required(VERSION 3.22) set(CMAKE_XCODE_GENERATE_TOP_LEVEL_PROJECT OFF) set(CMAKE_XCODE_EMIT_RELATIVE_PATH YES) -project(suyu) +project(suyu LANGUAGES C CXX OBJC OBJCXX) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules") diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index fabae6d687..09006f6223 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -124,7 +124,7 @@ ENUM(VSyncMode, Immediate, Mailbox, Fifo, FifoRelaxed); ENUM(VramUsageMode, Conservative, Aggressive); -ENUM(RendererBackend, OpenGL, Vulkan, Null); +ENUM(RendererBackend, OpenGL, Vulkan, Metal, Null); ENUM(ShaderBackend, Glsl, Glasm, SpirV); diff --git a/src/suyu/bootmanager.cpp b/src/suyu/bootmanager.cpp index 606325bf5e..a84dcdb3a3 100644 --- a/src/suyu/bootmanager.cpp +++ b/src/suyu/bootmanager.cpp @@ -284,8 +284,8 @@ struct NullRenderWidget : public RenderWidget { GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_, std::shared_ptr input_subsystem_, Core::System& system_) - : QWidget(parent), - emu_thread(emu_thread_), input_subsystem{std::move(input_subsystem_)}, system{system_} { + : QWidget(parent), emu_thread(emu_thread_), input_subsystem{std::move(input_subsystem_)}, + system{system_} { setWindowTitle(QStringLiteral("suyu %1 | %2-%3") .arg(QString::fromUtf8(Common::g_build_name), QString::fromUtf8(Common::g_scm_branch), @@ -933,6 +933,13 @@ bool GRenderWindow::InitRenderTarget() { return false; } break; +#ifdef __APPLE__ + case Settings::RendererBackend::Metal: + if (!InitializeMetal()) { + return false; + } + break; +#endif case Settings::RendererBackend::Null: InitializeNull(); break; @@ -1048,6 +1055,14 @@ bool GRenderWindow::InitializeVulkan() { return true; } +bool GRenderWindow::InitializeMetal() { + // TODO: initialize Metal + child_widget = new NullRenderWidget(this); + main_context = std::make_unique(); + + return true; +} + void GRenderWindow::InitializeNull() { child_widget = new NullRenderWidget(this); main_context = std::make_unique(); diff --git a/src/suyu/bootmanager.h b/src/suyu/bootmanager.h index 36eb956ec7..f301b812c7 100644 --- a/src/suyu/bootmanager.h +++ b/src/suyu/bootmanager.h @@ -239,6 +239,7 @@ private: bool InitializeOpenGL(); bool InitializeVulkan(); + bool InitializeMetal(); void InitializeNull(); bool LoadOpenGL(); QStringList GetUnsupportedGLExtensions() const; diff --git a/src/suyu/configuration/configure_graphics.cpp b/src/suyu/configuration/configure_graphics.cpp index d11110a74a..0810009125 100644 --- a/src/suyu/configuration/configure_graphics.cpp +++ b/src/suyu/configuration/configure_graphics.cpp @@ -466,6 +466,9 @@ void ConfigureGraphics::ApplyConfiguration() { Settings::values.vulkan_device.SetGlobal(Settings::IsConfiguringGlobal()); Settings::values.vulkan_device.SetValue(vulkan_device_combobox->currentIndex()); break; + case Settings::RendererBackend::Metal: + // TODO + break; case Settings::RendererBackend::Null: break; } diff --git a/src/suyu/configuration/shared_translation.cpp b/src/suyu/configuration/shared_translation.cpp index 0e4d13bc64..90548d4fe5 100644 --- a/src/suyu/configuration/shared_translation.cpp +++ b/src/suyu/configuration/shared_translation.cpp @@ -331,6 +331,9 @@ std::unique_ptr ComboboxEnumeration(QWidget* parent) { PAIR(RendererBackend, OpenGL, tr("OpenGL")), #endif PAIR(RendererBackend, Vulkan, tr("Vulkan")), +#ifdef __APPLE__ + PAIR(RendererBackend, Metal, tr("Metal")), +#endif PAIR(RendererBackend, Null, tr("Null")), }}); translations->insert( diff --git a/src/suyu/configuration/shared_translation.h b/src/suyu/configuration/shared_translation.h index d5fc3b8def..9cc8ba6a31 100644 --- a/src/suyu/configuration/shared_translation.h +++ b/src/suyu/configuration/shared_translation.h @@ -56,6 +56,9 @@ static const std::map gpu_accuracy_texts_map = { static const std::map renderer_backend_texts_map = { {Settings::RendererBackend::Vulkan, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Vulkan"))}, {Settings::RendererBackend::OpenGL, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "OpenGL"))}, +#ifdef __APPLE__ + {Settings::RendererBackend::Metal, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Metal"))}, +#endif {Settings::RendererBackend::Null, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Null"))}, }; diff --git a/src/suyu/main.cpp b/src/suyu/main.cpp index 9a3ee7f662..a23ba9e17d 100644 --- a/src/suyu/main.cpp +++ b/src/suyu/main.cpp @@ -3800,6 +3800,8 @@ void GMainWindow::OnToggleGraphicsAPI() { } else { #ifdef HAS_OPENGL api = Settings::RendererBackend::OpenGL; +#elif __APPLE__ + api = Settings::RendererBackend::Metal; #else api = Settings::RendererBackend::Null; #endif diff --git a/src/suyu_cmd/CMakeLists.txt b/src/suyu_cmd/CMakeLists.txt index 45cc281218..3aba7800ae 100644 --- a/src/suyu_cmd/CMakeLists.txt +++ b/src/suyu_cmd/CMakeLists.txt @@ -21,6 +21,8 @@ add_executable(suyu-cmd emu_window/emu_window_sdl2_null.h emu_window/emu_window_sdl2_vk.cpp emu_window/emu_window_sdl2_vk.h + emu_window/emu_window_sdl2_mtl.cpp + emu_window/emu_window_sdl2_mtl.h precompiled_headers.h sdl_config.cpp sdl_config.h diff --git a/src/suyu_cmd/emu_window/emu_window_sdl2_mtl.cpp b/src/suyu_cmd/emu_window/emu_window_sdl2_mtl.cpp new file mode 100644 index 0000000000..d84b939586 --- /dev/null +++ b/src/suyu_cmd/emu_window/emu_window_sdl2_mtl.cpp @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include + +#include "common/logging/log.h" +#include "common/scm_rev.h" +#include "suyu_cmd/emu_window/emu_window_sdl2_mtl.h" +#include "video_core/renderer_metal/renderer_metal.h" + +#include +#include + +EmuWindow_SDL2_MTL::EmuWindow_SDL2_MTL(InputCommon::InputSubsystem* input_subsystem_, + Core::System& system_, bool fullscreen) + : EmuWindow_SDL2{input_subsystem_, system_} { + const std::string window_title = fmt::format("suyu {} | {}-{} (Vulkan)", Common::g_build_name, + Common::g_scm_branch, Common::g_scm_desc); + render_window = + SDL_CreateWindow(window_title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height, + SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + + SDL_SysWMinfo wm; + SDL_VERSION(&wm.version); + if (SDL_GetWindowWMInfo(render_window, &wm) == SDL_FALSE) { + LOG_CRITICAL(Frontend, "Failed to get information from the window manager: {}", + SDL_GetError()); + std::exit(EXIT_FAILURE); + } + + SetWindowIcon(); + + if (fullscreen) { + Fullscreen(); + ShowCursor(false); + } + + switch (wm.subsystem) { +#ifdef SDL_VIDEO_DRIVER_COCOA + case SDL_SYSWM_TYPE::SDL_SYSWM_COCOA: + window_info.type = Core::Frontend::WindowSystemType::Cocoa; + window_info.render_surface = SDL_Metal_CreateView(render_window); + break; +#endif + default: + LOG_CRITICAL(Frontend, "Window manager subsystem {} not implemented", wm.subsystem); + std::exit(EXIT_FAILURE); + break; + } + + OnResize(); + OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); + SDL_PumpEvents(); + LOG_INFO(Frontend, "suyu Version: {} | {}-{} (Vulkan)", Common::g_build_name, + Common::g_scm_branch, Common::g_scm_desc); +} + +EmuWindow_SDL2_MTL::~EmuWindow_SDL2_MTL() = default; + +std::unique_ptr EmuWindow_SDL2_MTL::CreateSharedContext() const { + return std::make_unique(); +} diff --git a/src/suyu_cmd/emu_window/emu_window_sdl2_mtl.h b/src/suyu_cmd/emu_window/emu_window_sdl2_mtl.h new file mode 100644 index 0000000000..c4fcbe5d68 --- /dev/null +++ b/src/suyu_cmd/emu_window/emu_window_sdl2_mtl.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "core/frontend/emu_window.h" +#include "suyu_cmd/emu_window/emu_window_sdl2.h" + +namespace Core { +class System; +} + +namespace InputCommon { +class InputSubsystem; +} + +class EmuWindow_SDL2_MTL final : public EmuWindow_SDL2 { +public: + explicit EmuWindow_SDL2_MTL(InputCommon::InputSubsystem* input_subsystem_, Core::System& system, + bool fullscreen); + ~EmuWindow_SDL2_MTL() override; + + std::unique_ptr CreateSharedContext() const override; +}; diff --git a/src/suyu_cmd/suyu.cpp b/src/suyu_cmd/suyu.cpp index c9c35464e6..12114b565d 100644 --- a/src/suyu_cmd/suyu.cpp +++ b/src/suyu_cmd/suyu.cpp @@ -38,6 +38,7 @@ #include "sdl_config.h" #include "suyu_cmd/emu_window/emu_window_sdl2.h" #include "suyu_cmd/emu_window/emu_window_sdl2_gl.h" +#include "suyu_cmd/emu_window/emu_window_sdl2_mtl.h" #include "suyu_cmd/emu_window/emu_window_sdl2_null.h" #include "suyu_cmd/emu_window/emu_window_sdl2_vk.h" #include "video_core/renderer_base.h" @@ -385,6 +386,9 @@ int main(int argc, char** argv) { case Settings::RendererBackend::Vulkan: emu_window = std::make_unique(&input_subsystem, system, fullscreen); break; + case Settings::RendererBackend::Metal: + emu_window = std::make_unique(&input_subsystem, system, fullscreen); + break; case Settings::RendererBackend::Null: emu_window = std::make_unique(&input_subsystem, system, fullscreen); break; diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index e5cd0278fa..4b1a9e7503 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -372,6 +372,12 @@ if (APPLE) renderer_opengl/util_shaders.cpp renderer_opengl/util_shaders.h ) + + list(APPEND sources + renderer_metal/mtl_device.mm + renderer_metal/mtl_rasterizer.mm + renderer_metal/renderer_metal.mm + ) endif() add_library(video_core STATIC ${sources}) diff --git a/src/video_core/renderer_metal/mtl_device.h b/src/video_core/renderer_metal/mtl_device.h new file mode 100644 index 0000000000..d6414cd222 --- /dev/null +++ b/src/video_core/renderer_metal/mtl_device.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "video_core/renderer_metal/objc_bridge.h" + +namespace Metal { + +class Device { +public: + explicit Device(); + ~Device(); + + MTLDevice_t GetDevice() const { + return device; + } + + MTLCommandQueue_t GetCommandQueue() const { + return command_queue; + } + +private: + MTLDevice_t device; + MTLCommandQueue_t command_queue; +}; + +} // namespace Metal diff --git a/src/video_core/renderer_metal/mtl_device.mm b/src/video_core/renderer_metal/mtl_device.mm new file mode 100644 index 0000000000..2c79381faa --- /dev/null +++ b/src/video_core/renderer_metal/mtl_device.mm @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "video_core/renderer_metal/mtl_device.h" + +namespace Metal { + +Device::Device() { + device = MTLCreateSystemDefaultDevice(); + if (!device) { + throw std::runtime_error("Failed to create Metal device"); + } + command_queue = [device newCommandQueue]; + if (!command_queue) { + throw std::runtime_error("Failed to create Metal command queue"); + } +} + +Device::~Device() { + [command_queue release]; + [device release]; +} + +} // namespace Metal diff --git a/src/video_core/renderer_metal/mtl_rasterizer.h b/src/video_core/renderer_metal/mtl_rasterizer.h new file mode 100644 index 0000000000..5637e40344 --- /dev/null +++ b/src/video_core/renderer_metal/mtl_rasterizer.h @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_types.h" +#include "video_core/control/channel_state_cache.h" +#include "video_core/engines/maxwell_dma.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/renderer_metal/objc_bridge.h" + +namespace Core { +class System; +} + +namespace Metal { + +class Device; + +class RasterizerMetal; + +class AccelerateDMA : public Tegra::Engines::AccelerateDMAInterface { +public: + explicit AccelerateDMA(); + bool BufferCopy(GPUVAddr start_address, GPUVAddr end_address, u64 amount) override; + bool BufferClear(GPUVAddr src_address, u64 amount, u32 value) override; + bool ImageToBuffer(const Tegra::DMA::ImageCopy& copy_info, const Tegra::DMA::ImageOperand& src, + const Tegra::DMA::BufferOperand& dst) override { + return false; + } + bool BufferToImage(const Tegra::DMA::ImageCopy& copy_info, const Tegra::DMA::BufferOperand& src, + const Tegra::DMA::ImageOperand& dst) override { + return false; + } +}; + +class RasterizerMetal final : public VideoCore::RasterizerInterface, + protected VideoCommon::ChannelSetupCaches { +public: + explicit RasterizerMetal(Tegra::GPU& gpu_, const Device& device_, const CAMetalLayer* layer_); + ~RasterizerMetal() override; + + void Draw(bool is_indexed, u32 instance_count) override; + void DrawTexture() override; + void Clear(u32 layer_count) override; + void DispatchCompute() override; + void ResetCounter(VideoCommon::QueryType type) override; + void Query(GPUVAddr gpu_addr, VideoCommon::QueryType type, + VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) override; + void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override; + void DisableGraphicsUniformBuffer(size_t stage, u32 index) override; + void FlushAll() override; + void FlushRegion(DAddr addr, u64 size, + VideoCommon::CacheType which = VideoCommon::CacheType::All) override; + bool MustFlushRegion(DAddr addr, u64 size, + VideoCommon::CacheType which = VideoCommon::CacheType::All) override; + void InvalidateRegion(DAddr addr, u64 size, + VideoCommon::CacheType which = VideoCommon::CacheType::All) override; + void OnCacheInvalidation(DAddr addr, u64 size) override; + bool OnCPUWrite(DAddr addr, u64 size) override; + VideoCore::RasterizerDownloadArea GetFlushArea(DAddr addr, u64 size) override; + void InvalidateGPUCache() override; + void UnmapMemory(DAddr addr, u64 size) override; + void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override; + void SignalFence(std::function&& func) override; + void SyncOperation(std::function&& func) override; + void SignalSyncPoint(u32 value) override; + void SignalReference() override; + void ReleaseFences(bool force) override; + void FlushAndInvalidateRegion( + DAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override; + void WaitForIdle() override; + void FragmentBarrier() override; + void TiledCacheBarrier() override; + void FlushCommands() override; + void TickFrame() override; + bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src, + const Tegra::Engines::Fermi2D::Surface& dst, + const Tegra::Engines::Fermi2D::Config& copy_config) override; + Tegra::Engines::AccelerateDMAInterface& AccessAccelerateDMA() override; + void AccelerateInlineToMemory(GPUVAddr address, size_t copy_size, + std::span memory) override; + void LoadDiskResources(u64 title_id, std::stop_token stop_loading, + const VideoCore::DiskResourceLoadCallback& callback) override; + void InitializeChannel(Tegra::Control::ChannelState& channel) override; + void BindChannel(Tegra::Control::ChannelState& channel) override; + void ReleaseChannel(s32 channel_id) override; + +private: + Tegra::GPU& gpu; + AccelerateDMA accelerate_dma; + + const Device& device; + const CAMetalLayer* layer; +}; + +} // namespace Metal diff --git a/src/video_core/renderer_metal/mtl_rasterizer.mm b/src/video_core/renderer_metal/mtl_rasterizer.mm new file mode 100644 index 0000000000..aa44783bb1 --- /dev/null +++ b/src/video_core/renderer_metal/mtl_rasterizer.mm @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/alignment.h" +#include "video_core/control/channel_state.h" +#include "video_core/host1x/host1x.h" +#include "video_core/memory_manager.h" +#include "video_core/renderer_metal/mtl_rasterizer.h" +#include "video_core/renderer_metal/mtl_device.h" + +#include + +namespace Metal { + +AccelerateDMA::AccelerateDMA() = default; + +bool AccelerateDMA::BufferCopy(GPUVAddr start_address, GPUVAddr end_address, u64 amount) { + return true; +} +bool AccelerateDMA::BufferClear(GPUVAddr src_address, u64 amount, u32 value) { + return true; +} + +RasterizerMetal::RasterizerMetal(Tegra::GPU& gpu_, const Device& device_, const CAMetalLayer* layer_) + : gpu{gpu_}, device{device_}, layer{layer_} {} +RasterizerMetal::~RasterizerMetal() = default; + +void RasterizerMetal::Draw(bool is_indexed, u32 instance_count) {} +void RasterizerMetal::DrawTexture() {} +void RasterizerMetal::Clear(u32 layer_count) {} +void RasterizerMetal::DispatchCompute() {} +void RasterizerMetal::ResetCounter(VideoCommon::QueryType type) {} +void RasterizerMetal::Query(GPUVAddr gpu_addr, VideoCommon::QueryType type, + VideoCommon::QueryPropertiesFlags flags, u32 payload, u32 subreport) { + if (!gpu_memory) { + return; + } + if (True(flags & VideoCommon::QueryPropertiesFlags::HasTimeout)) { + u64 ticks = gpu.GetTicks(); + gpu_memory->Write(gpu_addr + 8, ticks); + gpu_memory->Write(gpu_addr, static_cast(payload)); + } else { + gpu_memory->Write(gpu_addr, payload); + } +} +void RasterizerMetal::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, + u32 size) {} +void RasterizerMetal::DisableGraphicsUniformBuffer(size_t stage, u32 index) {} +void RasterizerMetal::FlushAll() {} +void RasterizerMetal::FlushRegion(DAddr addr, u64 size, VideoCommon::CacheType) {} +bool RasterizerMetal::MustFlushRegion(DAddr addr, u64 size, VideoCommon::CacheType) { + return false; +} +void RasterizerMetal::InvalidateRegion(DAddr addr, u64 size, VideoCommon::CacheType) {} +bool RasterizerMetal::OnCPUWrite(PAddr addr, u64 size) { + return false; +} +void RasterizerMetal::OnCacheInvalidation(PAddr addr, u64 size) {} +VideoCore::RasterizerDownloadArea RasterizerMetal::GetFlushArea(PAddr addr, u64 size) { + VideoCore::RasterizerDownloadArea new_area{ + .start_address = Common::AlignDown(addr, Core::DEVICE_PAGESIZE), + .end_address = Common::AlignUp(addr + size, Core::DEVICE_PAGESIZE), + .preemtive = true, + }; + return new_area; +} +void RasterizerMetal::InvalidateGPUCache() {} +void RasterizerMetal::UnmapMemory(DAddr addr, u64 size) {} +void RasterizerMetal::ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) {} +void RasterizerMetal::SignalFence(std::function&& func) { + func(); +} +void RasterizerMetal::SyncOperation(std::function&& func) { + func(); +} +void RasterizerMetal::SignalSyncPoint(u32 value) { + auto& syncpoint_manager = gpu.Host1x().GetSyncpointManager(); + syncpoint_manager.IncrementGuest(value); + syncpoint_manager.IncrementHost(value); +} +void RasterizerMetal::SignalReference() {} +void RasterizerMetal::ReleaseFences(bool) {} +void RasterizerMetal::FlushAndInvalidateRegion(DAddr addr, u64 size, VideoCommon::CacheType) {} +void RasterizerMetal::WaitForIdle() {} +void RasterizerMetal::FragmentBarrier() {} +void RasterizerMetal::TiledCacheBarrier() {} +void RasterizerMetal::FlushCommands() {} +void RasterizerMetal::TickFrame() {} +Tegra::Engines::AccelerateDMAInterface& RasterizerMetal::AccessAccelerateDMA() { + return accelerate_dma; +} +bool RasterizerMetal::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src, + const Tegra::Engines::Fermi2D::Surface& dst, + const Tegra::Engines::Fermi2D::Config& copy_config) { + return true; +} +void RasterizerMetal::AccelerateInlineToMemory(GPUVAddr address, size_t copy_size, + std::span memory) {} +void RasterizerMetal::LoadDiskResources(u64 title_id, std::stop_token stop_loading, + const VideoCore::DiskResourceLoadCallback& callback) {} +void RasterizerMetal::InitializeChannel(Tegra::Control::ChannelState& channel) { + CreateChannel(channel); +} +void RasterizerMetal::BindChannel(Tegra::Control::ChannelState& channel) { + BindToChannel(channel.bind_id); +} +void RasterizerMetal::ReleaseChannel(s32 channel_id) { + EraseChannel(channel_id); +} + +} // namespace Metal diff --git a/src/video_core/renderer_metal/objc_bridge.h b/src/video_core/renderer_metal/objc_bridge.h new file mode 100644 index 0000000000..5fd55169c3 --- /dev/null +++ b/src/video_core/renderer_metal/objc_bridge.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#ifdef __OBJC__ +#import +#import +typedef id MTLDevice_t; +typedef id MTLCommandQueue_t; +typedef id MTLTexture_t; +#else +typedef void* MTLDevice_t; +typedef void* MTLCommandQueue_t; +typedef void* MTLTexture_t; +typedef void CAMetalLayer; +#endif diff --git a/src/video_core/renderer_metal/renderer_metal.h b/src/video_core/renderer_metal/renderer_metal.h new file mode 100644 index 0000000000..12c3396583 --- /dev/null +++ b/src/video_core/renderer_metal/renderer_metal.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "objc_bridge.h" +#include "video_core/host1x/gpu_device_memory_manager.h" +#include "video_core/renderer_base.h" +#include "video_core/renderer_metal/mtl_device.h" +#include "video_core/renderer_metal/mtl_rasterizer.h" + +namespace Core { +class TelemetrySession; +} + +namespace Core::Memory { +class Memory; +} + +namespace Tegra { +class GPU; +} + +namespace Metal { + +class RendererMetal final : public VideoCore::RendererBase { +public: + explicit RendererMetal(Core::Frontend::EmuWindow& emu_window, + Tegra::MaxwellDeviceMemoryManager& device_memory_, Tegra::GPU& gpu_, + std::unique_ptr context); + ~RendererMetal() override; + + void Composite(std::span framebuffer) override; + + std::vector GetAppletCaptureBuffer() override; + + VideoCore::RasterizerInterface* ReadRasterizer() override { + return &rasterizer; + } + + [[nodiscard]] std::string GetDeviceVendor() const override { + return "Apple"; + } + +private: + Tegra::MaxwellDeviceMemoryManager& device_memory; + Tegra::GPU& gpu; + + Device device; + // TODO: use the layer to get the drawable when drawing directly to the screen + const CAMetalLayer* layer; + + RasterizerMetal rasterizer; + + // HACK + MTLTexture_t renderTexture; +}; + +} // namespace Metal diff --git a/src/video_core/renderer_metal/renderer_metal.mm b/src/video_core/renderer_metal/renderer_metal.mm new file mode 100644 index 0000000000..a3416805d3 --- /dev/null +++ b/src/video_core/renderer_metal/renderer_metal.mm @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/frontend/emu_window.h" +#include "core/frontend/graphics_context.h" +#include "video_core/capture.h" +#include "video_core/renderer_metal/renderer_metal.h" +#include "video_core/renderer_metal/mtl_device.h" + +namespace Metal { + +RendererMetal::RendererMetal(Core::Frontend::EmuWindow& emu_window, + Tegra::MaxwellDeviceMemoryManager& device_memory_, Tegra::GPU& gpu_, + std::unique_ptr context_) + : RendererBase(emu_window, std::move(context_)), device_memory{device_memory_}, + gpu{gpu_}, device{}, + layer(static_cast(render_window.GetWindowInfo().render_surface)), + rasterizer(gpu_, device, layer) { + // HACK + MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm_sRGB + width:1280 + height:720 + mipmapped:NO]; + renderTexture = [device.GetDevice() newTextureWithDescriptor:textureDescriptor]; +} + +RendererMetal::~RendererMetal() = default; + +void RendererMetal::Composite(std::span framebuffers) { + if (framebuffers.empty()) { + return; + } + + // HACK + @autoreleasepool { + //id drawable = [layer nextDrawable]; + + MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor]; + renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(1.0, 0.5, 0.0, 1.0); + renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; + renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; + renderPassDescriptor.colorAttachments[0].texture = renderTexture;//drawable.texture; + + id commandBuffer = [device.GetCommandQueue() commandBuffer]; + id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; + [renderEncoder endEncoding]; + //[commandBuffer presentDrawable:drawable]; + [commandBuffer commit]; + } + + gpu.RendererFrameEndNotify(); + rasterizer.TickFrame(); + + render_window.OnFrameDisplayed(); +} + +std::vector RendererMetal::GetAppletCaptureBuffer() { + return std::vector(VideoCore::Capture::TiledSize); +} + +} // namespace Metal diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index 509ba8a5b4..efcf315e02 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -11,6 +11,9 @@ #include "video_core/renderer_null/renderer_null.h" #include "video_core/renderer_opengl/renderer_opengl.h" #include "video_core/renderer_vulkan/renderer_vulkan.h" +#ifdef __APPLE__ +#include "video_core/renderer_metal/renderer_metal.h" +#endif #include "video_core/video_core.h" namespace { @@ -22,7 +25,9 @@ std::unique_ptr CreateRenderer( switch (Settings::values.renderer_backend.GetValue()) { #ifdef __APPLE__ - // do nothing for now, include metal in here at later date. + case Settings::RendererBackend::Metal: + return std::make_unique(emu_window, device_memory, gpu, + std::move(context)); #else // openGL, not supported on Apple so not bothering to include if macos case Settings::RendererBackend::OpenGL: