Skip to content

Commit d8b95b5

Browse files
committed
Add Metal backend
1 parent 044dda5 commit d8b95b5

54 files changed

Lines changed: 1685 additions & 34 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CMakeLists.txt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
1010
# Options.
1111
option(PATHFINDER_BACKEND_OPENGL "Enable OpenGL backend" ON)
1212
option(PATHFINDER_BACKEND_VULKAN "Enable Vulkan backend" ON)
13+
option(PATHFINDER_BACKEND_METAL "Enable Metal backend" OFF)
1314
option(PATHFINDER_BUILD_DEMO "Build demo" OFF)
1415
option(PATHFINDER_ENABLE_COMPUTE "Enable COMPUTE render mode" ON)
1516
option(PATHFINDER_RUNTIME_SHADER_COMPLICATION "Compile shaders at runtime using SPV" OFF)
@@ -32,15 +33,20 @@ if (EMSCRIPTEN)
3233
elseif (APPLE)
3334
# No GL support for Mac.
3435
set(PATHFINDER_BACKEND_OPENGL OFF)
35-
set(PATHFINDER_BACKEND_VULKAN ON)
36+
set(PATHFINDER_BACKEND_VULKAN OFF)
37+
set(PATHFINDER_BACKEND_METAL ON)
38+
39+
enable_language(OBJC OBJCXX)
3640
endif ()
3741

3842
add_subdirectory(pathfinder)
3943

4044
target_compile_features(pathfinder PUBLIC cxx_std_14)
4145

4246
if (EMSCRIPTEN)
47+
# Do nothing
4348
elseif (APPLE)
49+
4450
elseif (LINUX)
4551
target_compile_definitions(pathfinder PUBLIC PATHFINDER_ENABLE_SIMD)
4652
if (NOT CMAKE_SYSTEM_PROCESSOR MATCHES "arm|aarch64")
@@ -96,6 +102,22 @@ else ()
96102
message("[Pathfinder] Disabled OpenGL backend")
97103
endif ()
98104

105+
if (PATHFINDER_BACKEND_METAL)
106+
message("[Pathfinder] Enabled Metal backend")
107+
108+
target_compile_definitions(pathfinder PRIVATE PATHFINDER_USE_METAL)
109+
110+
# Config ARC.
111+
target_compile_options(pathfinder PRIVATE
112+
$<$<COMPILE_LANGUAGE:OBJC,OBJCXX>:-fobjc-arc>
113+
)
114+
set_target_properties(pathfinder PROPERTIES XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC "YES")
115+
116+
target_link_libraries(pathfinder PUBLIC "-framework Metal" "-framework Cocoa" "-framework QuartzCore" "-framework MetalKit")
117+
else ()
118+
message("[Pathfinder] Disabled Metal backend")
119+
endif ()
120+
99121
# Include third_party headers.
100122
target_include_directories(pathfinder PUBLIC "third_party" "third_party/glad/include")
101123

demo/native/main.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@ constexpr int32_t WINDOW_WIDTH = 800;
66
constexpr int32_t WINDOW_HEIGHT = 480;
77

88
int main() {
9+
auto backend = Pathfinder::BackendType::Vulkan;
10+
#ifdef __APPLE__
11+
backend = Pathfinder::BackendType::Metal;
12+
#endif
13+
914
// Create the primary window.
10-
auto window_builder =
11-
Pathfinder::WindowBuilder::new_impl(Pathfinder::BackendType::Vulkan, {WINDOW_WIDTH, WINDOW_HEIGHT});
15+
auto window_builder = Pathfinder::WindowBuilder::new_impl(backend, {WINDOW_WIDTH, WINDOW_HEIGHT});
1216
auto window = window_builder->get_window(0).lock();
1317

1418
// Create device and queue.

download_deps.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111

1212
def download_deps():
13-
for url, name, branch in dev_deps:
13+
for url, name, branch in deps:
1414
if not os.path.exists('third_party/' + name):
1515
cmd = 'git clone -b ' + branch + ' ' + url + ' third_party/' + name
1616

@@ -19,7 +19,7 @@ def download_deps():
1919

2020
# Get glslang deps
2121
if name == "glslang":
22-
os.chdir('./3rd/glslang')
22+
os.chdir('./third_party/glslang')
2323
os.system(python_alias + ' update_glslang_sources.py')
2424
os.chdir('../..')
2525

pathfinder/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ if (PATHFINDER_BACKEND_VULKAN)
77
file(GLOB PF_SOURCE_FILES ${PF_SOURCE_FILES} gpu/vk/*.cpp)
88
endif ()
99

10+
if (PATHFINDER_BACKEND_METAL)
11+
file(GLOB PF_SOURCE_FILES ${PF_SOURCE_FILES} gpu/mtl/*.mm gpu/mtl/*.h)
12+
endif ()
13+
1014
if (PATHFINDER_BACKEND_OPENGL)
1115
file(GLOB PF_SOURCE_FILES ${PF_SOURCE_FILES} gpu/gl/*.cpp)
1216
if (CMAKE_SYSTEM_PROCESSOR MATCHES "arm|ARM|ARM64|aarch64")

pathfinder/gpu/command_encoder.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ void CommandEncoder::set_viewport(const RectI &viewport) {
3838
}
3939

4040
void CommandEncoder::bind_render_pipeline(const std::shared_ptr<RenderPipeline> &pipeline) {
41+
assert(pipeline != nullptr);
42+
4143
Command cmd{};
4244
cmd.type = CommandType::BindRenderPipeline;
4345

pathfinder/gpu/descriptor_set.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,11 @@ struct DescriptorLayout {
1616
uint32_t binding{};
1717
ShaderStage stage{};
1818
DescriptorType type{};
19-
/// For compatibility with lower versions of OpenGL. Deprecated, this has become automatic.
20-
// std::string binding_name;
2119
};
2220

2321
class DescriptorSetLayout {
2422
friend class DeviceGl;
23+
friend class DeviceMtl;
2524

2625
public:
2726
virtual ~DescriptorSetLayout() = default;
@@ -130,6 +129,7 @@ struct Descriptor {
130129
*/
131130
class DescriptorSet {
132131
friend class DeviceGl;
132+
friend class DeviceMtl;
133133

134134
public:
135135
virtual ~DescriptorSet() = default;

pathfinder/gpu/device.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ namespace Pathfinder {
1616
enum class BackendType {
1717
Opengl,
1818
Vulkan,
19+
Metal,
1920
};
2021

2122
/// We only need to provide a Driver to Canvas for rendering,

pathfinder/gpu/mtl/base.h

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
#pragma once
2+
3+
#include <MetalKit/MetalKit.h>
4+
5+
#include <cassert>
6+
7+
#include "../base.h"
8+
9+
namespace Pathfinder {
10+
11+
inline MTLPixelFormat to_mtl_pixel_format(const TextureFormat tf) {
12+
switch (tf) {
13+
case TextureFormat::R8: {
14+
return MTLPixelFormatR8Unorm;
15+
}
16+
case TextureFormat::Rg8: {
17+
return MTLPixelFormatRG8Unorm;
18+
}
19+
case TextureFormat::Rgba8Unorm: {
20+
return MTLPixelFormatRGBA8Unorm;
21+
}
22+
case TextureFormat::Bgra8Unorm: {
23+
return MTLPixelFormatBGRA8Unorm;
24+
}
25+
case TextureFormat::Rgba8Srgb: {
26+
return MTLPixelFormatRGBA8Unorm_sRGB;
27+
}
28+
case TextureFormat::Bgra8Srgb: {
29+
return MTLPixelFormatBGRA8Unorm_sRGB;
30+
}
31+
case TextureFormat::Rgba16Float: {
32+
return MTLPixelFormatRGBA16Float;
33+
}
34+
}
35+
}
36+
37+
inline TextureFormat from_mtl_pixel_format(const MTLPixelFormat mtl_pf) {
38+
TextureFormat fm = TextureFormat::R8;
39+
switch (mtl_pf) {
40+
case MTLPixelFormatR8Unorm:
41+
fm = TextureFormat::R8;
42+
break;
43+
case MTLPixelFormatRG8Unorm:
44+
fm = TextureFormat::Rg8;
45+
break;
46+
case MTLPixelFormatRGBA8Unorm:
47+
fm = TextureFormat::Rgba8Unorm;
48+
break;
49+
case MTLPixelFormatRGBA8Unorm_sRGB:
50+
fm = TextureFormat::Rgba8Srgb;
51+
break;
52+
case MTLPixelFormatBGRA8Unorm:
53+
fm = TextureFormat::Bgra8Unorm;
54+
break;
55+
case MTLPixelFormatBGRA8Unorm_sRGB:
56+
fm = TextureFormat::Bgra8Srgb;
57+
break;
58+
case MTLPixelFormatRGBA16Float:
59+
fm = TextureFormat::Rgba16Float;
60+
break;
61+
default:
62+
abort();
63+
}
64+
return fm;
65+
}
66+
67+
inline MTLBlendFactor to_mtl_blend_factor(const BlendFactor factor) {
68+
switch (factor) {
69+
case BlendFactor::One:
70+
return MTLBlendFactorOne;
71+
case BlendFactor::OneMinusSrcAlpha:
72+
return MTLBlendFactorOneMinusSourceAlpha;
73+
default:
74+
abort();
75+
}
76+
}
77+
78+
inline MTLBlendOperation to_mtl_blend_op(const BlendOperation op) {
79+
switch (op) {
80+
case BlendOperation::Add:
81+
return MTLBlendOperationAdd;
82+
default:
83+
abort();
84+
}
85+
}
86+
87+
static const MTLVertexFormat MTL_FORMATS[] = {
88+
MTLVertexFormatChar, MTLVertexFormatChar2, MTLVertexFormatChar3, MTLVertexFormatChar4, //
89+
MTLVertexFormatUChar, MTLVertexFormatUChar2, MTLVertexFormatUChar3, MTLVertexFormatUChar4, //
90+
MTLVertexFormatShort, MTLVertexFormatShort2, MTLVertexFormatShort3, MTLVertexFormatShort4, //
91+
MTLVertexFormatUShort, MTLVertexFormatUShort2, MTLVertexFormatUShort3, MTLVertexFormatUShort4, //
92+
MTLVertexFormatInt, MTLVertexFormatInt2, MTLVertexFormatInt3, MTLVertexFormatInt4, //
93+
MTLVertexFormatUInt, MTLVertexFormatUInt2, MTLVertexFormatUInt3, MTLVertexFormatUInt4, //
94+
MTLVertexFormatFloat, MTLVertexFormatFloat2, MTLVertexFormatFloat3, MTLVertexFormatFloat4, //
95+
MTLVertexFormatHalf, MTLVertexFormatHalf2, MTLVertexFormatHalf3, MTLVertexFormatHalf4, //
96+
};
97+
98+
inline MTLVertexFormat to_mtl_vertex_format(DataType type, uint32_t count) {
99+
switch (type) {
100+
case DataType::i8:
101+
return MTL_FORMATS[count - 1];
102+
case DataType::u8:
103+
return MTL_FORMATS[4 + count - 1];
104+
case DataType::i16:
105+
return MTL_FORMATS[8 + count - 1];
106+
case DataType::u16:
107+
return MTL_FORMATS[12 + count - 1];
108+
case DataType::i32:
109+
return MTL_FORMATS[16 + count - 1];
110+
case DataType::u32:
111+
return MTL_FORMATS[20 + count - 1];
112+
case DataType::f32:
113+
return MTL_FORMATS[24 + count - 1];
114+
case DataType::f16:
115+
return MTL_FORMATS[28 + count - 1];
116+
default:
117+
return MTLVertexFormatInvalid;
118+
}
119+
}
120+
121+
inline MTLResourceOptions to_mtl_resource_option(const MemoryProperty memory_property) {
122+
switch (memory_property) {
123+
case MemoryProperty::HostVisibleAndCoherent:
124+
return MTLResourceStorageModeShared;
125+
case MemoryProperty::DeviceLocal:
126+
return MTLResourceStorageModePrivate;
127+
default:
128+
abort();
129+
}
130+
}
131+
132+
/// For textures.
133+
inline MTLStorageMode to_mtl_storage_mode(const MemoryProperty memory_property) {
134+
switch (memory_property) {
135+
case MemoryProperty::HostVisibleAndCoherent:
136+
return MTLStorageModeShared;
137+
case MemoryProperty::DeviceLocal:
138+
return MTLStorageModePrivate;
139+
default:
140+
abort();
141+
}
142+
}
143+
144+
inline MTLSamplerMinMagFilter to_mtl_sampler_filter(const SamplerFilter filter) {
145+
return filter == SamplerFilter::Nearest ? MTLSamplerMinMagFilterNearest : MTLSamplerMinMagFilterLinear;
146+
}
147+
148+
inline MTLSamplerAddressMode to_mtl_sampler_address_mode(const SamplerAddressMode address_mode) {
149+
switch (address_mode) {
150+
case SamplerAddressMode::Repeat:
151+
return MTLSamplerAddressModeRepeat;
152+
case SamplerAddressMode::MirroredRepeat:
153+
return MTLSamplerAddressModeMirrorRepeat;
154+
case SamplerAddressMode::ClampToEdge:
155+
return MTLSamplerAddressModeClampToEdge;
156+
case SamplerAddressMode::ClampToBorder:
157+
return MTLSamplerAddressModeClampToBorderColor;
158+
default:
159+
abort();
160+
}
161+
}
162+
163+
inline MTLRenderStages to_mtl_render_stage(const ShaderStage stage) {
164+
switch (stage) {
165+
case ShaderStage::Vertex:
166+
return MTLRenderStageVertex;
167+
case ShaderStage::Fragment:
168+
return MTLRenderStageFragment;
169+
default:
170+
assert(false);
171+
abort();
172+
}
173+
}
174+
175+
} // namespace Pathfinder

pathfinder/gpu/mtl/buffer.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#pragma once
2+
3+
#include <memory>
4+
5+
#include "../buffer.h"
6+
#include "device.h"
7+
8+
namespace Pathfinder {
9+
10+
class BufferMtl final : public Buffer {
11+
friend class DeviceMtl;
12+
friend class CommandEncoderMtl;
13+
14+
public:
15+
void upload_via_mapping(size_t data_size, size_t offset, const void* data) override;
16+
17+
void download_via_mapping(size_t data_size, size_t offset, void* data) override;
18+
19+
void set_label(const std::string& label) override;
20+
21+
id<MTLBuffer> get_handle() noexcept {
22+
return mtl_buffer_;
23+
}
24+
25+
private:
26+
BufferMtl(const BufferDescriptor& descriptor);
27+
28+
id<MTLBuffer> mtl_buffer_ = nil;
29+
};
30+
31+
} // namespace Pathfinder

pathfinder/gpu/mtl/buffer.mm

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#include "buffer.h"
2+
3+
#include "base.h"
4+
#include "command_encoder.h"
5+
6+
namespace Pathfinder {
7+
8+
BufferMtl::BufferMtl(const BufferDescriptor& descriptor) : Buffer(descriptor) {}
9+
10+
void BufferMtl::upload_via_mapping(size_t data_size, size_t offset, const void* data) {
11+
unsigned char* data_ptr = reinterpret_cast<unsigned char*>([mtl_buffer_ contents]) + offset;
12+
memcpy(data_ptr, data, data_size);
13+
}
14+
15+
void BufferMtl::download_via_mapping(size_t data_size, size_t offset, void* data) {
16+
unsigned char* data_ptr = reinterpret_cast<unsigned char*>([mtl_buffer_ contents]) + offset;
17+
memcpy(data, data_ptr, data_size);
18+
}
19+
20+
void BufferMtl::set_label(const std::string& label) {
21+
Buffer::set_label(label);
22+
}
23+
24+
} // namespace Pathfinder

0 commit comments

Comments
 (0)