use crate::foundation::{Error, metal_error};
use block2::RcBlock;
use objc2::rc::Retained;
use objc2::runtime::{AnyObject, Sel};
use objc2::{msg_send, sel};
use objc2_foundation::{NSRange, NSString};
use objc2_metal::{
MTL4BufferRange, MTL4CopySparseBufferMappingOperation, MTL4CopySparseTextureMappingOperation,
MTL4UpdateSparseBufferMappingOperation, MTL4UpdateSparseTextureMappingOperation, MTLBlitOption,
MTLBuffer, MTLDevice, MTLIndexType, MTLScissorRect, MTLSize, MTLSparseTextureMappingMode,
MTLVertexAmplificationViewMapping, MTLViewport,
};
use super::generated_object_types::{metal, metal4, metal4_fx};
use super::generated_struct_types::{
CopySparseBufferMappingOperation, CopySparseTextureMappingOperation,
UpdateSparseBufferMappingOperation, UpdateSparseTextureMappingOperation,
};
use super::generated_struct_types::{ScissorRect, VertexAmplificationViewMapping};
use super::generated_value_types::{
AccelerationStructureRefitOptions, BlitOption, CullMode, DepthClipMode, IndexType,
RenderEncoderOptions, RenderStages, Stages, StoreAction, TensorPlaneType, TimestampGranularity,
TriangleFillMode, VisibilityOptions, VisibilityResultMode, Winding,
};
use super::{Origin, PrimitiveType, Region, Size, Viewport};
use std::ptr::NonNull;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Condvar, Mutex};
static NEXT_MTL4_SUBMISSION_ID: AtomicU64 = AtomicU64::new(1);
type CompletionState = Arc<(Mutex<Option<Result<(), Error>>>, Condvar)>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TextureSubresourceSpan {
pub slice: usize,
pub level: usize,
pub slice_count: usize,
pub level_count: usize,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TextureRegionSelection {
pub slice: usize,
pub level: usize,
pub region: Region,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TextureOriginSelection {
pub slice: usize,
pub level: usize,
pub origin: Origin,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BufferImageLayout {
pub offset: usize,
pub bytes_per_row: usize,
pub bytes_per_image: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct IndexedDraw {
pub index_count: usize,
pub index_type: IndexType,
pub index_range: std::ops::Range<usize>,
pub instance_count: usize,
pub base_vertex: isize,
pub base_instance: usize,
}
pub struct TensorCopyRegion<'a> {
pub source_origin: &'a super::CheckedTensorExtents,
pub source_dimensions: &'a super::CheckedTensorExtents,
pub destination_origin: &'a super::CheckedTensorExtents,
pub destination_dimensions: &'a super::CheckedTensorExtents,
}
fn checked_buffer_address(
buffer: &super::Buffer,
offset: usize,
required: usize,
alignment: usize,
operation: &str,
) -> Result<u64, Error> {
let end = offset
.checked_add(required)
.ok_or_else(|| Error::invalid_argument(format!("{operation} buffer range overflows")))?;
if end > buffer.length() || alignment == 0 || !offset.is_multiple_of(alignment) {
return Err(Error::invalid_argument(format!(
"{operation} requires an in-bounds {alignment}-byte aligned buffer range"
)));
}
buffer
.inner
.gpuAddress()
.checked_add(offset as u64)
.ok_or_else(|| Error::invalid_argument(format!("{operation} GPU address overflows")))
}
fn checked_mtl4_buffer_range(
buffer: &super::Buffer,
range: std::ops::Range<usize>,
operation: &str,
) -> Result<MTL4BufferRange, Error> {
let range = checked_range(buffer.length(), range, operation)?;
let address = checked_buffer_address(buffer, range.location, range.length, 1, operation)?;
Ok(MTL4BufferRange {
bufferAddress: address,
length: range.length as u64,
})
}
fn checked_range(
total: usize,
range: std::ops::Range<usize>,
operation: &str,
) -> Result<NSRange, Error> {
if range.start > range.end || range.end > total {
return Err(Error::invalid_argument(format!(
"{operation} range is out of bounds"
)));
}
Ok(NSRange::new(range.start, range.len()))
}
fn submission_contains(submission_ids: &[u64], readback_id: u64) -> bool {
submission_ids.contains(&readback_id)
}
fn checked_index_type(value: IndexType, operation: &str) -> Result<(MTLIndexType, usize), Error> {
match value.as_raw() {
0 => Ok((MTLIndexType::UInt16, 2)),
1 => Ok((MTLIndexType::UInt32, 4)),
_ => Err(Error::invalid_argument(format!(
"{operation} received an invalid index type"
))),
}
}
fn validate_texture_region(
texture: &super::Texture,
slice: usize,
level: usize,
region: Region,
operation: &str,
) -> Result<(objc2_metal::MTLOrigin, MTLSize), Error> {
let (depth, slices, levels, _) = texture.layout();
let end_x = region.origin.x.checked_add(region.size.width);
let end_y = region.origin.y.checked_add(region.size.height);
let end_z = region.origin.z.checked_add(region.size.depth);
if region.size.width == 0
|| region.size.height == 0
|| region.size.depth == 0
|| slice >= slices
|| level >= levels
|| end_x.filter(|end| *end <= texture.width()).is_none()
|| end_y.filter(|end| *end <= texture.height()).is_none()
|| end_z.filter(|end| *end <= depth).is_none()
{
return Err(Error::invalid_argument(format!(
"{operation} texture region is out of bounds"
)));
}
Ok((region.origin.into(), region.size.into()))
}
fn validate_image_buffer_range(
buffer: &super::Buffer,
offset: usize,
bytes_per_row: usize,
bytes_per_image: usize,
size: Size,
operation: &str,
) -> Result<(), Error> {
if bytes_per_row == 0 || (size.depth > 1 && bytes_per_image == 0) {
return Err(Error::invalid_argument(format!(
"{operation} requires non-zero image strides"
)));
}
let rows = size
.height
.saturating_sub(1)
.checked_mul(bytes_per_row)
.ok_or_else(|| Error::invalid_argument(format!("{operation} row stride overflows")))?;
let images = size
.depth
.saturating_sub(1)
.checked_mul(bytes_per_image)
.ok_or_else(|| Error::invalid_argument(format!("{operation} image stride overflows")))?;
let minimum = rows
.checked_add(images)
.and_then(|value| value.checked_add(bytes_per_row))
.ok_or_else(|| Error::invalid_argument(format!("{operation} buffer range overflows")))?;
offset
.checked_add(minimum)
.filter(|end| *end <= buffer.length())
.ok_or_else(|| {
Error::invalid_argument(format!("{operation} buffer range is out of bounds"))
})?;
Ok(())
}
fn require_selector(object: &AnyObject, selector: Sel, operation: &str) -> Result<(), Error> {
let supported: bool = unsafe { msg_send![object, respondsToSelector: selector] };
if supported {
Ok(())
} else {
Err(Error::unsupported(format!(
"{operation} is unavailable on this system"
)))
}
}
fn required_object(
value: Option<Retained<AnyObject>>,
operation: &str,
) -> Result<Retained<AnyObject>, Error> {
value.ok_or_else(|| Error::unsupported(format!("{operation} returned nil")))
}
fn validate_size(size: Size, operation: &str) -> Result<MTLSize, Error> {
if size.width == 0 || size.height == 0 || size.depth == 0 {
return Err(Error::invalid_argument(format!(
"{operation} requires non-zero dimensions"
)));
}
Ok(MTLSize {
width: size.width,
height: size.height,
depth: size.depth,
})
}
fn validate_viewport(viewport: Viewport) -> Result<MTLViewport, Error> {
let values = [
viewport.origin_x,
viewport.origin_y,
viewport.width,
viewport.height,
viewport.z_near,
viewport.z_far,
];
if !values.iter().all(|value| value.is_finite()) {
return Err(Error::invalid_argument(
"MTL4::RenderCommandEncoder::setViewport requires finite values",
));
}
if viewport.width < 0.0 || viewport.height < 0.0 || viewport.z_near > viewport.z_far {
return Err(Error::invalid_argument(
"MTL4::RenderCommandEncoder::setViewport received invalid bounds",
));
}
Ok(MTLViewport {
originX: viewport.origin_x,
originY: viewport.origin_y,
width: viewport.width,
height: viewport.height,
znear: viewport.z_near,
zfar: viewport.z_far,
})
}
fn push_debug_group(object: &AnyObject, value: &str, operation: &str) -> Result<(), Error> {
require_selector(object, sel!(pushDebugGroup:), operation)?;
let value = NSString::from_str(value);
unsafe { msg_send![object, pushDebugGroup: &*value] }
Ok(())
}
fn object_label(object: &AnyObject, operation: &str) -> Result<Option<String>, Error> {
require_selector(object, sel!(label), operation)?;
let value: Option<Retained<NSString>> = unsafe { msg_send![object, label] };
Ok(value.map(|value| value.to_string()))
}
fn set_object_label(object: &AnyObject, value: Option<&str>, operation: &str) -> Result<(), Error> {
require_selector(object, sel!(setLabel:), operation)?;
let value = value.map(NSString::from_str);
unsafe { msg_send![object, setLabel: value.as_deref()] }
Ok(())
}
fn end_encoding(object: &AnyObject, operation: &str) -> Result<(), Error> {
require_selector(object, sel!(endEncoding), operation)?;
unsafe { msg_send![object, endEncoding] }
Ok(())
}
impl super::Device {
pub fn new_mtl4_command_buffer(&self) -> Result<AvailableCommandBuffer, Error> {
require_selector(
self.as_any_object(),
sel!(newCommandBuffer),
"MTL::Device::newCommandBuffer(Metal4)",
)?;
let inner = self.inner.newCommandBuffer().ok_or_else(|| {
Error::unsupported("MTL::Device::newCommandBuffer(Metal4) returned nil")
})?;
let inner = unsafe { Retained::cast_unchecked(inner) };
Ok(AvailableCommandBuffer::from_generated(
metal4::CommandBuffer::from_inner(inner),
NEXT_MTL4_SUBMISSION_ID.fetch_add(1, Ordering::Relaxed),
))
}
pub fn new_mtl4_command_queue(&self) -> Result<Metal4CommandQueue, Error> {
require_selector(
self.as_any_object(),
sel!(newMTL4CommandQueue),
"MTL::Device::newMTL4CommandQueue",
)?;
let inner = self
.inner
.newMTL4CommandQueue()
.ok_or_else(|| Error::unsupported("MTL::Device::newMTL4CommandQueue returned nil"))?;
let inner = unsafe { Retained::cast_unchecked(inner) };
Ok(Metal4CommandQueue {
inner: metal4::CommandQueue::from_inner(inner),
})
}
}
impl metal4::CommandBuffer {
pub(crate) fn begin(
&self,
allocator: &metal4::CommandAllocator,
options: Option<&metal4::CommandBufferOptions>,
) -> Result<(), Error> {
let object = self.as_inner();
if let Some(options) = options {
require_selector(
object,
sel!(beginCommandBufferWithAllocator:options:),
"MTL4::CommandBuffer::beginCommandBuffer(options)",
)?;
unsafe {
msg_send![object,
beginCommandBufferWithAllocator: allocator.as_inner(),
options: options.as_inner()
]
}
} else {
require_selector(
object,
sel!(beginCommandBufferWithAllocator:),
"MTL4::CommandBuffer::beginCommandBuffer",
)?;
unsafe { msg_send![object, beginCommandBufferWithAllocator: allocator.as_inner()] }
}
Ok(())
}
pub(crate) fn compute_encoder(&self) -> Result<metal4::ComputeCommandEncoder, Error> {
let object = self.as_inner();
require_selector(
object,
sel!(computeCommandEncoder),
"MTL4::CommandBuffer::computeCommandEncoder",
)?;
let value = unsafe { msg_send![object, computeCommandEncoder] };
Ok(metal4::ComputeCommandEncoder::from_inner(required_object(
value,
"MTL4::CommandBuffer::computeCommandEncoder",
)?))
}
pub(crate) fn machine_learning_encoder(
&self,
) -> Result<metal4::MachineLearningCommandEncoder, Error> {
let object = self.as_inner();
require_selector(
object,
sel!(machineLearningCommandEncoder),
"MTL4::CommandBuffer::machineLearningCommandEncoder",
)?;
let value = unsafe { msg_send![object, machineLearningCommandEncoder] };
Ok(metal4::MachineLearningCommandEncoder::from_inner(
required_object(value, "MTL4::CommandBuffer::machineLearningCommandEncoder")?,
))
}
pub(crate) fn render_encoder(
&self,
descriptor: &metal4::RenderPassDescriptor,
) -> Result<metal4::RenderCommandEncoder, Error> {
let object = self.as_inner();
require_selector(
object,
sel!(renderCommandEncoderWithDescriptor:),
"MTL4::CommandBuffer::renderCommandEncoder",
)?;
let value =
unsafe { msg_send![object, renderCommandEncoderWithDescriptor: descriptor.as_inner()] };
Ok(metal4::RenderCommandEncoder::from_inner(required_object(
value,
"MTL4::CommandBuffer::renderCommandEncoder",
)?))
}
pub(crate) fn render_encoder_with_options(
&self,
descriptor: &metal4::RenderPassDescriptor,
options: RenderEncoderOptions,
) -> Result<metal4::RenderCommandEncoder, Error> {
if !options.is_valid() {
return Err(Error::invalid_argument(
"invalid render encoder option bits",
));
}
let object = self.as_inner();
require_selector(
object,
sel!(renderCommandEncoderWithDescriptor:options:),
"MTL4::CommandBuffer::renderCommandEncoder(options)",
)?;
let value = unsafe {
msg_send![object,
renderCommandEncoderWithDescriptor: descriptor.as_inner(),
options: options.as_raw()
]
};
Ok(metal4::RenderCommandEncoder::from_inner(required_object(
value,
"MTL4::CommandBuffer::renderCommandEncoder(options)",
)?))
}
pub(crate) fn use_residency_set(&self, set: &metal::ResidencySet) -> Result<(), Error> {
let object = self.as_inner();
require_selector(
object,
sel!(useResidencySet:),
"MTL4::CommandBuffer::useResidencySet",
)?;
unsafe { msg_send![object, useResidencySet: set.as_inner()] }
Ok(())
}
pub(crate) fn use_residency_sets(&self, sets: &[&metal::ResidencySet]) -> Result<(), Error> {
if sets.is_empty() {
return Err(Error::invalid_argument(
"residency set slice must not be empty",
));
}
let objects: Vec<*const AnyObject> = sets
.iter()
.map(|set| set.as_inner() as *const AnyObject)
.collect();
let object = self.as_inner();
require_selector(
object,
sel!(useResidencySets:count:),
"MTL4::CommandBuffer::useResidencySets",
)?;
unsafe { msg_send![object, useResidencySets: objects.as_ptr(), count: objects.len()] }
Ok(())
}
pub(crate) fn push_debug_group(&self, name: &str) -> Result<(), Error> {
push_debug_group(self.as_inner(), name, "MTL4::CommandBuffer::pushDebugGroup")
}
pub(crate) fn pop_debug_group(&self) -> Result<(), Error> {
let object = self.as_inner();
require_selector(
object,
sel!(popDebugGroup),
"MTL4::CommandBuffer::popDebugGroup",
)?;
unsafe { msg_send![object, popDebugGroup] }
Ok(())
}
pub(crate) fn end(&self) -> Result<(), Error> {
let object = self.as_inner();
require_selector(
object,
sel!(endCommandBuffer),
"MTL4::CommandBuffer::endCommandBuffer",
)?;
unsafe { msg_send![object, endCommandBuffer] }
Ok(())
}
}
pub struct AvailableCommandBuffer {
inner: metal4::CommandBuffer,
submission_id: u64,
}
impl AvailableCommandBuffer {
pub(crate) const fn from_generated(inner: metal4::CommandBuffer, submission_id: u64) -> Self {
Self {
inner,
submission_id,
}
}
pub fn begin(
self,
allocator: &metal4::CommandAllocator,
options: Option<&metal4::CommandBufferOptions>,
) -> Result<RecordingCommandBuffer, Error> {
self.inner.begin(allocator, options)?;
Ok(RecordingCommandBuffer {
inner: self.inner,
submission_id: self.submission_id,
active_encoder: false,
})
}
pub fn device(&self) -> Result<crate::Device, Error> {
self.inner.device()?.ok_or_else(|| {
Error::unsupported("MTL4::CommandBuffer::device unexpectedly returned nil")
})
}
pub fn label(&self) -> Result<Option<String>, Error> {
object_label(self.inner.as_inner(), "MTL4::CommandBuffer::label")
}
pub fn set_label(&self, label: Option<&str>) -> Result<(), Error> {
set_object_label(
self.inner.as_inner(),
label,
"MTL4::CommandBuffer::setLabel",
)
}
}
pub struct RecordingCommandBuffer {
pub(crate) inner: metal4::CommandBuffer,
submission_id: u64,
active_encoder: bool,
}
impl RecordingCommandBuffer {
#[doc(hidden)]
pub fn encode_frame_interpolator(
&mut self,
effect: &metal4_fx::FrameInterpolator,
) -> Result<(), Error> {
effect.encode_to_metal4_command_buffer(self)
}
#[doc(hidden)]
pub fn encode_spatial_scaler(
&mut self,
effect: &metal4_fx::SpatialScaler,
) -> Result<(), Error> {
effect.encode_to_metal4_command_buffer(self)
}
#[doc(hidden)]
pub fn encode_temporal_denoised_scaler(
&mut self,
effect: &metal4_fx::TemporalDenoisedScaler,
) -> Result<(), Error> {
effect.encode_to_metal4_command_buffer(self)
}
#[doc(hidden)]
pub fn encode_temporal_scaler(
&mut self,
effect: &metal4_fx::TemporalScaler,
) -> Result<(), Error> {
effect.encode_to_metal4_command_buffer(self)
}
pub fn device(&self) -> Result<crate::Device, Error> {
self.inner.device()?.ok_or_else(|| {
Error::unsupported("MTL4::CommandBuffer::device unexpectedly returned nil")
})
}
pub fn label(&self) -> Result<Option<String>, Error> {
object_label(self.inner.as_inner(), "MTL4::CommandBuffer::label")
}
pub fn set_label(&self, label: Option<&str>) -> Result<(), Error> {
set_object_label(
self.inner.as_inner(),
label,
"MTL4::CommandBuffer::setLabel",
)
}
pub fn compute_encoder(&mut self) -> Result<RecordingComputeEncoder<'_>, Error> {
if self.active_encoder {
return Err(Error::invalid_argument(
"a Metal 4 encoder is already active",
));
}
let inner = self.inner.compute_encoder()?;
require_selector(
inner.as_inner(),
sel!(endEncoding),
"MTL4::ComputeCommandEncoder::endEncoding",
)?;
self.active_encoder = true;
Ok(RecordingComputeEncoder {
inner,
recording: self,
ended: false,
})
}
pub fn render_encoder(
&mut self,
descriptor: &metal4::RenderPassDescriptor,
options: Option<RenderEncoderOptions>,
) -> Result<RecordingRenderEncoder<'_>, Error> {
if self.active_encoder {
return Err(Error::invalid_argument(
"a Metal 4 encoder is already active",
));
}
let inner = if let Some(options) = options {
self.inner
.render_encoder_with_options(descriptor, options)?
} else {
self.inner.render_encoder(descriptor)?
};
require_selector(
inner.as_inner(),
sel!(endEncoding),
"MTL4::RenderCommandEncoder::endEncoding",
)?;
self.active_encoder = true;
Ok(RecordingRenderEncoder {
inner,
recording: self,
ended: false,
})
}
pub fn machine_learning_encoder(
&mut self,
) -> Result<RecordingMachineLearningEncoder<'_>, Error> {
if self.active_encoder {
return Err(Error::invalid_argument(
"a Metal 4 encoder is already active",
));
}
let inner = self.inner.machine_learning_encoder()?;
require_selector(
inner.as_inner(),
sel!(endEncoding),
"MTL4::MachineLearningCommandEncoder::endEncoding",
)?;
self.active_encoder = true;
Ok(RecordingMachineLearningEncoder {
inner,
recording: self,
ended: false,
})
}
pub fn use_residency_set(&self, set: &metal::ResidencySet) -> Result<(), Error> {
self.inner.use_residency_set(set)
}
pub fn use_residency_sets(&self, sets: &[&metal::ResidencySet]) -> Result<(), Error> {
self.inner.use_residency_sets(sets)
}
pub fn push_debug_group(&self, name: &str) -> Result<(), Error> {
self.inner.push_debug_group(name)
}
pub fn pop_debug_group(&self) -> Result<(), Error> {
self.inner.pop_debug_group()
}
pub fn resolve_counter_heap(
&self,
heap: super::TimestampCounterHeap,
range: std::ops::Range<usize>,
) -> Result<super::mtl4_counters::CounterReadback, Error> {
let range = super::mtl4_counters::checked_range(
range,
heap.count(),
"MTL4::CommandBuffer::resolveCounterHeap",
)?;
if range.length == 0 {
return Err(Error::invalid_argument(
"counter readback range must not be empty",
));
}
let byte_length = range
.length
.checked_mul(std::mem::size_of::<u64>())
.ok_or_else(|| Error::invalid_argument("counter readback size overflows"))?;
let staging = self
.device()?
.new_buffer(byte_length, super::ResourceOptions::SHARED)?;
let buffer_range = MTL4BufferRange {
bufferAddress: staging.inner.gpuAddress(),
length: byte_length as u64,
};
let operation = "MTL4::CommandBuffer::resolveCounterHeap";
require_selector(
self.inner.as_inner(),
sel!(resolveCounterHeap:withRange:intoBuffer:waitFence:updateFence:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), resolveCounterHeap: heap.as_any_object(), withRange: range, intoBuffer: buffer_range, waitFence: Option::<&AnyObject>::None, updateFence: Option::<&AnyObject>::None]
}
Ok(super::mtl4_counters::CounterReadback {
heap,
buffer: staging,
submission_id: self.submission_id,
count: range.length,
})
}
pub fn write_timestamp_into_heap(
&self,
heap: &super::TimestampCounterHeap,
index: usize,
) -> Result<(), Error> {
let operation = "MTL4::CommandBuffer::writeTimestampIntoHeap";
if index >= heap.count() {
return Err(Error::invalid_argument(format!(
"{operation} index is out of bounds"
)));
}
require_selector(
self.inner.as_inner(),
sel!(writeTimestampIntoHeap:atIndex:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), writeTimestampIntoHeap: heap.as_any_object(), atIndex: index]
}
Ok(())
}
pub fn end(self) -> Result<EndedCommandBuffer, Error> {
if self.active_encoder {
return Err(Error::invalid_argument(
"cannot end a Metal 4 command buffer while an encoder is active",
));
}
self.inner.end()?;
Ok(EndedCommandBuffer {
inner: self.inner,
submission_id: self.submission_id,
})
}
}
pub struct EndedCommandBuffer {
inner: metal4::CommandBuffer,
submission_id: u64,
}
pub struct RecordingComputeEncoder<'a> {
inner: metal4::ComputeCommandEncoder,
recording: &'a mut RecordingCommandBuffer,
ended: bool,
}
impl RecordingComputeEncoder<'_> {
pub fn dispatch_threadgroups(&self, groups: Size, threads: Size) -> Result<(), Error> {
self.inner.dispatch_threadgroups(groups, threads)
}
pub fn dispatch_threads(&self, grid: Size, threads: Size) -> Result<(), Error> {
self.inner.dispatch_threads(grid, threads)
}
pub fn dispatch_threadgroups_indirect(
&self,
buffer: &super::Buffer,
offset: usize,
threads: Size,
) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::dispatchThreadgroups(indirect)";
let address = checked_buffer_address(buffer, offset, 12, 4, operation)?;
let threads = validate_size(threads, operation)?;
require_selector(
self.inner.as_inner(),
sel!(dispatchThreadgroupsWithIndirectBuffer:threadsPerThreadgroup:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), dispatchThreadgroupsWithIndirectBuffer: address, threadsPerThreadgroup: threads]
}
Ok(())
}
pub fn dispatch_threads_indirect(
&self,
buffer: &super::Buffer,
offset: usize,
) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::dispatchThreads(indirect)";
let address = checked_buffer_address(buffer, offset, 12, 4, operation)?;
require_selector(
self.inner.as_inner(),
sel!(dispatchThreadsWithIndirectBuffer:),
operation,
)?;
unsafe { msg_send![self.inner.as_inner(), dispatchThreadsWithIndirectBuffer: address] }
Ok(())
}
pub fn execute_commands(
&self,
commands: &metal::IndirectCommandBuffer,
range: std::ops::Range<usize>,
) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::executeCommandsInBuffer(range)";
let range = checked_range(commands.size()?, range, operation)?;
require_selector(
self.inner.as_inner(),
sel!(executeCommandsInBuffer:withRange:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), executeCommandsInBuffer: commands.as_inner(), withRange: range]
}
Ok(())
}
pub fn execute_commands_indirect(
&self,
commands: &metal::IndirectCommandBuffer,
range_buffer: &super::Buffer,
offset: usize,
) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::executeCommandsInBuffer(indirect)";
let address = checked_buffer_address(range_buffer, offset, 16, 4, operation)?;
require_selector(
self.inner.as_inner(),
sel!(executeCommandsInBuffer:indirectBuffer:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), executeCommandsInBuffer: commands.as_inner(), indirectBuffer: address]
}
Ok(())
}
pub fn fill_buffer(
&self,
buffer: &super::Buffer,
range: std::ops::Range<usize>,
value: u8,
) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::fillBuffer";
let range = checked_range(buffer.length(), range, operation)?;
require_selector(
self.inner.as_inner(),
sel!(fillBuffer:range:value:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), fillBuffer: buffer.as_any_object(), range: range, value: value]
}
Ok(())
}
pub fn copy_buffer(
&self,
source: &super::Buffer,
source_range: std::ops::Range<usize>,
destination: &super::Buffer,
destination_offset: usize,
) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::copyFromBuffer(toBuffer)";
let source_range = checked_range(source.length(), source_range, operation)?;
destination_offset
.checked_add(source_range.length)
.filter(|end| *end <= destination.length())
.ok_or_else(|| {
Error::invalid_argument(format!("{operation} destination range is out of bounds"))
})?;
require_selector(
self.inner.as_inner(),
sel!(copyFromBuffer:sourceOffset:toBuffer:destinationOffset:size:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), copyFromBuffer: source.as_any_object(), sourceOffset: source_range.location, toBuffer: destination.as_any_object(), destinationOffset: destination_offset, size: source_range.length]
}
Ok(())
}
pub fn generate_mipmaps(&self, texture: &super::Texture) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::generateMipmapsForTexture";
require_selector(
self.inner.as_inner(),
sel!(generateMipmapsForTexture:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), generateMipmapsForTexture: texture.as_any_object()]
}
Ok(())
}
pub fn copy_texture(
&self,
source: &super::Texture,
destination: &super::Texture,
) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::copyFromTexture(toTexture)";
require_selector(
self.inner.as_inner(),
sel!(copyFromTexture:toTexture:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), copyFromTexture: source.as_any_object(), toTexture: destination.as_any_object()]
}
Ok(())
}
pub fn copy_texture_levels(
&self,
source: &super::Texture,
source_span: TextureSubresourceSpan,
destination: &super::Texture,
destination_slice: usize,
destination_level: usize,
) -> Result<(), Error> {
let (_, source_slices, source_levels, _) = source.layout();
let (_, destination_slices, destination_levels, _) = destination.layout();
if source_span.slice_count == 0
|| source_span.level_count == 0
|| source_span
.slice
.checked_add(source_span.slice_count)
.filter(|end| *end <= source_slices)
.is_none()
|| destination_slice
.checked_add(source_span.slice_count)
.filter(|end| *end <= destination_slices)
.is_none()
|| source_span
.level
.checked_add(source_span.level_count)
.filter(|end| *end <= source_levels)
.is_none()
|| destination_level
.checked_add(source_span.level_count)
.filter(|end| *end <= destination_levels)
.is_none()
{
return Err(Error::invalid_argument(
"texture copy slice or level span is out of bounds",
));
}
let operation = "MTL4::ComputeCommandEncoder::copyFromTexture(slices)";
require_selector(
self.inner.as_inner(),
sel!(copyFromTexture:sourceSlice:sourceLevel:toTexture:destinationSlice:destinationLevel:sliceCount:levelCount:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), copyFromTexture: source.as_any_object(), sourceSlice: source_span.slice, sourceLevel: source_span.level, toTexture: destination.as_any_object(), destinationSlice: destination_slice, destinationLevel: destination_level, sliceCount: source_span.slice_count, levelCount: source_span.level_count]
}
Ok(())
}
pub fn copy_texture_region(
&self,
source: &super::Texture,
source_region: TextureRegionSelection,
destination: &super::Texture,
destination_origin: TextureOriginSelection,
) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::copyFromTexture(region)";
let (source_origin, source_size) = validate_texture_region(
source,
source_region.slice,
source_region.level,
source_region.region,
operation,
)?;
let destination_region = Region {
origin: destination_origin.origin,
size: source_region.region.size,
};
validate_texture_region(
destination,
destination_origin.slice,
destination_origin.level,
destination_region,
operation,
)?;
require_selector(
self.inner.as_inner(),
sel!(copyFromTexture:sourceSlice:sourceLevel:sourceOrigin:sourceSize:toTexture:destinationSlice:destinationLevel:destinationOrigin:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), copyFromTexture: source.as_any_object(), sourceSlice: source_region.slice, sourceLevel: source_region.level, sourceOrigin: source_origin, sourceSize: source_size, toTexture: destination.as_any_object(), destinationSlice: destination_origin.slice, destinationLevel: destination_origin.level, destinationOrigin: objc2_metal::MTLOrigin::from(destination_origin.origin)]
}
Ok(())
}
pub fn copy_texture_to_buffer(
&self,
source: &super::Texture,
source_region: TextureRegionSelection,
destination: &super::Buffer,
layout: BufferImageLayout,
options: Option<BlitOption>,
) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::copyFromTexture(toBuffer)";
let (origin, size) = validate_texture_region(
source,
source_region.slice,
source_region.level,
source_region.region,
operation,
)?;
validate_image_buffer_range(
destination,
layout.offset,
layout.bytes_per_row,
layout.bytes_per_image,
source_region.region.size,
operation,
)?;
if let Some(options) = options {
if !options.is_valid() {
return Err(Error::invalid_argument("invalid blit option bits"));
}
require_selector(
self.inner.as_inner(),
sel!(copyFromTexture:sourceSlice:sourceLevel:sourceOrigin:sourceSize:toBuffer:destinationOffset:destinationBytesPerRow:destinationBytesPerImage:options:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), copyFromTexture: source.as_any_object(), sourceSlice: source_region.slice, sourceLevel: source_region.level, sourceOrigin: origin, sourceSize: size, toBuffer: destination.as_any_object(), destinationOffset: layout.offset, destinationBytesPerRow: layout.bytes_per_row, destinationBytesPerImage: layout.bytes_per_image, options: MTLBlitOption(options.as_raw())]
}
} else {
require_selector(
self.inner.as_inner(),
sel!(copyFromTexture:sourceSlice:sourceLevel:sourceOrigin:sourceSize:toBuffer:destinationOffset:destinationBytesPerRow:destinationBytesPerImage:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), copyFromTexture: source.as_any_object(), sourceSlice: source_region.slice, sourceLevel: source_region.level, sourceOrigin: origin, sourceSize: size, toBuffer: destination.as_any_object(), destinationOffset: layout.offset, destinationBytesPerRow: layout.bytes_per_row, destinationBytesPerImage: layout.bytes_per_image]
}
}
Ok(())
}
pub fn copy_buffer_to_texture(
&self,
source: &super::Buffer,
layout: BufferImageLayout,
source_size: Size,
destination: &super::Texture,
destination_origin: TextureOriginSelection,
options: Option<BlitOption>,
) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::copyFromBuffer(toTexture)";
validate_size(source_size, operation)?;
validate_image_buffer_range(
source,
layout.offset,
layout.bytes_per_row,
layout.bytes_per_image,
source_size,
operation,
)?;
validate_texture_region(
destination,
destination_origin.slice,
destination_origin.level,
Region {
origin: destination_origin.origin,
size: source_size,
},
operation,
)?;
let size: MTLSize = source_size.into();
let origin: objc2_metal::MTLOrigin = destination_origin.origin.into();
if let Some(options) = options {
if !options.is_valid() {
return Err(Error::invalid_argument("invalid blit option bits"));
}
require_selector(
self.inner.as_inner(),
sel!(copyFromBuffer:sourceOffset:sourceBytesPerRow:sourceBytesPerImage:sourceSize:toTexture:destinationSlice:destinationLevel:destinationOrigin:options:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), copyFromBuffer: source.as_any_object(), sourceOffset: layout.offset, sourceBytesPerRow: layout.bytes_per_row, sourceBytesPerImage: layout.bytes_per_image, sourceSize: size, toTexture: destination.as_any_object(), destinationSlice: destination_origin.slice, destinationLevel: destination_origin.level, destinationOrigin: origin, options: MTLBlitOption(options.as_raw())]
}
} else {
require_selector(
self.inner.as_inner(),
sel!(copyFromBuffer:sourceOffset:sourceBytesPerRow:sourceBytesPerImage:sourceSize:toTexture:destinationSlice:destinationLevel:destinationOrigin:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), copyFromBuffer: source.as_any_object(), sourceOffset: layout.offset, sourceBytesPerRow: layout.bytes_per_row, sourceBytesPerImage: layout.bytes_per_image, sourceSize: size, toTexture: destination.as_any_object(), destinationSlice: destination_origin.slice, destinationLevel: destination_origin.level, destinationOrigin: origin]
}
}
Ok(())
}
pub fn optimize_texture_for_gpu(&self, texture: &super::Texture) -> Result<(), Error> {
self.optimize_texture(texture, None, true)
}
pub fn optimize_texture_level_for_gpu(
&self,
texture: &super::Texture,
slice: usize,
level: usize,
) -> Result<(), Error> {
self.optimize_texture(texture, Some((slice, level)), true)
}
pub fn optimize_texture_for_cpu(&self, texture: &super::Texture) -> Result<(), Error> {
self.optimize_texture(texture, None, false)
}
pub fn optimize_texture_level_for_cpu(
&self,
texture: &super::Texture,
slice: usize,
level: usize,
) -> Result<(), Error> {
self.optimize_texture(texture, Some((slice, level)), false)
}
fn optimize_texture(
&self,
texture: &super::Texture,
subresource: Option<(usize, usize)>,
gpu: bool,
) -> Result<(), Error> {
let operation = if gpu {
"MTL4::ComputeCommandEncoder::optimizeContentsForGPUAccess"
} else {
"MTL4::ComputeCommandEncoder::optimizeContentsForCPUAccess"
};
if let Some((slice, level)) = subresource {
let (_, slices, levels, _) = texture.layout();
if slice >= slices || level >= levels {
return Err(Error::invalid_argument(format!(
"{operation} subresource is out of bounds"
)));
}
if gpu {
require_selector(
self.inner.as_inner(),
sel!(optimizeContentsForGPUAccess:slice:level:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), optimizeContentsForGPUAccess: texture.as_any_object(), slice: slice, level: level]
}
} else {
require_selector(
self.inner.as_inner(),
sel!(optimizeContentsForCPUAccess:slice:level:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), optimizeContentsForCPUAccess: texture.as_any_object(), slice: slice, level: level]
}
}
} else if gpu {
require_selector(
self.inner.as_inner(),
sel!(optimizeContentsForGPUAccess:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), optimizeContentsForGPUAccess: texture.as_any_object()]
}
} else {
require_selector(
self.inner.as_inner(),
sel!(optimizeContentsForCPUAccess:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), optimizeContentsForCPUAccess: texture.as_any_object()]
}
}
Ok(())
}
pub fn reset_commands(
&self,
commands: &metal::IndirectCommandBuffer,
range: std::ops::Range<usize>,
) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::resetCommandsInBuffer";
let range = checked_range(commands.size()?, range, operation)?;
require_selector(
self.inner.as_inner(),
sel!(resetCommandsInBuffer:withRange:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), resetCommandsInBuffer: commands.as_inner(), withRange: range]
}
Ok(())
}
pub fn optimize_indirect_commands(
&self,
commands: &metal::IndirectCommandBuffer,
range: std::ops::Range<usize>,
) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::optimizeIndirectCommandBuffer";
let range = checked_range(commands.size()?, range, operation)?;
require_selector(
self.inner.as_inner(),
sel!(optimizeIndirectCommandBuffer:withRange:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), optimizeIndirectCommandBuffer: commands.as_inner(), withRange: range]
}
Ok(())
}
pub fn copy_indirect_commands(
&self,
source: &metal::IndirectCommandBuffer,
source_range: std::ops::Range<usize>,
destination: &metal::IndirectCommandBuffer,
destination_index: usize,
) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::copyIndirectCommandBuffer";
let range = checked_range(source.size()?, source_range, operation)?;
let destination_size = destination.size()?;
if range.length == 0
|| destination_index
.checked_add(range.length)
.filter(|end| *end <= destination_size)
.is_none()
{
return Err(Error::invalid_argument(format!(
"{operation} destination span is out of bounds"
)));
}
require_selector(
self.inner.as_inner(),
sel!(copyIndirectCommandBuffer:sourceRange:destination:destinationIndex:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), copyIndirectCommandBuffer: source.as_inner(), sourceRange: range, destination: destination.as_inner(), destinationIndex: destination_index]
}
Ok(())
}
pub fn write_timestamp(
&self,
granularity: TimestampGranularity,
heap: &super::TimestampCounterHeap,
index: usize,
) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::writeTimestamp";
if !granularity.is_valid() || index >= heap.count() {
return Err(Error::invalid_argument(format!(
"{operation} received invalid granularity or index"
)));
}
require_selector(
self.inner.as_inner(),
sel!(writeTimestampWithGranularity:intoHeap:atIndex:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), writeTimestampWithGranularity: granularity.as_raw(), intoHeap: heap.as_any_object(), atIndex: index]
}
Ok(())
}
pub fn build_acceleration_structure(
&self,
acceleration_structure: &metal::AccelerationStructure,
descriptor: &metal4::AccelerationStructureDescriptor,
scratch: &super::Buffer,
scratch_range: std::ops::Range<usize>,
) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::buildAccelerationStructure";
let scratch = checked_mtl4_buffer_range(scratch, scratch_range, operation)?;
require_selector(
self.inner.as_inner(),
sel!(buildAccelerationStructure:descriptor:scratchBuffer:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), buildAccelerationStructure: acceleration_structure.as_inner(), descriptor: descriptor.as_inner(), scratchBuffer: scratch]
}
Ok(())
}
pub fn refit_acceleration_structure(
&self,
source: &metal::AccelerationStructure,
descriptor: &metal4::AccelerationStructureDescriptor,
destination: Option<&metal::AccelerationStructure>,
scratch: &super::Buffer,
scratch_range: std::ops::Range<usize>,
options: Option<AccelerationStructureRefitOptions>,
) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::refitAccelerationStructure";
let scratch = checked_mtl4_buffer_range(scratch, scratch_range, operation)?;
if let Some(options) = options {
if !options.is_valid() {
return Err(Error::invalid_argument(
"invalid acceleration-structure refit option bits",
));
}
require_selector(
self.inner.as_inner(),
sel!(refitAccelerationStructure:descriptor:destination:scratchBuffer:options:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), refitAccelerationStructure: source.as_inner(), descriptor: descriptor.as_inner(), destination: destination.map(metal::AccelerationStructure::as_inner), scratchBuffer: scratch, options: options.as_raw()]
}
} else {
require_selector(
self.inner.as_inner(),
sel!(refitAccelerationStructure:descriptor:destination:scratchBuffer:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), refitAccelerationStructure: source.as_inner(), descriptor: descriptor.as_inner(), destination: destination.map(metal::AccelerationStructure::as_inner), scratchBuffer: scratch]
}
}
Ok(())
}
pub fn copy_acceleration_structure(
&self,
source: &metal::AccelerationStructure,
destination: &metal::AccelerationStructure,
) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::copyAccelerationStructure";
require_selector(
self.inner.as_inner(),
sel!(copyAccelerationStructure:toAccelerationStructure:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), copyAccelerationStructure: source.as_inner(), toAccelerationStructure: destination.as_inner()]
}
Ok(())
}
pub fn copy_and_compact_acceleration_structure(
&self,
source: &metal::AccelerationStructure,
destination: &metal::AccelerationStructure,
) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::copyAndCompactAccelerationStructure";
require_selector(
self.inner.as_inner(),
sel!(copyAndCompactAccelerationStructure:toAccelerationStructure:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), copyAndCompactAccelerationStructure: source.as_inner(), toAccelerationStructure: destination.as_inner()]
}
Ok(())
}
pub fn write_compacted_acceleration_structure_size(
&self,
acceleration_structure: &metal::AccelerationStructure,
destination: &super::Buffer,
destination_range: std::ops::Range<usize>,
) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::writeCompactedAccelerationStructureSize";
let destination = checked_mtl4_buffer_range(destination, destination_range, operation)?;
if destination.length < std::mem::size_of::<u64>() as u64 {
return Err(Error::invalid_argument(
"compacted-size destination must contain at least eight bytes",
));
}
require_selector(
self.inner.as_inner(),
sel!(writeCompactedAccelerationStructureSize:toBuffer:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), writeCompactedAccelerationStructureSize: acceleration_structure.as_inner(), toBuffer: destination]
}
Ok(())
}
pub fn copy_tensor(
&self,
source: &metal::Tensor,
destination: &metal::Tensor,
region: TensorCopyRegion<'_>,
planes: Option<(TensorPlaneType, TensorPlaneType)>,
) -> Result<(), Error> {
let operation = "MTL4::ComputeCommandEncoder::copyFromTensor";
let source_origin_values = region.source_origin.as_slice();
let source_dimension_values = region.source_dimensions.as_slice();
let destination_origin_values = region.destination_origin.as_slice();
let destination_dimension_values = region.destination_dimensions.as_slice();
if source_origin_values.len() != source_dimension_values.len()
|| destination_origin_values.len() != destination_dimension_values.len()
|| source_dimension_values != destination_dimension_values
|| source_dimension_values.contains(&0)
|| source_origin_values
.iter()
.zip(source_dimension_values)
.any(|(origin, size)| origin.checked_add(*size).is_none())
|| destination_origin_values
.iter()
.zip(destination_dimension_values)
.any(|(origin, size)| origin.checked_add(*size).is_none())
{
return Err(Error::invalid_argument(format!(
"{operation} received mismatched or overflowing extents"
)));
}
let source_bounds_object = source
.dimensions()?
.ok_or_else(|| Error::unsupported("source tensor dimensions returned nil"))?;
let destination_bounds_object = destination
.dimensions()?
.ok_or_else(|| Error::unsupported("destination tensor dimensions returned nil"))?;
let source_bounds = super::mtl_tensor_safe::read_extents(
source_bounds_object.as_inner(),
"source tensor dimensions",
)?;
let destination_bounds = super::mtl_tensor_safe::read_extents(
destination_bounds_object.as_inner(),
"destination tensor dimensions",
)?;
let in_bounds = |origins: &[usize], dimensions: &[usize], bounds: &[usize]| {
origins.len() == bounds.len()
&& origins
.iter()
.zip(dimensions)
.zip(bounds)
.all(|((origin, size), bound)| {
origin.checked_add(*size).is_some_and(|end| end <= *bound)
})
};
if !in_bounds(
source_origin_values,
source_dimension_values,
source_bounds.as_slice(),
) || !in_bounds(
destination_origin_values,
destination_dimension_values,
destination_bounds.as_slice(),
) {
return Err(Error::invalid_argument(format!(
"{operation} tensor region is out of bounds"
)));
}
let source_origin = super::mtl_tensor_safe::ObjectiveCTensorExtents::from_values(
region.source_origin.as_slice(),
)?;
let source_dimensions = super::mtl_tensor_safe::ObjectiveCTensorExtents::from_values(
region.source_dimensions.as_slice(),
)?;
let destination_origin = super::mtl_tensor_safe::ObjectiveCTensorExtents::from_values(
region.destination_origin.as_slice(),
)?;
let destination_dimensions = super::mtl_tensor_safe::ObjectiveCTensorExtents::from_values(
region.destination_dimensions.as_slice(),
)?;
if let Some((source_plane, destination_plane)) = planes {
if !source_plane.is_valid() || !destination_plane.is_valid() {
return Err(Error::invalid_argument(
"tensor copy received invalid plane values",
));
}
require_selector(
self.inner.as_inner(),
sel!(copyFromTensor:sourceOrigin:sourceDimensions:sourcePlane:toTensor:destinationOrigin:destinationDimensions:destinationPlane:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), copyFromTensor: source.as_inner(), sourceOrigin: &*source_origin.inner, sourceDimensions: &*source_dimensions.inner, sourcePlane: source_plane.as_raw(), toTensor: destination.as_inner(), destinationOrigin: &*destination_origin.inner, destinationDimensions: &*destination_dimensions.inner, destinationPlane: destination_plane.as_raw()]
}
} else {
require_selector(
self.inner.as_inner(),
sel!(copyFromTensor:sourceOrigin:sourceDimensions:toTensor:destinationOrigin:destinationDimensions:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), copyFromTensor: source.as_inner(), sourceOrigin: &*source_origin.inner, sourceDimensions: &*source_dimensions.inner, toTensor: destination.as_inner(), destinationOrigin: &*destination_origin.inner, destinationDimensions: &*destination_dimensions.inner]
}
}
Ok(())
}
pub fn set_argument_table(
&self,
table: Option<&super::Mtl4ArgumentTable>,
) -> Result<(), Error> {
self.inner
.set_argument_table(table.map(super::Mtl4ArgumentTable::as_generated))
}
pub fn set_compute_pipeline_state(
&self,
state: &super::ComputePipelineState,
) -> Result<(), Error> {
self.inner.set_compute_pipeline_state(state)
}
pub fn set_threadgroup_memory_length(&self, length: usize, index: usize) -> Result<(), Error> {
self.inner.set_threadgroup_memory_length(length, index)
}
pub fn set_imageblock_size(&self, width: usize, height: usize) -> Result<(), Error> {
self.inner.set_imageblock_size(width, height)
}
pub fn stages(&self) -> Result<Stages, Error> {
self.inner.stages()
}
pub fn end(mut self) -> Result<(), Error> {
self.inner.finish()?;
self.ended = true;
self.recording.active_encoder = false;
Ok(())
}
}
impl Drop for RecordingComputeEncoder<'_> {
fn drop(&mut self) {
if !self.ended {
let _ = self.inner.finish();
self.recording.active_encoder = false;
}
}
}
pub struct RecordingRenderEncoder<'a> {
inner: metal4::RenderCommandEncoder,
recording: &'a mut RecordingCommandBuffer,
ended: bool,
}
impl RecordingRenderEncoder<'_> {
pub fn tile_width(&self) -> Result<usize, Error> {
self.inner.tile_width()
}
pub fn tile_height(&self) -> Result<usize, Error> {
self.inner.tile_height()
}
pub fn dispatch_threads_per_tile(&self, threads: Size) -> Result<(), Error> {
let operation = "MTL4::RenderCommandEncoder::dispatchThreadsPerTile";
let threads = validate_size(threads, operation)?;
require_selector(
self.inner.as_inner(),
sel!(dispatchThreadsPerTile:),
operation,
)?;
unsafe { msg_send![self.inner.as_inner(), dispatchThreadsPerTile: threads] }
Ok(())
}
pub fn draw_indexed_primitives(
&self,
primitive: PrimitiveType,
index_count: usize,
index_type: IndexType,
index_buffer: &super::Buffer,
index_range: std::ops::Range<usize>,
) -> Result<(), Error> {
self.draw_indexed_primitives_instanced(
primitive,
index_count,
index_type,
index_buffer,
index_range,
1,
)
}
pub fn draw_indexed_primitives_instanced(
&self,
primitive: PrimitiveType,
index_count: usize,
index_type: IndexType,
index_buffer: &super::Buffer,
index_range: std::ops::Range<usize>,
instance_count: usize,
) -> Result<(), Error> {
let operation = "MTL4::RenderCommandEncoder::drawIndexedPrimitives";
let (index_type, stride) = checked_index_type(index_type, operation)?;
let range = checked_range(index_buffer.length(), index_range, operation)?;
let required = index_count
.checked_mul(stride)
.ok_or_else(|| Error::invalid_argument(format!("{operation} index size overflows")))?;
if range.length < required
|| !range.length.is_multiple_of(stride)
|| index_count == 0
|| instance_count == 0
{
return Err(Error::invalid_argument(format!(
"{operation} received an invalid index range or count"
)));
}
let address = checked_buffer_address(
index_buffer,
range.location,
range.length,
stride,
operation,
)?;
require_selector(
self.inner.as_inner(),
sel!(drawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferLength:instanceCount:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), drawIndexedPrimitives: primitive.as_raw(), indexCount: index_count, indexType: index_type, indexBuffer: address, indexBufferLength: range.length, instanceCount: instance_count]
}
Ok(())
}
pub fn draw_indexed_primitives_base(
&self,
primitive: PrimitiveType,
index_buffer: &super::Buffer,
draw: IndexedDraw,
) -> Result<(), Error> {
let operation = "MTL4::RenderCommandEncoder::drawIndexedPrimitives(base)";
let (index_type, stride) = checked_index_type(draw.index_type, operation)?;
let range = checked_range(index_buffer.length(), draw.index_range, operation)?;
let required = draw
.index_count
.checked_mul(stride)
.ok_or_else(|| Error::invalid_argument(format!("{operation} index size overflows")))?;
if range.length < required
|| !range.length.is_multiple_of(stride)
|| draw.index_count == 0
|| draw.instance_count == 0
{
return Err(Error::invalid_argument(format!(
"{operation} received an invalid index range or count"
)));
}
let address = checked_buffer_address(
index_buffer,
range.location,
range.length,
stride,
operation,
)?;
require_selector(
self.inner.as_inner(),
sel!(drawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferLength:instanceCount:baseVertex:baseInstance:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), drawIndexedPrimitives: primitive.as_raw(), indexCount: draw.index_count, indexType: index_type, indexBuffer: address, indexBufferLength: range.length, instanceCount: draw.instance_count, baseVertex: draw.base_vertex, baseInstance: draw.base_instance]
}
Ok(())
}
pub fn draw_primitives_indirect(
&self,
primitive: PrimitiveType,
buffer: &super::Buffer,
offset: usize,
) -> Result<(), Error> {
let operation = "MTL4::RenderCommandEncoder::drawPrimitives(indirect)";
let address = checked_buffer_address(buffer, offset, 16, 4, operation)?;
require_selector(
self.inner.as_inner(),
sel!(drawPrimitives:indirectBuffer:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), drawPrimitives: primitive.as_raw(), indirectBuffer: address]
}
Ok(())
}
pub fn draw_indexed_primitives_indirect(
&self,
primitive: PrimitiveType,
index_type: IndexType,
index_buffer: &super::Buffer,
index_range: std::ops::Range<usize>,
arguments: &super::Buffer,
arguments_offset: usize,
) -> Result<(), Error> {
let operation = "MTL4::RenderCommandEncoder::drawIndexedPrimitives(indirect)";
let (index_type, stride) = checked_index_type(index_type, operation)?;
let range = checked_range(index_buffer.length(), index_range, operation)?;
if range.is_empty() || !range.length.is_multiple_of(stride) {
return Err(Error::invalid_argument(format!(
"{operation} index range is invalid"
)));
}
let index_address = checked_buffer_address(
index_buffer,
range.location,
range.length,
stride,
operation,
)?;
let indirect_address =
checked_buffer_address(arguments, arguments_offset, 20, 4, operation)?;
require_selector(
self.inner.as_inner(),
sel!(drawIndexedPrimitives:indexType:indexBuffer:indexBufferLength:indirectBuffer:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), drawIndexedPrimitives: primitive.as_raw(), indexType: index_type, indexBuffer: index_address, indexBufferLength: range.length, indirectBuffer: indirect_address]
}
Ok(())
}
pub fn draw_mesh_threadgroups(
&self,
groups: Size,
object_threads: Size,
mesh_threads: Size,
) -> Result<(), Error> {
let operation = "MTL4::RenderCommandEncoder::drawMeshThreadgroups";
let groups = validate_size(groups, operation)?;
let object_threads = validate_size(object_threads, operation)?;
let mesh_threads = validate_size(mesh_threads, operation)?;
require_selector(
self.inner.as_inner(),
sel!(drawMeshThreadgroups:threadsPerObjectThreadgroup:threadsPerMeshThreadgroup:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), drawMeshThreadgroups: groups, threadsPerObjectThreadgroup: object_threads, threadsPerMeshThreadgroup: mesh_threads]
}
Ok(())
}
pub fn draw_mesh_threads(
&self,
grid: Size,
object_threads: Size,
mesh_threads: Size,
) -> Result<(), Error> {
let operation = "MTL4::RenderCommandEncoder::drawMeshThreads";
let grid = validate_size(grid, operation)?;
let object_threads = validate_size(object_threads, operation)?;
let mesh_threads = validate_size(mesh_threads, operation)?;
require_selector(
self.inner.as_inner(),
sel!(drawMeshThreads:threadsPerObjectThreadgroup:threadsPerMeshThreadgroup:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), drawMeshThreads: grid, threadsPerObjectThreadgroup: object_threads, threadsPerMeshThreadgroup: mesh_threads]
}
Ok(())
}
pub fn draw_mesh_threadgroups_indirect(
&self,
buffer: &super::Buffer,
offset: usize,
object_threads: Size,
mesh_threads: Size,
) -> Result<(), Error> {
let operation = "MTL4::RenderCommandEncoder::drawMeshThreadgroups(indirect)";
let address = checked_buffer_address(buffer, offset, 12, 4, operation)?;
let object_threads = validate_size(object_threads, operation)?;
let mesh_threads = validate_size(mesh_threads, operation)?;
require_selector(
self.inner.as_inner(),
sel!(drawMeshThreadgroupsWithIndirectBuffer:threadsPerObjectThreadgroup:threadsPerMeshThreadgroup:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), drawMeshThreadgroupsWithIndirectBuffer: address, threadsPerObjectThreadgroup: object_threads, threadsPerMeshThreadgroup: mesh_threads]
}
Ok(())
}
pub fn execute_commands(
&self,
commands: &metal::IndirectCommandBuffer,
range: std::ops::Range<usize>,
) -> Result<(), Error> {
let operation = "MTL4::RenderCommandEncoder::executeCommandsInBuffer(range)";
let range = checked_range(commands.size()?, range, operation)?;
require_selector(
self.inner.as_inner(),
sel!(executeCommandsInBuffer:withRange:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), executeCommandsInBuffer: commands.as_inner(), withRange: range]
}
Ok(())
}
pub fn execute_commands_indirect(
&self,
commands: &metal::IndirectCommandBuffer,
buffer: &super::Buffer,
offset: usize,
) -> Result<(), Error> {
let operation = "MTL4::RenderCommandEncoder::executeCommandsInBuffer(indirect)";
let address = checked_buffer_address(buffer, offset, 16, 4, operation)?;
require_selector(
self.inner.as_inner(),
sel!(executeCommandsInBuffer:indirectBuffer:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), executeCommandsInBuffer: commands.as_inner(), indirectBuffer: address]
}
Ok(())
}
pub fn set_scissor_rect(&self, rect: ScissorRect) -> Result<(), Error> {
if rect.width == 0 || rect.height == 0 {
return Err(Error::invalid_argument(
"scissor rectangle must be non-empty",
));
}
let raw = MTLScissorRect {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
};
let operation = "MTL4::RenderCommandEncoder::setScissorRect";
require_selector(self.inner.as_inner(), sel!(setScissorRect:), operation)?;
unsafe { msg_send![self.inner.as_inner(), setScissorRect: raw] }
Ok(())
}
pub fn set_scissor_rects(&self, rects: &[ScissorRect]) -> Result<(), Error> {
if rects.is_empty() {
return Err(Error::invalid_argument(
"scissor rectangle array must not be empty",
));
}
let raw: Result<Vec<_>, Error> = rects
.iter()
.map(|rect| {
if rect.width == 0 || rect.height == 0 {
Err(Error::invalid_argument(
"scissor rectangle must be non-empty",
))
} else {
Ok(MTLScissorRect {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
})
}
})
.collect();
let raw = raw?;
let operation = "MTL4::RenderCommandEncoder::setScissorRects";
require_selector(
self.inner.as_inner(),
sel!(setScissorRects:count:),
operation,
)?;
unsafe { msg_send![self.inner.as_inner(), setScissorRects: raw.as_ptr(), count: raw.len()] }
Ok(())
}
pub fn set_viewports(&self, viewports: &[Viewport]) -> Result<(), Error> {
if viewports.is_empty() {
return Err(Error::invalid_argument("viewport array must not be empty"));
}
let raw: Result<Vec<_>, _> = viewports.iter().copied().map(validate_viewport).collect();
let raw = raw?;
let operation = "MTL4::RenderCommandEncoder::setViewports";
require_selector(self.inner.as_inner(), sel!(setViewports:count:), operation)?;
unsafe { msg_send![self.inner.as_inner(), setViewports: raw.as_ptr(), count: raw.len()] }
Ok(())
}
pub fn set_color_attachment_map(
&self,
mapping: Option<&metal::LogicalToPhysicalColorAttachmentMap>,
) -> Result<(), Error> {
let operation = "MTL4::RenderCommandEncoder::setColorAttachmentMap";
require_selector(
self.inner.as_inner(),
sel!(setColorAttachmentMap:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), setColorAttachmentMap: mapping.map(metal::LogicalToPhysicalColorAttachmentMap::as_inner)]
}
Ok(())
}
pub fn set_vertex_amplification(
&self,
mappings: &[VertexAmplificationViewMapping],
) -> Result<(), Error> {
if mappings.is_empty() {
return Err(Error::invalid_argument(
"vertex amplification mappings must not be empty",
));
}
let raw: Vec<_> = mappings
.iter()
.map(|value| MTLVertexAmplificationViewMapping {
viewportArrayIndexOffset: value.viewport_array_index_offset,
renderTargetArrayIndexOffset: value.render_target_array_index_offset,
})
.collect();
let operation = "MTL4::RenderCommandEncoder::setVertexAmplificationCount";
require_selector(
self.inner.as_inner(),
sel!(setVertexAmplificationCount:viewMappings:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), setVertexAmplificationCount: raw.len(), viewMappings: raw.as_ptr()]
}
Ok(())
}
pub fn write_timestamp(
&self,
granularity: TimestampGranularity,
stage: RenderStages,
heap: &super::TimestampCounterHeap,
index: usize,
) -> Result<(), Error> {
let operation = "MTL4::RenderCommandEncoder::writeTimestamp";
if !granularity.is_valid() || !stage.is_valid() || index >= heap.count() {
return Err(Error::invalid_argument(format!(
"{operation} received invalid granularity, stage, or index"
)));
}
require_selector(
self.inner.as_inner(),
sel!(writeTimestampWithGranularity:afterStage:intoHeap:atIndex:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), writeTimestampWithGranularity: granularity.as_raw(), afterStage: stage.as_raw(), intoHeap: heap.as_any_object(), atIndex: index]
}
Ok(())
}
pub fn draw_primitives(
&self,
primitive: PrimitiveType,
vertex_start: usize,
vertex_count: usize,
) -> Result<(), Error> {
self.inner
.draw_primitives(primitive, vertex_start, vertex_count)
}
pub fn draw_primitives_instanced(
&self,
primitive: PrimitiveType,
vertex_start: usize,
vertex_count: usize,
instance_count: usize,
) -> Result<(), Error> {
self.inner
.draw_primitives_instanced(primitive, vertex_start, vertex_count, instance_count)
}
pub fn draw_primitives_instanced_base_instance(
&self,
primitive: PrimitiveType,
vertex_start: usize,
vertex_count: usize,
instance_count: usize,
base_instance: usize,
) -> Result<(), Error> {
self.inner.draw_primitives_instanced_base_instance(
primitive,
vertex_start,
vertex_count,
instance_count,
base_instance,
)
}
pub fn set_blend_color(
&self,
red: f32,
green: f32,
blue: f32,
alpha: f32,
) -> Result<(), Error> {
self.inner.set_blend_color(red, green, blue, alpha)
}
pub fn set_argument_table(
&self,
table: Option<&super::Mtl4ArgumentTable>,
stages: RenderStages,
) -> Result<(), Error> {
self.inner
.set_argument_table(table.map(super::Mtl4ArgumentTable::as_generated), stages)
}
pub fn set_cull_mode(&self, mode: CullMode) -> Result<(), Error> {
self.inner.set_cull_mode_checked(mode)
}
pub fn set_front_facing_winding(&self, winding: Winding) -> Result<(), Error> {
self.inner.set_front_facing_winding_checked(winding)
}
pub fn set_triangle_fill_mode(&self, mode: TriangleFillMode) -> Result<(), Error> {
self.inner.set_triangle_fill_mode_checked(mode)
}
pub fn set_depth_clip_mode(&self, mode: DepthClipMode) -> Result<(), Error> {
self.inner.set_depth_clip_mode_checked(mode)
}
pub fn set_depth_stencil_state(
&self,
state: Option<&metal::DepthStencilState>,
) -> Result<(), Error> {
self.inner.set_depth_stencil_state(state)
}
pub fn set_render_pipeline_state(
&self,
state: &super::RenderPipelineState,
) -> Result<(), Error> {
self.inner.set_render_pipeline_state(state)
}
pub fn set_viewport(&self, viewport: Viewport) -> Result<(), Error> {
self.inner.set_viewport_checked(viewport)
}
pub fn set_depth_bias(&self, bias: f32, slope: f32, clamp: f32) -> Result<(), Error> {
self.inner.set_depth_bias(bias, slope, clamp)
}
pub fn set_depth_test_bounds(&self, minimum: f32, maximum: f32) -> Result<(), Error> {
self.inner.set_depth_test_bounds(minimum, maximum)
}
pub fn set_stencil_reference_value(&self, value: u32) -> Result<(), Error> {
self.inner.set_stencil_reference_value(value)
}
pub fn set_stencil_reference_values(&self, front: u32, back: u32) -> Result<(), Error> {
self.inner.set_stencil_reference_values(front, back)
}
pub fn set_visibility_result_mode(
&self,
mode: VisibilityResultMode,
offset: usize,
) -> Result<(), Error> {
self.inner.set_visibility_result_mode(mode, offset)
}
pub fn set_color_store_action(&self, action: StoreAction, index: usize) -> Result<(), Error> {
self.inner.set_color_store_action(action, index)
}
pub fn set_depth_store_action(&self, action: StoreAction) -> Result<(), Error> {
self.inner.set_depth_store_action(action)
}
pub fn set_stencil_store_action(&self, action: StoreAction) -> Result<(), Error> {
self.inner.set_stencil_store_action(action)
}
pub fn set_threadgroup_memory_length(
&self,
length: usize,
offset: usize,
index: usize,
) -> Result<(), Error> {
self.inner
.set_threadgroup_memory_length(length, offset, index)
}
pub fn set_object_threadgroup_memory_length(
&self,
length: usize,
index: usize,
) -> Result<(), Error> {
self.inner
.set_object_threadgroup_memory_length(length, index)
}
pub fn end(mut self) -> Result<(), Error> {
self.inner.finish()?;
self.ended = true;
self.recording.active_encoder = false;
Ok(())
}
}
impl Drop for RecordingRenderEncoder<'_> {
fn drop(&mut self) {
if !self.ended {
let _ = self.inner.finish();
self.recording.active_encoder = false;
}
}
}
pub struct RecordingMachineLearningEncoder<'a> {
inner: metal4::MachineLearningCommandEncoder,
recording: &'a mut RecordingCommandBuffer,
ended: bool,
}
impl RecordingMachineLearningEncoder<'_> {
pub fn set_pipeline_state(
&self,
state: &metal4::MachineLearningPipelineState,
) -> Result<(), Error> {
let operation = "MTL4::MachineLearningCommandEncoder::setPipelineState";
require_selector(self.inner.as_inner(), sel!(setPipelineState:), operation)?;
unsafe { msg_send![self.inner.as_inner(), setPipelineState: state.as_inner()] }
Ok(())
}
pub fn set_argument_table(
&self,
table: Option<&super::Mtl4ArgumentTable>,
) -> Result<(), Error> {
let operation = "MTL4::MachineLearningCommandEncoder::setArgumentTable";
require_selector(self.inner.as_inner(), sel!(setArgumentTable:), operation)?;
unsafe {
msg_send![self.inner.as_inner(), setArgumentTable: table.map(super::Mtl4ArgumentTable::as_generated).map(metal4::ArgumentTable::as_inner)]
}
Ok(())
}
pub fn dispatch_network(&self, intermediates_heap: &metal::Heap) -> Result<(), Error> {
let operation = "MTL4::MachineLearningCommandEncoder::dispatchNetwork";
require_selector(
self.inner.as_inner(),
sel!(dispatchNetworkWithIntermediatesHeap:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), dispatchNetworkWithIntermediatesHeap: intermediates_heap.as_inner()]
}
Ok(())
}
pub fn end(mut self) -> Result<(), Error> {
end_encoding(
self.inner.as_inner(),
"MTL4::MachineLearningCommandEncoder::endEncoding",
)?;
self.ended = true;
self.recording.active_encoder = false;
Ok(())
}
}
impl Drop for RecordingMachineLearningEncoder<'_> {
fn drop(&mut self) {
if !self.ended {
let _ = end_encoding(
self.inner.as_inner(),
"MTL4::MachineLearningCommandEncoder::endEncoding",
);
self.recording.active_encoder = false;
}
}
}
macro_rules! impl_recording_encoder_common {
($type:ident) => {
impl $type<'_> {
pub fn label(&self) -> Result<Option<String>, Error> {
let object = self.inner.as_inner();
require_selector(object, sel!(label), "MTL4::CommandEncoder::label")?;
let value: Option<Retained<NSString>> = unsafe { msg_send![object, label] };
Ok(value.map(|value| value.to_string()))
}
pub fn set_label(&self, value: Option<&str>) -> Result<(), Error> {
let object = self.inner.as_inner();
require_selector(object, sel!(setLabel:), "MTL4::CommandEncoder::setLabel")?;
let value = value.map(NSString::from_str);
unsafe { msg_send![object, setLabel: value.as_deref()] }
Ok(())
}
pub fn insert_debug_signpost(&self, value: &str) -> Result<(), Error> {
let object = self.inner.as_inner();
require_selector(
object,
sel!(insertDebugSignpost:),
"MTL4::CommandEncoder::insertDebugSignpost",
)?;
let value = NSString::from_str(value);
unsafe { msg_send![object, insertDebugSignpost: &*value] }
Ok(())
}
pub fn push_debug_group(&self, value: &str) -> Result<(), Error> {
push_debug_group(
self.inner.as_inner(),
value,
"MTL4::CommandEncoder::pushDebugGroup",
)
}
pub fn pop_debug_group(&self) -> Result<(), Error> {
let object = self.inner.as_inner();
require_selector(
object,
sel!(popDebugGroup),
"MTL4::CommandEncoder::popDebugGroup",
)?;
unsafe { msg_send![object, popDebugGroup] }
Ok(())
}
pub fn barrier_after_encoder_stages(
&self,
after: Stages,
before: Stages,
visibility: VisibilityOptions,
) -> Result<(), Error> {
if !after.is_valid() || !before.is_valid() || !visibility.is_valid() {
return Err(Error::invalid_argument("invalid Metal 4 barrier option bits"));
}
let object = self.inner.as_inner();
require_selector(
object,
sel!(barrierAfterEncoderStages:beforeEncoderStages:visibilityOptions:),
"MTL4::CommandEncoder::barrierAfterEncoderStages",
)?;
unsafe {
msg_send![object,
barrierAfterEncoderStages: after.as_raw(),
beforeEncoderStages: before.as_raw(),
visibilityOptions: visibility.as_raw()
]
}
Ok(())
}
pub fn barrier_after_queue_stages(
&self,
after: Stages,
before: Stages,
visibility: VisibilityOptions,
) -> Result<(), Error> {
if !after.is_valid() || !before.is_valid() || !visibility.is_valid() {
return Err(Error::invalid_argument("invalid Metal 4 barrier option bits"));
}
let object = self.inner.as_inner();
require_selector(
object,
sel!(barrierAfterQueueStages:beforeStages:visibilityOptions:),
"MTL4::CommandEncoder::barrierAfterQueueStages",
)?;
unsafe {
msg_send![object,
barrierAfterQueueStages: after.as_raw(),
beforeStages: before.as_raw(),
visibilityOptions: visibility.as_raw()
]
}
Ok(())
}
pub fn barrier_after_stages(
&self,
after: Stages,
before: Stages,
visibility: VisibilityOptions,
) -> Result<(), Error> {
if !after.is_valid() || !before.is_valid() || !visibility.is_valid() {
return Err(Error::invalid_argument("invalid Metal 4 barrier option bits"));
}
let object = self.inner.as_inner();
require_selector(
object,
sel!(barrierAfterStages:beforeQueueStages:visibilityOptions:),
"MTL4::CommandEncoder::barrierAfterStages",
)?;
unsafe {
msg_send![object,
barrierAfterStages: after.as_raw(),
beforeQueueStages: before.as_raw(),
visibilityOptions: visibility.as_raw()
]
}
Ok(())
}
pub fn update_fence(&self, fence: &metal::Fence, after: Stages) -> Result<(), Error> {
if !after.is_valid() {
return Err(Error::invalid_argument("invalid Metal stage bits"));
}
let object = self.inner.as_inner();
require_selector(
object,
sel!(updateFence:afterEncoderStages:),
"MTL4::CommandEncoder::updateFence",
)?;
unsafe {
msg_send![object,
updateFence: fence.as_inner(),
afterEncoderStages: after.as_raw()
]
}
Ok(())
}
pub fn wait_for_fence(&self, fence: &metal::Fence, before: Stages) -> Result<(), Error> {
if !before.is_valid() {
return Err(Error::invalid_argument("invalid Metal stage bits"));
}
let object = self.inner.as_inner();
require_selector(
object,
sel!(waitForFence:beforeEncoderStages:),
"MTL4::CommandEncoder::waitForFence",
)?;
unsafe {
msg_send![object,
waitForFence: fence.as_inner(),
beforeEncoderStages: before.as_raw()
]
}
Ok(())
}
}
};
}
impl_recording_encoder_common!(RecordingComputeEncoder);
impl_recording_encoder_common!(RecordingRenderEncoder);
impl_recording_encoder_common!(RecordingMachineLearningEncoder);
pub struct SubmittedCommandBuffers {
count: usize,
submission_ids: Vec<u64>,
completion: CompletionState,
}
pub struct CompletedCommandBuffers {
count: usize,
submission_ids: Vec<u64>,
}
#[derive(Clone)]
pub struct Metal4CommandQueue {
inner: metal4::CommandQueue,
}
impl Metal4CommandQueue {
pub(crate) const fn from_generated(inner: metal4::CommandQueue) -> Self {
Self { inner }
}
#[allow(dead_code)]
pub(crate) const fn as_generated(&self) -> &metal4::CommandQueue {
&self.inner
}
pub fn device(&self) -> Result<crate::Device, Error> {
self.inner.device()?.ok_or_else(|| {
Error::unsupported("MTL4::CommandQueue::device unexpectedly returned nil")
})
}
pub fn label(&self) -> Result<Option<String>, Error> {
self.inner.label()
}
pub fn submit(
&self,
buffers: Vec<EndedCommandBuffer>,
options: Option<&metal4::CommitOptions>,
) -> Result<SubmittedCommandBuffers, Error> {
self.inner.submit(buffers, options)
}
pub fn add_residency_set(&self, set: &metal::ResidencySet) -> Result<(), Error> {
self.inner.add_residency_set(set)
}
pub fn add_residency_sets(&self, sets: &[&metal::ResidencySet]) -> Result<(), Error> {
self.inner.add_residency_sets(sets)
}
pub fn remove_residency_set(&self, set: &metal::ResidencySet) -> Result<(), Error> {
self.inner.remove_residency_set(set)
}
pub fn remove_residency_sets(&self, sets: &[&metal::ResidencySet]) -> Result<(), Error> {
self.inner.remove_residency_sets(sets)
}
pub fn signal_event(&self, event: &metal::Event, value: u64) -> Result<(), Error> {
self.inner.signal_event(event, value)
}
pub fn signal_drawable(&self, drawable: &metal::Drawable) -> Result<(), Error> {
self.inner.signal_drawable(drawable)
}
pub fn wait_for_event(&self, event: &metal::Event, value: u64) -> Result<(), Error> {
self.inner.wait_for_event(event, value)
}
pub fn wait_for_drawable(&self, drawable: &metal::Drawable) -> Result<(), Error> {
self.inner.wait_for_drawable(drawable)
}
pub fn update_buffer_mappings(
&self,
buffer: &super::Buffer,
heap: Option<&metal::Heap>,
operations: &[UpdateSparseBufferMappingOperation],
) -> Result<(), Error> {
if operations.is_empty() {
return Err(Error::invalid_argument(
"sparse mapping operations must not be empty",
));
}
let raw: Result<Vec<_>, Error> = operations
.iter()
.map(|value| {
let range = checked_range(
buffer.length(),
value.buffer_range.clone(),
"MTL4::CommandQueue::updateBufferMappings",
)?;
Ok(MTL4UpdateSparseBufferMappingOperation {
mode: MTLSparseTextureMappingMode(value.mode.as_raw()),
bufferRange: range,
heapOffset: value.heap_offset,
})
})
.collect();
let raw = raw?;
let operation = "MTL4::CommandQueue::updateBufferMappings";
require_selector(
self.inner.as_inner(),
sel!(updateBufferMappings:heap:operations:count:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), updateBufferMappings: buffer.as_any_object(), heap: heap.map(metal::Heap::as_inner), operations: raw.as_ptr(), count: raw.len()]
}
Ok(())
}
pub fn copy_buffer_mappings(
&self,
source: &super::Buffer,
destination: &super::Buffer,
operations: &[CopySparseBufferMappingOperation],
) -> Result<(), Error> {
if operations.is_empty() {
return Err(Error::invalid_argument(
"sparse mapping operations must not be empty",
));
}
let raw: Result<Vec<_>, Error> = operations
.iter()
.map(|value| {
let range = checked_range(
source.length(),
value.source_range.clone(),
"MTL4::CommandQueue::copyBufferMappings",
)?;
value
.destination_offset
.checked_add(range.length)
.filter(|end| *end <= destination.length())
.ok_or_else(|| {
Error::invalid_argument("sparse destination range is out of bounds")
})?;
Ok(MTL4CopySparseBufferMappingOperation {
sourceRange: range,
destinationOffset: value.destination_offset,
})
})
.collect();
let raw = raw?;
let operation = "MTL4::CommandQueue::copyBufferMappings";
require_selector(
self.inner.as_inner(),
sel!(copyBufferMappingsFromBuffer:toBuffer:operations:count:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), copyBufferMappingsFromBuffer: source.as_any_object(), toBuffer: destination.as_any_object(), operations: raw.as_ptr(), count: raw.len()]
}
Ok(())
}
pub fn update_texture_mappings(
&self,
texture: &super::Texture,
heap: Option<&metal::Heap>,
operations: &[UpdateSparseTextureMappingOperation],
) -> Result<(), Error> {
if operations.is_empty() {
return Err(Error::invalid_argument(
"sparse mapping operations must not be empty",
));
}
let raw: Result<Vec<_>, Error> = operations
.iter()
.map(|value| {
let (_, array_length, mipmap_levels, _) = texture.layout();
if value.texture_region.size.width == 0
|| value.texture_region.size.height == 0
|| value.texture_region.size.depth == 0
|| value.texture_level >= mipmap_levels
|| value.texture_slice >= array_length
{
return Err(Error::invalid_argument(
"sparse texture operation has invalid region, level, or slice",
));
}
Ok(MTL4UpdateSparseTextureMappingOperation {
mode: MTLSparseTextureMappingMode(value.mode.as_raw()),
textureRegion: value.texture_region.into(),
textureLevel: value.texture_level,
textureSlice: value.texture_slice,
heapOffset: value.heap_offset,
})
})
.collect();
let raw = raw?;
let operation = "MTL4::CommandQueue::updateTextureMappings";
require_selector(
self.inner.as_inner(),
sel!(updateTextureMappings:heap:operations:count:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), updateTextureMappings: texture.as_any_object(), heap: heap.map(metal::Heap::as_inner), operations: raw.as_ptr(), count: raw.len()]
}
Ok(())
}
pub fn copy_texture_mappings(
&self,
source: &super::Texture,
destination: &super::Texture,
operations: &[CopySparseTextureMappingOperation],
) -> Result<(), Error> {
if operations.is_empty() {
return Err(Error::invalid_argument(
"sparse mapping operations must not be empty",
));
}
let raw: Result<Vec<_>, Error> = operations
.iter()
.map(|value| {
let (_, source_array_length, source_mipmap_levels, _) = source.layout();
let (_, destination_array_length, destination_mipmap_levels, _) =
destination.layout();
if value.source_region.size.width == 0
|| value.source_region.size.height == 0
|| value.source_region.size.depth == 0
|| value.source_level >= source_mipmap_levels
|| value.destination_level >= destination_mipmap_levels
|| value.source_slice >= source_array_length
|| value.destination_slice >= destination_array_length
{
return Err(Error::invalid_argument(
"sparse texture copy has invalid region, level, or slice",
));
}
Ok(MTL4CopySparseTextureMappingOperation {
sourceRegion: value.source_region.into(),
sourceLevel: value.source_level,
sourceSlice: value.source_slice,
destinationOrigin: value.destination_origin.into(),
destinationLevel: value.destination_level,
destinationSlice: value.destination_slice,
})
})
.collect();
let raw = raw?;
let operation = "MTL4::CommandQueue::copyTextureMappings";
require_selector(
self.inner.as_inner(),
sel!(copyTextureMappingsFromTexture:toTexture:operations:count:),
operation,
)?;
unsafe {
msg_send![self.inner.as_inner(), copyTextureMappingsFromTexture: source.as_any_object(), toTexture: destination.as_any_object(), operations: raw.as_ptr(), count: raw.len()]
}
Ok(())
}
}
impl SubmittedCommandBuffers {
#[must_use]
pub const fn count(&self) -> usize {
self.count
}
pub fn wait(self) -> Result<CompletedCommandBuffers, Error> {
let (lock, ready) = &*self.completion;
let mut result = lock
.lock()
.map_err(|_| Error::unsupported("Metal 4 completion state was poisoned"))?;
while result.is_none() {
result = ready
.wait(result)
.map_err(|_| Error::unsupported("Metal 4 completion state was poisoned"))?;
}
result.take().expect("completion state checked")?;
Ok(CompletedCommandBuffers {
count: self.count,
submission_ids: self.submission_ids,
})
}
}
impl CompletedCommandBuffers {
#[must_use]
pub const fn count(&self) -> usize {
self.count
}
pub fn resolve_counters(
&self,
readback: super::mtl4_counters::CounterReadback,
) -> Result<(super::TimestampCounterHeap, Vec<u64>), Error> {
if !submission_contains(&self.submission_ids, readback.submission_id) {
return Err(Error::invalid_argument(
"counter readback belongs to a different Metal 4 submission",
));
}
let bytes = readback.buffer.completed_bytes(
readback
.count
.checked_mul(8)
.ok_or_else(|| Error::invalid_argument("counter readback size overflows"))?,
)?;
let values = bytes
.chunks_exact(8)
.map(|chunk| {
u64::from_ne_bytes(chunk.try_into().expect("timestamp chunk has fixed width"))
})
.collect();
Ok((readback.heap, values))
}
}
impl metal4::CommandQueue {
pub fn submit(
&self,
buffers: Vec<EndedCommandBuffer>,
options: Option<&metal4::CommitOptions>,
) -> Result<SubmittedCommandBuffers, Error> {
if buffers.is_empty() {
return Err(Error::invalid_argument(
"command buffer batch must not be empty",
));
}
let submission_ids: Vec<_> = buffers.iter().map(|buffer| buffer.submission_id).collect();
let objects: Vec<*const AnyObject> = buffers
.iter()
.map(|buffer| buffer.inner.as_inner() as *const AnyObject)
.collect();
let object = self.as_inner();
let options = options
.cloned()
.map(Ok)
.unwrap_or_else(metal4::CommitOptions::new)?;
require_selector(
options.as_inner(),
sel!(addFeedbackHandler:),
"MTL4::CommitOptions::addFeedbackHandler",
)?;
require_selector(
object,
sel!(commit:count:options:),
"MTL4::CommandQueue::commit(options)",
)?;
let completion: CompletionState = Arc::new((Mutex::new(None), Condvar::new()));
let callback_state = Arc::clone(&completion);
let block = RcBlock::new(move |feedback: NonNull<AnyObject>| {
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let error: Option<Retained<objc2_foundation::NSError>> =
unsafe { msg_send![feedback.as_ref(), error] };
error.map_or(Ok(()), |error| Err(metal_error(&error)))
}))
.unwrap_or_else(|_| Err(Error::unsupported("Metal 4 feedback callback panicked")));
let (lock, ready) = &*callback_state;
if let Ok(mut slot) = lock.lock() {
*slot = Some(outcome);
ready.notify_all();
}
});
unsafe { msg_send![options.as_inner(), addFeedbackHandler: &*block] }
unsafe {
msg_send![object, commit: objects.as_ptr(), count: objects.len(), options: options.as_inner()]
}
Ok(SubmittedCommandBuffers {
count: objects.len(),
submission_ids,
completion,
})
}
pub fn add_residency_set(&self, set: &metal::ResidencySet) -> Result<(), Error> {
let object = self.as_inner();
require_selector(
object,
sel!(addResidencySet:),
"MTL4::CommandQueue::addResidencySet",
)?;
unsafe { msg_send![object, addResidencySet: set.as_inner()] }
Ok(())
}
pub fn add_residency_sets(&self, sets: &[&metal::ResidencySet]) -> Result<(), Error> {
if sets.is_empty() {
return Err(Error::invalid_argument(
"residency set slice must not be empty",
));
}
let objects: Vec<*const AnyObject> = sets
.iter()
.map(|set| set.as_inner() as *const AnyObject)
.collect();
let object = self.as_inner();
require_selector(
object,
sel!(addResidencySets:count:),
"MTL4::CommandQueue::addResidencySets",
)?;
unsafe { msg_send![object, addResidencySets: objects.as_ptr(), count: objects.len()] }
Ok(())
}
pub fn remove_residency_set(&self, set: &metal::ResidencySet) -> Result<(), Error> {
let object = self.as_inner();
require_selector(
object,
sel!(removeResidencySet:),
"MTL4::CommandQueue::removeResidencySet",
)?;
unsafe { msg_send![object, removeResidencySet: set.as_inner()] }
Ok(())
}
pub fn remove_residency_sets(&self, sets: &[&metal::ResidencySet]) -> Result<(), Error> {
if sets.is_empty() {
return Err(Error::invalid_argument(
"residency set slice must not be empty",
));
}
let objects: Vec<*const AnyObject> = sets
.iter()
.map(|set| set.as_inner() as *const AnyObject)
.collect();
let object = self.as_inner();
require_selector(
object,
sel!(removeResidencySets:count:),
"MTL4::CommandQueue::removeResidencySets",
)?;
unsafe { msg_send![object, removeResidencySets: objects.as_ptr(), count: objects.len()] }
Ok(())
}
pub fn signal_event(&self, event: &metal::Event, value: u64) -> Result<(), Error> {
let object = self.as_inner();
require_selector(
object,
sel!(signalEvent:value:),
"MTL4::CommandQueue::signalEvent",
)?;
unsafe { msg_send![object, signalEvent: event.as_inner(), value: value] }
Ok(())
}
pub fn signal_drawable(&self, drawable: &metal::Drawable) -> Result<(), Error> {
let object = self.as_inner();
require_selector(
object,
sel!(signalDrawable:),
"MTL4::CommandQueue::signalDrawable",
)?;
unsafe { msg_send![object, signalDrawable: drawable.as_inner()] }
Ok(())
}
pub fn wait_for_event(&self, event: &metal::Event, value: u64) -> Result<(), Error> {
let object = self.as_inner();
require_selector(
object,
sel!(waitForEvent:value:),
"MTL4::CommandQueue::wait(event)",
)?;
unsafe { msg_send![object, waitForEvent: event.as_inner(), value: value] }
Ok(())
}
pub fn wait_for_drawable(&self, drawable: &metal::Drawable) -> Result<(), Error> {
let object = self.as_inner();
require_selector(
object,
sel!(waitForDrawable:),
"MTL4::CommandQueue::wait(drawable)",
)?;
unsafe { msg_send![object, waitForDrawable: drawable.as_inner()] }
Ok(())
}
}
impl metal4::RenderCommandEncoder {
pub(crate) fn finish(&self) -> Result<(), Error> {
end_encoding(self.as_inner(), "MTL4::RenderCommandEncoder::endEncoding")
}
pub(crate) fn set_blend_color(
&self,
red: f32,
green: f32,
blue: f32,
alpha: f32,
) -> Result<(), Error> {
if ![red, green, blue, alpha]
.iter()
.all(|value| value.is_finite())
{
return Err(Error::invalid_argument(
"blend color components must be finite",
));
}
let object = self.as_inner();
require_selector(
object,
sel!(setBlendColorRed:green:blue:alpha:),
"MTL4::RenderCommandEncoder::setBlendColor",
)?;
unsafe { msg_send![object, setBlendColorRed: red, green: green, blue: blue, alpha: alpha] }
Ok(())
}
pub(crate) fn set_argument_table(
&self,
table: Option<&metal4::ArgumentTable>,
stages: RenderStages,
) -> Result<(), Error> {
if !stages.is_valid() {
return Err(Error::invalid_argument("invalid render stage bits"));
}
let object = self.as_inner();
require_selector(
object,
sel!(setArgumentTable:stages:),
"MTL4::RenderCommandEncoder::setArgumentTable",
)?;
unsafe {
msg_send![object,
setArgumentTable: table.map(metal4::ArgumentTable::as_inner),
stages: stages.as_raw()
]
}
Ok(())
}
pub(crate) fn set_cull_mode_checked(&self, mode: CullMode) -> Result<(), Error> {
if !mode.is_valid() {
return Err(Error::invalid_argument("invalid cull mode"));
}
let object = self.as_inner();
require_selector(
object,
sel!(setCullMode:),
"MTL4::RenderCommandEncoder::setCullMode",
)?;
unsafe { msg_send![object, setCullMode: mode.as_raw()] }
Ok(())
}
pub(crate) fn set_front_facing_winding_checked(&self, winding: Winding) -> Result<(), Error> {
if !winding.is_valid() {
return Err(Error::invalid_argument("invalid winding"));
}
let object = self.as_inner();
require_selector(
object,
sel!(setFrontFacingWinding:),
"MTL4::RenderCommandEncoder::setFrontFacingWinding",
)?;
unsafe { msg_send![object, setFrontFacingWinding: winding.as_raw()] }
Ok(())
}
pub(crate) fn set_triangle_fill_mode_checked(
&self,
mode: TriangleFillMode,
) -> Result<(), Error> {
if !mode.is_valid() {
return Err(Error::invalid_argument("invalid triangle fill mode"));
}
let object = self.as_inner();
require_selector(
object,
sel!(setTriangleFillMode:),
"MTL4::RenderCommandEncoder::setTriangleFillMode",
)?;
unsafe { msg_send![object, setTriangleFillMode: mode.as_raw()] }
Ok(())
}
pub(crate) fn set_depth_clip_mode_checked(&self, mode: DepthClipMode) -> Result<(), Error> {
if !mode.is_valid() {
return Err(Error::invalid_argument("invalid depth clip mode"));
}
let object = self.as_inner();
require_selector(
object,
sel!(setDepthClipMode:),
"MTL4::RenderCommandEncoder::setDepthClipMode",
)?;
unsafe { msg_send![object, setDepthClipMode: mode.as_raw()] }
Ok(())
}
pub(crate) fn set_depth_stencil_state(
&self,
state: Option<&metal::DepthStencilState>,
) -> Result<(), Error> {
let object = self.as_inner();
require_selector(
object,
sel!(setDepthStencilState:),
"MTL4::RenderCommandEncoder::setDepthStencilState",
)?;
unsafe {
msg_send![object,
setDepthStencilState: state.map(metal::DepthStencilState::as_inner)
]
}
Ok(())
}
pub(crate) fn set_render_pipeline_state(
&self,
state: &super::RenderPipelineState,
) -> Result<(), Error> {
let object = self.as_inner();
require_selector(
object,
sel!(setRenderPipelineState:),
"MTL4::RenderCommandEncoder::setRenderPipelineState",
)?;
unsafe { msg_send![object, setRenderPipelineState: &*state.inner] }
Ok(())
}
pub(crate) fn set_viewport_checked(&self, viewport: Viewport) -> Result<(), Error> {
let viewport = validate_viewport(viewport)?;
let object = self.as_inner();
require_selector(
object,
sel!(setViewport:),
"MTL4::RenderCommandEncoder::setViewport",
)?;
unsafe { msg_send![object, setViewport: viewport] }
Ok(())
}
pub(crate) fn set_depth_bias(
&self,
bias: f32,
slope_scale: f32,
clamp: f32,
) -> Result<(), Error> {
if ![bias, slope_scale, clamp]
.iter()
.all(|value| value.is_finite())
{
return Err(Error::invalid_argument("depth bias values must be finite"));
}
let object = self.as_inner();
require_selector(
object,
sel!(setDepthBias:slopeScale:clamp:),
"MTL4::RenderCommandEncoder::setDepthBias",
)?;
unsafe { msg_send![object, setDepthBias: bias, slopeScale: slope_scale, clamp: clamp] }
Ok(())
}
pub(crate) fn set_depth_test_bounds(&self, minimum: f32, maximum: f32) -> Result<(), Error> {
if !minimum.is_finite() || !maximum.is_finite() || minimum > maximum {
return Err(Error::invalid_argument("invalid depth-test bounds"));
}
let object = self.as_inner();
require_selector(
object,
sel!(setDepthTestMinBound:maxBound:),
"MTL4::RenderCommandEncoder::setDepthTestBounds",
)?;
unsafe { msg_send![object, setDepthTestMinBound: minimum, maxBound: maximum] }
Ok(())
}
pub(crate) fn set_stencil_reference_value(&self, value: u32) -> Result<(), Error> {
let object = self.as_inner();
require_selector(
object,
sel!(setStencilReferenceValue:),
"MTL4::RenderCommandEncoder::setStencilReferenceValue",
)?;
unsafe { msg_send![object, setStencilReferenceValue: value] }
Ok(())
}
pub(crate) fn set_stencil_reference_values(&self, front: u32, back: u32) -> Result<(), Error> {
let object = self.as_inner();
require_selector(
object,
sel!(setStencilFrontReferenceValue:backReferenceValue:),
"MTL4::RenderCommandEncoder::setStencilReferenceValues",
)?;
unsafe { msg_send![object, setStencilFrontReferenceValue: front, backReferenceValue: back] }
Ok(())
}
pub(crate) fn draw_primitives(
&self,
primitive: PrimitiveType,
vertex_start: usize,
vertex_count: usize,
) -> Result<(), Error> {
if !primitive.is_valid() || vertex_count == 0 {
return Err(Error::invalid_argument("invalid primitive draw arguments"));
}
vertex_start
.checked_add(vertex_count)
.ok_or_else(|| Error::invalid_argument("vertex range overflows usize"))?;
let object = self.as_inner();
require_selector(
object,
sel!(drawPrimitives:vertexStart:vertexCount:),
"MTL4::RenderCommandEncoder::drawPrimitives",
)?;
unsafe {
msg_send![object, drawPrimitives: primitive.as_raw(), vertexStart: vertex_start, vertexCount: vertex_count]
}
Ok(())
}
pub(crate) fn draw_primitives_instanced(
&self,
primitive: PrimitiveType,
vertex_start: usize,
vertex_count: usize,
instance_count: usize,
) -> Result<(), Error> {
if !primitive.is_valid() || vertex_count == 0 || instance_count == 0 {
return Err(Error::invalid_argument("invalid instanced draw arguments"));
}
vertex_start
.checked_add(vertex_count)
.ok_or_else(|| Error::invalid_argument("vertex range overflows usize"))?;
let object = self.as_inner();
require_selector(
object,
sel!(drawPrimitives:vertexStart:vertexCount:instanceCount:),
"MTL4::RenderCommandEncoder::drawPrimitives(instanceCount)",
)?;
unsafe {
msg_send![object,
drawPrimitives: primitive.as_raw(),
vertexStart: vertex_start,
vertexCount: vertex_count,
instanceCount: instance_count
]
}
Ok(())
}
pub(crate) fn draw_primitives_instanced_base_instance(
&self,
primitive: PrimitiveType,
vertex_start: usize,
vertex_count: usize,
instance_count: usize,
base_instance: usize,
) -> Result<(), Error> {
if !primitive.is_valid() || vertex_count == 0 || instance_count == 0 {
return Err(Error::invalid_argument("invalid instanced draw arguments"));
}
vertex_start
.checked_add(vertex_count)
.ok_or_else(|| Error::invalid_argument("vertex range overflows usize"))?;
base_instance
.checked_add(instance_count)
.ok_or_else(|| Error::invalid_argument("instance range overflows usize"))?;
let object = self.as_inner();
require_selector(
object,
sel!(drawPrimitives:vertexStart:vertexCount:instanceCount:baseInstance:),
"MTL4::RenderCommandEncoder::drawPrimitives(baseInstance)",
)?;
unsafe {
msg_send![object,
drawPrimitives: primitive.as_raw(),
vertexStart: vertex_start,
vertexCount: vertex_count,
instanceCount: instance_count,
baseInstance: base_instance
]
}
Ok(())
}
pub(crate) fn set_visibility_result_mode(
&self,
mode: VisibilityResultMode,
offset: usize,
) -> Result<(), Error> {
if !mode.is_valid() {
return Err(Error::invalid_argument("invalid visibility result mode"));
}
let object = self.as_inner();
require_selector(
object,
sel!(setVisibilityResultMode:offset:),
"MTL4::RenderCommandEncoder::setVisibilityResultMode",
)?;
unsafe { msg_send![object, setVisibilityResultMode: mode.as_raw(), offset: offset] }
Ok(())
}
pub(crate) fn set_color_store_action(
&self,
action: StoreAction,
index: usize,
) -> Result<(), Error> {
if !action.is_valid() {
return Err(Error::invalid_argument("invalid store action"));
}
let object = self.as_inner();
require_selector(
object,
sel!(setColorStoreAction:atIndex:),
"MTL4::RenderCommandEncoder::setColorStoreAction",
)?;
unsafe { msg_send![object, setColorStoreAction: action.as_raw(), atIndex: index] }
Ok(())
}
pub(crate) fn set_depth_store_action(&self, action: StoreAction) -> Result<(), Error> {
if !action.is_valid() {
return Err(Error::invalid_argument("invalid store action"));
}
let object = self.as_inner();
require_selector(
object,
sel!(setDepthStoreAction:),
"MTL4::RenderCommandEncoder::setDepthStoreAction",
)?;
unsafe { msg_send![object, setDepthStoreAction: action.as_raw()] }
Ok(())
}
pub(crate) fn set_stencil_store_action(&self, action: StoreAction) -> Result<(), Error> {
if !action.is_valid() {
return Err(Error::invalid_argument("invalid store action"));
}
let object = self.as_inner();
require_selector(
object,
sel!(setStencilStoreAction:),
"MTL4::RenderCommandEncoder::setStencilStoreAction",
)?;
unsafe { msg_send![object, setStencilStoreAction: action.as_raw()] }
Ok(())
}
pub(crate) fn set_threadgroup_memory_length(
&self,
length: usize,
offset: usize,
index: usize,
) -> Result<(), Error> {
if index >= 31 {
return Err(Error::invalid_argument(
"threadgroup memory index must be below 31",
));
}
offset
.checked_add(length)
.ok_or_else(|| Error::invalid_argument("threadgroup memory range overflows usize"))?;
let object = self.as_inner();
require_selector(
object,
sel!(setThreadgroupMemoryLength:offset:atIndex:),
"MTL4::RenderCommandEncoder::setThreadgroupMemoryLength",
)?;
unsafe {
msg_send![object,
setThreadgroupMemoryLength: length,
offset: offset,
atIndex: index
]
}
Ok(())
}
pub(crate) fn set_object_threadgroup_memory_length(
&self,
length: usize,
index: usize,
) -> Result<(), Error> {
if index >= 31 {
return Err(Error::invalid_argument(
"object threadgroup memory index must be below 31",
));
}
let object = self.as_inner();
require_selector(
object,
sel!(setObjectThreadgroupMemoryLength:atIndex:),
"MTL4::RenderCommandEncoder::setObjectThreadgroupMemoryLength",
)?;
unsafe { msg_send![object, setObjectThreadgroupMemoryLength: length, atIndex: index] }
Ok(())
}
}
impl metal4::ComputeCommandEncoder {
pub(crate) fn finish(&self) -> Result<(), Error> {
end_encoding(self.as_inner(), "MTL4::ComputeCommandEncoder::endEncoding")
}
pub(crate) fn set_argument_table(
&self,
table: Option<&metal4::ArgumentTable>,
) -> Result<(), Error> {
let object = self.as_inner();
require_selector(
object,
sel!(setArgumentTable:),
"MTL4::ComputeCommandEncoder::setArgumentTable",
)?;
unsafe { msg_send![object, setArgumentTable: table.map(metal4::ArgumentTable::as_inner)] }
Ok(())
}
pub(crate) fn set_compute_pipeline_state(
&self,
state: &super::ComputePipelineState,
) -> Result<(), Error> {
let object = self.as_inner();
require_selector(
object,
sel!(setComputePipelineState:),
"MTL4::ComputeCommandEncoder::setComputePipelineState",
)?;
unsafe { msg_send![object, setComputePipelineState: &*state.inner] }
Ok(())
}
pub(crate) fn dispatch_threadgroups(
&self,
groups: Size,
threads_per_group: Size,
) -> Result<(), Error> {
let groups = validate_size(groups, "dispatchThreadgroups")?;
let threads = validate_size(threads_per_group, "dispatchThreadgroups")?;
let object = self.as_inner();
require_selector(
object,
sel!(dispatchThreadgroups:threadsPerThreadgroup:),
"MTL4::ComputeCommandEncoder::dispatchThreadgroups",
)?;
unsafe { msg_send![object, dispatchThreadgroups: groups, threadsPerThreadgroup: threads] }
Ok(())
}
pub(crate) fn dispatch_threads(
&self,
threads_per_grid: Size,
threads_per_group: Size,
) -> Result<(), Error> {
let grid = validate_size(threads_per_grid, "dispatchThreads")?;
let group = validate_size(threads_per_group, "dispatchThreads")?;
let object = self.as_inner();
require_selector(
object,
sel!(dispatchThreads:threadsPerThreadgroup:),
"MTL4::ComputeCommandEncoder::dispatchThreads",
)?;
unsafe { msg_send![object, dispatchThreads: grid, threadsPerThreadgroup: group] }
Ok(())
}
pub(crate) fn set_threadgroup_memory_length(
&self,
length: usize,
index: usize,
) -> Result<(), Error> {
if index >= 31 {
return Err(Error::invalid_argument(
"threadgroup memory index must be below 31",
));
}
let object = self.as_inner();
require_selector(
object,
sel!(setThreadgroupMemoryLength:atIndex:),
"MTL4::ComputeCommandEncoder::setThreadgroupMemoryLength",
)?;
unsafe { msg_send![object, setThreadgroupMemoryLength: length, atIndex: index] }
Ok(())
}
pub(crate) fn set_imageblock_size(&self, width: usize, height: usize) -> Result<(), Error> {
if width == 0 || height == 0 {
return Err(Error::invalid_argument(
"imageblock dimensions must be non-zero",
));
}
let object = self.as_inner();
require_selector(
object,
sel!(setImageblockWidth:height:),
"MTL4::ComputeCommandEncoder::setImageblockWidth",
)?;
unsafe { msg_send![object, setImageblockWidth: width, height: height] }
Ok(())
}
pub(crate) fn stages(&self) -> Result<Stages, Error> {
let object = self.as_inner();
require_selector(object, sel!(stages), "MTL4::ComputeCommandEncoder::stages")?;
let raw = unsafe { msg_send![object, stages] };
Ok(Stages::from_system_raw(raw))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dispatch_dimensions_must_be_non_zero() {
assert!(validate_size(Size::new(1, 1, 1), "dispatch").is_ok());
assert!(validate_size(Size::new(1, 0, 1), "dispatch").is_err());
}
#[test]
fn viewport_values_are_finite_and_ordered() {
assert!(validate_viewport(Viewport::new(0.0, 0.0, 64.0, 32.0, 0.0, 1.0)).is_ok());
assert!(validate_viewport(Viewport::new(0.0, 0.0, f64::NAN, 32.0, 0.0, 1.0)).is_err());
assert!(validate_viewport(Viewport::new(0.0, 0.0, 64.0, 32.0, 1.0, 0.0)).is_err());
}
#[test]
fn checked_ranges_reject_overflow_and_reversal() {
assert_eq!(
checked_range(16, 4..12, "test").unwrap(),
NSRange::new(4, 8)
);
assert!(checked_range(16, std::ops::Range { start: 12, end: 4 }, "test").is_err());
assert!(checked_range(16, 4..17, "test").is_err());
}
#[test]
fn index_types_define_required_alignment() {
assert_eq!(
checked_index_type(IndexType::IndexTypeUInt16, "test")
.unwrap()
.1,
2
);
assert_eq!(
checked_index_type(IndexType::IndexTypeUInt32, "test")
.unwrap()
.1,
4
);
}
#[test]
fn readbacks_are_bound_to_their_submission_batch() {
assert!(submission_contains(&[3, 5], 5));
assert!(!submission_contains(&[3, 5], 4));
}
}