mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
Merge pull request #10754 from tellowkrinkle/Metal
VideoBackends: Add Metal backend
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()
|
||||
|
||||
@@ -111,6 +111,7 @@ void VideoBackend::FillBackendInfo()
|
||||
g_Config.backend_info.bSupportsLodBiasInSampler = true;
|
||||
g_Config.backend_info.bSupportsLogicOp = D3D::SupportsLogicOp(g_Config.iAdapter);
|
||||
g_Config.backend_info.bSupportsSettingObjectNames = true;
|
||||
g_Config.backend_info.bSupportsPartialMultisampleResolve = true;
|
||||
|
||||
g_Config.backend_info.Adapters = D3DCommon::GetAdapterNames();
|
||||
g_Config.backend_info.AAModes = D3D::GetAAModes(g_Config.iAdapter);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ void VideoBackend::FillBackendInfo()
|
||||
g_Config.backend_info.bSupportsTextureQueryLevels = true;
|
||||
g_Config.backend_info.bSupportsLodBiasInSampler = true;
|
||||
g_Config.backend_info.bSupportsSettingObjectNames = true;
|
||||
g_Config.backend_info.bSupportsPartialMultisampleResolve = true;
|
||||
|
||||
// We can only check texture support once we have a device.
|
||||
if (g_dx_context)
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
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.mm
|
||||
MTLShader.h
|
||||
MTLStateTracker.mm
|
||||
MTLStateTracker.h
|
||||
MTLTexture.mm
|
||||
MTLTexture.h
|
||||
MTLUtil.mm
|
||||
MTLUtil.h
|
||||
MTLVertexFormat.mm
|
||||
MTLVertexFormat.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,72 @@
|
||||
// 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_options =
|
||||
MTLResourceStorageModeShared | 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]);
|
||||
[m_gpu_buffer setLabel:@"BBox Buffer"];
|
||||
m_cpu_buffer_ptr = static_cast<BBoxType*>([m_gpu_buffer contents]);
|
||||
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();
|
||||
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_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,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;
|
||||
}
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user