summaryrefslogtreecommitdiffstats
path: root/third_party/rust/gfx-backend-metal/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/gfx-backend-metal/src/lib.rs')
-rw-r--r--third_party/rust/gfx-backend-metal/src/lib.rs1126
1 files changed, 1126 insertions, 0 deletions
diff --git a/third_party/rust/gfx-backend-metal/src/lib.rs b/third_party/rust/gfx-backend-metal/src/lib.rs
new file mode 100644
index 0000000000..5c51cb91a7
--- /dev/null
+++ b/third_party/rust/gfx-backend-metal/src/lib.rs
@@ -0,0 +1,1126 @@
+/*!
+# Metal backend internals.
+
+## Pipeline Layout
+
+In Metal, push constants, vertex buffers, and resources in the descriptor sets
+are all placed together in the native resource bindings, which work similarly to D3D11:
+there are tables of textures, buffers, and samplers.
+
+We put push constants first (if any) in the table, followed by descriptor set 0
+resource, followed by other descriptor sets. The vertex buffers are bound at the very
+end of the VS buffer table.
+
+When argument buffers are supported, each descriptor set becomes a buffer binding,
+but the general placement rule is the same.
+
+## Command recording
+
+One-time-submit primary command buffers are recorded "live" into `MTLCommandBuffer`.
+Special care is taken to the recording state: active bindings are restored at the
+start of any render or compute pass.
+
+Multi-submit and secondary command buffers are recorded as "soft" commands into
+`Journal`. Actual native recording is done at either `submit` or `execute_commands`
+correspondingly. When that happens, we `enqueue` the command buffer at the start
+of recording, which allows the driver to work on pass translation at the same time
+as we are recording the following passes.
+
+## Memory
+
+In general, "Shared" storage is used for CPU-coherent memory. "Managed" is used for
+non-coherent CPU-visible memory. Finally, "Private" storage is backing device-local
+memory types.
+
+Metal doesn't have CPU-visible memory for textures. We only allow RGBA8 2D textures
+to be allocated from it, and only for the matter of transfer operations, which is
+the minimum required by Vulkan. In fact, these become just glorified staging buffers.
+
+## Events
+
+Events are represented by just an atomic bool. When recording, a command buffer keeps
+track of all events set or reset. Signalling within a command buffer is therefore a
+matter of simply checking that local list. When making a submission, used events are
+also accumulated temporarily, so that we can change their values in the completion
+handler of the last command buffer. We also check this list in order to resolve events
+fired in one command buffer and waited in another one within the same submission.
+
+Waiting for an event from a different submission is accomplished similar to waiting
+for the host. We block all the submissions until the host blockers are resolved, and
+these are checked at certain points like setting an event by the device, or waiting
+for a fence.
+!*/
+
+#[macro_use]
+extern crate bitflags;
+#[macro_use]
+extern crate objc;
+#[macro_use]
+extern crate log;
+
+use hal::{
+ adapter::{Adapter, AdapterInfo, DeviceType},
+ queue::{QueueFamilyId, QueueType},
+};
+use range_alloc::RangeAllocator;
+
+use cocoa_foundation::foundation::NSInteger;
+#[cfg(feature = "dispatch")]
+use dispatch;
+use foreign_types::ForeignTypeRef;
+use lazy_static::lazy_static;
+use metal::MTLFeatureSet;
+use metal::MTLLanguageVersion;
+use metal::{CGFloat, CGSize, CoreAnimationLayer, CoreAnimationLayerRef};
+use objc::{
+ declare::ClassDecl,
+ runtime::{Class, Object, Sel, BOOL, YES},
+};
+use parking_lot::{Condvar, Mutex};
+
+use std::mem;
+use std::os::raw::c_void;
+use std::ptr::NonNull;
+use std::sync::Arc;
+
+mod command;
+mod conversions;
+mod device;
+mod internal;
+mod native;
+mod soft;
+mod window;
+
+pub use crate::command::CommandPool;
+pub use crate::device::{Device, LanguageVersion, PhysicalDevice};
+pub use crate::window::{AcquireMode, Surface};
+
+pub type GraphicsCommandPool = CommandPool;
+
+//TODO: investigate why exactly using `u8` here is slower (~5% total).
+/// A type representing Metal binding's resource index.
+type ResourceIndex = u32;
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default)]
+pub struct CGPoint {
+ pub x: CGFloat,
+ pub y: CGFloat,
+}
+
+impl CGPoint {
+ #[inline]
+ pub fn new(x: CGFloat, y: CGFloat) -> CGPoint {
+ CGPoint { x, y }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default)]
+pub struct CGRect {
+ pub origin: CGPoint,
+ pub size: CGSize,
+}
+
+impl CGRect {
+ #[inline]
+ pub fn new(origin: CGPoint, size: CGSize) -> CGRect {
+ CGRect { origin, size }
+ }
+}
+
+/// Method of recording one-time-submit command buffers.
+#[derive(Clone, Debug, Hash, PartialEq)]
+pub enum OnlineRecording {
+ /// Record natively on-the-fly.
+ Immediate,
+ /// Store commands and only start recording at submission time.
+ Deferred,
+ #[cfg(feature = "dispatch")]
+ /// Start recording asynchronously upon finishing each pass.
+ Remote(dispatch::QueuePriority),
+}
+
+impl Default for OnlineRecording {
+ fn default() -> Self {
+ OnlineRecording::Immediate
+ }
+}
+
+const MAX_ACTIVE_COMMAND_BUFFERS: usize = 1 << 14;
+const MAX_VISIBILITY_QUERIES: usize = 1 << 14;
+const MAX_COLOR_ATTACHMENTS: usize = 8;
+const MAX_BOUND_DESCRIPTOR_SETS: usize = 8;
+
+#[derive(Debug, Clone, Copy)]
+pub struct QueueFamily {}
+
+impl hal::queue::QueueFamily for QueueFamily {
+ fn queue_type(&self) -> QueueType {
+ QueueType::General
+ }
+ fn max_queues(&self) -> usize {
+ 1
+ }
+ fn id(&self) -> QueueFamilyId {
+ QueueFamilyId(0)
+ }
+}
+
+#[derive(Debug)]
+struct VisibilityShared {
+ /// Availability buffer is in shared memory, it has N double words for
+ /// query results followed by N words for the availability.
+ buffer: metal::Buffer,
+ allocator: Mutex<RangeAllocator<hal::query::Id>>,
+ availability_offset: hal::buffer::Offset,
+ condvar: Condvar,
+}
+
+#[derive(Debug)]
+struct Shared {
+ device: Mutex<metal::Device>,
+ queue: Mutex<command::QueueInner>,
+ queue_blocker: Mutex<command::QueueBlocker>,
+ service_pipes: internal::ServicePipes,
+ disabilities: PrivateDisabilities,
+ private_caps: PrivateCapabilities,
+ visibility: VisibilityShared,
+}
+
+unsafe impl Send for Shared {}
+unsafe impl Sync for Shared {}
+
+impl Shared {
+ fn new(device: metal::Device, experiments: &Experiments) -> Self {
+ let private_caps = PrivateCapabilities::new(&device, experiments);
+ debug!("{:#?}", private_caps);
+
+ let visibility = VisibilityShared {
+ buffer: device.new_buffer(
+ MAX_VISIBILITY_QUERIES as u64
+ * (mem::size_of::<u64>() + mem::size_of::<u32>()) as u64,
+ metal::MTLResourceOptions::StorageModeShared,
+ ),
+ allocator: Mutex::new(RangeAllocator::new(
+ 0..MAX_VISIBILITY_QUERIES as hal::query::Id,
+ )),
+ availability_offset: (MAX_VISIBILITY_QUERIES * mem::size_of::<u64>())
+ as hal::buffer::Offset,
+ condvar: Condvar::new(),
+ };
+ Shared {
+ queue: Mutex::new(command::QueueInner::new(
+ &device,
+ Some(MAX_ACTIVE_COMMAND_BUFFERS),
+ )),
+ queue_blocker: Mutex::new(command::QueueBlocker::default()),
+ service_pipes: internal::ServicePipes::new(&device),
+ disabilities: PrivateDisabilities {
+ broken_viewport_near_depth: device.name().starts_with("Intel")
+ && !device.supports_feature_set(MTLFeatureSet::macOS_GPUFamily1_v4),
+ broken_layered_clear_image: device.name().starts_with("Intel"),
+ },
+ private_caps,
+ device: Mutex::new(device),
+ visibility,
+ }
+ }
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct Experiments {
+ pub argument_buffers: bool,
+}
+
+#[derive(Debug)]
+pub struct Instance {
+ pub experiments: Experiments,
+ gfx_managed_metal_layer_delegate: GfxManagedMetalLayerDelegate,
+}
+
+impl hal::Instance<Backend> for Instance {
+ fn create(_: &str, _: u32) -> Result<Self, hal::UnsupportedBackend> {
+ Ok(Instance {
+ experiments: Experiments::default(),
+ gfx_managed_metal_layer_delegate: GfxManagedMetalLayerDelegate::new(),
+ })
+ }
+
+ fn enumerate_adapters(&self) -> Vec<Adapter<Backend>> {
+ let devices = metal::Device::all();
+ let mut adapters: Vec<Adapter<Backend>> = devices
+ .into_iter()
+ .map(|dev| {
+ let name = dev.name().into();
+ let shared = Shared::new(dev, &self.experiments);
+ let physical_device = device::PhysicalDevice::new(Arc::new(shared));
+ Adapter {
+ info: AdapterInfo {
+ name,
+ vendor: 0,
+ device: 0,
+ device_type: if physical_device.shared.private_caps.low_power {
+ DeviceType::IntegratedGpu
+ } else {
+ DeviceType::DiscreteGpu
+ },
+ },
+ physical_device,
+ queue_families: vec![QueueFamily {}],
+ }
+ })
+ .collect();
+ adapters.sort_by_key(|adapt| {
+ (
+ adapt.physical_device.shared.private_caps.low_power,
+ adapt.physical_device.shared.private_caps.headless,
+ )
+ });
+ adapters
+ }
+
+ unsafe fn create_surface(
+ &self,
+ has_handle: &impl raw_window_handle::HasRawWindowHandle,
+ ) -> Result<Surface, hal::window::InitError> {
+ match has_handle.raw_window_handle() {
+ #[cfg(target_os = "ios")]
+ raw_window_handle::RawWindowHandle::IOS(handle) => {
+ Ok(self.create_surface_from_uiview(handle.ui_view))
+ }
+ #[cfg(target_os = "macos")]
+ raw_window_handle::RawWindowHandle::MacOS(handle) => {
+ Ok(self.create_surface_from_nsview(handle.ns_view))
+ }
+ _ => Err(hal::window::InitError::UnsupportedWindowHandle),
+ }
+ }
+
+ unsafe fn destroy_surface(&self, surface: Surface) {
+ surface.dispose();
+ }
+}
+
+lazy_static! {
+ static ref GFX_MANAGED_METAL_LAYER_DELEGATE_CLASS: &'static Class = unsafe {
+ let mut decl = ClassDecl::new("GfxManagedMetalLayerDelegate", class!(NSObject)).unwrap();
+ decl.add_method(
+ sel!(layer:shouldInheritContentsScale:fromWindow:),
+ layer_should_inherit_contents_scale_from_window
+ as extern "C" fn(&Object, Sel, *mut Object, CGFloat, *mut Object) -> BOOL,
+ );
+ decl.register()
+ };
+}
+
+extern "C" fn layer_should_inherit_contents_scale_from_window(
+ _: &Object,
+ _: Sel,
+ _layer: *mut Object,
+ _new_scale: CGFloat,
+ _from_window: *mut Object,
+) -> BOOL {
+ return YES;
+}
+
+#[derive(Debug)]
+struct GfxManagedMetalLayerDelegate(*mut Object);
+
+impl GfxManagedMetalLayerDelegate {
+ pub fn new() -> Self {
+ unsafe {
+ let mut delegate: *mut Object =
+ msg_send![*GFX_MANAGED_METAL_LAYER_DELEGATE_CLASS, alloc];
+ delegate = msg_send![delegate, init];
+ Self(delegate)
+ }
+ }
+}
+
+impl Drop for GfxManagedMetalLayerDelegate {
+ fn drop(&mut self) {
+ unsafe {
+ let () = msg_send![self.0, release];
+ }
+ }
+}
+
+unsafe impl Send for GfxManagedMetalLayerDelegate {}
+unsafe impl Sync for GfxManagedMetalLayerDelegate {}
+
+impl Instance {
+ #[cfg(target_os = "ios")]
+ unsafe fn create_from_uiview(&self, uiview: *mut c_void) -> Surface {
+ let view: cocoa_foundation::base::id = mem::transmute(uiview);
+ if view.is_null() {
+ panic!("window does not have a valid contentView");
+ }
+
+ let main_layer: *mut Object = msg_send![view, layer];
+ let class = class!(CAMetalLayer);
+ let is_valid_layer: BOOL = msg_send![main_layer, isKindOfClass: class];
+ let render_layer = if is_valid_layer == YES {
+ mem::transmute::<_, &CoreAnimationLayerRef>(main_layer).to_owned()
+ } else {
+ // If the main layer is not a CAMetalLayer, we create a CAMetalLayer sublayer and use it instead.
+ // Unlike on macOS, we cannot replace the main view as UIView does not allow it (when NSView does).
+ let new_layer: CoreAnimationLayer = msg_send![class, new];
+ let bounds: CGRect = msg_send![main_layer, bounds];
+ let () = msg_send![new_layer.as_ref(), setFrame: bounds];
+ let () = msg_send![main_layer, addSublayer: new_layer.as_ref()];
+ new_layer
+ };
+
+ let window: cocoa_foundation::base::id = msg_send![view, window];
+ if !window.is_null() {
+ let screen: cocoa_foundation::base::id = msg_send![window, screen];
+ assert!(!screen.is_null(), "window is not attached to a screen");
+
+ let scale_factor: CGFloat = msg_send![screen, nativeScale];
+ let () = msg_send![view, setContentScaleFactor: scale_factor];
+ }
+
+ let _: *mut c_void = msg_send![view, retain];
+ Surface::new(NonNull::new(view), render_layer)
+ }
+
+ #[cfg(target_os = "macos")]
+ unsafe fn create_from_nsview(&self, nsview: *mut c_void) -> Surface {
+ let view: cocoa_foundation::base::id = mem::transmute(nsview);
+ if view.is_null() {
+ panic!("window does not have a valid contentView");
+ }
+
+ let class = class!(CAMetalLayer);
+ // Deprecated! Clients should use `create_surface_from_layer` instead.
+ let is_actually_layer: BOOL = msg_send![view, isKindOfClass: class];
+ if is_actually_layer == YES {
+ return self.create_from_layer(mem::transmute(view));
+ }
+
+ let existing: *mut Object = msg_send![view, layer];
+ let use_current = if existing.is_null() {
+ false
+ } else {
+ let result: BOOL = msg_send![existing, isKindOfClass: class];
+ result == YES
+ };
+
+ let render_layer: CoreAnimationLayer = if use_current {
+ mem::transmute::<_, &CoreAnimationLayerRef>(existing).to_owned()
+ } else {
+ let layer: CoreAnimationLayer = msg_send![class, new];
+ let () = msg_send![view, setLayer: layer.as_ref()];
+ let () = msg_send![view, setWantsLayer: YES];
+ let bounds: CGRect = msg_send![view, bounds];
+ let () = msg_send![layer.as_ref(), setBounds: bounds];
+
+ let window: cocoa_foundation::base::id = msg_send![view, window];
+ if !window.is_null() {
+ let scale_factor: CGFloat = msg_send![window, backingScaleFactor];
+ let () = msg_send![layer, setContentsScale: scale_factor];
+ }
+ let () = msg_send![layer, setDelegate: self.gfx_managed_metal_layer_delegate.0];
+ layer
+ };
+
+ let _: *mut c_void = msg_send![view, retain];
+ Surface::new(NonNull::new(view), render_layer)
+ }
+
+ unsafe fn create_from_layer(&self, layer: &CoreAnimationLayerRef) -> Surface {
+ let class = class!(CAMetalLayer);
+ let proper_kind: BOOL = msg_send![layer, isKindOfClass: class];
+ assert_eq!(proper_kind, YES);
+ Surface::new(None, layer.to_owned())
+ }
+
+ pub fn create_surface_from_layer(&self, layer: &CoreAnimationLayerRef) -> Surface {
+ unsafe { self.create_from_layer(layer) }
+ }
+
+ #[cfg(target_os = "macos")]
+ pub fn create_surface_from_nsview(&self, nsview: *mut c_void) -> Surface {
+ unsafe { self.create_from_nsview(nsview) }
+ }
+
+ #[cfg(target_os = "ios")]
+ pub fn create_surface_from_uiview(&self, uiview: *mut c_void) -> Surface {
+ unsafe { self.create_from_uiview(uiview) }
+ }
+}
+
+#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
+pub enum Backend {}
+impl hal::Backend for Backend {
+ type Instance = Instance;
+ type PhysicalDevice = device::PhysicalDevice;
+ type Device = device::Device;
+ type Surface = Surface;
+
+ type QueueFamily = QueueFamily;
+ type CommandQueue = command::CommandQueue;
+ type CommandBuffer = command::CommandBuffer;
+
+ type Memory = native::Memory;
+ type CommandPool = command::CommandPool;
+
+ type ShaderModule = native::ShaderModule;
+ type RenderPass = native::RenderPass;
+ type Framebuffer = native::Framebuffer;
+
+ type Buffer = native::Buffer;
+ type BufferView = native::BufferView;
+ type Image = native::Image;
+ type ImageView = native::ImageView;
+ type Sampler = native::Sampler;
+
+ type ComputePipeline = native::ComputePipeline;
+ type GraphicsPipeline = native::GraphicsPipeline;
+ type PipelineCache = native::PipelineCache;
+ type PipelineLayout = native::PipelineLayout;
+ type DescriptorSetLayout = native::DescriptorSetLayout;
+ type DescriptorPool = native::DescriptorPool;
+ type DescriptorSet = native::DescriptorSet;
+
+ type Fence = native::Fence;
+ type Semaphore = native::Semaphore;
+ type Event = native::Event;
+ type QueryPool = native::QueryPool;
+}
+
+const RESOURCE_HEAP_SUPPORT: &[MTLFeatureSet] = &[
+ MTLFeatureSet::iOS_GPUFamily1_v3,
+ MTLFeatureSet::iOS_GPUFamily2_v3,
+ MTLFeatureSet::iOS_GPUFamily3_v2,
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::tvOS_GPUFamily1_v2,
+ MTLFeatureSet::tvOS_GPUFamily2_v1,
+ MTLFeatureSet::macOS_GPUFamily1_v3,
+ MTLFeatureSet::macOS_GPUFamily2_v1,
+];
+
+const ARGUMENT_BUFFER_SUPPORT: &[MTLFeatureSet] = &[
+ MTLFeatureSet::iOS_GPUFamily1_v4,
+ MTLFeatureSet::iOS_GPUFamily2_v4,
+ MTLFeatureSet::iOS_GPUFamily3_v3,
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::tvOS_GPUFamily1_v3,
+ MTLFeatureSet::macOS_GPUFamily1_v3,
+ MTLFeatureSet::macOS_GPUFamily2_v1,
+];
+
+const MUTABLE_COMPARISON_SAMPLER_SUPPORT: &[MTLFeatureSet] = &[
+ MTLFeatureSet::iOS_GPUFamily3_v1,
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::macOS_GPUFamily1_v1,
+ MTLFeatureSet::macOS_GPUFamily2_v1,
+];
+
+const SAMPLER_CLAMP_TO_BORDER_SUPPORT: &[MTLFeatureSet] = &[
+ MTLFeatureSet::macOS_GPUFamily1_v2,
+ MTLFeatureSet::macOS_GPUFamily2_v1,
+];
+
+const ASTC_PIXEL_FORMAT_FEATURES: &[MTLFeatureSet] = &[
+ MTLFeatureSet::iOS_GPUFamily2_v1,
+ MTLFeatureSet::iOS_GPUFamily3_v1,
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::tvOS_GPUFamily1_v1,
+ MTLFeatureSet::tvOS_GPUFamily2_v1,
+];
+
+const ANY8_UNORM_SRGB_ALL: &[MTLFeatureSet] = &[
+ MTLFeatureSet::iOS_GPUFamily2_v3,
+ MTLFeatureSet::iOS_GPUFamily3_v1,
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::tvOS_GPUFamily1_v2,
+ MTLFeatureSet::tvOS_GPUFamily2_v1,
+];
+
+const ANY8_SNORM_RESOLVE: &[MTLFeatureSet] = &[
+ MTLFeatureSet::iOS_GPUFamily2_v1,
+ MTLFeatureSet::iOS_GPUFamily3_v1,
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::tvOS_GPUFamily1_v1,
+ MTLFeatureSet::tvOS_GPUFamily2_v1,
+ MTLFeatureSet::macOS_GPUFamily1_v1,
+ MTLFeatureSet::macOS_GPUFamily2_v1,
+];
+
+const RGBA8_SRGB: &[MTLFeatureSet] = &[
+ MTLFeatureSet::iOS_GPUFamily2_v3,
+ MTLFeatureSet::iOS_GPUFamily3_v1,
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::tvOS_GPUFamily1_v2,
+ MTLFeatureSet::tvOS_GPUFamily2_v1,
+];
+
+const RGB10A2UNORM_ALL: &[MTLFeatureSet] = &[
+ MTLFeatureSet::iOS_GPUFamily3_v1,
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::tvOS_GPUFamily2_v1,
+ MTLFeatureSet::macOS_GPUFamily1_v1,
+ MTLFeatureSet::macOS_GPUFamily2_v1,
+];
+
+const RGB10A2UINT_COLOR_WRITE: &[MTLFeatureSet] = &[
+ MTLFeatureSet::iOS_GPUFamily3_v1,
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::tvOS_GPUFamily2_v1,
+ MTLFeatureSet::macOS_GPUFamily1_v1,
+ MTLFeatureSet::macOS_GPUFamily2_v1,
+];
+
+const RG11B10FLOAT_ALL: &[MTLFeatureSet] = &[
+ MTLFeatureSet::iOS_GPUFamily3_v1,
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::tvOS_GPUFamily2_v1,
+ MTLFeatureSet::macOS_GPUFamily1_v1,
+ MTLFeatureSet::macOS_GPUFamily2_v1,
+];
+
+const RGB9E5FLOAT_ALL: &[MTLFeatureSet] = &[
+ MTLFeatureSet::iOS_GPUFamily3_v1,
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::tvOS_GPUFamily2_v1,
+];
+
+const BGR10A2_ALL: &[MTLFeatureSet] = &[
+ MTLFeatureSet::iOS_GPUFamily1_v4,
+ MTLFeatureSet::iOS_GPUFamily2_v4,
+ MTLFeatureSet::iOS_GPUFamily3_v3,
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::tvOS_GPUFamily1_v3,
+ MTLFeatureSet::tvOS_GPUFamily2_v1,
+ MTLFeatureSet::macOS_GPUFamily1_v3,
+ MTLFeatureSet::macOS_GPUFamily2_v1,
+];
+
+const BASE_INSTANCE_SUPPORT: &[MTLFeatureSet] = &[
+ MTLFeatureSet::iOS_GPUFamily3_v1,
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::tvOS_GPUFamily2_v1,
+ MTLFeatureSet::macOS_GPUFamily1_v1,
+ MTLFeatureSet::macOS_GPUFamily2_v1,
+];
+
+const BASE_VERTEX_INSTANCE_SUPPORT: &[MTLFeatureSet] = &[
+ MTLFeatureSet::iOS_GPUFamily3_v1,
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::tvOS_GPUFamily2_v1,
+ MTLFeatureSet::macOS_GPUFamily1_v1,
+ MTLFeatureSet::macOS_GPUFamily2_v1,
+];
+
+const TEXTURE_CUBE_ARRAY_SUPPORT: &[MTLFeatureSet] = &[
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::tvOS_GPUFamily1_v2,
+ MTLFeatureSet::tvOS_GPUFamily2_v1,
+ MTLFeatureSet::macOS_GPUFamily1_v1,
+ MTLFeatureSet::macOS_GPUFamily2_v1,
+];
+
+const DUAL_SOURCE_BLEND_SUPPORT: &[MTLFeatureSet] = &[
+ MTLFeatureSet::iOS_GPUFamily1_v4,
+ MTLFeatureSet::iOS_GPUFamily2_v4,
+ MTLFeatureSet::iOS_GPUFamily3_v3,
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::tvOS_GPUFamily1_v3,
+ MTLFeatureSet::tvOS_GPUFamily2_v1,
+ MTLFeatureSet::macOS_GPUFamily1_v2,
+ MTLFeatureSet::macOS_GPUFamily2_v1,
+];
+
+const LAYERED_RENDERING_SUPPORT: &[MTLFeatureSet] = &[
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::macOS_GPUFamily1_v1,
+ MTLFeatureSet::macOS_GPUFamily2_v1,
+];
+
+const FUNCTION_SPECIALIZATION_SUPPORT: &[MTLFeatureSet] = &[
+ MTLFeatureSet::iOS_GPUFamily1_v3,
+ MTLFeatureSet::iOS_GPUFamily2_v3,
+ MTLFeatureSet::iOS_GPUFamily3_v2,
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::tvOS_GPUFamily1_v2,
+ MTLFeatureSet::macOS_GPUFamily1_v2,
+ MTLFeatureSet::macOS_GPUFamily2_v1,
+];
+
+const DEPTH_CLIP_MODE: &[MTLFeatureSet] = &[
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::tvOS_GPUFamily1_v3,
+ MTLFeatureSet::macOS_GPUFamily1_v1,
+ MTLFeatureSet::macOS_GPUFamily2_v1,
+];
+
+#[derive(Clone, Debug)]
+struct PrivateCapabilities {
+ pub os_is_mac: bool,
+ os_version: (u32, u32),
+ msl_version: metal::MTLLanguageVersion,
+ exposed_queues: usize,
+ // if TRUE, we'll report `NON_FILL_POLYGON_MODE` feature without the points support
+ expose_line_mode: bool,
+ resource_heaps: bool,
+ argument_buffers: bool,
+ shared_textures: bool,
+ mutable_comparison_samplers: bool,
+ sampler_clamp_to_border: bool,
+ base_instance: bool,
+ base_vertex_instance_drawing: bool,
+ dual_source_blending: bool,
+ low_power: bool,
+ headless: bool,
+ layered_rendering: bool,
+ function_specialization: bool,
+ depth_clip_mode: bool,
+ texture_cube_array: bool,
+ format_depth24_stencil8: bool,
+ format_depth32_stencil8_filter: bool,
+ format_depth32_stencil8_none: bool,
+ format_min_srgb_channels: u8,
+ format_b5: bool,
+ format_bc: bool,
+ format_eac_etc: bool,
+ format_astc: bool,
+ format_any8_unorm_srgb_all: bool,
+ format_any8_unorm_srgb_no_write: bool,
+ format_any8_snorm_all: bool,
+ format_r16_norm_all: bool,
+ format_r32_all: bool,
+ format_r32_no_write: bool,
+ format_r32float_no_write_no_filter: bool,
+ format_r32float_no_filter: bool,
+ format_r32float_all: bool,
+ format_rgba8_srgb_all: bool,
+ format_rgba8_srgb_no_write: bool,
+ format_rgb10a2_unorm_all: bool,
+ format_rgb10a2_unorm_no_write: bool,
+ format_rgb10a2_uint_color: bool,
+ format_rgb10a2_uint_color_write: bool,
+ format_rg11b10_all: bool,
+ format_rg11b10_no_write: bool,
+ format_rgb9e5_all: bool,
+ format_rgb9e5_no_write: bool,
+ format_rgb9e5_filter_only: bool,
+ format_rg32_color: bool,
+ format_rg32_color_write: bool,
+ format_rg32float_all: bool,
+ format_rg32float_color_blend: bool,
+ format_rg32float_no_filter: bool,
+ format_rgba32int_color: bool,
+ format_rgba32int_color_write: bool,
+ format_rgba32float_color: bool,
+ format_rgba32float_color_write: bool,
+ format_rgba32float_all: bool,
+ format_depth16unorm: bool,
+ format_depth32float_filter: bool,
+ format_depth32float_none: bool,
+ format_bgr10a2_all: bool,
+ format_bgr10a2_no_write: bool,
+ max_buffers_per_stage: ResourceIndex,
+ max_textures_per_stage: ResourceIndex,
+ max_samplers_per_stage: ResourceIndex,
+ buffer_alignment: u64,
+ max_buffer_size: u64,
+ max_texture_size: u64,
+ max_texture_3d_size: u64,
+ max_texture_layers: u64,
+ max_fragment_input_components: u64,
+ max_color_render_targets: u8,
+ max_total_threadgroup_memory: u32,
+ sample_count_mask: u8,
+ supports_debug_markers: bool,
+}
+
+impl PrivateCapabilities {
+ fn version_at_least(major: u32, minor: u32, needed_major: u32, needed_minor: u32) -> bool {
+ major > needed_major || (major == needed_major && minor >= needed_minor)
+ }
+
+ fn supports_any(raw: &metal::DeviceRef, features_sets: &[MTLFeatureSet]) -> bool {
+ features_sets
+ .iter()
+ .cloned()
+ .any(|x| raw.supports_feature_set(x))
+ }
+
+ fn new(device: &metal::Device, experiments: &Experiments) -> Self {
+ #[repr(C)]
+ #[derive(Clone, Copy, Debug)]
+ struct NSOperatingSystemVersion {
+ major: NSInteger,
+ minor: NSInteger,
+ patch: NSInteger,
+ }
+
+ let version: NSOperatingSystemVersion = unsafe {
+ let process_info: *mut Object = msg_send![class!(NSProcessInfo), processInfo];
+ msg_send![process_info, operatingSystemVersion]
+ };
+
+ let major = version.major as u32;
+ let minor = version.minor as u32;
+ let os_is_mac = device.supports_feature_set(MTLFeatureSet::macOS_GPUFamily1_v1);
+
+ let mut sample_count_mask: u8 = 1 | 4; // 1 and 4 samples are supported on all devices
+ if device.supports_sample_count(2) {
+ sample_count_mask |= 2;
+ }
+ if device.supports_sample_count(8) {
+ sample_count_mask |= 8;
+ }
+
+ PrivateCapabilities {
+ os_is_mac,
+ os_version: (major as u32, minor as u32),
+ msl_version: if os_is_mac {
+ if Self::version_at_least(major, minor, 10, 15) {
+ MTLLanguageVersion::V2_2
+ } else if Self::version_at_least(major, minor, 10, 14) {
+ MTLLanguageVersion::V2_1
+ } else if Self::version_at_least(major, minor, 10, 13) {
+ MTLLanguageVersion::V2_0
+ } else if Self::version_at_least(major, minor, 10, 12) {
+ MTLLanguageVersion::V1_2
+ } else if Self::version_at_least(major, minor, 10, 11) {
+ MTLLanguageVersion::V1_1
+ } else {
+ MTLLanguageVersion::V1_0
+ }
+ } else if Self::version_at_least(major, minor, 13, 0) {
+ MTLLanguageVersion::V2_2
+ } else if Self::version_at_least(major, minor, 12, 0) {
+ MTLLanguageVersion::V2_1
+ } else if Self::version_at_least(major, minor, 11, 0) {
+ MTLLanguageVersion::V2_0
+ } else if Self::version_at_least(major, minor, 10, 0) {
+ MTLLanguageVersion::V1_2
+ } else if Self::version_at_least(major, minor, 9, 0) {
+ MTLLanguageVersion::V1_1
+ } else {
+ MTLLanguageVersion::V1_0
+ },
+ exposed_queues: 1,
+ expose_line_mode: true,
+ resource_heaps: Self::supports_any(&device, RESOURCE_HEAP_SUPPORT),
+ argument_buffers: experiments.argument_buffers
+ && Self::supports_any(&device, ARGUMENT_BUFFER_SUPPORT),
+ shared_textures: !os_is_mac,
+ mutable_comparison_samplers: Self::supports_any(
+ &device,
+ MUTABLE_COMPARISON_SAMPLER_SUPPORT,
+ ),
+ sampler_clamp_to_border: Self::supports_any(&device, SAMPLER_CLAMP_TO_BORDER_SUPPORT),
+ base_instance: Self::supports_any(&device, BASE_INSTANCE_SUPPORT),
+ base_vertex_instance_drawing: Self::supports_any(&device, BASE_VERTEX_INSTANCE_SUPPORT),
+ dual_source_blending: Self::supports_any(&device, DUAL_SOURCE_BLEND_SUPPORT),
+ low_power: !os_is_mac || device.is_low_power(),
+ headless: os_is_mac && device.is_headless(),
+ layered_rendering: Self::supports_any(&device, LAYERED_RENDERING_SUPPORT),
+ function_specialization: Self::supports_any(&device, FUNCTION_SPECIALIZATION_SUPPORT),
+ depth_clip_mode: Self::supports_any(&device, DEPTH_CLIP_MODE),
+ texture_cube_array: Self::supports_any(&device, TEXTURE_CUBE_ARRAY_SUPPORT),
+ format_depth24_stencil8: os_is_mac && device.d24_s8_supported(),
+ format_depth32_stencil8_filter: os_is_mac,
+ format_depth32_stencil8_none: !os_is_mac,
+ format_min_srgb_channels: if os_is_mac { 4 } else { 1 },
+ format_b5: !os_is_mac,
+ format_bc: os_is_mac,
+ format_eac_etc: !os_is_mac,
+ format_astc: Self::supports_any(&device, ASTC_PIXEL_FORMAT_FEATURES),
+ format_any8_unorm_srgb_all: Self::supports_any(&device, ANY8_UNORM_SRGB_ALL),
+ format_any8_unorm_srgb_no_write: !Self::supports_any(&device, ANY8_UNORM_SRGB_ALL)
+ && !os_is_mac,
+ format_any8_snorm_all: Self::supports_any(&device, ANY8_SNORM_RESOLVE),
+ format_r16_norm_all: os_is_mac,
+ format_r32_all: !Self::supports_any(
+ &device,
+ &[
+ MTLFeatureSet::iOS_GPUFamily1_v1,
+ MTLFeatureSet::iOS_GPUFamily2_v1,
+ ],
+ ),
+ format_r32_no_write: Self::supports_any(
+ &device,
+ &[
+ MTLFeatureSet::iOS_GPUFamily1_v1,
+ MTLFeatureSet::iOS_GPUFamily2_v1,
+ ],
+ ),
+ format_r32float_no_write_no_filter: Self::supports_any(
+ &device,
+ &[
+ MTLFeatureSet::iOS_GPUFamily1_v1,
+ MTLFeatureSet::iOS_GPUFamily2_v1,
+ ],
+ ) && !os_is_mac,
+ format_r32float_no_filter: !Self::supports_any(
+ &device,
+ &[
+ MTLFeatureSet::iOS_GPUFamily1_v1,
+ MTLFeatureSet::iOS_GPUFamily2_v1,
+ ],
+ ) && !os_is_mac,
+ format_r32float_all: os_is_mac,
+ format_rgba8_srgb_all: Self::supports_any(&device, RGBA8_SRGB),
+ format_rgba8_srgb_no_write: !Self::supports_any(&device, RGBA8_SRGB),
+ format_rgb10a2_unorm_all: Self::supports_any(&device, RGB10A2UNORM_ALL),
+ format_rgb10a2_unorm_no_write: !Self::supports_any(&device, RGB10A2UNORM_ALL),
+ format_rgb10a2_uint_color: !Self::supports_any(&device, RGB10A2UINT_COLOR_WRITE),
+ format_rgb10a2_uint_color_write: Self::supports_any(&device, RGB10A2UINT_COLOR_WRITE),
+ format_rg11b10_all: Self::supports_any(&device, RG11B10FLOAT_ALL),
+ format_rg11b10_no_write: !Self::supports_any(&device, RG11B10FLOAT_ALL),
+ format_rgb9e5_all: Self::supports_any(&device, RGB9E5FLOAT_ALL),
+ format_rgb9e5_no_write: !Self::supports_any(&device, RGB9E5FLOAT_ALL) && !os_is_mac,
+ format_rgb9e5_filter_only: os_is_mac,
+ format_rg32_color: Self::supports_any(
+ &device,
+ &[
+ MTLFeatureSet::iOS_GPUFamily1_v1,
+ MTLFeatureSet::iOS_GPUFamily2_v1,
+ ],
+ ),
+ format_rg32_color_write: !Self::supports_any(
+ &device,
+ &[
+ MTLFeatureSet::iOS_GPUFamily1_v1,
+ MTLFeatureSet::iOS_GPUFamily2_v1,
+ ],
+ ),
+ format_rg32float_all: os_is_mac,
+ format_rg32float_color_blend: Self::supports_any(
+ &device,
+ &[
+ MTLFeatureSet::iOS_GPUFamily1_v1,
+ MTLFeatureSet::iOS_GPUFamily2_v1,
+ ],
+ ),
+ format_rg32float_no_filter: !os_is_mac
+ && !Self::supports_any(
+ &device,
+ &[
+ MTLFeatureSet::iOS_GPUFamily1_v1,
+ MTLFeatureSet::iOS_GPUFamily2_v1,
+ ],
+ ),
+ format_rgba32int_color: Self::supports_any(
+ &device,
+ &[
+ MTLFeatureSet::iOS_GPUFamily1_v1,
+ MTLFeatureSet::iOS_GPUFamily2_v1,
+ ],
+ ),
+ format_rgba32int_color_write: !Self::supports_any(
+ &device,
+ &[
+ MTLFeatureSet::iOS_GPUFamily1_v1,
+ MTLFeatureSet::iOS_GPUFamily2_v1,
+ ],
+ ),
+ format_rgba32float_color: Self::supports_any(
+ &device,
+ &[
+ MTLFeatureSet::iOS_GPUFamily1_v1,
+ MTLFeatureSet::iOS_GPUFamily2_v1,
+ ],
+ ),
+ format_rgba32float_color_write: !Self::supports_any(
+ &device,
+ &[
+ MTLFeatureSet::iOS_GPUFamily1_v1,
+ MTLFeatureSet::iOS_GPUFamily2_v1,
+ ],
+ ) && !os_is_mac,
+ format_rgba32float_all: os_is_mac,
+ format_depth16unorm: device.supports_feature_set(MTLFeatureSet::macOS_GPUFamily1_v2),
+ format_depth32float_filter: device
+ .supports_feature_set(MTLFeatureSet::macOS_GPUFamily1_v1),
+ format_depth32float_none: !device
+ .supports_feature_set(MTLFeatureSet::macOS_GPUFamily1_v1),
+ format_bgr10a2_all: Self::supports_any(&device, BGR10A2_ALL),
+ format_bgr10a2_no_write: !device
+ .supports_feature_set(MTLFeatureSet::macOS_GPUFamily1_v3),
+ max_buffers_per_stage: 31,
+ max_textures_per_stage: if os_is_mac { 128 } else { 31 },
+ max_samplers_per_stage: 16,
+ buffer_alignment: if os_is_mac { 256 } else { 64 },
+ max_buffer_size: if device.supports_feature_set(MTLFeatureSet::macOS_GPUFamily1_v2) {
+ 1 << 30 // 1GB on macOS 1.2 and up
+ } else {
+ 1 << 28 // 256MB otherwise
+ },
+ max_texture_size: if Self::supports_any(
+ &device,
+ &[
+ MTLFeatureSet::iOS_GPUFamily3_v1,
+ MTLFeatureSet::tvOS_GPUFamily2_v1,
+ MTLFeatureSet::macOS_GPUFamily1_v1,
+ ],
+ ) {
+ 16384
+ } else if Self::supports_any(
+ &device,
+ &[
+ MTLFeatureSet::iOS_GPUFamily1_v2,
+ MTLFeatureSet::iOS_GPUFamily2_v2,
+ MTLFeatureSet::tvOS_GPUFamily1_v1,
+ ],
+ ) {
+ 8192
+ } else {
+ 4096
+ },
+ max_texture_3d_size: 2048,
+ max_texture_layers: 2048,
+ max_fragment_input_components: if os_is_mac { 128 } else { 60 },
+ max_color_render_targets: if Self::supports_any(
+ &device,
+ &[
+ MTLFeatureSet::iOS_GPUFamily2_v1,
+ MTLFeatureSet::iOS_GPUFamily3_v1,
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::tvOS_GPUFamily1_v1,
+ MTLFeatureSet::tvOS_GPUFamily2_v1,
+ MTLFeatureSet::macOS_GPUFamily1_v1,
+ MTLFeatureSet::macOS_GPUFamily2_v1,
+ ],
+ ) {
+ 8
+ } else {
+ 4
+ },
+ max_total_threadgroup_memory: if Self::supports_any(
+ &device,
+ &[
+ MTLFeatureSet::iOS_GPUFamily4_v2,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ ],
+ ) {
+ 64 << 10
+ } else if Self::supports_any(
+ &device,
+ &[
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::macOS_GPUFamily1_v2,
+ MTLFeatureSet::macOS_GPUFamily2_v1,
+ ],
+ ) {
+ 32 << 10
+ } else {
+ 16 << 10
+ },
+ sample_count_mask,
+ supports_debug_markers: Self::supports_any(
+ &device,
+ &[
+ MTLFeatureSet::macOS_GPUFamily1_v2,
+ MTLFeatureSet::macOS_GPUFamily2_v1,
+ MTLFeatureSet::iOS_GPUFamily1_v3,
+ MTLFeatureSet::iOS_GPUFamily2_v3,
+ MTLFeatureSet::iOS_GPUFamily3_v2,
+ MTLFeatureSet::iOS_GPUFamily4_v1,
+ MTLFeatureSet::iOS_GPUFamily5_v1,
+ MTLFeatureSet::tvOS_GPUFamily1_v2,
+ MTLFeatureSet::tvOS_GPUFamily2_v1,
+ ],
+ ),
+ }
+ }
+
+ fn has_version_at_least(&self, needed_major: u32, needed_minor: u32) -> bool {
+ let (major, minor) = self.os_version;
+ Self::version_at_least(major, minor, needed_major, needed_minor)
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+struct PrivateDisabilities {
+ /// Near depth is not respected properly on some Intel GPUs.
+ broken_viewport_near_depth: bool,
+ /// Multi-target clears don't appear to work properly on Intel GPUs.
+ broken_layered_clear_image: bool,
+}
+
+trait AsNative {
+ type Native;
+ fn from(native: &Self::Native) -> Self;
+ fn as_native(&self) -> &Self::Native;
+}
+
+pub type BufferPtr = NonNull<metal::MTLBuffer>;
+pub type TexturePtr = NonNull<metal::MTLTexture>;
+pub type SamplerPtr = NonNull<metal::MTLSamplerState>;
+pub type ResourcePtr = NonNull<metal::MTLResource>;
+
+//TODO: make this a generic struct with a single generic implementation
+
+impl AsNative for BufferPtr {
+ type Native = metal::BufferRef;
+ #[inline]
+ fn from(native: &metal::BufferRef) -> Self {
+ unsafe { NonNull::new_unchecked(native.as_ptr()) }
+ }
+ #[inline]
+ fn as_native(&self) -> &metal::BufferRef {
+ unsafe { metal::BufferRef::from_ptr(self.as_ptr()) }
+ }
+}
+
+impl AsNative for TexturePtr {
+ type Native = metal::TextureRef;
+ #[inline]
+ fn from(native: &metal::TextureRef) -> Self {
+ unsafe { NonNull::new_unchecked(native.as_ptr()) }
+ }
+ #[inline]
+ fn as_native(&self) -> &metal::TextureRef {
+ unsafe { metal::TextureRef::from_ptr(self.as_ptr()) }
+ }
+}
+
+impl AsNative for SamplerPtr {
+ type Native = metal::SamplerStateRef;
+ #[inline]
+ fn from(native: &metal::SamplerStateRef) -> Self {
+ unsafe { NonNull::new_unchecked(native.as_ptr()) }
+ }
+ #[inline]
+ fn as_native(&self) -> &metal::SamplerStateRef {
+ unsafe { metal::SamplerStateRef::from_ptr(self.as_ptr()) }
+ }
+}
+
+impl AsNative for ResourcePtr {
+ type Native = metal::ResourceRef;
+ #[inline]
+ fn from(native: &metal::ResourceRef) -> Self {
+ unsafe { NonNull::new_unchecked(native.as_ptr()) }
+ }
+ #[inline]
+ fn as_native(&self) -> &metal::ResourceRef {
+ unsafe { metal::ResourceRef::from_ptr(self.as_ptr()) }
+ }
+}