diff --git a/CMakeLists.txt b/CMakeLists.txt index 9406dd64a1..01b752d97d 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..023ece4868 100644 --- a/src/suyu/bootmanager.cpp +++ b/src/suyu/bootmanager.cpp @@ -277,6 +277,14 @@ struct VulkanRenderWidget : public RenderWidget { } }; +struct MetalRenderWidget : public RenderWidget { + explicit MetalRenderWidget(GRenderWindow* parent) : RenderWidget(parent) { + // HACK: manually resize the renderable area + resize(600, 400); + windowHandle()->setSurfaceType(QWindow::MetalSurface); + } +}; + struct NullRenderWidget : public RenderWidget { explicit NullRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {} }; @@ -284,8 +292,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 +941,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 +1063,15 @@ bool GRenderWindow::InitializeVulkan() { return true; } +bool GRenderWindow::InitializeMetal() { + auto child = new MetalRenderWidget(this); + child_widget = child; + child_widget->windowHandle()->create(); + 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 0afa3d7036..3306fe69ee 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -372,6 +372,17 @@ if (APPLE) renderer_opengl/util_shaders.cpp renderer_opengl/util_shaders.h ) + + list(APPEND sources + renderer_metal/mtl_command_recorder.mm + renderer_metal/mtl_device.mm + renderer_metal/mtl_rasterizer.mm + renderer_metal/mtl_staging_buffer_pool.mm + renderer_metal/mtl_swap_chain.mm + renderer_metal/mtl_texture_cache.mm + renderer_metal/mtl_texture_cache_base.cpp + renderer_metal/renderer_metal.mm + ) endif() add_library(video_core STATIC ${sources}) diff --git a/src/video_core/renderer_metal/mtl_command_recorder.h b/src/video_core/renderer_metal/mtl_command_recorder.h new file mode 100644 index 0000000000..81895572f9 --- /dev/null +++ b/src/video_core/renderer_metal/mtl_command_recorder.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "objc_bridge.h" +#include "video_core/renderer_metal/objc_bridge.h" + +namespace Metal { + +class Device; + +enum class EncoderType { Render, Compute, Blit }; + +class CommandRecorder { +public: + CommandRecorder(const Device& device_); + ~CommandRecorder(); + + void BeginRenderPass(MTLRenderPassDescriptor* render_pass_descriptor); + + void CheckIfRenderPassIsActive() { + if (!encoder || encoder_type != EncoderType::Render) { + throw std::runtime_error( + "Trying to perform render command, but render pass is not active"); + } + } + + void RequireComputeEncoder(); + + void RequireBlitEncoder(); + + void EndEncoding(); + + void Present(CAMetalDrawable_t drawable); + + void Submit(); + + MTLCommandBuffer_t GetCommandBuffer() { + return command_buffer; + } + + MTLCommandEncoder_t GetCommandEncoder() { + return encoder; + } + +private: + const Device& device; + + MTLCommandBuffer_t command_buffer = nil; + MTLCommandEncoder_t encoder = nil; + + EncoderType encoder_type; + + void RequireCommandBuffer(); +}; + +} // namespace Metal diff --git a/src/video_core/renderer_metal/mtl_command_recorder.mm b/src/video_core/renderer_metal/mtl_command_recorder.mm new file mode 100644 index 0000000000..b005d55b66 --- /dev/null +++ b/src/video_core/renderer_metal/mtl_command_recorder.mm @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "video_core/renderer_metal/mtl_command_recorder.h" +#include "video_core/renderer_metal/mtl_device.h" + +#include + +namespace Metal { + +CommandRecorder::CommandRecorder(const Device& device_) : device(device_) {} + +CommandRecorder::~CommandRecorder() = default; + +void CommandRecorder::BeginRenderPass(MTLRenderPassDescriptor* render_pass_descriptor) { + RequireCommandBuffer(); + EndEncoding(); + encoder = [command_buffer renderCommandEncoderWithDescriptor:render_pass_descriptor]; + encoder_type = EncoderType::Render; +} + +void CommandRecorder::RequireComputeEncoder() { + RequireCommandBuffer(); + if (!encoder || encoder_type != EncoderType::Compute) { + EndEncoding(); + encoder = [command_buffer computeCommandEncoder]; + encoder_type = EncoderType::Compute; + } +} + +void CommandRecorder::RequireBlitEncoder() { + RequireCommandBuffer(); + if (!encoder || encoder_type != EncoderType::Blit) { + EndEncoding(); + encoder = [command_buffer blitCommandEncoder]; + encoder_type = EncoderType::Blit; + } +} + +void CommandRecorder::EndEncoding() { + if (encoder) { + [encoder endEncoding]; + [encoder release]; + encoder = nil; + } +} + +void CommandRecorder::Present(CAMetalDrawable_t drawable) { + [command_buffer presentDrawable:drawable]; +} + +void CommandRecorder::Submit() { + if (command_buffer) { + EndEncoding(); + [command_buffer commit]; + [command_buffer release]; + command_buffer = nil; + } +} + +void CommandRecorder::RequireCommandBuffer() { + if (!command_buffer) { + command_buffer = [device.GetCommandQueue() commandBuffer]; + } +} + +} // namespace Metal 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..9ea17af354 --- /dev/null +++ b/src/video_core/renderer_metal/mtl_rasterizer.h @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_types.h" +#include "mtl_texture_cache.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/mtl_texture_cache.h" + +namespace Core { +class System; +} + +namespace Metal { + +class Device; +class CommandRecorder; +class SwapChain; +class TextureCacheRuntime; + +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_, Tegra::MaxwellDeviceMemoryManager& device_memory_, + const Device& device_, CommandRecorder& command_recorder_, + const SwapChain& swap_chain_); + ~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; + Tegra::MaxwellDeviceMemoryManager& device_memory; + + const Device& device; + CommandRecorder& command_recorder; + const SwapChain& swap_chain; + + StagingBufferPool staging_buffer_pool; + TextureCacheRuntime texture_cache_runtime; + TextureCache texture_cache; +}; + +} // 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..ce93692547 --- /dev/null +++ b/src/video_core/renderer_metal/mtl_rasterizer.mm @@ -0,0 +1,145 @@ +// 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/buffer_cache/buffer_cache.h" +#include "video_core/engines/draw_manager.h" +#include "video_core/engines/kepler_compute.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_metal/mtl_command_recorder.h" +#include "video_core/renderer_metal/mtl_device.h" +#include "video_core/renderer_metal/mtl_rasterizer.h" +#include "video_core/texture_cache/texture_cache_base.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_, + Tegra::MaxwellDeviceMemoryManager& device_memory_, + const Device& device_, CommandRecorder& command_recorder_, + const SwapChain& swap_chain_) + : gpu{gpu_}, device_memory{device_memory_}, device{device_}, + command_recorder{command_recorder_}, swap_chain{swap_chain_}, + staging_buffer_pool(device, command_recorder), + texture_cache_runtime(device, command_recorder, staging_buffer_pool), + texture_cache(texture_cache_runtime, device_memory) {} +RasterizerMetal::~RasterizerMetal() = default; + +void RasterizerMetal::Draw(bool is_indexed, u32 instance_count) { + // TODO: uncomment + //command_recorder.CheckIfRenderPassIsActive(); + //const auto& draw_state = maxwell3d->draw_manager->GetDrawState(); + if (is_indexed) { + std::cout << "DrawIndexed" << std::endl; + /*[command_buffer drawIndexedPrimitives:MTLPrimitiveTypeTriangle + indexCount:draw_params.num_indices + indexType:MTLIndexTypeUInt32 + indexBuffer:draw_state.index_buffer + indexBufferOffset:draw_params.first_index * sizeof(u32) + instanceCount:draw_params.num_instances + baseVertex:draw_params.base_vertex + baseInstance:draw_params.base_instance];*/ + //cmdbuf.DrawIndexed(draw_params.num_vertices, draw_params.num_instances, + // draw_params.first_index, draw_params.base_vertex, + // draw_params.base_instance); + } else { + std::cout << "Draw" << std::endl; + //cmdbuf.Draw(draw_params.num_vertices, draw_params.num_instances, + // draw_params.base_vertex, draw_params.base_instance); + } +} +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/mtl_staging_buffer_pool.h b/src/video_core/renderer_metal/mtl_staging_buffer_pool.h new file mode 100644 index 0000000000..24584c9ac9 --- /dev/null +++ b/src/video_core/renderer_metal/mtl_staging_buffer_pool.h @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +#include "common/common_types.h" + +#include "objc_bridge.h" +#include "video_core/renderer_metal/objc_bridge.h" + +namespace Metal { + +class Device; +class CommandRecorder; + +enum class MemoryUsage { + DeviceLocal, + Upload, + Download, +}; + +struct StagingBufferRef { + StagingBufferRef(MTLBuffer_t buffer_, size_t offset_, std::span mapped_span_); + ~StagingBufferRef(); + + MTLBuffer_t buffer; + size_t offset; + std::span mapped_span; +}; + +struct StagingBuffer { + StagingBuffer(MTLBuffer_t buffer_, std::span mapped_span_); + ~StagingBuffer(); + + MTLBuffer_t buffer; + std::span mapped_span; + + StagingBufferRef Ref() const noexcept; +}; + +class StagingBufferPool { +public: + static constexpr size_t NUM_SYNCS = 16; + + explicit StagingBufferPool(const Device& device, CommandRecorder& command_recorder_); + ~StagingBufferPool(); + + StagingBufferRef Request(size_t size, MemoryUsage usage, bool deferred = false); + void FreeDeferred(StagingBufferRef& ref); + + [[nodiscard]] MTLBuffer_t GetSTreamBufferHandle() const noexcept { + return stream_buffer; + } + + void TickFrame(); + +private: + struct StagingBuffers { + std::vector entries; + size_t delete_index = 0; + size_t iterate_index = 0; + }; + + static constexpr size_t NUM_LEVELS = sizeof(size_t) * CHAR_BIT; + using StagingBuffersCache = std::array; + + StagingBufferRef GetStreamBuffer(size_t size); + + StagingBufferRef GetStagingBuffer(size_t size, MemoryUsage usage, bool deferred = false); + + StagingBufferRef CreateStagingBuffer(size_t size, MemoryUsage usage, bool deferred); + + StagingBuffersCache& GetCache(MemoryUsage usage); + + void ReleaseCache(MemoryUsage usage); + + void ReleaseLevel(StagingBuffersCache& cache, size_t log2); + + const Device& device; + CommandRecorder& command_recorder; + + MTLBuffer_t stream_buffer{}; + + size_t iterator = 0; + size_t used_iterator = 0; + size_t free_iterator = 0; + std::array sync_ticks{}; + + StagingBuffersCache device_local_cache; + StagingBuffersCache upload_cache; + StagingBuffersCache download_cache; + + size_t current_delete_level = 0; + u64 buffer_index = 0; + u64 unique_ids{}; +}; + +} // namespace Metal diff --git a/src/video_core/renderer_metal/mtl_staging_buffer_pool.mm b/src/video_core/renderer_metal/mtl_staging_buffer_pool.mm new file mode 100644 index 0000000000..7f420c64a5 --- /dev/null +++ b/src/video_core/renderer_metal/mtl_staging_buffer_pool.mm @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include + +#include + +#include "common/alignment.h" +#include "common/assert.h" +#include "common/bit_util.h" +#include "common/common_types.h" +#include "common/literals.h" +#include "video_core/renderer_metal/mtl_command_recorder.h" +#include "video_core/renderer_metal/mtl_device.h" +#include "video_core/renderer_metal/mtl_staging_buffer_pool.h" + +namespace Metal { + +StagingBufferRef::StagingBufferRef(MTLBuffer_t buffer_, size_t offset_, std::span mapped_span_) + : buffer{[buffer_ retain]}, offset{offset_}, mapped_span{mapped_span_} {} + +StagingBufferRef::~StagingBufferRef() { + [buffer release]; +} + +StagingBuffer::StagingBuffer(MTLBuffer_t buffer_, std::span mapped_span_) + : buffer{[buffer_ retain]}, mapped_span{mapped_span_} {} + +StagingBuffer::~StagingBuffer() { + [buffer release]; +} + +StagingBufferRef StagingBuffer::Ref() const noexcept { + return StagingBufferRef(buffer, 0, mapped_span); +} + +// TODO: use the _MiB suffix +constexpr size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024;//128_MiB; +constexpr size_t REGION_SIZE = STREAM_BUFFER_SIZE / StagingBufferPool::NUM_SYNCS; + +StagingBufferPool::StagingBufferPool(const Device& device_, CommandRecorder& command_recorder_) + : device{device_}, command_recorder{command_recorder_} { + stream_buffer = [device.GetDevice() newBufferWithLength:STREAM_BUFFER_SIZE + options:MTLResourceStorageModeShared]; +} + +StagingBufferPool::~StagingBufferPool() = default; + +StagingBufferRef StagingBufferPool::Request(size_t size, MemoryUsage usage, bool deferred) { + if (!deferred && usage == MemoryUsage::Upload && size <= REGION_SIZE) { + return GetStreamBuffer(size); + } + + return GetStagingBuffer(size, usage, deferred); +} + +void StagingBufferPool::FreeDeferred(StagingBufferRef& ref) { + // TODO: implement this +} + +void StagingBufferPool::TickFrame() { + current_delete_level = (current_delete_level + 1) % NUM_LEVELS; + + ReleaseCache(MemoryUsage::DeviceLocal); + ReleaseCache(MemoryUsage::Upload); + ReleaseCache(MemoryUsage::Download); +} + +StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) { + // TODO: implement this + + // HACK + return GetStagingBuffer(size, MemoryUsage::Upload); +} + +StagingBufferRef StagingBufferPool::GetStagingBuffer(size_t size, MemoryUsage usage, + bool deferred) { + return CreateStagingBuffer(size, usage, deferred); +} + +StagingBufferRef StagingBufferPool::CreateStagingBuffer(size_t size, MemoryUsage usage, + bool deferred) { + const u32 log2 = Common::Log2Ceil64(size); + MTLBuffer_t buffer = [device.GetDevice() newBufferWithLength:size + options:MTLResourceStorageModeShared]; + // TODO: check if the mapped span is correct + std::span mapped_span(static_cast([buffer contents]), size); + auto& entry = GetCache(usage)[log2].entries.emplace_back(buffer, mapped_span); + + return entry.Ref(); +} + +StagingBufferPool::StagingBuffersCache& StagingBufferPool::GetCache(MemoryUsage usage) { + switch (usage) { + case MemoryUsage::DeviceLocal: + return device_local_cache; + case MemoryUsage::Upload: + return upload_cache; + case MemoryUsage::Download: + return download_cache; + default: + ASSERT_MSG(false, "Invalid memory usage={}", usage); + return upload_cache; + } +} + +void StagingBufferPool::ReleaseCache(MemoryUsage usage) { + ReleaseLevel(GetCache(usage), current_delete_level); +} + +void StagingBufferPool::ReleaseLevel(StagingBuffersCache& cache, size_t log2) { + // TODO: implement this +} + +} // namespace Metal diff --git a/src/video_core/renderer_metal/mtl_swap_chain.h b/src/video_core/renderer_metal/mtl_swap_chain.h new file mode 100644 index 0000000000..7c492f5dc0 --- /dev/null +++ b/src/video_core/renderer_metal/mtl_swap_chain.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "video_core/renderer_metal/objc_bridge.h" + +namespace Metal { + +class Device; +class CommandRecorder; + +class SwapChain { +public: + SwapChain(const Device& device_, CommandRecorder& command_recorder_, + const CAMetalLayer* layer_); + ~SwapChain(); + + void AcquireNextDrawable(); + + void Present(); + + MTLTexture_t GetDrawableTexture(); + +private: + const Device& device; + CommandRecorder& command_recorder; + const CAMetalLayer* layer; + + CAMetalDrawable_t drawable = nil; +}; + +} // namespace Metal diff --git a/src/video_core/renderer_metal/mtl_swap_chain.mm b/src/video_core/renderer_metal/mtl_swap_chain.mm new file mode 100644 index 0000000000..22bbd43f09 --- /dev/null +++ b/src/video_core/renderer_metal/mtl_swap_chain.mm @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "video_core/renderer_metal/mtl_command_recorder.h" +#include "video_core/renderer_metal/mtl_device.h" +#include "video_core/renderer_metal/mtl_swap_chain.h" + +namespace Metal { + +SwapChain::SwapChain(const Device& device_, CommandRecorder& command_recorder_, + const CAMetalLayer* layer_) + : device(device_), command_recorder(command_recorder_), layer([layer_ retain]) { + // Give the layer our device + layer.device = device.GetDevice(); +} + +SwapChain::~SwapChain() { + if (drawable) { + // TODO: should drawable be released? + [drawable release]; + } + [layer release]; +} + +void SwapChain::AcquireNextDrawable() { + // Get the next drawable + drawable = [layer nextDrawable]; +} + +void SwapChain::Present() { + command_recorder.EndEncoding(); + command_recorder.Present(drawable); +} + +MTLTexture_t SwapChain::GetDrawableTexture() { + return drawable.texture; +} + +} // namespace Metal diff --git a/src/video_core/renderer_metal/mtl_texture_cache.h b/src/video_core/renderer_metal/mtl_texture_cache.h new file mode 100644 index 0000000000..328a074263 --- /dev/null +++ b/src/video_core/renderer_metal/mtl_texture_cache.h @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "mtl_staging_buffer_pool.h" +#include "video_core/texture_cache/texture_cache_base.h" + +#include "shader_recompiler/shader_info.h" +#include "video_core/renderer_metal/mtl_staging_buffer_pool.h" +#include "video_core/renderer_metal/objc_bridge.h" +#include "video_core/texture_cache/image_view_base.h" + +namespace Settings { +struct ResolutionScalingInfo; +} + +namespace Metal { + +using Common::SlotVector; +using VideoCommon::ImageId; +using VideoCommon::NUM_RT; +using VideoCommon::Region2D; +using VideoCommon::RenderTargets; +using VideoCore::Surface::PixelFormat; + +class CommandRecorder; +class Device; +class Image; +class ImageView; +class Framebuffer; + +class TextureCacheRuntime { +public: + explicit TextureCacheRuntime(const Device& device_, CommandRecorder& command_recorder_, + StagingBufferPool& staging_buffer_pool_); + + // TODO: implement + void Finish() {} + + void TickFrame(); + + StagingBufferRef UploadStagingBuffer(size_t size); + + StagingBufferRef DownloadStagingBuffer(size_t size, bool deferred = false); + + void FreeDeferredStagingBuffer(StagingBufferRef& ref); + + bool CanUploadMSAA() const noexcept { + return true; + } + + u64 GetDeviceLocalMemory() const { + return 0; + } + + u64 GetDeviceMemoryUsage() const { + return 0; + } + + bool CanReportMemoryUsage() const { + return false; + } + + // TODO: implement + void BlitImage(Framebuffer* dst_framebuffer, ImageView& dst, ImageView& src, + const Region2D& dst_region, const Region2D& src_region, + Tegra::Engines::Fermi2D::Filter filter, + Tegra::Engines::Fermi2D::Operation operation) {} + + // TODO: implement + void CopyImage(Image& dst, Image& src, std::span copies) {} + + // TODO: implement + void CopyImageMSAA(Image& dst, Image& src, std::span copies) {} + + bool ShouldReinterpret(Image& dst, Image& src) { + // HACK + return false; + } + + // TODO: implement + void ReinterpretImage(Image& dst, Image& src, std::span copies) {} + + // TODO: implement + void ConvertImage(Framebuffer* dst, ImageView& dst_view, ImageView& src_view) {} + + // TODO: implement + void InsertUploadMemoryBarrier() {} + + void TransitionImageLayout(Image& image) {} + + // TODO: implement + void AccelerateImageUpload(Image&, const StagingBufferRef&, + std::span) {} + + bool HasNativeBgr() const noexcept { + return true; + } + + bool HasBrokenTextureViewFormats() const noexcept { + return false; + } + + // TODO: implement + void BarrierFeedbackLoop() {} + + const Device& device; + CommandRecorder& command_recorder; + StagingBufferPool& staging_buffer_pool; + const Settings::ResolutionScalingInfo& resolution; +}; + +class Image : public VideoCommon::ImageBase { +public: + explicit Image(TextureCacheRuntime& runtime, const VideoCommon::ImageInfo& info, + GPUVAddr gpu_addr, VAddr cpu_addr); + explicit Image(const VideoCommon::NullImageParams&); + + ~Image(); + + Image(const Image&) = delete; + Image& operator=(const Image&) = delete; + + Image(Image&&) = default; + Image& operator=(Image&&) = default; + + void UploadMemory(MTLBuffer_t buffer, size_t offset, + std::span copies); + + void UploadMemory(const StagingBufferRef& map, + std::span copies); + + void DownloadMemory(MTLBuffer_t buffer, size_t offset, + std::span copies); + + // For some reason, this function cannot be defined in the .mm file since it would report + // undefined symbols + void DownloadMemory(std::span buffers, std::span offsets, + std::span copies) { + // TODO: implement + } + + void DownloadMemory(const StagingBufferRef& map, + std::span copies); + + bool IsRescaled() const { + return rescaled; + } + + bool ScaleUp(bool ignore = false) { + // HACK + return true; + } + + bool ScaleDown(bool ignore = false) { + // HACK + return true; + } + + MTLTexture_t GetHandle() const noexcept { + return texture; + } + +private: + MTLTexture_t texture = nil; + bool initialized = false; + + bool rescaled = false; +}; + +class ImageView : public VideoCommon::ImageViewBase { +public: + explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageViewInfo&, ImageId, Image&); + explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageViewInfo&, ImageId, Image&, + const SlotVector&); + explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo&, + const VideoCommon::ImageViewInfo&, GPUVAddr); + explicit ImageView(TextureCacheRuntime&, const VideoCommon::NullImageViewParams&); + + ~ImageView(); + + ImageView(const ImageView&) = delete; + ImageView& operator=(const ImageView&) = delete; + + ImageView(ImageView&&) = default; + ImageView& operator=(ImageView&&) = default; + + MTLTexture_t GetHandle() const noexcept { + return texture; + } + +private: + MTLTexture_t texture; +}; + +class ImageAlloc : public VideoCommon::ImageAllocBase {}; + +class Sampler { +public: + explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&); + + MTLSamplerState_t GetHandle() const noexcept { + return sampler_state; + } + +private: + MTLSamplerState_t sampler_state; +}; + +class Framebuffer { +public: + explicit Framebuffer(TextureCacheRuntime& runtime, std::span color_buffers, + ImageView* depth_buffer, const VideoCommon::RenderTargets& key); + ~Framebuffer(); + + Framebuffer(const Framebuffer&) = delete; + Framebuffer& operator=(const Framebuffer&) = delete; + + Framebuffer(Framebuffer&&) = default; + Framebuffer& operator=(Framebuffer&&) = default; + + void CreateRenderPassDescriptor(TextureCacheRuntime& runtime, + std::span color_buffers, + ImageView* depth_buffer, bool is_rescaled, size_t width, + size_t height); + + MTLRenderPassDescriptor* GetHandle() const noexcept { + return render_pass; + } + +private: + MTLRenderPassDescriptor* render_pass{}; +}; + +struct TextureCacheParams { + static constexpr bool ENABLE_VALIDATION = true; + static constexpr bool FRAMEBUFFER_BLITS = false; + static constexpr bool HAS_EMULATED_COPIES = false; + static constexpr bool HAS_DEVICE_MEMORY_INFO = true; + static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true; + + using Runtime = Metal::TextureCacheRuntime; + using Image = Metal::Image; + using ImageAlloc = Metal::ImageAlloc; + using ImageView = Metal::ImageView; + using Sampler = Metal::Sampler; + using Framebuffer = Metal::Framebuffer; + using AsyncBuffer = Metal::StagingBufferRef; + using BufferType = MTLBuffer_t; +}; + +using TextureCache = VideoCommon::TextureCache; + +} // namespace Metal diff --git a/src/video_core/renderer_metal/mtl_texture_cache.mm b/src/video_core/renderer_metal/mtl_texture_cache.mm new file mode 100644 index 0000000000..6bc65929f3 --- /dev/null +++ b/src/video_core/renderer_metal/mtl_texture_cache.mm @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include +#include + +#include "common/bit_cast.h" +#include "common/bit_util.h" +#include "common/settings.h" + +#include "video_core/renderer_metal/mtl_device.h" +#include "video_core/renderer_metal/mtl_texture_cache.h" + +#include "video_core/engines/fermi_2d.h" +#include "video_core/texture_cache/formatter.h" +#include "video_core/texture_cache/samples_helper.h" +#include "video_core/texture_cache/util.h" + +namespace Metal { + +using Tegra::Engines::Fermi2D; +using Tegra::Texture::SwizzleSource; +using Tegra::Texture::TextureMipmapFilter; +using VideoCommon::BufferImageCopy; +using VideoCommon::ImageFlagBits; +using VideoCommon::ImageInfo; +using VideoCommon::ImageType; +using VideoCommon::SubresourceRange; +using VideoCore::Surface::BytesPerBlock; +using VideoCore::Surface::IsPixelFormatASTC; +using VideoCore::Surface::IsPixelFormatInteger; +using VideoCore::Surface::SurfaceType; + +TextureCacheRuntime::TextureCacheRuntime(const Device& device_, CommandRecorder& command_recorder_, + StagingBufferPool& staging_buffer_pool_) + : device{device_}, command_recorder{command_recorder_}, + staging_buffer_pool{staging_buffer_pool_}, + resolution{Settings::values.resolution_info} {} + +void TextureCacheRuntime::TickFrame() {} + +StagingBufferRef TextureCacheRuntime::UploadStagingBuffer(size_t size) { + return staging_buffer_pool.Request(size, MemoryUsage::Upload); +} + +StagingBufferRef TextureCacheRuntime::DownloadStagingBuffer(size_t size, bool deferred) { + return staging_buffer_pool.Request(size, MemoryUsage::Download, deferred); +} + +void TextureCacheRuntime::FreeDeferredStagingBuffer(StagingBufferRef& ref) { + staging_buffer_pool.FreeDeferred(ref); +} + +Image::Image(TextureCacheRuntime& runtime, const ImageInfo& info, + GPUVAddr gpu_addr_, VAddr cpu_addr_) + : VideoCommon::ImageBase(info, gpu_addr_, cpu_addr_) { + MTLTextureDescriptor *texture_descriptor = + [[MTLTextureDescriptor alloc] init]; + // TODO: don't hardcode the format + texture_descriptor.pixelFormat = MTLPixelFormatRGBA8Unorm; + texture_descriptor.width = info.size.width; + texture_descriptor.height = info.size.height; + + texture = + [runtime.device.GetDevice() newTextureWithDescriptor:texture_descriptor]; +} + +Image::Image(const VideoCommon::NullImageParams& params) : VideoCommon::ImageBase{params} {} + +Image::~Image() { + if (texture) { + [texture release]; + } +} + +// TODO: implement these +void Image::UploadMemory(MTLBuffer_t buffer, size_t offset, + std::span copies) { + ; +} + +void Image::UploadMemory(const StagingBufferRef& map, + std::span copies) { + ; +} + +void Image::DownloadMemory(MTLBuffer_t buffer, size_t offset, + std::span copies) { + ; +} + +// TODO: uncomment +/* +void Image::DownloadMemory(std::span buffers, std::span offsets, + std::span copies) { + ; +} +*/ + +void Image::DownloadMemory(const StagingBufferRef& map, + std::span copies) { + ; +} + +ImageView::ImageView(TextureCacheRuntime& runtime, + const VideoCommon::ImageViewInfo& info, ImageId image_id_, + Image& image) + : VideoCommon::ImageViewBase{info, image.info, image_id_, image.gpu_addr} { + using Shader::TextureType; + + texture = [image.GetHandle() retain]; + + // TODO: create texture view +} + +ImageView::ImageView(TextureCacheRuntime& runtime, + const VideoCommon::ImageViewInfo& info, ImageId image_id_, + Image& image, const SlotVector& slot_imgs) + : ImageView(runtime, info, image_id_, image) { + // TODO: save slot images +} + +ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, + const VideoCommon::ImageViewInfo& view_info, GPUVAddr gpu_addr_) + : VideoCommon::ImageViewBase{info, view_info, gpu_addr_} { + // TODO: implement +} + +ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::NullImageViewParams& params) + : VideoCommon::ImageViewBase{params} { + // TODO: implement +} + +ImageView::~ImageView() { [texture release]; } + +Sampler::Sampler(TextureCacheRuntime& runtime, + const Tegra::Texture::TSCEntry& tsc) { + MTLSamplerDescriptor* sampler_descriptor = + [[MTLSamplerDescriptor alloc] init]; + + // TODO: configure the descriptor + + sampler_state = [runtime.device.GetDevice() + newSamplerStateWithDescriptor:sampler_descriptor]; +} + +Framebuffer::Framebuffer(TextureCacheRuntime& runtime, + std::span color_buffers, + ImageView* depth_buffer, + const VideoCommon::RenderTargets& key) { + CreateRenderPassDescriptor(runtime, color_buffers, depth_buffer, + key.is_rescaled, key.size.width, key.size.height); +} + +Framebuffer::~Framebuffer() = default; + +void Framebuffer::CreateRenderPassDescriptor( + TextureCacheRuntime& runtime, std::span color_buffers, + ImageView* depth_buffer, bool is_rescaled, size_t width, size_t height) { + render_pass = [MTLRenderPassDescriptor renderPassDescriptor]; + + for (size_t index = 0; index < NUM_RT; ++index) { + const ImageView* const color_buffer = color_buffers[index]; + if (!color_buffer) { + continue; + } + // TODO: don't use index as attachment index + render_pass.colorAttachments[index].clearColor = + MTLClearColorMake(0.5, 1.0, 0.0, 1.0); + render_pass.colorAttachments[index].loadAction = MTLLoadActionClear; + render_pass.colorAttachments[index].storeAction = MTLStoreActionStore; + render_pass.colorAttachments[index].texture = color_buffer->GetHandle(); + } + if (depth_buffer) { + render_pass.depthAttachment.clearDepth = 1.0; + render_pass.depthAttachment.loadAction = MTLLoadActionClear; + render_pass.depthAttachment.storeAction = MTLStoreActionStore; + render_pass.depthAttachment.texture = depth_buffer->GetHandle(); + } +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_metal/mtl_texture_cache_base.cpp b/src/video_core/renderer_metal/mtl_texture_cache_base.cpp new file mode 100644 index 0000000000..714484b747 --- /dev/null +++ b/src/video_core/renderer_metal/mtl_texture_cache_base.cpp @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "video_core/renderer_metal/mtl_texture_cache.h" +#include "video_core/texture_cache/texture_cache.h" + +namespace VideoCommon { +template class VideoCommon::TextureCache; +} 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..37c120626d --- /dev/null +++ b/src/video_core/renderer_metal/objc_bridge.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#ifdef __OBJC__ +#import +#import +typedef id MTLDevice_t; +typedef id MTLCommandQueue_t; +typedef id MTLCommandBuffer_t; +typedef id MTLCommandEncoder_t; +typedef id MTLBuffer_t; +typedef id MTLTexture_t; +typedef id MTLSamplerState_t; +typedef id CAMetalDrawable_t; +#else +typedef void* MTLDevice_t; +typedef void* MTLCommandQueue_t; +typedef void* MTLCommandBuffer_t; +typedef void* MTLCommandEncoder_t; +typedef void* MTLBuffer_t; +typedef void* MTLTexture_t; +typedef void* MTLSamplerState_t; +typedef void MTLRenderPassDescriptor; +typedef void CAMetalLayer; +typedef void* CAMetalDrawable_t; +#define nil NULL +#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..877c50fe77 --- /dev/null +++ b/src/video_core/renderer_metal/renderer_metal.h @@ -0,0 +1,60 @@ +// 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_command_recorder.h" +#include "video_core/renderer_metal/mtl_device.h" +#include "video_core/renderer_metal/mtl_rasterizer.h" +#include "video_core/renderer_metal/mtl_swap_chain.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; + CommandRecorder command_recorder; + SwapChain swap_chain; + + RasterizerMetal rasterizer; +}; + +} // 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..8911f0e1b2 --- /dev/null +++ b/src/video_core/renderer_metal/renderer_metal.mm @@ -0,0 +1,53 @@ +// 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{}, + command_recorder(device), + swap_chain(device, command_recorder, + static_cast(render_window.GetWindowInfo().render_surface)), + rasterizer(gpu_, device_memory, device, command_recorder, swap_chain) {} + +RendererMetal::~RendererMetal() = default; + +void RendererMetal::Composite(std::span framebuffers) { + if (framebuffers.empty()) { + return; + } + + // Ask the swap chain to get next drawable + swap_chain.AcquireNextDrawable(); + + // TODO: copy the framebuffer to the drawable texture instead of this dummy render pass + MTLRenderPassDescriptor* render_pass_descriptor = [MTLRenderPassDescriptor renderPassDescriptor]; + render_pass_descriptor.colorAttachments[0].clearColor = MTLClearColorMake(1.0, 0.5, 0.0, 1.0); + render_pass_descriptor.colorAttachments[0].loadAction = MTLLoadActionClear; + render_pass_descriptor.colorAttachments[0].storeAction = MTLStoreActionStore; + render_pass_descriptor.colorAttachments[0].texture = swap_chain.GetDrawableTexture(); + + command_recorder.BeginRenderPass(render_pass_descriptor); + + swap_chain.Present(); + command_recorder.Submit(); + + 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: