mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
VideoBackends: Add Metal renderer
This commit is contained in:
@@ -631,6 +631,10 @@ if(ENABLE_VULKAN)
|
||||
target_link_libraries(core PUBLIC videovulkan)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
target_link_libraries(core PUBLIC videometal)
|
||||
endif()
|
||||
|
||||
if(USE_MGBA)
|
||||
target_sources(core PRIVATE
|
||||
HW/GBACore.cpp
|
||||
|
||||
@@ -8,6 +8,10 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
add_subdirectory(D3D12)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
add_subdirectory(Metal)
|
||||
endif()
|
||||
|
||||
if(ENABLE_VULKAN)
|
||||
add_subdirectory(Vulkan)
|
||||
endif()
|
||||
|
||||
@@ -150,8 +150,8 @@ void Renderer::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex)
|
||||
D3D::context->DrawIndexed(num_indices, base_index, base_vertex);
|
||||
}
|
||||
|
||||
void Renderer::DispatchComputeShader(const AbstractShader* shader, u32 groups_x, u32 groups_y,
|
||||
u32 groups_z)
|
||||
void Renderer::DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y,
|
||||
u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z)
|
||||
{
|
||||
D3D::stateman->SetComputeShader(static_cast<const DXShader*>(shader)->GetD3DComputeShader());
|
||||
D3D::stateman->SyncComputeBindings();
|
||||
|
||||
@@ -57,8 +57,8 @@ public:
|
||||
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 groups_x, u32 groups_y,
|
||||
u32 groups_z) 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;
|
||||
void SetFullscreen(bool enable_fullscreen) override;
|
||||
|
||||
@@ -365,8 +365,8 @@ void Renderer::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex)
|
||||
g_dx_context->GetCommandList()->DrawIndexedInstanced(num_indices, 1, base_index, base_vertex, 0);
|
||||
}
|
||||
|
||||
void Renderer::DispatchComputeShader(const AbstractShader* shader, u32 groups_x, u32 groups_y,
|
||||
u32 groups_z)
|
||||
void Renderer::DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y,
|
||||
u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z)
|
||||
{
|
||||
SetRootSignatures();
|
||||
SetDescriptorHeaps();
|
||||
|
||||
@@ -69,8 +69,8 @@ public:
|
||||
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 groups_x, u32 groups_y,
|
||||
u32 groups_z) 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;
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
add_library(videometal
|
||||
MRCHelpers.h
|
||||
MTLBoundingBox.mm
|
||||
MTLBoundingBox.h
|
||||
MTLMain.mm
|
||||
MTLObjectCache.h
|
||||
MTLObjectCache.mm
|
||||
MTLPerfQuery.mm
|
||||
MTLPerfQuery.h
|
||||
MTLPipeline.mm
|
||||
MTLPipeline.h
|
||||
MTLRenderer.mm
|
||||
MTLRenderer.h
|
||||
MTLShader.h
|
||||
MTLStateTracker.mm
|
||||
MTLStateTracker.h
|
||||
MTLTexture.mm
|
||||
MTLTexture.h
|
||||
MTLUtil.mm
|
||||
MTLUtil.h
|
||||
MTLVertexManager.mm
|
||||
MTLVertexManager.h
|
||||
VideoBackend.h
|
||||
)
|
||||
|
||||
find_library(METAL_LIBRARY Metal)
|
||||
find_library(QUARTZCORE_LIBRARY QuartzCore)
|
||||
|
||||
target_link_libraries(videometal
|
||||
PUBLIC
|
||||
common
|
||||
videocommon
|
||||
PRIVATE
|
||||
spirv_cross
|
||||
${METAL_LIBRARY}
|
||||
${QUARTZCORE_LIBRARY}
|
||||
)
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// clang-format off
|
||||
#ifndef __OBJC__
|
||||
#error This header is for use with Objective-C++ only.
|
||||
#endif
|
||||
#if __has_feature(objc_arc)
|
||||
#error This file is for manual reference counting! Compile without -fobjc-arc
|
||||
#endif
|
||||
// clang-format on
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
/// Managed Obj-C pointer
|
||||
template <typename T>
|
||||
class MRCOwned
|
||||
{
|
||||
T ptr;
|
||||
MRCOwned(T ptr) : ptr(ptr) {}
|
||||
|
||||
public:
|
||||
MRCOwned() : ptr(nullptr) {}
|
||||
MRCOwned(std::nullptr_t) : ptr(nullptr) {}
|
||||
MRCOwned(MRCOwned&& other) : ptr(other.ptr) { other.ptr = nullptr; }
|
||||
MRCOwned(const MRCOwned& other) : ptr(other.ptr) { [ptr retain]; }
|
||||
~MRCOwned()
|
||||
{
|
||||
if (ptr)
|
||||
[ptr release];
|
||||
}
|
||||
operator T() const { return ptr; }
|
||||
MRCOwned& operator=(const MRCOwned& other)
|
||||
{
|
||||
[other.ptr retain];
|
||||
if (ptr)
|
||||
[ptr release];
|
||||
ptr = other.ptr;
|
||||
return *this;
|
||||
}
|
||||
MRCOwned& operator=(MRCOwned&& other)
|
||||
{
|
||||
std::swap(ptr, other.ptr);
|
||||
return *this;
|
||||
}
|
||||
void Reset()
|
||||
{
|
||||
[ptr release];
|
||||
ptr = nullptr;
|
||||
}
|
||||
T Disown()
|
||||
{
|
||||
T tmp = ptr;
|
||||
ptr = nullptr;
|
||||
return tmp;
|
||||
}
|
||||
T Get() const { return ptr; }
|
||||
static MRCOwned Transfer(T ptr) { return MRCOwned(ptr); }
|
||||
static MRCOwned Retain(T ptr)
|
||||
{
|
||||
[ptr retain];
|
||||
return MRCOwned(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
/// Take ownership of an Obj-C pointer (equivalent to __bridge_transfer)
|
||||
template <typename T>
|
||||
static inline MRCOwned<T> MRCTransfer(T ptr)
|
||||
{
|
||||
return MRCOwned<T>::Transfer(ptr);
|
||||
}
|
||||
|
||||
/// Retain an Obj-C pointer (equivalent to __bridge)
|
||||
template <typename T>
|
||||
static inline MRCOwned<T> MRCRetain(T ptr)
|
||||
{
|
||||
return MRCOwned<T>::Retain(ptr);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/BoundingBox.h"
|
||||
|
||||
#include <Metal/Metal.h>
|
||||
|
||||
#include "VideoBackends/Metal/MRCHelpers.h"
|
||||
|
||||
namespace Metal
|
||||
{
|
||||
class BoundingBox final : public ::BoundingBox
|
||||
{
|
||||
public:
|
||||
~BoundingBox() override;
|
||||
|
||||
bool Initialize() override;
|
||||
|
||||
protected:
|
||||
std::vector<BBoxType> Read(u32 index, u32 length) override;
|
||||
void Write(u32 index, const std::vector<BBoxType>& values) override;
|
||||
|
||||
private:
|
||||
BBoxType* m_cpu_buffer_ptr;
|
||||
MRCOwned<id<MTLFence>> m_download_fence;
|
||||
MRCOwned<id<MTLFence>> m_upload_fence;
|
||||
MRCOwned<id<MTLBuffer>> m_cpu_buffer;
|
||||
MRCOwned<id<MTLBuffer>> m_gpu_buffer;
|
||||
};
|
||||
} // namespace Metal
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoBackends/Metal/MTLBoundingBox.h"
|
||||
|
||||
#include "VideoBackends/Metal/MTLObjectCache.h"
|
||||
#include "VideoBackends/Metal/MTLStateTracker.h"
|
||||
|
||||
static constexpr size_t BUFFER_SIZE = sizeof(BBoxType) * NUM_BBOX_VALUES;
|
||||
|
||||
Metal::BoundingBox::~BoundingBox()
|
||||
{
|
||||
if (g_state_tracker)
|
||||
g_state_tracker->SetBBoxBuffer(nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
bool Metal::BoundingBox::Initialize()
|
||||
{
|
||||
const MTLResourceOptions gpu_storage_mode =
|
||||
g_features.unified_memory ? MTLResourceStorageModeShared : MTLResourceStorageModePrivate;
|
||||
const MTLResourceOptions gpu_options = gpu_storage_mode | MTLResourceHazardTrackingModeUntracked;
|
||||
const id<MTLDevice> dev = g_device;
|
||||
m_upload_fence = MRCTransfer([dev newFence]);
|
||||
[m_upload_fence setLabel:@"BBox Upload Fence"];
|
||||
m_download_fence = MRCTransfer([dev newFence]);
|
||||
[m_download_fence setLabel:@"BBox Download Fence"];
|
||||
m_gpu_buffer = MRCTransfer([dev newBufferWithLength:BUFFER_SIZE options:gpu_options]);
|
||||
if (g_features.unified_memory)
|
||||
{
|
||||
[m_gpu_buffer setLabel:@"BBox Buffer"];
|
||||
m_cpu_buffer_ptr = static_cast<BBoxType*>([m_gpu_buffer contents]);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cpu_buffer = MRCTransfer([dev newBufferWithLength:BUFFER_SIZE
|
||||
options:MTLResourceStorageModeShared]);
|
||||
m_cpu_buffer_ptr = static_cast<BBoxType*>([m_cpu_buffer contents]);
|
||||
[m_gpu_buffer setLabel:@"BBox GPU Buffer"];
|
||||
[m_cpu_buffer setLabel:@"BBox CPU Buffer"];
|
||||
}
|
||||
g_state_tracker->SetBBoxBuffer(m_gpu_buffer, m_upload_fence, m_download_fence);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<BBoxType> Metal::BoundingBox::Read(u32 index, u32 length)
|
||||
{
|
||||
@autoreleasepool
|
||||
{
|
||||
g_state_tracker->EndRenderPass();
|
||||
if (!g_features.unified_memory)
|
||||
{
|
||||
id<MTLBlitCommandEncoder> download = [g_state_tracker->GetRenderCmdBuf() blitCommandEncoder];
|
||||
[download setLabel:@"BBox Download"];
|
||||
[download waitForFence:m_download_fence];
|
||||
[download copyFromBuffer:m_gpu_buffer
|
||||
sourceOffset:0
|
||||
toBuffer:m_cpu_buffer
|
||||
destinationOffset:0
|
||||
size:BUFFER_SIZE];
|
||||
[download endEncoding];
|
||||
}
|
||||
g_state_tracker->FlushEncoders();
|
||||
g_state_tracker->WaitForFlushedEncoders();
|
||||
return std::vector<BBoxType>(m_cpu_buffer_ptr + index, m_cpu_buffer_ptr + index + length);
|
||||
}
|
||||
}
|
||||
|
||||
void Metal::BoundingBox::Write(u32 index, const std::vector<BBoxType>& values)
|
||||
{
|
||||
const u32 size = values.size() * sizeof(BBoxType);
|
||||
if (g_features.unified_memory && !g_state_tracker->HasUnflushedData() &&
|
||||
!g_state_tracker->GPUBusy())
|
||||
{
|
||||
// We can just write directly to the buffer!
|
||||
memcpy(m_cpu_buffer_ptr + index, values.data(), size);
|
||||
}
|
||||
else
|
||||
{
|
||||
@autoreleasepool
|
||||
{
|
||||
StateTracker::Map map = g_state_tracker->Allocate(StateTracker::UploadBuffer::Other, size,
|
||||
StateTracker::AlignMask::Other);
|
||||
memcpy(map.cpu_buffer, values.data(), size);
|
||||
g_state_tracker->EndRenderPass();
|
||||
id<MTLBlitCommandEncoder> upload = [g_state_tracker->GetRenderCmdBuf() blitCommandEncoder];
|
||||
[upload setLabel:@"BBox Upload"];
|
||||
[upload waitForFence:m_download_fence];
|
||||
[upload copyFromBuffer:map.gpu_buffer
|
||||
sourceOffset:map.gpu_offset
|
||||
toBuffer:m_gpu_buffer
|
||||
destinationOffset:index * sizeof(BBoxType)
|
||||
size:size];
|
||||
[upload updateFence:m_upload_fence];
|
||||
[upload endEncoding];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// 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);
|
||||
Util::PopulateBackendInfoAdapters(&g_Config, Util::GetAdapterList());
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// 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"
|
||||
|
||||
namespace Metal
|
||||
{
|
||||
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) { return m_samplers[sel.value]; }
|
||||
|
||||
id<MTLSamplerState> GetSampler(SamplerState state) { return GetSampler(SamplerSelector(state)); }
|
||||
|
||||
void ReloadSamplers();
|
||||
|
||||
private:
|
||||
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
|
||||
@@ -0,0 +1,175 @@
|
||||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoBackends/Metal/MTLObjectCache.h"
|
||||
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
|
||||
MRCOwned<id<MTLDevice>> Metal::g_device;
|
||||
MRCOwned<id<MTLCommandQueue>> Metal::g_queue;
|
||||
std::unique_ptr<Metal::ObjectCache> Metal::g_object_cache;
|
||||
|
||||
static void SetupDepthStencil(
|
||||
MRCOwned<id<MTLDepthStencilState>> (&dss)[Metal::DepthStencilSelector::N_VALUES]);
|
||||
static void SetupSamplers(MRCOwned<id<MTLSamplerState>> (&samplers)[Metal::SamplerSelector::N_VALUES]);
|
||||
|
||||
Metal::ObjectCache::ObjectCache()
|
||||
{
|
||||
SetupDepthStencil(m_dss);
|
||||
SetupSamplers(m_samplers);
|
||||
}
|
||||
|
||||
Metal::ObjectCache::~ObjectCache()
|
||||
{
|
||||
}
|
||||
|
||||
void Metal::ObjectCache::Initialize(MRCOwned<id<MTLDevice>> device)
|
||||
{
|
||||
g_device = std::move(device);
|
||||
g_queue = MRCTransfer([g_device newCommandQueue]);
|
||||
g_object_cache = std::unique_ptr<ObjectCache>(new ObjectCache);
|
||||
}
|
||||
|
||||
void Metal::ObjectCache::Shutdown()
|
||||
{
|
||||
g_object_cache.reset();
|
||||
g_queue = nullptr;
|
||||
g_device = nullptr;
|
||||
}
|
||||
|
||||
// MARK: Depth Stencil State
|
||||
|
||||
// clang-format off
|
||||
|
||||
static MTLCompareFunction Convert(CompareMode mode)
|
||||
{
|
||||
const bool invert_depth = !g_Config.backend_info.bSupportsReversedDepthRange;
|
||||
switch (mode)
|
||||
{
|
||||
case CompareMode::Never: return MTLCompareFunctionNever;
|
||||
case CompareMode::Less: return invert_depth ? MTLCompareFunctionGreater
|
||||
: MTLCompareFunctionLess;
|
||||
case CompareMode::Equal: return MTLCompareFunctionEqual;
|
||||
case CompareMode::LEqual: return invert_depth ? MTLCompareFunctionGreaterEqual
|
||||
: MTLCompareFunctionLessEqual;
|
||||
case CompareMode::Greater: return invert_depth ? MTLCompareFunctionLess
|
||||
: MTLCompareFunctionGreater;
|
||||
case CompareMode::NEqual: return MTLCompareFunctionNotEqual;
|
||||
case CompareMode::GEqual: return invert_depth ? MTLCompareFunctionLessEqual
|
||||
: MTLCompareFunctionGreaterEqual;
|
||||
case CompareMode::Always: return MTLCompareFunctionAlways;
|
||||
}
|
||||
}
|
||||
|
||||
static const char* to_string(MTLCompareFunction compare)
|
||||
{
|
||||
switch (compare)
|
||||
{
|
||||
case MTLCompareFunctionNever: return "Never";
|
||||
case MTLCompareFunctionGreater: return "Greater";
|
||||
case MTLCompareFunctionEqual: return "Equal";
|
||||
case MTLCompareFunctionGreaterEqual: return "GEqual";
|
||||
case MTLCompareFunctionLess: return "Less";
|
||||
case MTLCompareFunctionNotEqual: return "NEqual";
|
||||
case MTLCompareFunctionLessEqual: return "LEqual";
|
||||
case MTLCompareFunctionAlways: return "Always";
|
||||
}
|
||||
}
|
||||
|
||||
// clang-format on
|
||||
|
||||
static void SetupDepthStencil(
|
||||
MRCOwned<id<MTLDepthStencilState>> (&dss)[Metal::DepthStencilSelector::N_VALUES])
|
||||
{
|
||||
auto desc = MRCTransfer([MTLDepthStencilDescriptor new]);
|
||||
Metal::DepthStencilSelector sel;
|
||||
for (size_t i = 0; i < std::size(dss); ++i)
|
||||
{
|
||||
sel.value = i;
|
||||
MTLCompareFunction mcompare = Convert(sel.CompareMode());
|
||||
[desc setDepthWriteEnabled:sel.UpdateEnable()];
|
||||
[desc setDepthCompareFunction:mcompare];
|
||||
[desc setLabel:[NSString stringWithFormat:@"DSS %s%s", to_string(mcompare),
|
||||
sel.UpdateEnable() ? "+Write" : ""]];
|
||||
dss[i] = MRCTransfer([Metal::g_device newDepthStencilStateWithDescriptor:desc]);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Samplers
|
||||
|
||||
// clang-format off
|
||||
|
||||
static MTLSamplerMinMagFilter ConvertMinMag(FilterMode filter)
|
||||
{
|
||||
switch (filter)
|
||||
{
|
||||
case FilterMode::Linear: return MTLSamplerMinMagFilterLinear;
|
||||
case FilterMode::Near: return MTLSamplerMinMagFilterNearest;
|
||||
}
|
||||
}
|
||||
|
||||
static MTLSamplerMipFilter ConvertMip(FilterMode filter)
|
||||
{
|
||||
switch (filter)
|
||||
{
|
||||
case FilterMode::Linear: return MTLSamplerMipFilterLinear;
|
||||
case FilterMode::Near: return MTLSamplerMipFilterNearest;
|
||||
}
|
||||
}
|
||||
|
||||
static MTLSamplerAddressMode Convert(WrapMode wrap)
|
||||
{
|
||||
switch (wrap)
|
||||
{
|
||||
case WrapMode::Clamp: return MTLSamplerAddressModeClampToEdge;
|
||||
case WrapMode::Mirror: return MTLSamplerAddressModeMirrorRepeat;
|
||||
case WrapMode::Repeat: return MTLSamplerAddressModeRepeat;
|
||||
}
|
||||
}
|
||||
|
||||
static const char* to_string(FilterMode filter)
|
||||
{
|
||||
switch (filter)
|
||||
{
|
||||
case FilterMode::Linear: return "Ln";
|
||||
case FilterMode::Near: return "Pt";
|
||||
}
|
||||
}
|
||||
static const char* to_string(WrapMode wrap)
|
||||
{
|
||||
switch (wrap)
|
||||
{
|
||||
case WrapMode::Clamp: return "C";
|
||||
case WrapMode::Mirror: return "M";
|
||||
case WrapMode::Repeat: return "R";
|
||||
}
|
||||
}
|
||||
|
||||
// clang-format on
|
||||
|
||||
static void SetupSamplers(MRCOwned<id<MTLSamplerState>> (&samplers)[Metal::SamplerSelector::N_VALUES])
|
||||
{
|
||||
auto desc = MRCTransfer([MTLSamplerDescriptor new]);
|
||||
Metal::SamplerSelector sel;
|
||||
for (size_t i = 0; i < std::size(samplers); i++)
|
||||
{
|
||||
sel.value = i;
|
||||
[desc setMinFilter:ConvertMinMag(sel.MinFilter())];
|
||||
[desc setMagFilter:ConvertMinMag(sel.MagFilter())];
|
||||
[desc setMipFilter:ConvertMip(sel.MipFilter())];
|
||||
[desc setSAddressMode:Convert(sel.WrapU())];
|
||||
[desc setTAddressMode:Convert(sel.WrapV())];
|
||||
[desc setMaxAnisotropy:1 << (sel.AnisotropicFiltering() ? g_ActiveConfig.iMaxAnisotropy : 0)];
|
||||
[desc setLabel:MRCTransfer([[NSString alloc]
|
||||
initWithFormat:@"%s%s%s %s%s%s", to_string(sel.MinFilter()),
|
||||
to_string(sel.MagFilter()), to_string(sel.MipFilter()),
|
||||
to_string(sel.WrapU()), to_string(sel.WrapV()),
|
||||
sel.AnisotropicFiltering() ? "(AF)" : ""])];
|
||||
samplers[i] = MRCTransfer([Metal::g_device newSamplerStateWithDescriptor:desc]);
|
||||
}
|
||||
}
|
||||
|
||||
void Metal::ObjectCache::ReloadSamplers()
|
||||
{
|
||||
SetupSamplers(m_samplers);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#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 { return 0; }
|
||||
void FlushResults() override {}
|
||||
bool IsFlushed() const override { return true; }
|
||||
};
|
||||
} // namespace Metal
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoBackends/Metal/MTLPerfQuery.h"
|
||||
@@ -0,0 +1,64 @@
|
||||
// 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
|
||||
{
|
||||
class Pipeline final : public AbstractPipeline
|
||||
{
|
||||
public:
|
||||
explicit Pipeline(MRCOwned<id<MTLRenderPipelineState>> pipeline,
|
||||
MTLRenderPipelineReflection* 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_textures; }
|
||||
u32 GetSamplers() const { return m_samplers; }
|
||||
u32 GetVertexBuffers() const { return m_vertex_buffers; }
|
||||
u32 GetFragmentBuffers() const { return m_fragment_buffers; }
|
||||
bool UsesVertexBuffer(u32 index) const { return m_vertex_buffers & (1 << index); }
|
||||
bool UsesFragmentBuffer(u32 index) const { return m_fragment_buffers & (1 << index); }
|
||||
|
||||
private:
|
||||
MRCOwned<id<MTLRenderPipelineState>> m_pipeline;
|
||||
MTLPrimitiveType m_prim;
|
||||
MTLCullMode m_cull;
|
||||
DepthStencilSelector m_depth_stencil;
|
||||
AbstractPipelineUsage m_usage;
|
||||
u32 m_textures = 0;
|
||||
u32 m_samplers = 0;
|
||||
u32 m_vertex_buffers = 0;
|
||||
u32 m_fragment_buffers = 0;
|
||||
};
|
||||
|
||||
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
|
||||
@@ -0,0 +1,66 @@
|
||||
// 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::Pipeline::Pipeline(MRCOwned<id<MTLRenderPipelineState>> pipeline,
|
||||
MTLRenderPipelineReflection* 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)
|
||||
{
|
||||
GetArguments([reflection vertexArguments], nullptr, nullptr, &m_vertex_buffers);
|
||||
GetArguments([reflection fragmentArguments], &m_textures, &m_samplers, &m_fragment_buffers);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// 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 = {};
|
||||
u32 m_pipeline_counter = 0;
|
||||
|
||||
void CheckForSurfaceChange();
|
||||
void CheckForSurfaceResize();
|
||||
void SetupSurface();
|
||||
};
|
||||
} // namespace Metal
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
// 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)
|
||||
: AbstractShader(stage), m_msl(std::move(msl)), m_shader(std::move(shader))
|
||||
{
|
||||
}
|
||||
|
||||
id<MTLFunction> GetShader() const { return m_shader; }
|
||||
BinaryData GetBinary() const override { return BinaryData(m_msl.begin(), m_msl.end()); }
|
||||
|
||||
private:
|
||||
std::string m_msl;
|
||||
MRCOwned<id<MTLFunction>> m_shader;
|
||||
};
|
||||
} // namespace Metal
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user