| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,166 @@ | ||
| // Copyright 2022 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #include "VideoBackends/Metal/VideoBackend.h" | ||
|
|
||
| #include <AppKit/AppKit.h> | ||
| #include <Metal/Metal.h> | ||
| #include <QuartzCore/QuartzCore.h> | ||
|
|
||
| #include "Common/Common.h" | ||
| #include "Common/MsgHandler.h" | ||
|
|
||
| #include "VideoBackends/Metal/MTLObjectCache.h" | ||
| #include "VideoBackends/Metal/MTLPerfQuery.h" | ||
| #include "VideoBackends/Metal/MTLRenderer.h" | ||
| #include "VideoBackends/Metal/MTLStateTracker.h" | ||
| #include "VideoBackends/Metal/MTLUtil.h" | ||
| #include "VideoBackends/Metal/MTLVertexManager.h" | ||
|
|
||
| #include "VideoCommon/FramebufferManager.h" | ||
| #include "VideoCommon/VideoCommon.h" | ||
| #include "VideoCommon/VideoConfig.h" | ||
|
|
||
| std::string Metal::VideoBackend::GetName() const | ||
| { | ||
| return NAME; | ||
| } | ||
|
|
||
| std::string Metal::VideoBackend::GetDisplayName() const | ||
| { | ||
| // i18n: Apple's Metal graphics API (https://developer.apple.com/metal/) | ||
| return _trans("Metal"); | ||
| } | ||
|
|
||
| std::optional<std::string> Metal::VideoBackend::GetWarningMessage() const | ||
| { | ||
| if (Util::GetAdapterList().empty()) | ||
| { | ||
| return _trans("No Metal-compatible GPUs were found. " | ||
| "Use the OpenGL backend or upgrade your computer/GPU"); | ||
| } | ||
|
|
||
| return std::nullopt; | ||
| } | ||
|
|
||
| static bool WindowSystemTypeSupportsMetal(WindowSystemType type) | ||
| { | ||
| switch (type) | ||
| { | ||
| case WindowSystemType::MacOS: | ||
| return true; | ||
| default: | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| bool Metal::VideoBackend::Initialize(const WindowSystemInfo& wsi) | ||
| { | ||
| @autoreleasepool | ||
| { | ||
| if (!WindowSystemTypeSupportsMetal(wsi.type) || !wsi.render_surface) | ||
| { | ||
| PanicAlertFmt("Bad WindowSystemInfo for Metal renderer."); | ||
| return false; | ||
| } | ||
|
|
||
| auto devs = Util::GetAdapterList(); | ||
| if (devs.empty()) | ||
| { | ||
| PanicAlertFmt("No Metal GPUs detected."); | ||
| return false; | ||
| } | ||
|
|
||
| Util::PopulateBackendInfo(&g_Config); | ||
| Util::PopulateBackendInfoAdapters(&g_Config, devs); | ||
|
|
||
| // Since we haven't called InitializeShared yet, iAdapter may be out of range, | ||
| // so we have to check it ourselves. | ||
| size_t selected_adapter_index = static_cast<size_t>(g_Config.iAdapter); | ||
| if (selected_adapter_index >= devs.size()) | ||
| { | ||
| WARN_LOG_FMT(VIDEO, "Metal adapter index out of range, selecting default adapter."); | ||
| selected_adapter_index = 0; | ||
| } | ||
| MRCOwned<id<MTLDevice>> adapter = std::move(devs[selected_adapter_index]); | ||
| Util::PopulateBackendInfoFeatures(&g_Config, adapter); | ||
|
|
||
| // With the backend information populated, we can now initialize videocommon. | ||
| InitializeShared(); | ||
|
|
||
| MRCOwned<CAMetalLayer*> layer = MRCRetain(static_cast<CAMetalLayer*>(wsi.render_surface)); | ||
| [layer setDevice:adapter]; | ||
| if (Util::ToAbstract([layer pixelFormat]) == AbstractTextureFormat::Undefined) | ||
| [layer setPixelFormat:MTLPixelFormatBGRA8Unorm]; | ||
| CGSize size = [layer bounds].size; | ||
| float scale = [layer contentsScale]; | ||
|
|
||
| ObjectCache::Initialize(std::move(adapter)); | ||
| g_state_tracker = std::make_unique<StateTracker>(); | ||
| g_renderer = std::make_unique<Renderer>(std::move(layer), size.width * scale, | ||
| size.height * scale, scale); | ||
| g_vertex_manager = std::make_unique<VertexManager>(); | ||
| g_perf_query = std::make_unique<PerfQuery>(); | ||
| g_framebuffer_manager = std::make_unique<FramebufferManager>(); | ||
| g_texture_cache = std::make_unique<TextureCacheBase>(); | ||
| g_shader_cache = std::make_unique<VideoCommon::ShaderCache>(); | ||
|
|
||
| if (!g_vertex_manager->Initialize() || !g_shader_cache->Initialize() || | ||
| !g_renderer->Initialize() || !g_framebuffer_manager->Initialize() || | ||
| !g_texture_cache->Initialize()) | ||
| { | ||
| PanicAlertFmt("Failed to initialize renderer classes"); | ||
| Shutdown(); | ||
| return false; | ||
| } | ||
|
|
||
| g_shader_cache->InitializeShaderCache(); | ||
|
|
||
| return true; | ||
| } | ||
| } | ||
|
|
||
| void Metal::VideoBackend::Shutdown() | ||
| { | ||
| g_shader_cache->Shutdown(); | ||
| g_renderer->Shutdown(); | ||
|
|
||
| g_shader_cache.reset(); | ||
| g_texture_cache.reset(); | ||
| g_framebuffer_manager.reset(); | ||
| g_perf_query.reset(); | ||
| g_vertex_manager.reset(); | ||
| g_renderer.reset(); | ||
| g_state_tracker.reset(); | ||
| ObjectCache::Shutdown(); | ||
| ShutdownShared(); | ||
| } | ||
|
|
||
| void Metal::VideoBackend::InitBackendInfo() | ||
| { | ||
| @autoreleasepool | ||
| { | ||
| Util::PopulateBackendInfo(&g_Config); | ||
| auto adapters = Util::GetAdapterList(); | ||
| Util::PopulateBackendInfoAdapters(&g_Config, adapters); | ||
| if (!adapters.empty()) | ||
| { | ||
| // Use the selected adapter, or the first to fill features. | ||
| size_t index = static_cast<size_t>(g_Config.iAdapter); | ||
| if (index >= adapters.size()) | ||
| index = 0; | ||
| Util::PopulateBackendInfoFeatures(&g_Config, adapters[index]); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void Metal::VideoBackend::PrepareWindow(WindowSystemInfo& wsi) | ||
| { | ||
| if (wsi.type != WindowSystemType::MacOS) | ||
| return; | ||
| NSView* view = static_cast<NSView*>(wsi.render_surface); | ||
| CAMetalLayer* layer = [CAMetalLayer layer]; | ||
| [view setWantsLayer:YES]; | ||
| [view setLayer:layer]; | ||
| wsi.render_surface = layer; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| // Copyright 2022 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <Metal/Metal.h> | ||
| #include <memory> | ||
|
|
||
| #include "VideoBackends/Metal/MRCHelpers.h" | ||
|
|
||
| #include "VideoCommon/RenderState.h" | ||
|
|
||
| struct AbstractPipelineConfig; | ||
| class AbstractPipeline; | ||
|
|
||
| namespace Metal | ||
| { | ||
| class Shader; | ||
| extern MRCOwned<id<MTLDevice>> g_device; | ||
| extern MRCOwned<id<MTLCommandQueue>> g_queue; | ||
|
|
||
| struct DepthStencilSelector | ||
| { | ||
| u8 value; | ||
|
|
||
| DepthStencilSelector() : value(0) {} | ||
| DepthStencilSelector(bool update_enable, enum CompareMode cmp) | ||
| : value(update_enable | (static_cast<u32>(cmp) << 1)) | ||
| { | ||
| } | ||
| DepthStencilSelector(DepthState state) | ||
| : DepthStencilSelector(state.testenable ? state.updateenable : false, | ||
| state.testenable ? state.func : CompareMode::Always) | ||
| { | ||
| } | ||
|
|
||
| bool UpdateEnable() const { return value & 1; } | ||
| enum CompareMode CompareMode() const { return static_cast<enum CompareMode>(value >> 1); } | ||
|
|
||
| bool operator==(const DepthStencilSelector& other) { return value == other.value; } | ||
| bool operator!=(const DepthStencilSelector& other) { return !(*this == other); } | ||
| static constexpr size_t N_VALUES = 1 << 4; | ||
| }; | ||
|
|
||
| struct SamplerSelector | ||
| { | ||
| u8 value; | ||
| SamplerSelector() : value(0) {} | ||
| SamplerSelector(SamplerState state) | ||
| { | ||
| value = (static_cast<u32>(state.tm0.min_filter.Value()) << 0) | | ||
| (static_cast<u32>(state.tm0.mag_filter.Value()) << 1) | | ||
| (static_cast<u32>(state.tm0.mipmap_filter.Value()) << 2) | | ||
| (static_cast<u32>(state.tm0.anisotropic_filtering) << 3); | ||
| value |= (static_cast<u32>(state.tm0.wrap_u.Value()) + | ||
| 3 * static_cast<u32>(state.tm0.wrap_v.Value())) | ||
| << 4; | ||
| } | ||
| FilterMode MinFilter() const { return static_cast<FilterMode>(value & 1); } | ||
| FilterMode MagFilter() const { return static_cast<FilterMode>((value >> 1) & 1); } | ||
| FilterMode MipFilter() const { return static_cast<FilterMode>((value >> 2) & 1); } | ||
| WrapMode WrapU() const { return static_cast<WrapMode>((value >> 4) % 3); } | ||
| WrapMode WrapV() const { return static_cast<WrapMode>((value >> 4) / 3); } | ||
| bool AnisotropicFiltering() const { return ((value >> 3) & 1); } | ||
|
|
||
| bool operator==(const SamplerSelector& other) { return value == other.value; } | ||
| bool operator!=(const SamplerSelector& other) { return !(*this == other); } | ||
| static constexpr size_t N_VALUES = (1 << 4) * 9; | ||
| }; | ||
|
|
||
| class ObjectCache | ||
| { | ||
| ObjectCache(); | ||
|
|
||
| public: | ||
| ~ObjectCache(); | ||
|
|
||
| static void Initialize(MRCOwned<id<MTLDevice>> device); | ||
| static void Shutdown(); | ||
|
|
||
| id<MTLDepthStencilState> GetDepthStencil(DepthStencilSelector sel) { return m_dss[sel.value]; } | ||
|
|
||
| id<MTLSamplerState> GetSampler(SamplerSelector sel) | ||
| { | ||
| if (__builtin_expect(!m_samplers[sel.value], false)) | ||
| m_samplers[sel.value] = CreateSampler(sel); | ||
| return m_samplers[sel.value]; | ||
| } | ||
|
|
||
| id<MTLSamplerState> GetSampler(SamplerState state) { return GetSampler(SamplerSelector(state)); } | ||
|
|
||
| void ReloadSamplers(); | ||
|
|
||
| std::unique_ptr<AbstractPipeline> CreatePipeline(const AbstractPipelineConfig& config); | ||
| void ShaderDestroyed(const Shader* shader); | ||
|
|
||
| private: | ||
| class Internal; | ||
| std::unique_ptr<Internal> m_internal; | ||
| MRCOwned<id<MTLSamplerState>> CreateSampler(SamplerSelector sel); | ||
| MRCOwned<id<MTLDepthStencilState>> m_dss[DepthStencilSelector::N_VALUES]; | ||
| MRCOwned<id<MTLSamplerState>> m_samplers[SamplerSelector::N_VALUES]; | ||
| }; | ||
|
|
||
| extern std::unique_ptr<ObjectCache> g_object_cache; | ||
| } // namespace Metal |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| // Copyright 2022 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <condition_variable> | ||
| #include <mutex> | ||
|
|
||
| #include "VideoCommon/PerfQueryBase.h" | ||
|
|
||
| namespace Metal | ||
| { | ||
| class PerfQuery final : public PerfQueryBase | ||
| { | ||
| public: | ||
| void EnableQuery(PerfQueryGroup type) override; | ||
| void DisableQuery(PerfQueryGroup type) override; | ||
| void ResetQuery() override; | ||
| u32 GetQueryResult(PerfQueryType type) override; | ||
| void FlushResults() override; | ||
| bool IsFlushed() const override; | ||
|
|
||
| /// Notify PerfQuery of a new pending encoder | ||
| /// One call to ReturnResults should be made for every call to IncCount | ||
| void IncCount() { m_query_count.fetch_add(1, std::memory_order_relaxed); } | ||
| /// May be called from any thread | ||
| void ReturnResults(const u64* data, const PerfQueryGroup* groups, size_t count, u32 query_id); | ||
|
|
||
| private: | ||
| u32 m_current_query = 0; | ||
| std::mutex m_results_mtx; | ||
| std::condition_variable m_cv; | ||
| }; | ||
| } // namespace Metal |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| // Copyright 2022 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #include "VideoBackends/Metal/MTLPerfQuery.h" | ||
|
|
||
| #include "VideoBackends/Metal/MTLStateTracker.h" | ||
|
|
||
| void Metal::PerfQuery::EnableQuery(PerfQueryGroup type) | ||
| { | ||
| if (type == PQG_ZCOMP_ZCOMPLOC || type == PQG_ZCOMP) | ||
| g_state_tracker->EnablePerfQuery(type, m_current_query); | ||
| } | ||
|
|
||
| void Metal::PerfQuery::DisableQuery(PerfQueryGroup type) | ||
| { | ||
| if (type == PQG_ZCOMP_ZCOMPLOC || type == PQG_ZCOMP) | ||
| g_state_tracker->DisablePerfQuery(); | ||
| } | ||
|
|
||
| void Metal::PerfQuery::ResetQuery() | ||
| { | ||
| std::lock_guard<std::mutex> lock(m_results_mtx); | ||
| m_current_query++; | ||
| for (std::atomic<u32>& result : m_results) | ||
| result.store(0, std::memory_order_relaxed); | ||
| } | ||
|
|
||
| u32 Metal::PerfQuery::GetQueryResult(PerfQueryType type) | ||
| { | ||
| u32 result = 0; | ||
| if (type == PQ_ZCOMP_INPUT_ZCOMPLOC || type == PQ_ZCOMP_OUTPUT_ZCOMPLOC) | ||
| { | ||
| result = m_results[PQG_ZCOMP_ZCOMPLOC].load(std::memory_order_relaxed); | ||
| } | ||
| else if (type == PQ_ZCOMP_INPUT || type == PQ_ZCOMP_OUTPUT) | ||
| { | ||
| result = m_results[PQG_ZCOMP].load(std::memory_order_relaxed); | ||
| } | ||
| else if (type == PQ_BLEND_INPUT) | ||
| { | ||
| result = m_results[PQG_ZCOMP].load(std::memory_order_relaxed) + | ||
| m_results[PQG_ZCOMP_ZCOMPLOC].load(std::memory_order_relaxed); | ||
| } | ||
| else if (type == PQ_EFB_COPY_CLOCKS) | ||
| { | ||
| result = m_results[PQG_EFB_COPY_CLOCKS].load(std::memory_order_relaxed); | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| void Metal::PerfQuery::FlushResults() | ||
| { | ||
| if (IsFlushed()) | ||
| return; | ||
|
|
||
| // There's a possibility that some active performance queries are unflushed | ||
| g_state_tracker->FlushEncoders(); | ||
|
|
||
| std::unique_lock<std::mutex> lock(m_results_mtx); | ||
| while (!IsFlushed()) | ||
| m_cv.wait(lock); | ||
| } | ||
|
|
||
| bool Metal::PerfQuery::IsFlushed() const | ||
| { | ||
| return m_query_count.load(std::memory_order_acquire) == 0; | ||
| } | ||
|
|
||
| void Metal::PerfQuery::ReturnResults(const u64* data, const PerfQueryGroup* groups, size_t count, | ||
| u32 query_id) | ||
| { | ||
| { | ||
| std::lock_guard<std::mutex> lock(m_results_mtx); | ||
| if (m_current_query == query_id) | ||
| { | ||
| for (size_t i = 0; i < count; ++i) | ||
| { | ||
| u64 native_res_result = data[i] * (EFB_WIDTH * EFB_HEIGHT) / | ||
| (g_renderer->GetTargetWidth() * g_renderer->GetTargetHeight()); | ||
|
|
||
| native_res_result /= g_ActiveConfig.iMultisamples; | ||
|
|
||
| m_results[groups[i]].fetch_add(native_res_result, std::memory_order_relaxed); | ||
| } | ||
| } | ||
| m_query_count.fetch_sub(1, std::memory_order_release); | ||
| } | ||
| m_cv.notify_one(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| // Copyright 2022 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <Metal/Metal.h> | ||
|
|
||
| #include "VideoBackends/Metal/MRCHelpers.h" | ||
| #include "VideoBackends/Metal/MTLObjectCache.h" | ||
| #include "VideoBackends/Metal/MTLShader.h" | ||
|
|
||
| #include "VideoCommon/AbstractPipeline.h" | ||
| #include "VideoCommon/AbstractShader.h" | ||
|
|
||
| namespace Metal | ||
| { | ||
| struct PipelineReflection | ||
| { | ||
| u32 textures = 0; | ||
| u32 samplers = 0; | ||
| u32 vertex_buffers = 0; | ||
| u32 fragment_buffers = 0; | ||
| PipelineReflection() = default; | ||
| explicit PipelineReflection(MTLRenderPipelineReflection* reflection); | ||
| }; | ||
|
|
||
| class Pipeline final : public AbstractPipeline | ||
| { | ||
| public: | ||
| explicit Pipeline(MRCOwned<id<MTLRenderPipelineState>> pipeline, | ||
| const PipelineReflection& reflection, MTLPrimitiveType prim, MTLCullMode cull, | ||
| DepthState depth, AbstractPipelineUsage usage); | ||
|
|
||
| id<MTLRenderPipelineState> Get() const { return m_pipeline; } | ||
| MTLPrimitiveType Prim() const { return m_prim; } | ||
| MTLCullMode Cull() const { return m_cull; } | ||
| DepthStencilSelector DepthStencil() const { return m_depth_stencil; } | ||
| AbstractPipelineUsage Usage() const { return m_usage; } | ||
| u32 GetTextures() const { return m_reflection.textures; } | ||
| u32 GetSamplers() const { return m_reflection.samplers; } | ||
| u32 GetVertexBuffers() const { return m_reflection.vertex_buffers; } | ||
| u32 GetFragmentBuffers() const { return m_reflection.fragment_buffers; } | ||
| bool UsesVertexBuffer(u32 index) const { return m_reflection.vertex_buffers & (1 << index); } | ||
| bool UsesFragmentBuffer(u32 index) const { return m_reflection.fragment_buffers & (1 << index); } | ||
|
|
||
| private: | ||
| MRCOwned<id<MTLRenderPipelineState>> m_pipeline; | ||
| MTLPrimitiveType m_prim; | ||
| MTLCullMode m_cull; | ||
| DepthStencilSelector m_depth_stencil; | ||
| AbstractPipelineUsage m_usage; | ||
| PipelineReflection m_reflection; | ||
| }; | ||
|
|
||
| class ComputePipeline : public Shader | ||
| { | ||
| public: | ||
| explicit ComputePipeline(ShaderStage stage, MTLComputePipelineReflection* reflection, | ||
| std::string msl, MRCOwned<id<MTLFunction>> shader, | ||
| MRCOwned<id<MTLComputePipelineState>> pipeline); | ||
|
|
||
| id<MTLComputePipelineState> GetComputePipeline() const { return m_compute_pipeline; } | ||
| bool UsesTexture(u32 index) const { return m_textures & (1 << index); } | ||
| bool UsesBuffer(u32 index) const { return m_buffers & (1 << index); } | ||
|
|
||
| private: | ||
| MRCOwned<id<MTLComputePipelineState>> m_compute_pipeline; | ||
| u32 m_textures = 0; | ||
| u32 m_buffers = 0; | ||
| }; | ||
| } // namespace Metal |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| // Copyright 2022 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #include "VideoBackends/Metal/MTLPipeline.h" | ||
|
|
||
| #include "Common/MsgHandler.h" | ||
|
|
||
| static void MarkAsUsed(u32* list, u32 start, u32 length) | ||
| { | ||
| for (u32 i = start; i < start + length; ++i) | ||
| *list |= 1 << i; | ||
| } | ||
|
|
||
| static void GetArguments(NSArray<MTLArgument*>* arguments, u32* textures, u32* samplers, | ||
| u32* buffers) | ||
| { | ||
| for (MTLArgument* argument in arguments) | ||
| { | ||
| const u32 idx = [argument index]; | ||
| const u32 length = [argument arrayLength]; | ||
| if (idx + length > 32) | ||
| { | ||
| PanicAlertFmt("Making a MTLPipeline with high argument index {:d}..<{:d} for {:s}", // | ||
| idx, idx + length, [[argument name] UTF8String]); | ||
| continue; | ||
| } | ||
| switch ([argument type]) | ||
| { | ||
| case MTLArgumentTypeTexture: | ||
| if (textures) | ||
| MarkAsUsed(textures, idx, length); | ||
| else | ||
| PanicAlertFmt("Vertex function wants a texture!"); | ||
| break; | ||
| case MTLArgumentTypeSampler: | ||
| if (samplers) | ||
| MarkAsUsed(samplers, idx, length); | ||
| else | ||
| PanicAlertFmt("Vertex function wants a sampler!"); | ||
| break; | ||
| case MTLArgumentTypeBuffer: | ||
| MarkAsUsed(buffers, idx, length); | ||
| break; | ||
| default: | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Metal::PipelineReflection::PipelineReflection(MTLRenderPipelineReflection* reflection) | ||
| { | ||
| GetArguments([reflection vertexArguments], nullptr, nullptr, &vertex_buffers); | ||
| GetArguments([reflection fragmentArguments], &textures, &samplers, &fragment_buffers); | ||
| } | ||
|
|
||
| Metal::Pipeline::Pipeline(MRCOwned<id<MTLRenderPipelineState>> pipeline, | ||
| const PipelineReflection& reflection, MTLPrimitiveType prim, | ||
| MTLCullMode cull, DepthState depth, AbstractPipelineUsage usage) | ||
| : m_pipeline(std::move(pipeline)), m_prim(prim), m_cull(cull), m_depth_stencil(depth), | ||
| m_usage(usage), m_reflection(reflection) | ||
| { | ||
| } | ||
|
|
||
| Metal::ComputePipeline::ComputePipeline(ShaderStage stage, MTLComputePipelineReflection* reflection, | ||
| std::string msl, MRCOwned<id<MTLFunction>> shader, | ||
| MRCOwned<id<MTLComputePipelineState>> pipeline) | ||
| : Shader(stage, std::move(msl), std::move(shader)), m_compute_pipeline(std::move(pipeline)) | ||
| { | ||
| GetArguments([reflection arguments], &m_textures, nullptr, &m_buffers); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| // Copyright 2022 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <Metal/Metal.h> | ||
| #include <QuartzCore/QuartzCore.h> | ||
|
|
||
| #include "VideoCommon/RenderBase.h" | ||
|
|
||
| #include "VideoBackends/Metal/MRCHelpers.h" | ||
|
|
||
| namespace Metal | ||
| { | ||
| class Framebuffer; | ||
| class Texture; | ||
|
|
||
| class Renderer final : public ::Renderer | ||
| { | ||
| public: | ||
| Renderer(MRCOwned<CAMetalLayer*> layer, int width, int height, float layer_scale); | ||
| ~Renderer() override; | ||
|
|
||
| bool IsHeadless() const override; | ||
|
|
||
| bool Initialize() override; | ||
|
|
||
| std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config, | ||
| std::string_view name) override; | ||
| std::unique_ptr<AbstractStagingTexture> | ||
| CreateStagingTexture(StagingTextureType type, const TextureConfig& config) override; | ||
| std::unique_ptr<AbstractFramebuffer> | ||
| CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment) override; | ||
|
|
||
| std::unique_ptr<AbstractShader> CreateShaderFromSource(ShaderStage stage, std::string_view source, | ||
| std::string_view name) override; | ||
| std::unique_ptr<AbstractShader> CreateShaderFromBinary(ShaderStage stage, const void* data, | ||
| size_t length, | ||
| std::string_view name) override; | ||
| std::unique_ptr<AbstractShader> CreateShaderFromMSL(ShaderStage stage, std::string msl, | ||
| std::string_view glsl, std::string_view name); | ||
| std::unique_ptr<NativeVertexFormat> | ||
| CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) override; | ||
| std::unique_ptr<AbstractPipeline> CreatePipeline(const AbstractPipelineConfig& config, | ||
| const void* cache_data = nullptr, | ||
| size_t cache_data_length = 0) override; | ||
|
|
||
| void Flush() override; | ||
| void WaitForGPUIdle() override; | ||
| void OnConfigChanged(u32 bits) override; | ||
|
|
||
| void ClearScreen(const MathUtil::Rectangle<int>& rc, bool color_enable, bool alpha_enable, | ||
| bool z_enable, u32 color, u32 z) override; | ||
|
|
||
| void SetPipeline(const AbstractPipeline* pipeline) override; | ||
| void SetFramebuffer(AbstractFramebuffer* framebuffer) override; | ||
| void SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) override; | ||
| void SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, const ClearColor& color_value = {}, | ||
| float depth_value = 0.0f) override; | ||
| void SetScissorRect(const MathUtil::Rectangle<int>& rc) override; | ||
| void SetTexture(u32 index, const AbstractTexture* texture) override; | ||
| void SetSamplerState(u32 index, const SamplerState& state) override; | ||
| void SetComputeImageTexture(AbstractTexture* texture, bool read, bool write) override; | ||
| void UnbindTexture(const AbstractTexture* texture) override; | ||
| void SetViewport(float x, float y, float width, float height, float near_depth, | ||
| float far_depth) override; | ||
| void Draw(u32 base_vertex, u32 num_vertices) override; | ||
| void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) override; | ||
| void DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y, | ||
| u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z) override; | ||
| void BindBackbuffer(const ClearColor& clear_color = {}) override; | ||
| void PresentBackbuffer() override; | ||
|
|
||
| protected: | ||
| std::unique_ptr<::BoundingBox> CreateBoundingBox() const override; | ||
|
|
||
| private: | ||
| MRCOwned<CAMetalLayer*> m_layer; | ||
| MRCOwned<id<CAMetalDrawable>> m_drawable; | ||
| std::unique_ptr<Texture> m_bb_texture; | ||
| std::unique_ptr<Framebuffer> m_backbuffer; | ||
| u32 m_texture_counter = 0; | ||
| u32 m_staging_texture_counter = 0; | ||
| std::array<u32, 4> m_shader_counter = {}; | ||
|
|
||
| void CheckForSurfaceChange(); | ||
| void CheckForSurfaceResize(); | ||
| void SetupSurface(); | ||
| }; | ||
| } // namespace Metal |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| // Copyright 2022 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <Metal/Metal.h> | ||
|
|
||
| #include "VideoBackends/Metal/MRCHelpers.h" | ||
|
|
||
| #include "VideoCommon/AbstractPipeline.h" | ||
| #include "VideoCommon/AbstractShader.h" | ||
|
|
||
| namespace Metal | ||
| { | ||
| class Shader : public AbstractShader | ||
| { | ||
| public: | ||
| explicit Shader(ShaderStage stage, std::string msl, MRCOwned<id<MTLFunction>> shader); | ||
| ~Shader(); | ||
|
|
||
| id<MTLFunction> GetShader() const { return m_shader; } | ||
| BinaryData GetBinary() const override; | ||
|
|
||
| private: | ||
| std::string m_msl; | ||
| MRCOwned<id<MTLFunction>> m_shader; | ||
| }; | ||
| } // namespace Metal |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
|
|
||
| #include "VideoBackends/Metal/MTLShader.h" | ||
|
|
||
| #include "VideoBackends/Metal/MTLObjectCache.h" | ||
|
|
||
| Metal::Shader::Shader(ShaderStage stage, std::string msl, MRCOwned<id<MTLFunction>> shader) | ||
| : AbstractShader(stage), m_msl(std::move(msl)), m_shader(std::move(shader)) | ||
| { | ||
| } | ||
|
|
||
| Metal::Shader::~Shader() | ||
| { | ||
| g_object_cache->ShaderDestroyed(this); | ||
| } | ||
|
|
||
| AbstractShader::BinaryData Metal::Shader::GetBinary() const | ||
| { | ||
| return BinaryData(m_msl.begin(), m_msl.end()); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,263 @@ | ||
| // Copyright 2022 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <Metal/Metal.h> | ||
| #include <atomic> | ||
| #include <memory> | ||
| #include <vector> | ||
|
|
||
| #include "Common/Assert.h" | ||
| #include "Common/CommonTypes.h" | ||
| #include "Common/MathUtil.h" | ||
|
|
||
| #include "VideoBackends/Metal/MRCHelpers.h" | ||
| #include "VideoBackends/Metal/MTLObjectCache.h" | ||
| #include "VideoBackends/Metal/MTLTexture.h" | ||
| #include "VideoBackends/Metal/MTLUtil.h" | ||
|
|
||
| #include "VideoCommon/PerfQueryBase.h" | ||
| #include "VideoCommon/RenderBase.h" | ||
|
|
||
| namespace Metal | ||
| { | ||
| class Pipeline; | ||
| class ComputePipeline; | ||
|
|
||
| class StateTracker | ||
| { | ||
| public: | ||
| enum class UploadBuffer | ||
| { | ||
| Other, | ||
| Uniform, | ||
| Vertex, | ||
| Index, | ||
| TextureData, | ||
| Texels, | ||
| Last = Texels | ||
| }; | ||
|
|
||
| struct Map | ||
| { | ||
| id<MTLBuffer> gpu_buffer; | ||
| size_t gpu_offset; | ||
| void* cpu_buffer; | ||
| }; | ||
|
|
||
| enum class AlignMask : size_t | ||
| { | ||
| None = 0, | ||
| Other = 15, | ||
| Uniform = 255, | ||
| }; | ||
|
|
||
| StateTracker(StateTracker&&) = delete; | ||
| explicit StateTracker(); | ||
| ~StateTracker(); | ||
|
|
||
| Framebuffer* GetCurrentFramebuffer() { return m_current_framebuffer; }; | ||
| void SetCurrentFramebuffer(Framebuffer* framebuffer); | ||
| void BeginClearRenderPass(MTLClearColor color, float depth); | ||
| void BeginRenderPass(MTLLoadAction load_action); | ||
| void BeginRenderPass(MTLRenderPassDescriptor* descriptor); | ||
| void BeginComputePass(); | ||
| MTLRenderPassDescriptor* GetRenderPassDescriptor(Framebuffer* framebuffer, | ||
| MTLLoadAction load_action); | ||
|
|
||
| void EndRenderPass(); | ||
| void FlushEncoders(); | ||
| void WaitForFlushedEncoders(); | ||
| bool HasUnflushedData() { return static_cast<bool>(m_current_render_cmdbuf); } | ||
| bool GPUBusy() | ||
| { | ||
| return m_current_draw != 1 + m_last_finished_draw.load(std::memory_order_acquire); | ||
| } | ||
| void ReloadSamplers(); | ||
|
|
||
| void SetPipeline(const Pipeline* pipe); | ||
| void SetPipeline(const ComputePipeline* pipe); | ||
| void SetScissor(const MathUtil::Rectangle<int>& rect); | ||
| void SetViewport(float x, float y, float width, float height, float near_depth, float far_depth); | ||
| void SetTexture(u32 idx, id<MTLTexture> texture); | ||
| void SetSampler(u32 idx, const SamplerState& sampler); | ||
| void SetComputeTexture(const Texture* texture); | ||
| void InvalidateUniforms(bool vertex, bool fragment); | ||
| void SetUtilityUniform(const void* buffer, size_t size); | ||
| void SetTexelBuffer(id<MTLBuffer> buffer, u32 offset0, u32 offset1); | ||
| void SetVerticesAndIndices(id<MTLBuffer> vertices, id<MTLBuffer> indices); | ||
| void SetBBoxBuffer(id<MTLBuffer> bbox, id<MTLFence> upload, id<MTLFence> download); | ||
| void SetVertexBufferNow(u32 idx, id<MTLBuffer> buffer, u32 offset); | ||
| void SetFragmentBufferNow(u32 idx, id<MTLBuffer> buffer, u32 offset); | ||
| /// Use around utility draws that are commonly used immediately before gx draws to the same buffer | ||
| void EnableEncoderLabel(bool enabled) { m_flags.should_apply_label = enabled; } | ||
| void EnablePerfQuery(PerfQueryGroup group, u32 query_id); | ||
| void DisablePerfQuery(); | ||
| void UnbindTexture(id<MTLTexture> texture); | ||
|
|
||
| void Draw(u32 base_vertex, u32 num_vertices); | ||
| void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex); | ||
| void DispatchComputeShader(u32 groupsize_x, u32 groupsize_y, u32 groupsize_z, u32 groups_x, | ||
| u32 groups_y, u32 groups_z); | ||
| void ResolveTexture(id<MTLTexture> src, id<MTLTexture> dst, u32 layer, u32 level); | ||
|
|
||
| size_t Align(size_t amt, AlignMask align) | ||
| { | ||
| return (amt + static_cast<size_t>(align)) & ~static_cast<size_t>(align); | ||
| } | ||
| Map Allocate(UploadBuffer buffer_idx, size_t amt, AlignMask align) | ||
| { | ||
| Preallocate(buffer_idx, amt); | ||
| return CommitPreallocation(buffer_idx, amt, align); | ||
| } | ||
| std::pair<void*, size_t> Preallocate(UploadBuffer buffer_idx, size_t amt); | ||
| /// Must follow a call to Preallocate where amt is >= to the one provided here | ||
| Map CommitPreallocation(UploadBuffer buffer_idx, size_t amt, AlignMask align) | ||
| { | ||
| DEBUG_ASSERT((m_upload_buffers[static_cast<int>(buffer_idx)].usage.Pos() & | ||
| static_cast<size_t>(align)) == 0); | ||
| return CommitPreallocation(buffer_idx, Align(amt, align)); | ||
| } | ||
| id<MTLBlitCommandEncoder> GetTextureUploadEncoder(); | ||
| id<MTLCommandBuffer> GetRenderCmdBuf(); | ||
|
|
||
| private: | ||
| class UsageTracker | ||
| { | ||
| struct UsageEntry | ||
| { | ||
| u64 drawno; | ||
| size_t pos; | ||
| }; | ||
| std::vector<UsageEntry> m_usage; | ||
| size_t m_size = 0; | ||
| size_t m_pos = 0; | ||
|
|
||
| public: | ||
| size_t Size() { return m_size; } | ||
| size_t Pos() { return m_pos; } | ||
| bool PrepareForAllocation(u64 last_draw, size_t amt); | ||
| size_t Allocate(u64 current_draw, size_t amt); | ||
| void Reset(size_t new_size); | ||
| }; | ||
|
|
||
| struct Buffer | ||
| { | ||
| UsageTracker usage; | ||
| MRCOwned<id<MTLBuffer>> mtlbuffer; | ||
| void* buffer = nullptr; | ||
| }; | ||
|
|
||
| struct Backref; | ||
| struct PerfQueryTracker; | ||
|
|
||
| std::shared_ptr<Backref> m_backref; | ||
| std::vector<std::shared_ptr<PerfQueryTracker>> m_perf_query_tracker_cache; | ||
| MRCOwned<id<MTLCommandBuffer>> m_upload_cmdbuf; | ||
| MRCOwned<id<MTLBlitCommandEncoder>> m_upload_encoder; | ||
| MRCOwned<id<MTLCommandBuffer>> m_texture_upload_cmdbuf; | ||
| MRCOwned<id<MTLBlitCommandEncoder>> m_texture_upload_encoder; | ||
| MRCOwned<id<MTLCommandBuffer>> m_current_render_cmdbuf; | ||
| MRCOwned<id<MTLCommandBuffer>> m_last_render_cmdbuf; | ||
| MRCOwned<id<MTLRenderCommandEncoder>> m_current_render_encoder; | ||
| MRCOwned<id<MTLComputeCommandEncoder>> m_current_compute_encoder; | ||
| MRCOwned<MTLRenderPassDescriptor*> m_render_pass_desc[3]; | ||
| MRCOwned<MTLRenderPassDescriptor*> m_resolve_pass_desc; | ||
| Framebuffer* m_current_framebuffer; | ||
| Buffer m_upload_buffers[static_cast<int>(UploadBuffer::Last) + 1]; | ||
| u64 m_current_draw = 1; | ||
| std::atomic<u64> m_last_finished_draw{0}; | ||
|
|
||
| MRCOwned<id<MTLTexture>> m_dummy_texture; | ||
|
|
||
| // MARK: State | ||
| u8 m_dirty_textures; | ||
| u8 m_dirty_samplers; | ||
| union Flags | ||
| { | ||
| struct | ||
| { | ||
| // clang-format off | ||
| bool has_gx_vs_uniform : 1; | ||
| bool has_gx_ps_uniform : 1; | ||
| bool has_utility_vs_uniform : 1; | ||
| bool has_utility_ps_uniform : 1; | ||
| bool has_compute_texture : 1; | ||
| bool has_pipeline : 1; | ||
| bool has_scissor : 1; | ||
| bool has_viewport : 1; | ||
| bool has_vertices : 1; | ||
| bool has_texel_buffer : 1; | ||
| bool bbox_fence : 1; | ||
| bool should_apply_label : 1; | ||
| // clang-format on | ||
| }; | ||
| u16 bits = 0; | ||
| void NewEncoder() | ||
| { | ||
| Flags reset_mask; | ||
| // Set the flags you *don't* want to reset | ||
| reset_mask.should_apply_label = true; | ||
| bits &= reset_mask.bits; | ||
| } | ||
| } m_flags; | ||
|
|
||
| /// Things that represent the state of the encoder | ||
| struct Current | ||
| { | ||
| NSString* label; | ||
| id<MTLRenderPipelineState> pipeline; | ||
| std::array<id<MTLBuffer>, 2> vertex_buffers; | ||
| std::array<id<MTLBuffer>, 2> fragment_buffers; | ||
| u32 width; | ||
| u32 height; | ||
| MathUtil::Rectangle<int> scissor_rect; | ||
| Util::Viewport viewport; | ||
| MTLDepthClipMode depth_clip_mode; | ||
| MTLCullMode cull_mode; | ||
| DepthStencilSelector depth_stencil; | ||
| PerfQueryGroup perf_query_group; | ||
| } m_current; | ||
| std::shared_ptr<PerfQueryTracker> m_current_perf_query; | ||
|
|
||
| /// Things that represent what we'd *like* to have on the encoder for the next draw | ||
| struct State | ||
| { | ||
| MathUtil::Rectangle<int> scissor_rect; | ||
| Util::Viewport viewport; | ||
| const Pipeline* render_pipeline = nullptr; | ||
| const ComputePipeline* compute_pipeline = nullptr; | ||
| std::array<id<MTLTexture>, 8> textures = {}; | ||
| std::array<id<MTLSamplerState>, 8> samplers = {}; | ||
| std::array<float, 8> sampler_min_lod; | ||
| std::array<float, 8> sampler_max_lod; | ||
| std::array<SamplerState, 8> sampler_states; | ||
| const Texture* compute_texture = nullptr; | ||
| std::unique_ptr<u8[]> utility_uniform; | ||
| u32 utility_uniform_size = 0; | ||
| u32 utility_uniform_capacity = 0; | ||
| id<MTLBuffer> bbox = nullptr; | ||
| id<MTLFence> bbox_upload_fence = nullptr; | ||
| id<MTLFence> bbox_download_fence = nullptr; | ||
| id<MTLBuffer> vertices = nullptr; | ||
| id<MTLBuffer> indices = nullptr; | ||
| id<MTLBuffer> texels = nullptr; | ||
| u32 texel_buffer_offset0; | ||
| u32 texel_buffer_offset1; | ||
| PerfQueryGroup perf_query_group = static_cast<PerfQueryGroup>(-1); | ||
| } m_state; | ||
|
|
||
| u32 m_perf_query_tracker_counter = 0; | ||
|
|
||
| std::shared_ptr<PerfQueryTracker> NewPerfQueryTracker(); | ||
| void SetSamplerForce(u32 idx, const SamplerState& sampler); | ||
| Map CommitPreallocation(UploadBuffer buffer_idx, size_t actual_amt); | ||
| void CheckViewport(); | ||
| void CheckScissor(); | ||
| void PrepareRender(); | ||
| void PrepareCompute(); | ||
| }; | ||
|
|
||
| extern std::unique_ptr<StateTracker> g_state_tracker; | ||
| } // namespace Metal |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| // Copyright 2022 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <Metal/Metal.h> | ||
|
|
||
| #include "VideoCommon/AbstractFramebuffer.h" | ||
| #include "VideoCommon/AbstractStagingTexture.h" | ||
| #include "VideoCommon/AbstractTexture.h" | ||
|
|
||
| #include "VideoBackends/Metal/MRCHelpers.h" | ||
|
|
||
| namespace Metal | ||
| { | ||
| class Texture final : public AbstractTexture | ||
| { | ||
| public: | ||
| explicit Texture(MRCOwned<id<MTLTexture>> tex, const TextureConfig& config); | ||
| ~Texture(); | ||
|
|
||
| void CopyRectangleFromTexture(const AbstractTexture* src, | ||
| const MathUtil::Rectangle<int>& src_rect, u32 src_layer, | ||
| u32 src_level, const MathUtil::Rectangle<int>& dst_rect, | ||
| u32 dst_layer, u32 dst_level) override; | ||
| void ResolveFromTexture(const AbstractTexture* src, const MathUtil::Rectangle<int>& rect, | ||
| u32 layer, u32 level) override; | ||
| void Load(u32 level, u32 width, u32 height, u32 row_length, const u8* buffer, | ||
| size_t buffer_size) override; | ||
|
|
||
| id<MTLTexture> GetMTLTexture() const { return m_tex; } | ||
| void SetMTLTexture(MRCOwned<id<MTLTexture>> tex) { m_tex = std::move(tex); } | ||
|
|
||
| private: | ||
| MRCOwned<id<MTLTexture>> m_tex; | ||
| }; | ||
|
|
||
| class StagingTexture final : public AbstractStagingTexture | ||
| { | ||
| public: | ||
| StagingTexture(MRCOwned<id<MTLBuffer>> buffer, StagingTextureType type, | ||
| const TextureConfig& config); | ||
| ~StagingTexture(); | ||
|
|
||
| void CopyFromTexture(const AbstractTexture* src, const MathUtil::Rectangle<int>& src_rect, | ||
| u32 src_layer, u32 src_level, | ||
| const MathUtil::Rectangle<int>& dst_rect) override; | ||
| void CopyToTexture(const MathUtil::Rectangle<int>& src_rect, AbstractTexture* dst, | ||
| const MathUtil::Rectangle<int>& dst_rect, u32 dst_layer, | ||
| u32 dst_level) override; | ||
|
|
||
| bool Map() override; | ||
| void Unmap() override; | ||
| void Flush() override; | ||
|
|
||
| private: | ||
| MRCOwned<id<MTLBuffer>> m_buffer; | ||
| MRCOwned<id<MTLCommandBuffer>> m_wait_buffer; | ||
| }; | ||
|
|
||
| class Framebuffer final : public AbstractFramebuffer | ||
| { | ||
| public: | ||
| Framebuffer(AbstractTexture* color, AbstractTexture* depth, u32 width, u32 height, u32 layers, | ||
| u32 samples); | ||
| ~Framebuffer(); | ||
|
|
||
| id<MTLTexture> GetColor() const | ||
| { | ||
| return static_cast<Texture*>(GetColorAttachment())->GetMTLTexture(); | ||
| } | ||
| id<MTLTexture> GetDepth() const | ||
| { | ||
| return static_cast<Texture*>(GetDepthAttachment())->GetMTLTexture(); | ||
| } | ||
| }; | ||
| } // namespace Metal |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,180 @@ | ||
| // Copyright 2022 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #include "VideoBackends/Metal/MTLTexture.h" | ||
|
|
||
| #include "Common/Align.h" | ||
| #include "Common/Assert.h" | ||
|
|
||
| #include "VideoBackends/Metal/MTLStateTracker.h" | ||
|
|
||
| Metal::Texture::Texture(MRCOwned<id<MTLTexture>> tex, const TextureConfig& config) | ||
| : AbstractTexture(config), m_tex(std::move(tex)) | ||
| { | ||
| } | ||
|
|
||
| Metal::Texture::~Texture() | ||
| { | ||
| if (g_state_tracker) | ||
| g_state_tracker->UnbindTexture(m_tex); | ||
| } | ||
|
|
||
| void Metal::Texture::CopyRectangleFromTexture(const AbstractTexture* src, | ||
| const MathUtil::Rectangle<int>& src_rect, | ||
| u32 src_layer, u32 src_level, | ||
| const MathUtil::Rectangle<int>& dst_rect, | ||
| u32 dst_layer, u32 dst_level) | ||
| { | ||
| g_state_tracker->EndRenderPass(); | ||
| id<MTLTexture> msrc = static_cast<const Texture*>(src)->GetMTLTexture(); | ||
| id<MTLBlitCommandEncoder> blit = [g_state_tracker->GetRenderCmdBuf() blitCommandEncoder]; | ||
| MTLSize size = MTLSizeMake(src_rect.right - src_rect.left, src_rect.bottom - src_rect.top, 1); | ||
| [blit setLabel:@"Texture Copy"]; | ||
| [blit copyFromTexture:msrc | ||
| sourceSlice:src_layer | ||
| sourceLevel:src_level | ||
| sourceOrigin:MTLOriginMake(src_rect.left, src_rect.top, 0) | ||
| sourceSize:size | ||
| toTexture:m_tex | ||
| destinationSlice:dst_layer | ||
| destinationLevel:dst_level | ||
| destinationOrigin:MTLOriginMake(dst_rect.left, dst_rect.top, 0)]; | ||
| [blit endEncoding]; | ||
| } | ||
|
|
||
| void Metal::Texture::ResolveFromTexture(const AbstractTexture* src, | ||
| const MathUtil::Rectangle<int>& rect, u32 layer, u32 level) | ||
| { | ||
| ASSERT(rect == MathUtil::Rectangle<int>(0, 0, src->GetWidth(), src->GetHeight())); | ||
| id<MTLTexture> src_tex = static_cast<const Texture*>(src)->GetMTLTexture(); | ||
| g_state_tracker->ResolveTexture(src_tex, m_tex, layer, level); | ||
| } | ||
|
|
||
| void Metal::Texture::Load(u32 level, u32 width, u32 height, u32 row_length, // | ||
| const u8* buffer, size_t buffer_size) | ||
| { | ||
| @autoreleasepool | ||
| { | ||
| const u32 block_size = GetBlockSizeForFormat(GetFormat()); | ||
| const u32 num_rows = Common::AlignUp(height, block_size) / block_size; | ||
| const u32 source_pitch = CalculateStrideForFormat(m_config.format, row_length); | ||
| const u32 upload_size = source_pitch * num_rows; | ||
| StateTracker::Map map = g_state_tracker->Allocate(StateTracker::UploadBuffer::TextureData, | ||
| upload_size, StateTracker::AlignMask::Other); | ||
| memcpy(map.cpu_buffer, buffer, upload_size); | ||
| id<MTLBlitCommandEncoder> encoder = g_state_tracker->GetTextureUploadEncoder(); | ||
| [encoder copyFromBuffer:map.gpu_buffer | ||
| sourceOffset:map.gpu_offset | ||
| sourceBytesPerRow:source_pitch | ||
| sourceBytesPerImage:upload_size | ||
| sourceSize:MTLSizeMake(width, height, 1) | ||
| toTexture:m_tex | ||
| destinationSlice:0 | ||
| destinationLevel:level | ||
| destinationOrigin:MTLOriginMake(0, 0, 0)]; | ||
| } | ||
| } | ||
|
|
||
| Metal::StagingTexture::StagingTexture(MRCOwned<id<MTLBuffer>> buffer, StagingTextureType type, | ||
| const TextureConfig& config) | ||
| : AbstractStagingTexture(type, config), m_buffer(std::move(buffer)) | ||
| { | ||
| m_map_pointer = static_cast<char*>([m_buffer contents]); | ||
| m_map_stride = config.GetStride(); | ||
| } | ||
|
|
||
| Metal::StagingTexture::~StagingTexture() = default; | ||
|
|
||
| void Metal::StagingTexture::CopyFromTexture(const AbstractTexture* src, | ||
| const MathUtil::Rectangle<int>& src_rect, // | ||
| u32 src_layer, u32 src_level, | ||
| const MathUtil::Rectangle<int>& dst_rect) | ||
| { | ||
| @autoreleasepool | ||
| { | ||
| const size_t stride = m_config.GetStride(); | ||
| const u32 offset = dst_rect.top * stride + dst_rect.left * m_texel_size; | ||
| const MTLSize size = | ||
| MTLSizeMake(src_rect.right - src_rect.left, src_rect.bottom - src_rect.top, 1); | ||
| g_state_tracker->EndRenderPass(); | ||
| m_wait_buffer = MRCRetain(g_state_tracker->GetRenderCmdBuf()); | ||
| id<MTLBlitCommandEncoder> download_encoder = [m_wait_buffer blitCommandEncoder]; | ||
| [download_encoder setLabel:@"Texture Download"]; | ||
| [download_encoder copyFromTexture:static_cast<const Texture*>(src)->GetMTLTexture() | ||
| sourceSlice:src_layer | ||
| sourceLevel:src_level | ||
| sourceOrigin:MTLOriginMake(src_rect.left, src_rect.top, 0) | ||
| sourceSize:size | ||
| toBuffer:m_buffer | ||
| destinationOffset:offset | ||
| destinationBytesPerRow:stride | ||
| destinationBytesPerImage:stride * size.height]; | ||
| [download_encoder endEncoding]; | ||
| m_needs_flush = true; | ||
| } | ||
| } | ||
|
|
||
| void Metal::StagingTexture::CopyToTexture(const MathUtil::Rectangle<int>& src_rect, // | ||
| AbstractTexture* dst, | ||
| const MathUtil::Rectangle<int>& dst_rect, // | ||
| u32 dst_layer, u32 dst_level) | ||
| { | ||
| @autoreleasepool | ||
| { | ||
| const size_t stride = m_config.GetStride(); | ||
| const u32 offset = dst_rect.top * stride + dst_rect.left * m_texel_size; | ||
| const MTLSize size = | ||
| MTLSizeMake(src_rect.right - src_rect.left, src_rect.bottom - src_rect.top, 1); | ||
| g_state_tracker->EndRenderPass(); | ||
| m_wait_buffer = MRCRetain(g_state_tracker->GetRenderCmdBuf()); | ||
| id<MTLBlitCommandEncoder> upload_encoder = [m_wait_buffer blitCommandEncoder]; | ||
| [upload_encoder setLabel:@"Texture Upload"]; | ||
| [upload_encoder copyFromBuffer:m_buffer | ||
| sourceOffset:offset | ||
| sourceBytesPerRow:stride | ||
| sourceBytesPerImage:stride * size.height | ||
| sourceSize:size | ||
| toTexture:static_cast<Texture*>(dst)->GetMTLTexture() | ||
| destinationSlice:dst_layer | ||
| destinationLevel:dst_level | ||
| destinationOrigin:MTLOriginMake(dst_rect.left, dst_rect.top, 0)]; | ||
| [upload_encoder endEncoding]; | ||
| m_needs_flush = true; | ||
| } | ||
| } | ||
|
|
||
| bool Metal::StagingTexture::Map() | ||
| { | ||
| // Always mapped | ||
| return true; | ||
| } | ||
|
|
||
| void Metal::StagingTexture::Unmap() | ||
| { | ||
| // Always mapped | ||
| } | ||
|
|
||
| void Metal::StagingTexture::Flush() | ||
| { | ||
| m_needs_flush = false; | ||
| if (!m_wait_buffer) | ||
| return; | ||
| if ([m_wait_buffer status] != MTLCommandBufferStatusCompleted) | ||
| { | ||
| // Flush while we wait, since who knows how long we'll be sitting here | ||
| g_state_tracker->FlushEncoders(); | ||
| [m_wait_buffer waitUntilCompleted]; | ||
| } | ||
| m_wait_buffer = nullptr; | ||
| } | ||
|
|
||
| Metal::Framebuffer::Framebuffer(AbstractTexture* color, AbstractTexture* depth, // | ||
| u32 width, u32 height, u32 layers, u32 samples) | ||
| : AbstractFramebuffer(color, depth, | ||
| color ? color->GetFormat() : AbstractTextureFormat::Undefined, // | ||
| depth ? depth->GetFormat() : AbstractTextureFormat::Undefined, // | ||
| width, height, layers, samples) | ||
| { | ||
| } | ||
|
|
||
| Metal::Framebuffer::~Framebuffer() = default; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| // Copyright 2022 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <Metal/Metal.h> | ||
| #include <vector> | ||
|
|
||
| #include "VideoCommon/AbstractShader.h" | ||
| #include "VideoCommon/TextureConfig.h" | ||
| #include "VideoCommon/VideoConfig.h" | ||
|
|
||
| #include "VideoBackends/Metal/MRCHelpers.h" | ||
|
|
||
| namespace Metal | ||
| { | ||
| struct DeviceFeatures | ||
| { | ||
| bool subgroup_ops; | ||
| }; | ||
|
|
||
| extern DeviceFeatures g_features; | ||
|
|
||
| namespace Util | ||
| { | ||
| struct Viewport | ||
| { | ||
| float x; | ||
| float y; | ||
| float width; | ||
| float height; | ||
| float near_depth; | ||
| float far_depth; | ||
| }; | ||
|
|
||
| /// Gets the list of Metal devices, ordered so the system default device is first | ||
| std::vector<MRCOwned<id<MTLDevice>>> GetAdapterList(); | ||
| void PopulateBackendInfo(VideoConfig* config); | ||
| void PopulateBackendInfoAdapters(VideoConfig* config, | ||
| const std::vector<MRCOwned<id<MTLDevice>>>& adapters); | ||
| void PopulateBackendInfoFeatures(VideoConfig* config, id<MTLDevice> device); | ||
|
|
||
| AbstractTextureFormat ToAbstract(MTLPixelFormat format); | ||
| MTLPixelFormat FromAbstract(AbstractTextureFormat format); | ||
| static inline bool HasStencil(AbstractTextureFormat format) | ||
| { | ||
| return format == AbstractTextureFormat::D24_S8 || format == AbstractTextureFormat::D32F_S8; | ||
| } | ||
|
|
||
| std::optional<std::string> TranslateShaderToMSL(ShaderStage stage, std::string_view source); | ||
|
|
||
| } // namespace Util | ||
| } // namespace Metal |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| // Copyright 2022 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <Metal/Metal.h> | ||
|
|
||
| #include "VideoBackends/Metal/MRCHelpers.h" | ||
|
|
||
| #include "VideoCommon/NativeVertexFormat.h" | ||
|
|
||
| namespace Metal | ||
| { | ||
| class VertexFormat : public NativeVertexFormat | ||
| { | ||
| public: | ||
| VertexFormat(const PortableVertexDeclaration& vtx_decl); | ||
|
|
||
| MTLVertexDescriptor* Get() const { return m_desc; } | ||
|
|
||
| MRCOwned<MTLVertexDescriptor*> m_desc; | ||
| }; | ||
| } // namespace Metal |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| // Copyright 2022 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #include "VideoBackends/Metal/MTLVertexFormat.h" | ||
|
|
||
| #include "VideoCommon/VertexShaderGen.h" | ||
|
|
||
| static MTLVertexFormat ConvertFormat(ComponentFormat format, int count, bool int_format) | ||
| { | ||
| // clang-format off | ||
| if (int_format) | ||
| { | ||
| switch (format) | ||
| { | ||
| case ComponentFormat::UByte: | ||
| switch (count) | ||
| { | ||
| case 1: return MTLVertexFormatUChar; | ||
| case 2: return MTLVertexFormatUChar2; | ||
| case 3: return MTLVertexFormatUChar3; | ||
| case 4: return MTLVertexFormatUChar4; | ||
| default: return MTLVertexFormatInvalid; | ||
| } | ||
| case ComponentFormat::Byte: | ||
| switch (count) | ||
| { | ||
| case 1: return MTLVertexFormatChar; | ||
| case 2: return MTLVertexFormatChar2; | ||
| case 3: return MTLVertexFormatChar3; | ||
| case 4: return MTLVertexFormatChar4; | ||
| default: return MTLVertexFormatInvalid; | ||
| } | ||
| case ComponentFormat::UShort: | ||
| switch (count) | ||
| { | ||
| case 1: return MTLVertexFormatUShort; | ||
| case 2: return MTLVertexFormatUShort2; | ||
| case 3: return MTLVertexFormatUShort3; | ||
| case 4: return MTLVertexFormatUShort4; | ||
| default: return MTLVertexFormatInvalid; | ||
| } | ||
| case ComponentFormat::Short: | ||
| switch (count) | ||
| { | ||
| case 1: return MTLVertexFormatShort; | ||
| case 2: return MTLVertexFormatShort2; | ||
| case 3: return MTLVertexFormatShort3; | ||
| case 4: return MTLVertexFormatShort4; | ||
| default: return MTLVertexFormatInvalid; | ||
| } | ||
| case ComponentFormat::Float: | ||
| switch (count) | ||
| { | ||
| case 1: return MTLVertexFormatFloat; | ||
| case 2: return MTLVertexFormatFloat2; | ||
| case 3: return MTLVertexFormatFloat3; | ||
| case 4: return MTLVertexFormatFloat4; | ||
| default: return MTLVertexFormatInvalid; | ||
| } | ||
| } | ||
| } | ||
| else | ||
| { | ||
| switch (format) | ||
| { | ||
| case ComponentFormat::UByte: | ||
| switch (count) | ||
| { | ||
| case 1: return MTLVertexFormatUCharNormalized; | ||
| case 2: return MTLVertexFormatUChar2Normalized; | ||
| case 3: return MTLVertexFormatUChar3Normalized; | ||
| case 4: return MTLVertexFormatUChar4Normalized; | ||
| default: return MTLVertexFormatInvalid; | ||
| } | ||
| case ComponentFormat::Byte: | ||
| switch (count) | ||
| { | ||
| case 1: return MTLVertexFormatCharNormalized; | ||
| case 2: return MTLVertexFormatChar2Normalized; | ||
| case 3: return MTLVertexFormatChar3Normalized; | ||
| case 4: return MTLVertexFormatChar4Normalized; | ||
| default: return MTLVertexFormatInvalid; | ||
| } | ||
| case ComponentFormat::UShort: | ||
| switch (count) | ||
| { | ||
| case 1: return MTLVertexFormatUShortNormalized; | ||
| case 2: return MTLVertexFormatUShort2Normalized; | ||
| case 3: return MTLVertexFormatUShort3Normalized; | ||
| case 4: return MTLVertexFormatUShort4Normalized; | ||
| default: return MTLVertexFormatInvalid; | ||
| } | ||
| case ComponentFormat::Short: | ||
| switch (count) | ||
| { | ||
| case 1: return MTLVertexFormatShortNormalized; | ||
| case 2: return MTLVertexFormatShort2Normalized; | ||
| case 3: return MTLVertexFormatShort3Normalized; | ||
| case 4: return MTLVertexFormatShort4Normalized; | ||
| default: return MTLVertexFormatInvalid; | ||
| } | ||
| case ComponentFormat::Float: | ||
| switch (count) | ||
| { | ||
| case 1: return MTLVertexFormatFloat; | ||
| case 2: return MTLVertexFormatFloat2; | ||
| case 3: return MTLVertexFormatFloat3; | ||
| case 4: return MTLVertexFormatFloat4; | ||
| default: return MTLVertexFormatInvalid; | ||
| } | ||
| } | ||
| } | ||
| // clang-format on | ||
| } | ||
|
|
||
| static void SetAttribute(MTLVertexDescriptor* desc, u32 attribute, const AttributeFormat& format) | ||
| { | ||
| if (!format.enable) | ||
| return; | ||
| MTLVertexAttributeDescriptor* attr_desc = [[desc attributes] objectAtIndexedSubscript:attribute]; | ||
| [attr_desc setFormat:ConvertFormat(format.type, format.components, format.integer)]; | ||
| [attr_desc setOffset:format.offset]; | ||
| [attr_desc setBufferIndex:0]; | ||
| } | ||
|
|
||
| template <size_t N> | ||
| static void SetAttributes(MTLVertexDescriptor* desc, u32 attribute, | ||
| const AttributeFormat (&format)[N]) | ||
| { | ||
| for (size_t i = 0; i < N; ++i) | ||
| SetAttribute(desc, attribute + i, format[i]); | ||
| } | ||
|
|
||
| Metal::VertexFormat::VertexFormat(const PortableVertexDeclaration& vtx_decl) | ||
| : NativeVertexFormat(vtx_decl), m_desc(MRCTransfer([MTLVertexDescriptor new])) | ||
| { | ||
| [[[m_desc layouts] objectAtIndexedSubscript:0] setStride:vtx_decl.stride]; | ||
| SetAttribute(m_desc, SHADER_POSITION_ATTRIB, vtx_decl.position); | ||
| SetAttributes(m_desc, SHADER_NORMAL_ATTRIB, vtx_decl.normals); | ||
| SetAttributes(m_desc, SHADER_COLOR0_ATTRIB, vtx_decl.colors); | ||
| SetAttributes(m_desc, SHADER_TEXTURE0_ATTRIB, vtx_decl.texcoords); | ||
| SetAttribute(m_desc, SHADER_POSMTX_ATTRIB, vtx_decl.posmtx); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| // Copyright 2022 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #pragma once | ||
|
|
||
| #include "VideoBackends/Metal/MTLUtil.h" | ||
| #include "VideoCommon/VertexManagerBase.h" | ||
|
|
||
| namespace Metal | ||
| { | ||
| class VertexManager final : public VertexManagerBase | ||
| { | ||
| public: | ||
| VertexManager(); | ||
| ~VertexManager() override; | ||
|
|
||
| void UploadUtilityUniforms(const void* uniforms, u32 uniforms_size) override; | ||
| bool UploadTexelBuffer(const void* data, u32 data_size, TexelBufferFormat format, | ||
| u32* out_offset) override; | ||
| bool UploadTexelBuffer(const void* data, u32 data_size, TexelBufferFormat format, u32* out_offset, | ||
| const void* palette_data, u32 palette_size, | ||
| TexelBufferFormat palette_format, u32* out_palette_offset) override; | ||
|
|
||
| protected: | ||
| void ResetBuffer(u32 vertex_stride) override; | ||
| void CommitBuffer(u32 num_vertices, u32 vertex_stride, u32 num_indices, u32* out_base_vertex, | ||
| u32* out_base_index) override; | ||
| void UploadUniforms() override; | ||
|
|
||
| private: | ||
| u32 m_vertex_offset; | ||
| u32 m_base_vertex; | ||
| }; | ||
| } // namespace Metal |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| // Copyright 2022 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #include "VideoBackends/Metal/MTLVertexManager.h" | ||
|
|
||
| #include "VideoBackends/Metal/MTLStateTracker.h" | ||
|
|
||
| #include "VideoCommon/PixelShaderManager.h" | ||
| #include "VideoCommon/Statistics.h" | ||
| #include "VideoCommon/VertexShaderManager.h" | ||
|
|
||
| Metal::VertexManager::VertexManager() | ||
| { | ||
| } | ||
|
|
||
| Metal::VertexManager::~VertexManager() = default; | ||
|
|
||
| void Metal::VertexManager::UploadUtilityUniforms(const void* uniforms, u32 uniforms_size) | ||
| { | ||
| g_state_tracker->SetUtilityUniform(uniforms, uniforms_size); | ||
| } | ||
|
|
||
| bool Metal::VertexManager::UploadTexelBuffer(const void* data, u32 data_size, | ||
| TexelBufferFormat format, u32* out_offset) | ||
| { | ||
| *out_offset = 0; | ||
| StateTracker::Map map = g_state_tracker->Allocate(StateTracker::UploadBuffer::Texels, data_size, | ||
| StateTracker::AlignMask::Other); | ||
| memcpy(map.cpu_buffer, data, data_size); | ||
| g_state_tracker->SetTexelBuffer(map.gpu_buffer, map.gpu_offset, 0); | ||
| return true; | ||
| } | ||
|
|
||
| bool Metal::VertexManager::UploadTexelBuffer(const void* data, u32 data_size, | ||
| TexelBufferFormat format, u32* out_offset, | ||
| const void* palette_data, u32 palette_size, | ||
| TexelBufferFormat palette_format, | ||
| u32* out_palette_offset) | ||
| { | ||
| *out_offset = 0; | ||
| *out_palette_offset = 0; | ||
|
|
||
| const u32 aligned_data_size = g_state_tracker->Align(data_size, StateTracker::AlignMask::Other); | ||
| const u32 total_size = aligned_data_size + palette_size; | ||
| StateTracker::Map map = g_state_tracker->Allocate(StateTracker::UploadBuffer::Texels, total_size, | ||
| StateTracker::AlignMask::Other); | ||
| memcpy(map.cpu_buffer, data, data_size); | ||
| memcpy(static_cast<char*>(map.cpu_buffer) + aligned_data_size, palette_data, palette_size); | ||
| g_state_tracker->SetTexelBuffer(map.gpu_buffer, map.gpu_offset, | ||
| map.gpu_offset + aligned_data_size); | ||
| return true; | ||
| } | ||
|
|
||
| void Metal::VertexManager::ResetBuffer(u32 vertex_stride) | ||
| { | ||
| const u32 max_vertex_size = 65535 * vertex_stride; | ||
| const u32 vertex_alloc = max_vertex_size + vertex_stride - 1; // for alignment | ||
| auto vertex = g_state_tracker->Preallocate(StateTracker::UploadBuffer::Vertex, vertex_alloc); | ||
| auto index = | ||
| g_state_tracker->Preallocate(StateTracker::UploadBuffer::Index, MAXIBUFFERSIZE * sizeof(u16)); | ||
|
|
||
| // Align the base vertex | ||
| m_base_vertex = (vertex.second + vertex_stride - 1) / vertex_stride; | ||
| m_vertex_offset = m_base_vertex * vertex_stride - vertex.second; | ||
| m_cur_buffer_pointer = m_base_buffer_pointer = static_cast<u8*>(vertex.first) + m_vertex_offset; | ||
| m_end_buffer_pointer = m_base_buffer_pointer + max_vertex_size; | ||
| m_index_generator.Start(static_cast<u16*>(index.first)); | ||
| } | ||
|
|
||
| void Metal::VertexManager::CommitBuffer(u32 num_vertices, u32 vertex_stride, u32 num_indices, | ||
| u32* out_base_vertex, u32* out_base_index) | ||
| { | ||
| const u32 vsize = num_vertices * vertex_stride + m_vertex_offset; | ||
| const u32 isize = num_indices * sizeof(u16); | ||
| StateTracker::Map vmap = g_state_tracker->CommitPreallocation( | ||
| StateTracker::UploadBuffer::Vertex, vsize, StateTracker::AlignMask::None); | ||
| StateTracker::Map imap = g_state_tracker->CommitPreallocation( | ||
| StateTracker::UploadBuffer::Index, isize, StateTracker::AlignMask::None); | ||
|
|
||
| ADDSTAT(g_stats.this_frame.bytes_vertex_streamed, vsize); | ||
| ADDSTAT(g_stats.this_frame.bytes_index_streamed, isize); | ||
|
|
||
| DEBUG_ASSERT(vmap.gpu_offset + m_vertex_offset == m_base_vertex * vertex_stride); | ||
| g_state_tracker->SetVerticesAndIndices(vmap.gpu_buffer, imap.gpu_buffer); | ||
| *out_base_vertex = m_base_vertex; | ||
| *out_base_index = imap.gpu_offset / sizeof(u16); | ||
| } | ||
|
|
||
| void Metal::VertexManager::UploadUniforms() | ||
| { | ||
| g_state_tracker->InvalidateUniforms(VertexShaderManager::dirty, PixelShaderManager::dirty); | ||
| VertexShaderManager::dirty = false; | ||
| PixelShaderManager::dirty = false; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // Copyright 2022 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <string> | ||
| #include "VideoCommon/VideoBackendBase.h" | ||
|
|
||
| namespace Metal | ||
| { | ||
| class VideoBackend : public VideoBackendBase | ||
| { | ||
| public: | ||
| bool Initialize(const WindowSystemInfo& wsi) override; | ||
| void Shutdown() override; | ||
|
|
||
| std::string GetName() const override; | ||
| std::string GetDisplayName() const override; | ||
| std::optional<std::string> GetWarningMessage() const override; | ||
|
|
||
| void InitBackendInfo() override; | ||
|
|
||
| void PrepareWindow(WindowSystemInfo& wsi) override; | ||
|
|
||
| static constexpr const char* NAME = "Metal"; | ||
| }; | ||
| } // namespace Metal |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,6 +39,7 @@ enum class APIType | |
| OpenGL, | ||
| D3D, | ||
| Vulkan, | ||
| Metal, | ||
| Nothing | ||
| }; | ||
|
|
||
|
|
||