type DeviceResult<T> = Result<T, crate::DeviceError>;
/// True on arm64_32 (watchOS ILP32) targets. /// /// There are no Apple OSes that support both 32-bit applications and Metal, /// so `target_pointer_width = "32"` is a reliable proxy for ILP32 watchOS /// devices (Apple Watch S4–S9, SE, Ultra). Several AGXMetalS4 driver bugs /// require workarounds gated on this flag. const IS_WATCHOS_ILP32: bool = cfg!(target_pointer_width = "32");
/// Bindings of WGSL `storage` globals that contain variable-sized arrays. /// /// In order to implement bounds checks and the `arrayLength` function for /// WGSL runtime-sized arrays, we pass the entry point a struct with a /// member for each global variable that contains such an array. That member /// is a `u32` holding the variable's total size in bytes---which is simply /// the size of the `Buffer` supplying that variable's contents for the /// draw call.
sized_bindings: Vec<naga::ResourceBinding>,
let ep_index = module
.entry_points
.iter()
.position(|ep| ep.stage == naga_stage && ep.name == stage.entry_point)
.ok_or(crate::PipelineError::EntryPoint(naga_stage))?; let ep = &module.entry_points[ep_index]; let translated_ep_name = info.entry_point_names[0]
.as_ref()
.map_err(|e| crate::PipelineError::Linkage(stage_bit, format!("{e}")))?;
let wg_size = MTLSize {
width: ep.workgroup_size[0] as _,
height: ep.workgroup_size[1] as _,
depth: ep.workgroup_size[2] as _,
};
let function = library
.newFunctionWithName(&NSString::from_str(translated_ep_name))
.ok_or_else(|| {
log::error!("Function '{translated_ep_name}' does not exist"); crate::PipelineError::EntryPoint(naga_stage)
})?;
// collect sizes indices, immutable buffers, and work group memory sizes let ep_info = &module_info.get_entry_point(ep_index); letmut wg_memory_sizes = Vec::new(); letmut sized_bindings = Vec::new(); letmut immutable_buffer_mask = 0; for (var_handle, var) in module.global_variables.iter() { match var.space {
naga::AddressSpace::WorkGroup => { if !ep_info[var_handle].is_empty() { let size = module.types[var.ty].inner.size(module.to_ctx());
wg_memory_sizes.push(size);
}
}
naga::AddressSpace::Uniform | naga::AddressSpace::Storage { .. } => { let br = match var.binding {
Some(br) => br,
None => continue,
}; let storage_access_store = match var.space {
naga::AddressSpace::Storage { access } => {
access.contains(naga::StorageAccess::STORE)
}
_ => false,
};
// check for an immutable buffer if !ep_info[var_handle].is_empty() && !storage_access_store { let slot = ep_resources.resources[&br].buffer.unwrap();
immutable_buffer_mask |= 1 << slot;
}
let mtl_storage_mode = if desc.usage.contains(wgt::TextureUses::TRANSIENT)
&& self.shared.private_caps.supports_memoryless_storage
{
MTLStorageMode::Memoryless
} elseif IS_WATCHOS_ILP32 { // The AGXMetalS4 driver (A13/S6 GPU) crashes in // copyFromTexture:toBuffer: on Private textures — null deref at // offset 0x50 in the driver's internal texture state. Use Shared // storage which works correctly on Apple's unified memory // architecture and matches what native Swift Metal code uses on // these devices.
MTLStorageMode::Shared
} else {
MTLStorageMode::Private
};
descriptor.setTextureType(mtl_type); unsafe { descriptor.setWidth(desc.size.width as usize) }; unsafe { descriptor.setHeight(desc.size.height as usize) }; unsafe { descriptor.setMipmapLevelCount(desc.mip_level_count as usize) };
descriptor.setPixelFormat(mtl_format);
descriptor.setUsage(conv::map_texture_usage(desc.format, desc.usage));
descriptor.setStorageMode(mtl_storage_mode);
let raw = self
.shared
.device
.newTextureWithDescriptor(&descriptor)
.ok_or(crate::DeviceError::OutOfMemory)?; iflet Some(label) = desc.label {
raw.setLabel(Some(&NSString::from_str(label)));
}
let aspects = crate::FormatAspects::new(texture.format, desc.range.aspect);
let raw_format = self
.shared
.private_texture_format_caps
.map_view_format(desc.format, aspects);
let format_equal = raw_format
== self
.shared
.private_texture_format_caps
.map_format(texture.format); let type_equal = raw_type == texture.raw_type; let range_full_resource =
desc.range
.is_full_resource(desc.format, texture.mip_levels, texture.array_layers);
let raw = if format_equal && type_equal && range_full_resource { // Some images are marked as framebuffer-only, and we can't create aliases of them. // Also helps working around Metal bugs with aliased array textures.
texture.raw.to_owned()
} else { let mip_level_count = desc
.range
.mip_level_count
.unwrap_or(texture.mip_levels - desc.range.base_mip_level); let array_layer_count = desc
.range
.array_layer_count
.unwrap_or(texture.array_layers - desc.range.base_array_layer);
autoreleasepool(|_| { let level_range = NSRange {
location: desc.range.base_mip_level as _,
length: mip_level_count as _,
}; let slice_range = NSRange {
location: desc.range.base_array_layer as _,
length: array_layer_count as _,
}; let raw = unsafe {
texture
.raw
.newTextureViewWithPixelFormat_textureType_levels_slices(
raw_format,
raw_type,
level_range,
slice_range,
)
.unwrap()
}; iflet Some(label) = desc.label {
raw.setLabel(Some(&NSString::from_str(label)));
}
raw
})
};
// First, place the immediates for info in stage_data.iter_mut() {
info.pc_limit = desc.immediate_size;
// handle the immediate data buffer assignment and shader overrides if info.pc_limit != 0 {
info.pc_buffer = Some(info.counters.buffers);
info.counters.buffers += 1;
}
}
// Second, place the described resources for (group_index, bgl) in desc.bind_group_layouts.iter().enumerate() { let Some(bgl) = bgl else { continue;
};
// remember where the resources for this set start at each shader stage let base_resource_indices = stage_data.map_ref(|info| info.counters.clone());
for entry in bgl.entries.iter() { iflet wgt::BindingType::Buffer {
ty: wgt::BufferBindingType::Storage { .. },
..
} = entry.ty
{ for info in stage_data.iter_mut() { if entry.visibility.contains(map_naga_stage(info.stage)) {
info.need_sizes_buffer = true;
}
}
}
for info in stage_data.iter_mut() { if !entry.visibility.contains(map_naga_stage(info.stage)) { continue;
}
// Finally, make sure we fit the limits for info in stage_data.iter_mut() { if info.need_sizes_buffer || info.stage == naga::ShaderStage::Vertex { // Set aside space for the sizes_buffer, which is required // for variable-length buffers, or to support vertex pulling.
info.sizes_buffer = Some(info.counters.buffers);
info.counters.buffers += 1;
}
}
let per_stage_map = stage_data.map(|info| naga::back::msl::EntryPointResources {
immediates_buffer: info
.pc_buffer
.map(|buffer_index| buffer_index as naga::back::msl::Slot),
sizes_buffer: info
.sizes_buffer
.map(|buffer_index| buffer_index as naga::back::msl::Slot),
resources: info.resources,
});
unsafefn create_bind_group(
&self,
desc: &crate::BindGroupDescriptor< super::BindGroupLayout, super::Buffer, super::Sampler, super::TextureView, super::AccelerationStructure,
>,
) -> DeviceResult<super::BindGroup> {
autoreleasepool(|_| { letmut bg = super::BindGroup::default(); for (&stage, counter) insuper::NAGA_STAGES.iter().zip(bg.counters.iter_mut()) { let stage_bit = map_naga_stage(stage); letmut dynamic_offsets_count = 0u32; let layout_and_entry_iter = desc.entries.iter().map(|entry| { let layout = desc
.layout
.entries
.iter()
.find(|layout_entry| layout_entry.binding == entry.binding)
.expect("internal error: no layout entry found with binding slot");
(entry, layout)
}); for (entry, layout) in layout_and_entry_iter { // Bindless path if layout.count.is_some() { if !layout.visibility.contains(stage_bit) { continue;
}
let count = entry.count;
let stages = conv::map_render_stages(layout.visibility); let uses = conv::map_resource_usage(&layout.ty);
// Create argument buffer for this array let buffer = self
.shared
.device
.newBufferWithLength_options( 8 * count as usize,
MTLResourceOptions::HazardTrackingModeUntracked
| MTLResourceOptions::StorageModeShared,
)
.unwrap();
let contents: &mut [MTLResourceID] = unsafe {
core::slice::from_raw_parts_mut(
buffer.contents().cast().as_ptr(),
count as usize,
)
};
match layout.ty {
wgt::BindingType::Texture { .. }
| wgt::BindingType::StorageTexture { .. } => { let start = entry.resource_index as usize; let end = start + count as usize; let textures = &desc.textures[start..end];
for (idx, tex) in textures.iter().enumerate() {
contents[idx] = tex.view.raw.gpuResourceID();
let use_info = bg
.resources_to_use
.entry(tex.view.as_raw().cast())
.or_default();
use_info.stages |= stages;
use_info.uses |= uses;
use_info.visible_in_compute |=
layout.visibility.contains(wgt::ShaderStages::COMPUTE);
}
}
wgt::BindingType::Sampler { .. } => { let start = entry.resource_index as usize; let end = start + count as usize; let samplers = &desc.samplers[start..end];
for (idx, &sampler) in samplers.iter().enumerate() {
contents[idx] = sampler.raw.gpuResourceID(); // Samplers aren't resources like buffers and textures, so don't // need to be passed to useResource
}
}
wgt::BindingType::AccelerationStructure { .. } => { let start = entry.resource_index as usize; let end = start + count as usize; let acceleration_structures =
&desc.acceleration_structures[start..end];
for (idx, &acceleration_structure) in
acceleration_structures.iter().enumerate()
{
contents[idx] = acceleration_structure.raw.gpuResourceID();
// https://developer.apple.com/documentation/metal/mtlpipelinebufferdescriptor/mutability // Disabled on watchOS ILP32: the AGXMetalS4 driver exhibits instability // when mutability hints are combined with Shared storage mode textures. // Conservative disable until broader device coverage. let supports_mutability = !IS_WATCHOS_ILP32
&& available!(macos = 10.13, ios = 11.0, tvos = 11.0, visionos = 1.0);
let (primitive_class, raw_primitive_type) =
conv::map_primitive_topology(desc.primitive.topology);
let vs_info; let ts_info; let ms_info;
// Create the pipeline descriptor and do vertex/mesh pipeline specific setup let descriptor = match desc.vertex_processor { crate::VertexProcessor::Standard {
vertex_buffers, ref vertex_stage,
} => { // Vertex pipeline specific setup
let descriptor = MTLRenderPipelineDescriptor::new();
ts_info = None;
ms_info = None;
// Collect vertex buffer mappings letmut vertex_buffer_mappings =
Vec::<naga::back::msl::VertexBufferMapping>::new(); for (i, vbl) in vertex_buffers.iter().enumerate() { let Some(vbl) = vbl else { continue;
}; letmut attributes = Vec::<naga::back::msl::AttributeMapping>::new(); for attribute in vbl.attributes.iter() {
attributes.push(naga::back::msl::AttributeMapping {
shader_location: attribute.shader_location,
offset: attribute.offset as u32,
format: convert_vertex_format_to_naga(attribute.format),
});
}
// Set the pipeline vertex buffer info if !vertex_buffers.is_empty() { let vertex_descriptor = MTLVertexDescriptor::new(); for (i, vb) in vertex_buffers.iter().enumerate() { let Some(vb) = vb else { continue;
};
let buffer_index = VERTEX_BUFFER_SLOT_START as usize + i; let buffer_desc = unsafe {
vertex_descriptor
.layouts()
.objectAtIndexedSubscript(buffer_index)
};
// Metal expects the stride to be the actual size of the attributes. // The semantics of array_stride == 0 can be achieved by setting // the step function to constant and rate to 0. if vb.array_stride == 0 { let stride = vb
.attributes
.iter()
.map(|attribute| attribute.offset + attribute.format.size())
.max()
.unwrap_or(0); unsafe {
buffer_desc.setStride(wgt::math::align_to(
NSUInteger::try_from(stride).unwrap(), 4,
))
};
buffer_desc.setStepFunction(MTLVertexStepFunction::Constant); unsafe { buffer_desc.setStepRate(0) };
} else { unsafe { buffer_desc.setStride(vb.array_stride as _) };
buffer_desc.setStepFunction(conv::map_step_mode(vb.step_mode));
}
for at in vb.attributes { let attribute_desc = unsafe {
vertex_descriptor
.attributes()
.objectAtIndexedSubscript(at.shader_location as _)
};
attribute_desc.setFormat(conv::map_vertex_format(at.format)); unsafe { attribute_desc.setBufferIndex(buffer_index) }; unsafe { attribute_desc.setOffset(at.offset as _) };
}
}
descriptor.setVertexDescriptor(Some(&vertex_descriptor));
}
let raw_triangle_fill_mode = match desc.primitive.polygon_mode {
wgt::PolygonMode::Fill => MTLTriangleFillMode::Fill,
wgt::PolygonMode::Line => MTLTriangleFillMode::Lines,
wgt::PolygonMode::Point => panic!( "{:?} is not enabled for this backend",
wgt::Features::POLYGON_MODE_POINT
),
};
// Fragment shader let fs_info = match desc.fragment_stage {
Some(ref stage) => { let fs = self.load_shader(
stage,
&[],
desc.layout,
primitive_class,
naga::ShaderStage::Fragment,
)?;
Some(super::PipelineStageInfo {
immediates: desc.layout.immediates_infos.fs,
sizes_slot: desc.layout.per_stage_map.fs.sizes_buffer,
sized_bindings: fs.sized_bindings,
vertex_buffer_mappings: vec![],
library: Some(fs.library),
raw_wg_size: MTLSize {
width: 0,
height: 0,
depth: 0,
},
work_group_memory_sizes: vec![],
})
}
None => { // TODO: This is a workaround for what appears to be a Metal validation bug // A pixel format is required even though no attachments are provided if desc.color_targets.is_empty() && desc.depth_stencil.is_none() {
descriptor.setDepthAttachmentPixelFormat(MTLPixelFormat::Depth32Float);
}
None
}
};
// Setup pipeline color attachments for (i, ct) in desc.color_targets.iter().enumerate() { let at_descriptor = unsafe { descriptor.colorAttachments().objectAtIndexedSubscript(i) }; let ct = iflet Some(color_target) = ct.as_ref() {
color_target
} else {
at_descriptor.setPixelFormat(MTLPixelFormat::Invalid); continue;
};
let raw_format = self
.shared
.private_texture_format_caps
.map_format(ct.format);
at_descriptor.setPixelFormat(raw_format);
at_descriptor.setWriteMask(conv::map_color_write(ct.write_mask));
iflet Some(ref blend) = ct.blend {
at_descriptor.setBlendingEnabled(true); let (color_op, color_src, color_dst) = conv::map_blend_component(&blend.color); let (alpha_op, alpha_src, alpha_dst) = conv::map_blend_component(&blend.alpha);
unsafefn create_fence(&self) -> DeviceResult<super::Fence> { self.counters.fences.add(1); // https://developer.apple.com/documentation/metal/mtlsharedevent let shared_event = if available!(macos = 10.14, ios = 12.0, tvos = 12.0, visionos = 1.0) { self.shared.device.newSharedEvent() // This should be supported on said devices, but some sandbox environments may still restrict it, making it return `None`.
} else {
None
};
Ok(super::Fence {
completed_value: Arc::new(atomic::AtomicU64::new(0)),
pending_command_buffers: RwLock::new(Vec::new()),
shared_event,
})
}
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.