From 91de65d1271cd5af28529038e7bc755450c5b0cc Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Wed, 1 May 2024 09:42:02 +0200 Subject: [PATCH 1/2] 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. --- .github/workflows/ci.yml | 9 --- Cargo.toml | 21 ++++- README.md | 6 +- examples/metal-buffer.rs | 30 +++---- src/lib.rs | 17 ++-- src/metal/mod.rs | 163 +++++++++++++++++++++++---------------- src/vulkan/mod.rs | 2 - 7 files changed, 145 insertions(+), 103 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index adb8c43..a1b3395 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,15 +69,6 @@ jobs: uses: dtolnay/rust-toolchain@nightly - name: Generate lockfile with minimal dependency versions run: cargo +nightly generate-lockfile -Zminimal-versions - - name: Bump `libc 0.1` version to `0.2` via `malloc_buf 0.0.6` - if: ${{ runner.os == 'macOS' }} - run: | - # The 7-year-unmaintained malloc_buf (depended on via metal-rs->objc) - # only allows using libc 0.2 since the 0.0.6 release, which is necessary - # since the libc 0.1 range no longer compiles. Fortunately objc which - # is also unmaintained for 4 years depends on malloc_buf >=0.0,<0.1.0, - # allowing the 0.0.6 release to be used (but not the 1.0.0 release). - cargo update -p malloc_buf --precise 0.0.6 - name: Cargo clippy with minimal-versions run: cargo +stable clippy --workspace --all-targets --features ${{ matrix.features }} --no-default-features -- -D warnings diff --git a/Cargo.toml b/Cargo.toml index cf2ab9f..5a5617a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,17 @@ egui = { version = ">=0.24, <=0.27", optional = true, default-features = false } egui_extras = { version = ">=0.24, <=0.27", optional = true, default-features = false } [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] -metal = { version = "0.28.0", git = "https://github.com/gfx-rs/metal-rs", rev = "0d6214f", default-features = false, features = ["link", "dispatch"], optional = true } +objc2 = { version = "0.5", default-features = false, optional = true } +objc2-foundation = { version = "0.2", default-features = false, optional = true } +objc2-metal = { version = "0.2.2", default-features = false, features = [ + "MTLAccelerationStructure", + "MTLBuffer", + "MTLDevice", + "MTLHeap", + "MTLResource", + "MTLTexture", + "std", +], optional = true } [target.'cfg(windows)'.dependencies] # Only needed for public-winapi interop helpers @@ -64,6 +74,11 @@ features = [ "Win32_Graphics_Dxgi_Common", ] +[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dev-dependencies] +objc2-metal = { version = "0.2.2", default-features = false, features = [ + "MTLPixelFormat", +] } + [[example]] name = "vulkan-buffer" required-features = ["vulkan", "ash/loaded"] @@ -84,8 +99,8 @@ required-features = ["metal"] visualizer = ["dep:egui", "dep:egui_extras"] vulkan = ["dep:ash"] d3d12 = ["dep:windows"] -metal = ["dep:metal"] +metal = ["dep:objc2", "dep:objc2-metal", "dep:objc2-foundation"] # Expose helper functionality for winapi types to interface with gpu-allocator, which is primarily windows-rs driven public-winapi = ["dep:winapi"] -default = ["d3d12", "vulkan"] +default = ["d3d12", "vulkan", "metal"] diff --git a/README.md b/README.md index 9fe60bc..f3bc3eb 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ allocator.free(allocation).unwrap(); ```rust use gpu_allocator::metal::*; - +use objc2_metal as metal; let mut allocator = Allocator::new(&AllocatorCreateDesc { device: device.clone(), debug_settings: Default::default(), @@ -146,12 +146,12 @@ let mut allocator = Allocator::new(&AllocatorCreateDesc { ```rust use gpu_allocator::metal::*; use gpu_allocator::MemoryLocation; - +use objc2_metal as metal; let allocation_desc = AllocationCreateDesc::buffer( &device, "Example allocation", 512, // size in bytes - gpu_allocator::MemoryLocation::GpuOnly, + MemoryLocation::GpuOnly, ); let allocation = allocator.allocate(&allocation_desc).unwrap(); let resource = allocation.make_buffer().unwrap(); diff --git a/examples/metal-buffer.rs b/examples/metal-buffer.rs index 5674bef..9829e4c 100644 --- a/examples/metal-buffer.rs +++ b/examples/metal-buffer.rs @@ -1,12 +1,16 @@ -use std::sync::Arc; - use gpu_allocator::metal::{AllocationCreateDesc, Allocator, AllocatorCreateDesc}; use log::info; +use metal::MTLDevice as _; +use objc2::rc::Id; +use objc2_foundation::NSArray; +use objc2_metal as metal; fn main() { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")).init(); - let device = Arc::new(metal::Device::system_default().unwrap()); + let device = unsafe { metal::MTLCreateSystemDefaultDevice() }; + // TODO: Not SendSync + let device = unsafe { Id::from_raw(device) }.expect("No MTLDevice found"); // Setting up the allocator let mut allocator = Allocator::new(&AllocatorCreateDesc { @@ -60,11 +64,11 @@ fn main() { // Test allocating texture { - let texture_desc = metal::TextureDescriptor::new(); - texture_desc.set_pixel_format(metal::MTLPixelFormat::RGBA8Unorm); - texture_desc.set_width(64); - texture_desc.set_height(64); - texture_desc.set_storage_mode(metal::MTLStorageMode::Private); + let texture_desc = unsafe { metal::MTLTextureDescriptor::new() }; + texture_desc.setPixelFormat(metal::MTLPixelFormat::RGBA8Unorm); + unsafe { texture_desc.setWidth(64) }; + unsafe { texture_desc.setHeight(64) }; + texture_desc.setStorageMode(metal::MTLStorageMode::Private); let allocation_desc = AllocationCreateDesc::texture(&device, "Test allocation (Texture)", &texture_desc); let allocation = allocator.allocate(&allocation_desc).unwrap(); @@ -75,14 +79,14 @@ fn main() { // Test allocating acceleration structure { - let empty_array = metal::Array::from_slice(&[]); - let acc_desc = metal::PrimitiveAccelerationStructureDescriptor::descriptor(); - acc_desc.set_geometry_descriptors(empty_array); - let sizes = device.acceleration_structure_sizes_with_descriptor(&acc_desc); + let empty_array = NSArray::from_slice(&[]); + let acc_desc = metal::MTLPrimitiveAccelerationStructureDescriptor::descriptor(); + acc_desc.setGeometryDescriptors(Some(&empty_array)); + let sizes = device.accelerationStructureSizesWithDescriptor(&acc_desc); let allocation_desc = AllocationCreateDesc::acceleration_structure_with_size( &device, "Test allocation (Acceleration structure)", - sizes.acceleration_structure_size, + sizes.accelerationStructureSize as u64, gpu_allocator::MemoryLocation::GpuOnly, ); let allocation = allocator.allocate(&allocation_desc).unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 7d2112d..1e5b2d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -161,10 +161,11 @@ //! ```no_run //! # #[cfg(feature = "metal")] //! # fn main() { -//! # use std::sync::Arc; //! use gpu_allocator::metal::*; -//! -//! # let device = Arc::new(metal::Device::system_default().unwrap()); +//! # use objc2::rc::Id; +//! use objc2_metal as metal; +//! # let device = unsafe { metal::MTLCreateSystemDefaultDevice() }; +//! # let device = unsafe { Id::from_raw(device) }.expect("No MTLDevice found"); //! let mut allocator = Allocator::new(&AllocatorCreateDesc { //! device: device.clone(), //! debug_settings: Default::default(), @@ -179,22 +180,23 @@ //! ```no_run //! # #[cfg(feature = "metal")] //! # fn main() { -//! # use std::sync::Arc; //! use gpu_allocator::metal::*; //! use gpu_allocator::MemoryLocation; -//! # let device = Arc::new(metal::Device::system_default().unwrap()); +//! # use objc2::rc::Id; +//! use objc2_metal as metal; +//! # let device = unsafe { metal::MTLCreateSystemDefaultDevice() }; +//! # let device = unsafe { Id::from_raw(device) }.expect("No MTLDevice found"); //! # let mut allocator = Allocator::new(&AllocatorCreateDesc { //! # device: device.clone(), //! # debug_settings: Default::default(), //! # allocation_sizes: Default::default(), //! # }) //! # .unwrap(); -//! //! let allocation_desc = AllocationCreateDesc::buffer( //! &device, //! "Example allocation", //! 512, // size in bytes -//! gpu_allocator::MemoryLocation::GpuOnly, +//! MemoryLocation::GpuOnly, //! ); //! let allocation = allocator.allocate(&allocation_desc).unwrap(); //! let resource = allocation.make_buffer().unwrap(); @@ -206,6 +208,7 @@ //! # #[cfg(not(feature = "metal"))] //! # fn main() {} //! ``` +#![deny(clippy::unimplemented, clippy::unwrap_used, clippy::ok_expect)] mod result; pub use result::*; diff --git a/src/metal/mod.rs b/src/metal/mod.rs index b14096c..f1dab75 100644 --- a/src/metal/mod.rs +++ b/src/metal/mod.rs @@ -1,7 +1,10 @@ -#![deny(clippy::unimplemented, clippy::unwrap_used, clippy::ok_expect)] use std::{backtrace::Backtrace, sync::Arc}; use log::debug; +use metal::{MTLDevice as _, MTLHeap as _, MTLResource as _}; +use objc2::{rc::Retained, runtime::ProtocolObject}; +use objc2_foundation::NSString; +use objc2_metal as metal; use crate::{ allocator::{self, AllocatorReport, MemoryBlockReport}, @@ -10,9 +13,9 @@ use crate::{ fn memory_location_to_metal(location: MemoryLocation) -> metal::MTLResourceOptions { match location { - MemoryLocation::GpuOnly => metal::MTLResourceOptions::StorageModePrivate, + MemoryLocation::GpuOnly => metal::MTLResourceOptions::MTLResourceStorageModePrivate, MemoryLocation::CpuToGpu | MemoryLocation::GpuToCpu | MemoryLocation::Unknown => { - metal::MTLResourceOptions::StorageModeShared + metal::MTLResourceOptions::MTLResourceStorageModeShared } } } @@ -24,44 +27,57 @@ pub struct Allocation { size: u64, memory_block_index: usize, memory_type_index: usize, - heap: Arc, + heap: Retained>, name: Option>, } impl Allocation { - pub fn heap(&self) -> &metal::Heap { - self.heap.as_ref() + pub fn heap(&self) -> &ProtocolObject { + &self.heap } - pub fn make_buffer(&self) -> Option { - let resource = - self.heap - .new_buffer_with_offset(self.size, self.heap.resource_options(), self.offset); + pub fn make_buffer(&self) -> Option>> { + let resource = unsafe { + self.heap.newBufferWithLength_options_offset( + self.size as usize, + self.heap.resourceOptions(), + self.offset as usize, + ) + }; if let Some(resource) = &resource { if let Some(name) = &self.name { - resource.set_label(name); + resource.setLabel(Some(&NSString::from_str(name))); } } resource } - pub fn make_texture(&self, desc: &metal::TextureDescriptor) -> Option { - let resource = self.heap.new_texture_with_offset(desc, self.offset); + pub fn make_texture( + &self, + desc: &metal::MTLTextureDescriptor, + ) -> Option>> { + let resource = unsafe { + self.heap + .newTextureWithDescriptor_offset(desc, self.offset as usize) + }; if let Some(resource) = &resource { if let Some(name) = &self.name { - resource.set_label(name); + resource.setLabel(Some(&NSString::from_str(name))); } } resource } - pub fn make_acceleration_structure(&self) -> Option { - let resource = self - .heap - .new_acceleration_structure_with_size_offset(self.size, self.offset); + pub fn make_acceleration_structure( + &self, + ) -> Option>> { + let resource = unsafe { + self.heap + .newAccelerationStructureWithSize_offset(self.size as usize, self.offset as usize) + }; if let Some(resource) = &resource { if let Some(name) = &self.name { - resource.set_label(name); + resource.setLabel(Some(&NSString::from_str(name))); } } resource @@ -84,54 +100,64 @@ pub struct AllocationCreateDesc<'a> { impl<'a> AllocationCreateDesc<'a> { pub fn buffer( - device: &metal::Device, + device: &ProtocolObject, name: &'a str, length: u64, location: MemoryLocation, ) -> Self { - let size_and_align = - device.heap_buffer_size_and_align(length, memory_location_to_metal(location)); + let size_and_align = device.heapBufferSizeAndAlignWithLength_options( + length as usize, + memory_location_to_metal(location), + ); Self { name, location, - size: size_and_align.size, - alignment: size_and_align.align, + size: size_and_align.size as u64, + alignment: size_and_align.align as u64, } } - pub fn texture(device: &metal::Device, name: &'a str, desc: &metal::TextureDescriptor) -> Self { - let size_and_align = device.heap_texture_size_and_align(desc); + pub fn texture( + device: &ProtocolObject, + name: &'a str, + desc: &metal::MTLTextureDescriptor, + ) -> Self { + let size_and_align = device.heapTextureSizeAndAlignWithDescriptor(desc); Self { name, - location: match desc.storage_mode() { + location: match desc.storageMode() { metal::MTLStorageMode::Shared | metal::MTLStorageMode::Managed | metal::MTLStorageMode::Memoryless => MemoryLocation::Unknown, metal::MTLStorageMode::Private => MemoryLocation::GpuOnly, + metal::MTLStorageMode(mode /* @ 4.. */) => todo!("Unknown storage mode {mode}"), }, - size: size_and_align.size, - alignment: size_and_align.align, + size: size_and_align.size as u64, + alignment: size_and_align.align as u64, } } pub fn acceleration_structure_with_size( - device: &metal::Device, + device: &ProtocolObject, name: &'a str, - size: u64, + size: u64, // TODO: usize location: MemoryLocation, ) -> Self { - let size_and_align = device.heap_acceleration_structure_size_and_align_with_size(size); + // TODO: See if we can mark this function as safe, after checking what happens if size is too large? + // What other preconditions need to be upheld? + let size_and_align = + unsafe { device.heapAccelerationStructureSizeAndAlignWithSize(size as usize) }; Self { name, location, - size: size_and_align.size, - alignment: size_and_align.align, + size: size_and_align.size as u64, + alignment: size_and_align.align as u64, } } } pub struct Allocator { - device: Arc, + device: Retained>, debug_settings: AllocatorDebugSettings, memory_types: Vec, allocation_sizes: AllocationSizes, @@ -139,7 +165,7 @@ pub struct Allocator { #[derive(Debug)] pub struct AllocatorCreateDesc { - pub device: Arc, + pub device: Retained>, pub debug_settings: AllocatorDebugSettings, pub allocation_sizes: AllocationSizes, } @@ -152,23 +178,28 @@ pub struct CommittedAllocationStatistics { #[derive(Debug)] struct MemoryBlock { - heap: Arc, + heap: Retained>, size: u64, sub_allocator: Box, } impl MemoryBlock { fn new( - device: &Arc, + device: &ProtocolObject, size: u64, - heap_descriptor: &metal::HeapDescriptor, + heap_descriptor: &metal::MTLHeapDescriptor, dedicated: bool, memory_location: MemoryLocation, ) -> Result { - heap_descriptor.set_size(size); + heap_descriptor.setSize(size as usize); - let heap = Arc::new(device.new_heap(heap_descriptor)); - heap.set_label(&format!("MemoryBlock {memory_location:?}")); + let heap = device + .newHeapWithDescriptor(heap_descriptor) + .ok_or_else(|| AllocationError::Internal("No MTLHeap was returned".to_string()))?; + + heap.setLabel(Some(&NSString::from_str(&format!( + "MemoryBlock {memory_location:?}" + )))); let sub_allocator: Box = if dedicated { Box::new(allocator::DedicatedBlockAllocator::new(size)) @@ -189,7 +220,7 @@ struct MemoryType { memory_blocks: Vec>, _committed_allocations: CommittedAllocationStatistics, memory_location: MemoryLocation, - heap_properties: metal::HeapDescriptor, + heap_properties: Retained, memory_type_index: usize, active_general_blocks: usize, } @@ -197,14 +228,14 @@ struct MemoryType { impl MemoryType { fn allocate( &mut self, - device: &Arc, + device: &ProtocolObject, desc: &AllocationCreateDesc<'_>, backtrace: Arc, allocation_sizes: &AllocationSizes, ) -> Result { let allocation_type = allocator::AllocationType::Linear; - let memblock_size = if self.heap_properties.storage_mode() == metal::MTLStorageMode::Private + let memblock_size = if self.heap_properties.storageMode() == metal::MTLStorageMode::Private { allocation_sizes.device_memblock_size } else { @@ -380,24 +411,24 @@ impl Allocator { pub fn new(desc: &AllocatorCreateDesc) -> Result { let heap_types = [ (MemoryLocation::GpuOnly, { - let heap_desc = metal::HeapDescriptor::new(); - heap_desc.set_cpu_cache_mode(metal::MTLCPUCacheMode::DefaultCache); - heap_desc.set_storage_mode(metal::MTLStorageMode::Private); - heap_desc.set_heap_type(metal::MTLHeapType::Placement); + let heap_desc = unsafe { metal::MTLHeapDescriptor::new() }; + heap_desc.setCpuCacheMode(metal::MTLCPUCacheMode::DefaultCache); + heap_desc.setStorageMode(metal::MTLStorageMode::Private); + heap_desc.setType(metal::MTLHeapType::Placement); heap_desc }), (MemoryLocation::CpuToGpu, { - let heap_desc = metal::HeapDescriptor::new(); - heap_desc.set_cpu_cache_mode(metal::MTLCPUCacheMode::WriteCombined); - heap_desc.set_storage_mode(metal::MTLStorageMode::Shared); - heap_desc.set_heap_type(metal::MTLHeapType::Placement); + let heap_desc = unsafe { metal::MTLHeapDescriptor::new() }; + heap_desc.setCpuCacheMode(metal::MTLCPUCacheMode::WriteCombined); + heap_desc.setStorageMode(metal::MTLStorageMode::Shared); + heap_desc.setType(metal::MTLHeapType::Placement); heap_desc }), (MemoryLocation::GpuToCpu, { - let heap_desc = metal::HeapDescriptor::new(); - heap_desc.set_cpu_cache_mode(metal::MTLCPUCacheMode::DefaultCache); - heap_desc.set_storage_mode(metal::MTLStorageMode::Shared); - heap_desc.set_heap_type(metal::MTLHeapType::Placement); + let heap_desc = unsafe { metal::MTLHeapDescriptor::new() }; + heap_desc.setCpuCacheMode(metal::MTLCPUCacheMode::DefaultCache); + heap_desc.setStorageMode(metal::MTLStorageMode::Shared); + heap_desc.setType(metal::MTLHeapType::Placement); heap_desc }), ]; @@ -482,15 +513,15 @@ impl Allocator { Ok(()) } - pub fn get_heaps(&self) -> Vec<&metal::HeapRef> { - // Get all memory blocks - let mut heaps: Vec<&metal::HeapRef> = Vec::new(); - for memory_type in &self.memory_types { - for block in memory_type.memory_blocks.iter().flatten() { - heaps.push(block.heap.as_ref()); - } - } - heaps + /// Returns heaps for all memory blocks + pub fn heaps(&self) -> impl Iterator> { + self.memory_types.iter().flat_map(|memory_type| { + memory_type + .memory_blocks + .iter() + .flatten() + .map(|block| block.heap.as_ref()) + }) } pub fn generate_report(&self) -> AllocatorReport { diff --git a/src/vulkan/mod.rs b/src/vulkan/mod.rs index 560d7cb..bb189d4 100644 --- a/src/vulkan/mod.rs +++ b/src/vulkan/mod.rs @@ -1,5 +1,3 @@ -#![deny(clippy::unimplemented, clippy::unwrap_used, clippy::ok_expect)] - #[cfg(feature = "visualizer")] mod visualizer; use std::{backtrace::Backtrace, fmt, marker::PhantomData, sync::Arc}; From 5173453c3f8a43429859b38902270267ede5ba2e Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Tue, 21 May 2024 23:54:11 +0200 Subject: [PATCH 2/2] Use `target_vendor = "apple"` instead of many custom `target_os`es --- Cargo.toml | 4 ++-- src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5a5617a..15c6042 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ ash = { version = "0.38", optional = true, default-features = false, features = egui = { version = ">=0.24, <=0.27", optional = true, default-features = false } egui_extras = { version = ">=0.24, <=0.27", optional = true, default-features = false } -[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] +[target.'cfg(target_vendor = "apple")'.dependencies] objc2 = { version = "0.5", default-features = false, optional = true } objc2-foundation = { version = "0.2", default-features = false, optional = true } objc2-metal = { version = "0.2.2", default-features = false, features = [ @@ -74,7 +74,7 @@ features = [ "Win32_Graphics_Dxgi_Common", ] -[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dev-dependencies] +[target.'cfg(target_vendor = "apple")'.dev-dependencies] objc2-metal = { version = "0.2.2", default-features = false, features = [ "MTLPixelFormat", ] } diff --git a/src/lib.rs b/src/lib.rs index 1e5b2d9..878082c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -226,7 +226,7 @@ pub mod vulkan; #[cfg(all(windows, feature = "d3d12"))] pub mod d3d12; -#[cfg(all(any(target_os = "macos", target_os = "ios"), feature = "metal"))] +#[cfg(all(target_vendor = "apple", feature = "metal"))] pub mod metal; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]