Skip to content

Commit 031b39d

Browse files
authored
🤘 metal: Migrate to objc2 architecture with objc2-metal bindings (#225)
* metal: Migrate to `objc2` architecture with `objc2-metal` bindings The current `objc` crate stack is completely unmaintained and has severely fallen out of date with no updates for over 4 years. The `metal-rs` crate, built on top of this architecture, is completely written by hand which is tedious to keep up-to-date, not to mention has inconsistencies in its implementation. All of this is superseded by the new `objc2` crate stack built by @madsmtm. Beyond providing what seems like a better, safer abstraction over Objective-C, _all_ bindings are completely autogenerated meaning we'll no longer lag behind upstream bindings (requiring painstaking manual patching) or have inconsistencies in the implementations, as long as the generator is properly able to represent the bindings. * Use `target_vendor = "apple"` instead of many custom `target_os`es * Work around unused_qualifications lint for Rust 1.80 prelude extension `size_of(_val)()` was added to the prelude in Rust 1.80, causing `unused_qualifications` warnings whenever we qualify a call to it with via `std::mem::size_of_val()`. The easiest workaround is to remove the prefix and explicitly import the function in scope. We annotate the import with a `TODO` to remove it once bumping our MSRV on or past 1.80.
1 parent a1aee6d commit 031b39d

8 files changed

Lines changed: 159 additions & 107 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,6 @@ jobs:
6969
uses: dtolnay/rust-toolchain@nightly
7070
- name: Generate lockfile with minimal dependency versions
7171
run: cargo +nightly generate-lockfile -Zminimal-versions
72-
- name: Bump `libc 0.1` version to `0.2` via `malloc_buf 0.0.6`
73-
if: ${{ runner.os == 'macOS' }}
74-
run: |
75-
# The 7-year-unmaintained malloc_buf (depended on via metal-rs->objc)
76-
# only allows using libc 0.2 since the 0.0.6 release, which is necessary
77-
# since the libc 0.1 range no longer compiles. Fortunately objc which
78-
# is also unmaintained for 4 years depends on malloc_buf >=0.0,<0.1.0,
79-
# allowing the 0.0.6 release to be used (but not the 1.0.0 release).
80-
cargo update -p malloc_buf --precise 0.0.6
8172
- name: Cargo clippy with minimal-versions
8273
run: cargo +stable clippy --workspace --features ${{ matrix.features }} --no-default-features -- -D warnings
8374

‎Cargo.toml‎

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,18 @@ ash = { version = "0.38", optional = true, default-features = false, features =
3333
egui = { version = ">=0.24, <=0.27", optional = true, default-features = false }
3434
egui_extras = { version = ">=0.24, <=0.27", optional = true, default-features = false }
3535

36-
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
37-
metal = { version = "0.29.0", default-features = false, features = ["link", "dispatch"], optional = true }
36+
[target.'cfg(target_vendor = "apple")'.dependencies]
37+
objc2 = { version = "0.5.2", default-features = false, optional = true }
38+
objc2-foundation = { version = "0.2", default-features = false, optional = true }
39+
objc2-metal = { version = "0.2.1", default-features = false, features = [
40+
"MTLAccelerationStructure",
41+
"MTLBuffer",
42+
"MTLDevice",
43+
"MTLHeap",
44+
"MTLResource",
45+
"MTLTexture",
46+
"std",
47+
], optional = true }
3848

3949
[target.'cfg(windows)'.dependencies]
4050
# Only needed for public-winapi interop helpers
@@ -65,6 +75,11 @@ features = [
6575
"Win32_Graphics_Dxgi_Common",
6676
]
6777

78+
[target.'cfg(target_vendor = "apple")'.dev-dependencies]
79+
objc2-metal = { version = "0.2.1", default-features = false, features = [
80+
"MTLPixelFormat",
81+
] }
82+
6883
[[example]]
6984
name = "vulkan-buffer"
7085
required-features = ["vulkan", "ash/loaded"]
@@ -85,8 +100,8 @@ required-features = ["metal"]
85100
visualizer = ["dep:egui", "dep:egui_extras"]
86101
vulkan = ["dep:ash"]
87102
d3d12 = ["dep:windows"]
88-
metal = ["dep:metal"]
103+
metal = ["dep:objc2", "dep:objc2-metal", "dep:objc2-foundation"]
89104
# Expose helper functionality for winapi types to interface with gpu-allocator, which is primarily windows-rs driven
90105
public-winapi = ["dep:winapi"]
91106

92-
default = ["d3d12", "vulkan"]
107+
default = ["d3d12", "vulkan", "metal"]

‎README.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ allocator.free(allocation).unwrap();
134134

135135
```rust
136136
use gpu_allocator::metal::*;
137-
137+
use objc2_metal as metal;
138138
let mut allocator = Allocator::new(&AllocatorCreateDesc {
139139
device: device.clone(),
140140
debug_settings: Default::default(),
@@ -146,12 +146,12 @@ let mut allocator = Allocator::new(&AllocatorCreateDesc {
146146
```rust
147147
use gpu_allocator::metal::*;
148148
use gpu_allocator::MemoryLocation;
149-
149+
use objc2_metal as metal;
150150
let allocation_desc = AllocationCreateDesc::buffer(
151151
&device,
152152
"Example allocation",
153153
512, // size in bytes
154-
gpu_allocator::MemoryLocation::GpuOnly,
154+
MemoryLocation::GpuOnly,
155155
);
156156
let allocation = allocator.allocate(&allocation_desc).unwrap();
157157
let resource = allocation.make_buffer().unwrap();

‎examples/metal-buffer.rs‎

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
1-
use std::sync::Arc;
2-
31
use gpu_allocator::metal::{AllocationCreateDesc, Allocator, AllocatorCreateDesc};
42
use log::info;
3+
use metal::MTLDevice as _;
4+
use objc2::rc::Id;
5+
use objc2_foundation::NSArray;
6+
use objc2_metal as metal;
57

68
fn main() {
79
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")).init();
810

9-
let device = Arc::new(metal::Device::system_default().unwrap());
11+
// Allow the innards of objc2-metal to link the static function below:
12+
// https://docs.rs/objc2-metal/0.2.2/objc2_metal/index.html
13+
#[link(name = "CoreGraphics", kind = "framework")]
14+
extern "C" {}
15+
16+
let device =
17+
unsafe { Id::from_raw(metal::MTLCreateSystemDefaultDevice()) }.expect("No MTLDevice found");
1018

1119
// Setting up the allocator
1220
let mut allocator = Allocator::new(&AllocatorCreateDesc {
@@ -60,11 +68,11 @@ fn main() {
6068

6169
// Test allocating texture
6270
{
63-
let texture_desc = metal::TextureDescriptor::new();
64-
texture_desc.set_pixel_format(metal::MTLPixelFormat::RGBA8Unorm);
65-
texture_desc.set_width(64);
66-
texture_desc.set_height(64);
67-
texture_desc.set_storage_mode(metal::MTLStorageMode::Private);
71+
let texture_desc = unsafe { metal::MTLTextureDescriptor::new() };
72+
texture_desc.setPixelFormat(metal::MTLPixelFormat::RGBA8Unorm);
73+
unsafe { texture_desc.setWidth(64) };
74+
unsafe { texture_desc.setHeight(64) };
75+
texture_desc.setStorageMode(metal::MTLStorageMode::Private);
6876
let allocation_desc =
6977
AllocationCreateDesc::texture(&device, "Test allocation (Texture)", &texture_desc);
7078
let allocation = allocator.allocate(&allocation_desc).unwrap();
@@ -75,14 +83,14 @@ fn main() {
7583

7684
// Test allocating acceleration structure
7785
{
78-
let empty_array = metal::Array::from_slice(&[]);
79-
let acc_desc = metal::PrimitiveAccelerationStructureDescriptor::descriptor();
80-
acc_desc.set_geometry_descriptors(empty_array);
81-
let sizes = device.acceleration_structure_sizes_with_descriptor(&acc_desc);
86+
let empty_array = NSArray::from_slice(&[]);
87+
let acc_desc = metal::MTLPrimitiveAccelerationStructureDescriptor::descriptor();
88+
acc_desc.setGeometryDescriptors(Some(&empty_array));
89+
let sizes = device.accelerationStructureSizesWithDescriptor(&acc_desc);
8290
let allocation_desc = AllocationCreateDesc::acceleration_structure_with_size(
8391
&device,
8492
"Test allocation (Acceleration structure)",
85-
sizes.acceleration_structure_size,
93+
sizes.accelerationStructureSize as u64,
8694
gpu_allocator::MemoryLocation::GpuOnly,
8795
);
8896
let allocation = allocator.allocate(&allocation_desc).unwrap();

‎src/d3d12/mod.rs‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
use std::{backtrace::Backtrace, fmt, sync::Arc};
1+
use std::{
2+
backtrace::Backtrace,
3+
fmt,
4+
// TODO: Remove when bumping MSRV to 1.80
5+
mem::size_of_val,
6+
sync::Arc,
7+
};
28

39
use log::{debug, warn, Level};
410
use windows::Win32::{
@@ -628,7 +634,7 @@ impl Allocator {
628634
device.CheckFeatureSupport(
629635
D3D12_FEATURE_D3D12_OPTIONS,
630636
<*mut D3D12_FEATURE_DATA_D3D12_OPTIONS>::cast(&mut options),
631-
std::mem::size_of_val(&options) as u32,
637+
size_of_val(&options) as u32,
632638
)
633639
}
634640
.map_err(|e| {

‎src/lib.rs‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,11 @@
161161
//! ```no_run
162162
//! # #[cfg(feature = "metal")]
163163
//! # fn main() {
164-
//! # use std::sync::Arc;
165164
//! use gpu_allocator::metal::*;
166-
//!
167-
//! # let device = Arc::new(metal::Device::system_default().unwrap());
165+
//! # use objc2::rc::Id;
166+
//! use objc2_metal as metal;
167+
//! # let device = unsafe { metal::MTLCreateSystemDefaultDevice() };
168+
//! # let device = unsafe { Id::from_raw(device) }.expect("No MTLDevice found");
168169
//! let mut allocator = Allocator::new(&AllocatorCreateDesc {
169170
//! device: device.clone(),
170171
//! debug_settings: Default::default(),
@@ -179,22 +180,23 @@
179180
//! ```no_run
180181
//! # #[cfg(feature = "metal")]
181182
//! # fn main() {
182-
//! # use std::sync::Arc;
183183
//! use gpu_allocator::metal::*;
184184
//! use gpu_allocator::MemoryLocation;
185-
//! # let device = Arc::new(metal::Device::system_default().unwrap());
185+
//! # use objc2::rc::Id;
186+
//! use objc2_metal as metal;
187+
//! # let device = unsafe { metal::MTLCreateSystemDefaultDevice() };
188+
//! # let device = unsafe { Id::from_raw(device) }.expect("No MTLDevice found");
186189
//! # let mut allocator = Allocator::new(&AllocatorCreateDesc {
187190
//! # device: device.clone(),
188191
//! # debug_settings: Default::default(),
189192
//! # allocation_sizes: Default::default(),
190193
//! # })
191194
//! # .unwrap();
192-
//!
193195
//! let allocation_desc = AllocationCreateDesc::buffer(
194196
//! &device,
195197
//! "Example allocation",
196198
//! 512, // size in bytes
197-
//! gpu_allocator::MemoryLocation::GpuOnly,
199+
//! MemoryLocation::GpuOnly,
198200
//! );
199201
//! let allocation = allocator.allocate(&allocation_desc).unwrap();
200202
//! let resource = allocation.make_buffer().unwrap();
@@ -206,6 +208,7 @@
206208
//! # #[cfg(not(feature = "metal"))]
207209
//! # fn main() {}
208210
//! ```
211+
#![deny(clippy::unimplemented, clippy::unwrap_used, clippy::ok_expect)]
209212

210213
mod result;
211214
pub use result::*;
@@ -223,7 +226,7 @@ pub mod vulkan;
223226
#[cfg(all(windows, feature = "d3d12"))]
224227
pub mod d3d12;
225228

226-
#[cfg(all(any(target_os = "macos", target_os = "ios"), feature = "metal"))]
229+
#[cfg(all(target_vendor = "apple", feature = "metal"))]
227230
pub mod metal;
228231

229232
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]

0 commit comments

Comments
 (0)