use objc2::rc::autoreleasepool; use objc2::runtime::{AnyObject, ProtocolObject, Sel}; use objc2::{available, sel}; use objc2_foundation::{NSOperatingSystemVersion, NSProcessInfo}; use objc2_metal::{
MTLArgumentBuffersTier, MTLCounterSamplingPoint, MTLDevice, MTLFeatureSet, MTLGPUFamily,
MTLIndirectAccelerationStructureInstanceDescriptor, MTLLanguageVersion, MTLPixelFormat,
MTLReadWriteTextureTier,
}; use wgt::{AstcBlock, AstcChannel};
use alloc::{string::ToString as _, sync::Arc, vec::Vec}; use core::sync::atomic;
usecrate::metal::QueueShared;
usesuper::{OsFeatures, TimestampQuerySupport};
/// Check if a device's class has a given method in its method table. /// /// This mirrors the check that `objc2` performs internally (in debug builds) /// before sending a message. We use it to skip method calls that would panic /// on proxy objects like Apple's `CaptureMTLDevice`, which forwards messages /// at runtime but doesn't declare the methods in its class. fn device_class_responds_to(device: &ProtocolObject<dyn MTLDevice>, sel: Sel) -> bool {
AnyObject::class(device.as_ref()).responds_to(sel)
}
/// Maximum number of command buffers for `MTLCommandQueue`s that we create. /// /// If a [new command buffer] is requested when Metal has run out of command /// buffers, it waits indefinitely for one to become available. If the /// outstanding command buffers are actively executing on the GPU, this will /// happen relatively quickly. But if the outstanding command buffers will only /// be recovered upon GC, and attempting to get a new command buffer prevents /// forward progress towards that GC, there is a deadlock. /// /// This is mostly a problem for the CTS, which frequently creates command /// buffers that it does not submit. It is unclear how likely command buffer /// exhaustion is in real applications. /// /// This limit was increased from a previous value of 2048 for /// <https://bugzilla.mozilla.org/show_bug.cgi?id=1971452>. /// /// [new command buffer]: https://developer.apple.com/documentation/metal/mtlcommandqueue/makecommandbuffer()?language=objc pub(super) const MAX_COMMAND_BUFFERS: usize = 4096;
// Metal has a single buffer limit that we must split across 3 WebGPU limits: // The Metal limit is: 31 "Maximum number of entries in the buffer argument table, per graphics or kernel function". // We must split it across: // - maxStorageBuffersPerShaderStage; must be at least 8 // - maxUniformBuffersPerShaderStage; must be at least 12 // - maxVertexBuffers; must be at least 8 // We require 2 additional internal buffers: // - one for immediate data // - one for sizes of other buffers // We use the last buffer for an acceleration structure. const MAX_STORAGE_BUFFERS_PER_SHADER_STAGE: u32 = 8; const MAX_UNIFORM_BUFFERS_PER_SHADER_STAGE: u32 = 12; const MAX_VERTEX_BUFFERS: u32 = 8; const MAX_ACCELERATION_STRUCTURES_PER_SHADER_STAGE: u32 = 1; // Use the end of the range for vertex buffers. pubconst VERTEX_BUFFER_SLOT_START: u32 = 31 - 8;
// Acquiring the meaning of timestamp ticks is hard with Metal! // The only thing there is a method correlating cpu & gpu timestamps (`device.sample_timestamps`). // Users are supposed to call this method twice and calculate the difference, // see "Converting GPU Timestamps into CPU Time": // https://developer.apple.com/documentation/metal/gpu_counters_and_counter_sample_buffers/converting_gpu_timestamps_into_cpu_time // Not only does this mean we get an approximate value, this is as also *very slow*! // Chromium opted to solve this using a linear regression that they stop at some point // https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:third_party/dawn/src/dawn/native/metal/DeviceMTL.mm;drc=76be2f9f117654f3fe4faa477b0445114fccedda;bpv=0;bpt=1;l=46 // Generally, the assumption is that timestamp values aren't changing over time, after all all other APIs provide stable values. // // We should do as Chromium does for the general case, but this requires quite some state tracking // and doesn't even provide perfectly accurate values, especially at the start of the application when // we didn't have the chance to sample a lot of values just yet. // // So instead, we're doing the dangerous but easy thing and use our "knowledge" of timestamps // conversions on different devices, after all Metal isn't supported on that many ;) // Based on: // * https://github.com/gfx-rs/wgpu/pull/2528 // * https://github.com/gpuweb/gpuweb/issues/1325#issuecomment-761041326 let timestamp_period = ifself.shared.device.name().to_string().starts_with("Intel") { 83.333
} else { // Known for Apple Silicon (at least M1 & M2, iPad Pro 2018) and AMD GPUs. 1.0
};
let msaa_resolve_desktop_if = if pc.msaa_desktop {
Tfc::MULTISAMPLE_RESOLVE
} else {
Tfc::empty()
}; let msaa_resolve_apple3x_if = if pc.msaa_desktop | pc.msaa_apple3 {
Tfc::MULTISAMPLE_RESOLVE
} else {
Tfc::empty()
}; let is_not_apple1x = super::CapabilitiesQuery::supports_any( self.shared.device.as_ref(),
&[
MTLFeatureSet::iOS_GPUFamily2_v1,
MTLFeatureSet::macOS_GPUFamily1_v1,
MTLFeatureSet::tvOS_GPUFamily1_v1,
],
);
let image_atomic_if = if msl_version >= MTLLanguageVersion::Version3_1 {
Tfc::STORAGE_ATOMIC
} else {
Tfc::empty()
};
let image_64_atomic_if = if pc.int64_atomics {
Tfc::STORAGE_ATOMIC
} else {
Tfc::empty()
};
// Metal defined pixel format capabilities let all_caps = Tfc::SAMPLED_LINEAR
| Tfc::STORAGE_WRITE_ONLY
| Tfc::COLOR_ATTACHMENT
| Tfc::COLOR_ATTACHMENT_BLEND
| msaa_count
| Tfc::MULTISAMPLE_RESOLVE;
Some(crate::SurfaceCapabilities {
formats, // We use this here to govern the maximum number of drawables + 1. // See https://developer.apple.com/documentation/quartzcore/cametallayer/maximumdrawablecount
maximum_frame_latency: if available!(
macos = 10.13.2,
ios = 11.2,
tvos = 11.2,
visionos = 1.0
) { 1..=2
} else { // 3 is the default value for maximum drawables in `CAMetalLayer` documentation // iOS 10.3 was tested to use 3 on iphone5s 2..=2
}, // We enable Immediate mode using `-[CAMetalLayer setDisplaySyncEnabled: false]`.
present_modes: if OsFeatures::display_sync() {
vec![wgt::PresentMode::Fifo, wgt::PresentMode::Immediate]
} else {
vec![wgt::PresentMode::Fifo]
},
composite_alpha_modes: vec![
wgt::CompositeAlphaMode::Opaque,
wgt::CompositeAlphaMode::PostMultiplied,
],
/// "Indirect draw & dispatch arguments" in the Metal feature set tables const INDIRECT_DRAW_DISPATCH_SUPPORT: &[MTLFeatureSet] = &[
MTLFeatureSet::iOS_GPUFamily3_v1,
MTLFeatureSet::tvOS_GPUFamily2_v1,
MTLFeatureSet::macOS_GPUFamily1_v1,
];
/// "Base vertex/instance drawing" in the Metal feature set tables /// /// in our terms, `base_vertex` and `first_instance` must be 0 const BASE_VERTEX_FIRST_INSTANCE_SUPPORT: &[MTLFeatureSet] = INDIRECT_DRAW_DISPATCH_SUPPORT;
/// Query the capabilities of the device. pubfn new(device: &ProtocolObject<dyn MTLDevice>) -> Self { // There are four different OSes we can target: macOS, iOS, tvOS and // visionOS. This can be detected using `cfg!(target_os = "ios")`, or // more conveniently using the `available!(...)` macro, which also // checks that the OS version that the binary is currently running on // is higher than or equal to the specified version. // // Along with the different OSes, there is also two other modes that // applications can run in: the Simulator, and Mac Catalyst. This can // be detected using `cfg!(target_env = "sim")` or // `cfg!(target_env = "macabi")`. // // Finally, iOS applications can be run on macOS and visionOS directly // using the "Designed for iPad" mode. This cannot be detected at // compile-time. // // All of this means that it only makes sense to use `cfg!(...)` and // `available!(...)` in here to check which Metal APIs are available; // we cannot rely on it for knowing properties of the device. For // that, we'll want to use `supportsFeatureSet` or `supportsFamily`. // // See the following link for further details: // https://developer.apple.com/documentation/metal/developing-metal-apps-that-run-in-simulator
let version = NSProcessInfo::processInfo().operatingSystemVersion(); let os_type = super::OsType::new(version, device);
let family_check = available!(macos = 10.15, ios = 13.0, tvos = 13.0, visionos = 1.0); let metal3 = family_check && device.supportsFamily(MTLGPUFamily::Metal3); let metal4 = family_check && device.supportsFamily(MTLGPUFamily::Metal4); letmut sample_count_mask = crate::TextureFormatCapabilities::MULTISAMPLE_X4; // 1 and 4 samples are supported on all devices if device.supportsTextureSampleCount(2) {
sample_count_mask |= crate::TextureFormatCapabilities::MULTISAMPLE_X2;
} if device.supportsTextureSampleCount(8) {
sample_count_mask |= crate::TextureFormatCapabilities::MULTISAMPLE_X8;
} if device.supportsTextureSampleCount(16) {
sample_count_mask |= crate::TextureFormatCapabilities::MULTISAMPLE_X16;
}
letmut timestamp_query_support = TimestampQuerySupport::empty(); if available!(macos = 11.0, ios = 14.0, tvos = 14.0, visionos = 1.0)
&& device.supportsCounterSampling(MTLCounterSamplingPoint::AtStageBoundary)
{ // If we don't support at stage boundary, don't support anything else.
timestamp_query_support.insert(TimestampQuerySupport::STAGE_BOUNDARIES);
if device.supportsCounterSampling(MTLCounterSamplingPoint::AtDrawBoundary) {
timestamp_query_support.insert(TimestampQuerySupport::ON_RENDER_ENCODER);
} if device.supportsCounterSampling(MTLCounterSamplingPoint::AtDispatchBoundary) {
timestamp_query_support.insert(TimestampQuerySupport::ON_COMPUTE_ENCODER);
} if device.supportsCounterSampling(MTLCounterSamplingPoint::AtBlitBoundary) {
timestamp_query_support.insert(TimestampQuerySupport::ON_BLIT_ENCODER);
} // `TimestampQuerySupport::INSIDE_WGPU_PASSES` emerges from the other flags.
}
let is_virtual = device.name().to_string().to_lowercase().contains("virtual");
let mesh_shaders = family_check
&& (device.supportsFamily(MTLGPUFamily::Metal3)
|| device.supportsFamily(MTLGPUFamily::Apple7)
|| device.supportsFamily(MTLGPUFamily::Mac2)) // Mesh shaders don't work on virtual devices even if they should be supported. CI thing
&& !is_virtual;
let limits = crate::auxil::adjust_raw_limits(wgt::Limits { // // WebGPU LIMITS: // Based on https://gpuweb.github.io/gpuweb/correspondence/#limits //
max_texture_dimension_1d: self.max_texture_size as u32,
max_texture_dimension_2d: self.max_texture_size as u32,
max_texture_dimension_3d: self.max_texture_3d_size as u32,
max_texture_array_layers: self.max_texture_layers as u32, // No limit.
max_bind_groups: u32::MAX, // No limit. Once we start using argument buffers we should set this appropriately.
max_bind_groups_plus_vertex_buffers: u32::MAX, // No limit.
max_bindings_per_bind_group: u32::MAX, // No limit, use maxUniformBuffersPerShaderStage.
max_dynamic_uniform_buffers_per_pipeline_layout: MAX_UNIFORM_BUFFERS_PER_SHADER_STAGE, // No limit, use maxStorageBuffersPerShaderStage.
max_dynamic_storage_buffers_per_pipeline_layout: MAX_STORAGE_BUFFERS_PER_SHADER_STAGE, // "Maximum number of entries in the sampler state argument table, per graphics or kernel function"
max_samplers_per_shader_stage: 16,
max_sampled_textures_per_shader_stage: self.max_textures_per_stage.0,
max_storage_textures_per_shader_stage: self.max_textures_per_stage.1,
max_storage_buffers_per_shader_stage: MAX_STORAGE_BUFFERS_PER_SHADER_STAGE,
max_uniform_buffers_per_shader_stage: MAX_UNIFORM_BUFFERS_PER_SHADER_STAGE,
max_vertex_buffers: MAX_VERTEX_BUFFERS,
max_buffer_size: self.max_buffer_size, // No limit, use maxBufferSize.
max_uniform_buffer_binding_size: self.max_buffer_size, // No limit, use maxBufferSize.
max_storage_buffer_binding_size: self.max_buffer_size,
min_uniform_buffer_offset_alignment: self.constant_buffer_offset_alignment, // No documented limit. Use 32, which is the lowest allowed value.
min_storage_buffer_offset_alignment: 32, // "Maximum number of vertex attributes, per vertex descriptor"
max_vertex_attributes: 31, // No documented limit, matches Vulkan's minimum limit and D3D12's static limit.
max_vertex_buffer_array_stride: 2048,
max_inter_stage_shader_variables: self.max_inter_stage_shader_variables,
max_color_attachments: self.max_color_render_targets as u32,
max_color_attachment_bytes_per_sample: self.max_color_attachment_bytes_per_sample as u32,
max_compute_workgroup_storage_size: self.max_total_threadgroup_memory,
max_compute_invocations_per_workgroup: self.max_threads_per_group,
max_compute_workgroup_size_x: self.max_threads_per_group,
max_compute_workgroup_size_y: self.max_threads_per_group,
max_compute_workgroup_size_z: self.max_threads_per_group, // No documented limit, matches Vulkan's minimum limit and D3D12's static limit.
max_compute_workgroups_per_dimension: 0xFFFF,
max_immediate_size: 0x1000, // // NATIVE (Non-WebGPU) LIMITS: //
max_non_sampler_bindings: u32::MAX,
// Should be not too large
max_task_workgroup_total_count: self.max_task_workgroup_count,
max_task_workgroups_per_dimension: self.max_task_workgroup_count,
max_mesh_workgroup_total_count: self.max_mesh_workgroup_count,
max_mesh_workgroups_per_dimension: self.max_mesh_workgroup_count,
max_task_invocations_per_workgroup: ifself.mesh_shaders { 1024 } else { 0 },
max_task_invocations_per_dimension: ifself.mesh_shaders { 1024 } else { 0 },
max_mesh_invocations_per_workgroup: ifself.mesh_shaders { 1024 } else { 0 },
max_mesh_invocations_per_dimension: ifself.mesh_shaders { 1024 } else { 0 }, // Using certain variables or debuggers can reduce the size by 32 bytes
max_task_payload_size: self.max_task_payload_size,
max_mesh_output_vertices: 256,
max_mesh_output_primitives: 256,
max_mesh_output_layers: self.max_texture_layers as u32,
max_mesh_multiview_view_count: 0,
});
crate::Capabilities {
limits,
alignments: crate::Alignments {
buffer_copy_offset: wgt::BufferSize::new(self.buffer_alignment).unwrap(),
buffer_copy_pitch: wgt::BufferSize::new(4).unwrap(), // This backend has Naga incorporate bounds checks into the // Metal Shading Language it generates, so from `wgpu_hal`'s // users' point of view, references are tightly checked.
uniform_bounds_check_alignment: wgt::BufferSize::new(1).unwrap(),
raw_tlas_instance_size: u32::try_from(size_of::<
MTLIndirectAccelerationStructureInstanceDescriptor,
>())
.unwrap(),
ray_tracing_scratch_buffer_alignment: 1,
},
downlevel,
cooperative_matrix_properties: self.cooperative_matrix_properties(),
}
}
/// Returns the supported cooperative matrix configurations for Metal. /// /// Metal's simdgroup_matrix supports 8x8 tiles with f16 and f32 element types. fn cooperative_matrix_properties(&self) -> Vec<wgt::CooperativeMatrixProperties> { if !self.supports_cooperative_matrix || self.msl_version < MTLLanguageVersion::Version2_3 { return Vec::new();
}
implsuper::OsType { fn new(version: NSOperatingSystemVersion, device: &ProtocolObject<dyn MTLDevice>) -> Self { // Metal was first introduced in OS X 10.11 and iOS 8. The current version number of visionOS is 1.0.0. Additionally, // on the Simulator, Apple only provides the Apple2 GPU capability, and the Apple2+ GPU capability covers the capabilities of Apple2. // Therefore, the following conditions can be used to determine if it is visionOS. // https://developer.apple.com/documentation/metal/developing_metal_apps_that_run_in_simulator let os_is_vision = version.majorVersion < 8 && device.supportsFamily(MTLGPUFamily::Apple2); let os_is_mac = device.supportsFeatureSet(MTLFeatureSet::macOS_GPUFamily1_v1); let os_is_tvos = device.supportsFeatureSet(MTLFeatureSet::tvOS_GPUFamily1_v1); if os_is_vision { Self::VisionOs
} elseif os_is_mac { Self::Macos
} elseif os_is_tvos { Self::Tvos
} else { Self::Ios
}
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.26 Sekunden
(vorverarbeitet am 2026-08-26)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.