#![allow(clippy::missing_errors_doc)]
use crate::{
ffi, storage_mode, util::take_optional_string, CommandBuffer, CommandBufferPhase, CommandQueue,
ComputePipelineState, CounterSampleBuffer, DepthStencilState, Event, Fence, MetalBuffer,
MetalTexture, RenderPipelineState, SamplerState,
};
use core::ffi::c_void;
use core::ops::Range;
use std::collections::HashSet;
const MAX_BUFFER_BINDINGS: usize = 31;
const MAX_TEXTURE_BINDINGS: usize = 128;
const MAX_SAMPLER_BINDINGS: usize = 16;
pub mod command_buffer_status {
pub const NOT_ENQUEUED: usize = 0;
pub const ENQUEUED: usize = 1;
pub const COMMITTED: usize = 2;
pub const SCHEDULED: usize = 3;
pub const COMPLETED: usize = 4;
pub const ERROR: usize = 5;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CommandBufferError {
StateLockPoisoned,
InvalidState {
operation: &'static str,
state: &'static str,
},
ActiveEncoder,
EncoderEnded,
EncoderCreationFailed { encoder: &'static str },
RangeOutOfBounds {
resource: &'static str,
offset: usize,
length: usize,
resource_length: usize,
},
InvalidRange,
InvalidBindingIndex {
binding: &'static str,
index: usize,
limit: usize,
},
IntegerOutOfRange { field: &'static str, value: usize },
EmptyDispatch { field: &'static str },
ManagedStorageRequired { storage_mode: usize },
FenceWaitAfterUpdate,
NativeRejected { operation: &'static str },
ExecutionFailed(String),
}
impl core::fmt::Display for CommandBufferError {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::StateLockPoisoned => formatter.write_str("command-buffer state lock is poisoned"),
Self::InvalidState { operation, state } => {
write!(
formatter,
"{operation} is invalid while command buffer is {state}"
)
}
Self::ActiveEncoder => formatter.write_str("a command encoder is still active"),
Self::EncoderEnded => formatter.write_str("the command encoder has already ended"),
Self::EncoderCreationFailed { encoder } => {
write!(
formatter,
"Metal could not create a {encoder} command encoder"
)
}
Self::RangeOutOfBounds {
resource,
offset,
length,
resource_length,
} => write!(
formatter,
"{resource} range {offset}..{} exceeds length {resource_length}",
offset.saturating_add(*length)
),
Self::InvalidRange => formatter.write_str("range end precedes range start"),
Self::InvalidBindingIndex {
binding,
index,
limit,
} => write!(
formatter,
"{binding} binding index {index} is outside 0..{limit}"
),
Self::IntegerOutOfRange { field, value } => {
write!(formatter, "{field} value {value} exceeds native Int")
}
Self::EmptyDispatch { field } => {
write!(formatter, "dispatch dimension {field} must be non-zero")
}
Self::ManagedStorageRequired { storage_mode } => {
write!(
formatter,
"managed storage required, got mode {storage_mode}"
)
}
Self::FenceWaitAfterUpdate => {
formatter.write_str("cannot wait for a fence after updating it in the same encoder")
}
Self::NativeRejected { operation } => {
write!(formatter, "Metal rejected {operation}")
}
Self::ExecutionFailed(message) => write!(formatter, "GPU execution failed: {message}"),
}
}
}
impl std::error::Error for CommandBufferError {}
struct EncoderCore {
ptr: *mut c_void,
command_buffer: CommandBuffer,
ended: bool,
updated_fences: HashSet<usize>,
}
impl EncoderCore {
fn new(ptr: *mut c_void, command_buffer: CommandBuffer) -> Self {
Self {
ptr,
command_buffer,
ended: false,
updated_fences: HashSet::new(),
}
}
fn with_active<T>(
&self,
operation: &'static str,
encode: impl FnOnce(*mut c_void) -> T,
) -> Result<T, CommandBufferError> {
if self.ended {
return Err(CommandBufferError::EncoderEnded);
}
let state = self
.command_buffer
.inner
.state
.lock()
.map_err(|_| CommandBufferError::StateLockPoisoned)?;
ensure_recording(state.phase, operation)?;
if !state.active_encoder {
return Err(CommandBufferError::EncoderEnded);
}
drop(state);
Ok(encode(self.ptr))
}
fn finish(&mut self) -> Result<(), CommandBufferError> {
if self.ended {
return Err(CommandBufferError::EncoderEnded);
}
let mut state = self
.command_buffer
.inner
.state
.lock()
.map_err(|_| CommandBufferError::StateLockPoisoned)?;
ensure_recording(state.phase, "end_encoding")?;
if !state.active_encoder {
return Err(CommandBufferError::EncoderEnded);
}
unsafe { ffi::am_command_encoder_end_encoding(self.ptr) };
state.active_encoder = false;
drop(state);
self.ended = true;
Ok(())
}
fn finish_on_drop(&mut self) {
if self.ended {
return;
}
let mut state = self
.command_buffer
.inner
.state
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if matches!(
state.phase,
CommandBufferPhase::Recording | CommandBufferPhase::Enqueued
) && state.active_encoder
{
unsafe { ffi::am_command_encoder_end_encoding(self.ptr) };
state.active_encoder = false;
}
drop(state);
self.ended = true;
}
fn record_fence_update(&mut self, fence: &Fence) {
self.updated_fences.insert(fence.as_ptr() as usize);
}
fn ensure_fence_wait_allowed(&self, fence: &Fence) -> Result<(), CommandBufferError> {
if self.updated_fences.contains(&(fence.as_ptr() as usize)) {
Err(CommandBufferError::FenceWaitAfterUpdate)
} else {
Ok(())
}
}
}
impl Drop for EncoderCore {
fn drop(&mut self) {
self.finish_on_drop();
if !self.ptr.is_null() {
unsafe { ffi::am_object_release(self.ptr) };
self.ptr = core::ptr::null_mut();
}
}
}
macro_rules! command_encoder {
($(#[$meta:meta])* pub struct $name:ident;) => {
$(#[$meta])*
pub struct $name {
core: EncoderCore,
}
impl $name {
fn new(ptr: *mut c_void, command_buffer: CommandBuffer) -> Self {
Self {
core: EncoderCore::new(ptr, command_buffer),
}
}
#[must_use]
pub fn as_ptr(&self) -> *mut c_void {
self.core.ptr
}
pub fn end_encoding(mut self) -> Result<(), CommandBufferError> {
self.core.finish()
}
}
};
}
command_encoder!(
pub struct BlitCommandEncoder;
);
command_encoder!(
pub struct ComputeCommandEncoder;
);
command_encoder!(
pub struct RenderCommandEncoder;
);
impl CommandQueue {
#[must_use]
pub unsafe fn new_command_buffer_with_unretained_references(&self) -> Option<CommandBuffer> {
let ptr =
ffi::am_command_queue_new_command_buffer_with_unretained_references(self.as_ptr());
if ptr.is_null() {
None
} else {
Some(CommandBuffer::from_retained_ptr(ptr))
}
}
}
impl CommandBuffer {
pub fn enqueue(&self) -> Result<(), CommandBufferError> {
let mut state = self
.inner
.state
.lock()
.map_err(|_| CommandBufferError::StateLockPoisoned)?;
if state.phase != CommandBufferPhase::Recording {
return Err(invalid_state("enqueue", state.phase));
}
if state.active_encoder {
return Err(CommandBufferError::ActiveEncoder);
}
unsafe { ffi::am_command_buffer_enqueue(self.as_ptr()) };
state.phase = CommandBufferPhase::Enqueued;
drop(state);
Ok(())
}
pub fn commit(&self) -> Result<(), CommandBufferError> {
let mut state = self
.inner
.state
.lock()
.map_err(|_| CommandBufferError::StateLockPoisoned)?;
ensure_recording(state.phase, "commit")?;
if state.active_encoder {
return Err(CommandBufferError::ActiveEncoder);
}
unsafe { ffi::am_command_buffer_commit(self.as_ptr()) };
state.phase = CommandBufferPhase::Committed;
drop(state);
Ok(())
}
pub fn wait_until_scheduled(&self) -> Result<(), CommandBufferError> {
let state = self
.inner
.state
.lock()
.map_err(|_| CommandBufferError::StateLockPoisoned)?;
match state.phase {
CommandBufferPhase::Completed => return Ok(()),
CommandBufferPhase::Error => return Err(self.execution_error()),
CommandBufferPhase::Committed => {}
phase => return Err(invalid_state("wait_until_scheduled", phase)),
}
drop(state);
unsafe { ffi::am_command_buffer_wait_until_scheduled(self.as_ptr()) };
Ok(())
}
pub fn wait_until_completed(&self) -> Result<(), CommandBufferError> {
{
let state = self
.inner
.state
.lock()
.map_err(|_| CommandBufferError::StateLockPoisoned)?;
match state.phase {
CommandBufferPhase::Completed => return Ok(()),
CommandBufferPhase::Error => return Err(self.execution_error()),
CommandBufferPhase::Committed => {}
phase => return Err(invalid_state("wait_until_completed", phase)),
}
}
unsafe { ffi::am_command_buffer_wait_until_completed(self.as_ptr()) };
let status = unsafe { ffi::am_command_buffer_status(self.as_ptr()) };
let mut state = self
.inner
.state
.lock()
.map_err(|_| CommandBufferError::StateLockPoisoned)?;
if status == command_buffer_status::ERROR {
state.phase = CommandBufferPhase::Error;
drop(state);
Err(self.execution_error())
} else {
state.phase = CommandBufferPhase::Completed;
drop(state);
Ok(())
}
}
#[must_use]
pub fn status(&self) -> usize {
let status = unsafe { ffi::am_command_buffer_status(self.as_ptr()) };
let mut state = self
.inner
.state
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
match status {
command_buffer_status::COMPLETED => state.phase = CommandBufferPhase::Completed,
command_buffer_status::ERROR => state.phase = CommandBufferPhase::Error,
_ => {}
}
status
}
#[must_use]
pub fn error(&self) -> Option<String> {
unsafe { take_optional_string(ffi::am_command_buffer_error_message(self.as_ptr())) }
}
pub fn new_blit_command_encoder(&self) -> Result<BlitCommandEncoder, CommandBufferError> {
let core = self.begin_encoder("blit", || unsafe {
ffi::am_command_buffer_new_blit_command_encoder(self.as_ptr())
})?;
Ok(BlitCommandEncoder::new(core, self.clone()))
}
pub fn new_compute_command_encoder(&self) -> Result<ComputeCommandEncoder, CommandBufferError> {
let core = self.begin_encoder("compute", || unsafe {
ffi::am_command_buffer_new_compute_command_encoder(self.as_ptr())
})?;
Ok(ComputeCommandEncoder::new(core, self.clone()))
}
pub fn new_render_command_encoder(
&self,
texture: &MetalTexture,
load_action: usize,
store_action: usize,
clear_color: [f64; 4],
) -> Result<RenderCommandEncoder, CommandBufferError> {
ensure_native_int(load_action, "load_action")?;
ensure_native_int(store_action, "store_action")?;
let core = self.begin_encoder("render", || unsafe {
ffi::am_command_buffer_new_render_command_encoder(
self.as_ptr(),
texture.as_ptr(),
load_action,
store_action,
clear_color[0],
clear_color[1],
clear_color[2],
clear_color[3],
)
})?;
Ok(RenderCommandEncoder::new(core, self.clone()))
}
pub fn encode_wait_for_event(
&self,
event: &Event,
value: u64,
) -> Result<(), CommandBufferError> {
self.encode_without_encoder("encode_wait_for_event", || unsafe {
ffi::am_command_buffer_encode_wait_for_event(self.as_ptr(), event.as_ptr(), value);
})
}
pub fn encode_signal_event(&self, event: &Event, value: u64) -> Result<(), CommandBufferError> {
self.encode_without_encoder("encode_signal_event", || unsafe {
ffi::am_command_buffer_encode_signal_event(self.as_ptr(), event.as_ptr(), value);
})
}
pub fn blit_copy_buffer(
&self,
src: &MetalBuffer,
src_offset: usize,
dst: &MetalBuffer,
dst_offset: usize,
size: usize,
) -> Result<(), CommandBufferError> {
let mut encoder = self.new_blit_command_encoder()?;
encoder.copy_buffer(src, src_offset, dst, dst_offset, size)?;
encoder.end_encoding()
}
pub fn dispatch_compute_1d(
&self,
pipeline: &ComputePipelineState,
buffers: &[&MetalBuffer],
threadgroups: usize,
threads_per_group: usize,
) -> Result<(), CommandBufferError> {
let mut encoder = self.new_compute_command_encoder()?;
encoder.set_compute_pipeline_state(pipeline)?;
for (index, buffer) in buffers.iter().enumerate() {
encoder.set_buffer(buffer, 0, index)?;
}
encoder.dispatch_threadgroups((threadgroups, 1, 1), (threads_per_group, 1, 1))?;
encoder.end_encoding()
}
pub(crate) fn encode_without_encoder(
&self,
operation: &'static str,
encode: impl FnOnce(),
) -> Result<(), CommandBufferError> {
let state = self
.inner
.state
.lock()
.map_err(|_| CommandBufferError::StateLockPoisoned)?;
ensure_recording(state.phase, operation)?;
if state.active_encoder {
return Err(CommandBufferError::ActiveEncoder);
}
encode();
drop(state);
Ok(())
}
fn begin_encoder(
&self,
encoder: &'static str,
create: impl FnOnce() -> *mut c_void,
) -> Result<*mut c_void, CommandBufferError> {
let mut state = self
.inner
.state
.lock()
.map_err(|_| CommandBufferError::StateLockPoisoned)?;
ensure_recording(state.phase, "create command encoder")?;
if state.active_encoder {
return Err(CommandBufferError::ActiveEncoder);
}
let pointer = create();
if pointer.is_null() {
return Err(CommandBufferError::EncoderCreationFailed { encoder });
}
state.active_encoder = true;
drop(state);
Ok(pointer)
}
fn execution_error(&self) -> CommandBufferError {
let message =
unsafe { take_optional_string(ffi::am_command_buffer_error_message(self.as_ptr())) }
.unwrap_or_else(|| {
"Metal reported an unspecified command-buffer error".to_string()
});
CommandBufferError::ExecutionFailed(message)
}
}
impl BlitCommandEncoder {
pub fn copy_buffer(
&mut self,
src: &MetalBuffer,
src_offset: usize,
dst: &MetalBuffer,
dst_offset: usize,
size: usize,
) -> Result<(), CommandBufferError> {
checked_resource_range("source buffer", src_offset, size, src.length())?;
checked_resource_range("destination buffer", dst_offset, size, dst.length())?;
ensure_native_int(src_offset, "source offset")?;
ensure_native_int(dst_offset, "destination offset")?;
ensure_native_int(size, "copy size")?;
let accepted = self.core.with_active("copy_buffer", |encoder| unsafe {
ffi::am_blit_command_encoder_copy_buffer(
encoder,
src.as_ptr(),
src_offset,
dst.as_ptr(),
dst_offset,
size,
)
})?;
if accepted {
Ok(())
} else {
Err(CommandBufferError::NativeRejected {
operation: "buffer copy",
})
}
}
pub fn fill_buffer(
&mut self,
buffer: &MetalBuffer,
range: Range<usize>,
value: u8,
) -> Result<(), CommandBufferError> {
if range.start > range.end {
return Err(CommandBufferError::InvalidRange);
}
let length = range.end - range.start;
checked_resource_range("buffer", range.start, length, buffer.length())?;
ensure_native_int(range.start, "fill offset")?;
ensure_native_int(length, "fill length")?;
let accepted = self.core.with_active("fill_buffer", |encoder| unsafe {
ffi::am_blit_command_encoder_fill_buffer(
encoder,
buffer.as_ptr(),
range.start,
length,
value,
)
})?;
if accepted {
Ok(())
} else {
Err(CommandBufferError::NativeRejected {
operation: "buffer fill",
})
}
}
pub fn sample_counters(
&mut self,
sample_buffer: &CounterSampleBuffer,
sample_index: usize,
barrier: bool,
) -> Result<(), CommandBufferError> {
if sample_index >= sample_buffer.sample_count() {
return Err(CommandBufferError::InvalidBindingIndex {
binding: "counter sample",
index: sample_index,
limit: sample_buffer.sample_count(),
});
}
ensure_native_int(sample_index, "sample index")?;
let accepted = self.core.with_active("sample_counters", |encoder| unsafe {
ffi::am_blit_command_encoder_sample_counters(
encoder,
sample_buffer.as_ptr(),
sample_index,
barrier,
)
})?;
if accepted {
Ok(())
} else {
Err(CommandBufferError::NativeRejected {
operation: "counter sampling",
})
}
}
pub fn synchronize_resource(&mut self, buffer: &MetalBuffer) -> Result<(), CommandBufferError> {
synchronize_resource(&self.core, buffer.as_ptr(), buffer.storage_mode())
}
pub fn synchronize_texture(
&mut self,
texture: &MetalTexture,
) -> Result<(), CommandBufferError> {
synchronize_resource(&self.core, texture.as_ptr(), texture.storage_mode())
}
pub fn update_fence(&mut self, fence: &Fence) -> Result<(), CommandBufferError> {
self.core.with_active("update_fence", |encoder| unsafe {
ffi::am_blit_command_encoder_update_fence(encoder, fence.as_ptr());
})?;
self.core.record_fence_update(fence);
Ok(())
}
pub fn wait_for_fence(&mut self, fence: &Fence) -> Result<(), CommandBufferError> {
self.core.ensure_fence_wait_allowed(fence)?;
self.core.with_active("wait_for_fence", |encoder| unsafe {
ffi::am_blit_command_encoder_wait_for_fence(encoder, fence.as_ptr());
})
}
}
impl ComputeCommandEncoder {
pub fn set_compute_pipeline_state(
&mut self,
pipeline: &ComputePipelineState,
) -> Result<(), CommandBufferError> {
self.core
.with_active("set_compute_pipeline_state", |encoder| unsafe {
ffi::am_compute_command_encoder_set_pipeline_state(encoder, pipeline.as_ptr());
})
}
pub fn set_buffer(
&mut self,
buffer: &MetalBuffer,
offset: usize,
index: usize,
) -> Result<(), CommandBufferError> {
validate_binding_index("buffer", index, MAX_BUFFER_BINDINGS)?;
checked_resource_range("buffer", offset, 0, buffer.length())?;
ensure_native_int(offset, "buffer offset")?;
self.core.with_active("set_buffer", |encoder| unsafe {
ffi::am_compute_command_encoder_set_buffer(encoder, buffer.as_ptr(), offset, index);
})
}
pub fn set_texture(
&mut self,
texture: &MetalTexture,
index: usize,
) -> Result<(), CommandBufferError> {
validate_binding_index("texture", index, MAX_TEXTURE_BINDINGS)?;
self.core.with_active("set_texture", |encoder| unsafe {
ffi::am_compute_command_encoder_set_texture(encoder, texture.as_ptr(), index);
})
}
pub fn set_sampler_state(
&mut self,
sampler: &SamplerState,
index: usize,
) -> Result<(), CommandBufferError> {
validate_binding_index("sampler", index, MAX_SAMPLER_BINDINGS)?;
self.core
.with_active("set_sampler_state", |encoder| unsafe {
ffi::am_compute_command_encoder_set_sampler_state(encoder, sampler.as_ptr(), index);
})
}
pub fn set_visible_function_table(
&mut self,
table: &crate::VisibleFunctionTable,
index: usize,
) -> Result<(), CommandBufferError> {
validate_binding_index("visible function table", index, MAX_BUFFER_BINDINGS)?;
self.core
.with_active("set_visible_function_table", |encoder| unsafe {
ffi::am_compute_command_encoder_set_visible_function_table(
encoder,
table.as_ptr(),
index,
);
})
}
pub fn set_intersection_function_table(
&mut self,
table: &crate::IntersectionFunctionTable,
index: usize,
) -> Result<(), CommandBufferError> {
validate_binding_index("intersection function table", index, MAX_BUFFER_BINDINGS)?;
self.core
.with_active("set_intersection_function_table", |encoder| unsafe {
ffi::am_compute_command_encoder_set_intersection_function_table(
encoder,
table.as_ptr(),
index,
);
})
}
pub fn set_acceleration_structure(
&mut self,
acceleration_structure: &crate::AccelerationStructure,
index: usize,
) -> Result<(), CommandBufferError> {
validate_binding_index("acceleration structure", index, MAX_BUFFER_BINDINGS)?;
self.core
.with_active("set_acceleration_structure", |encoder| unsafe {
ffi::am_compute_command_encoder_set_acceleration_structure(
encoder,
acceleration_structure.as_ptr(),
index,
);
})
}
pub fn dispatch_threadgroups(
&mut self,
threadgroups: (usize, usize, usize),
threads_per_threadgroup: (usize, usize, usize),
) -> Result<(), CommandBufferError> {
validate_size(threadgroups, "threadgroup")?;
validate_size(threads_per_threadgroup, "threads-per-threadgroup")?;
self.core
.with_active("dispatch_threadgroups", |encoder| unsafe {
ffi::am_compute_command_encoder_dispatch_threadgroups(
encoder,
threadgroups.0,
threadgroups.1,
threadgroups.2,
threads_per_threadgroup.0,
threads_per_threadgroup.1,
threads_per_threadgroup.2,
);
})
}
pub fn dispatch_threads(
&mut self,
threads: (usize, usize, usize),
threads_per_threadgroup: (usize, usize, usize),
) -> Result<(), CommandBufferError> {
validate_size(threads, "thread grid")?;
validate_size(threads_per_threadgroup, "threads-per-threadgroup")?;
self.core.with_active("dispatch_threads", |encoder| unsafe {
ffi::am_compute_command_encoder_dispatch_threads(
encoder,
threads.0,
threads.1,
threads.2,
threads_per_threadgroup.0,
threads_per_threadgroup.1,
threads_per_threadgroup.2,
);
})
}
pub fn update_fence(&mut self, fence: &Fence) -> Result<(), CommandBufferError> {
self.core.with_active("update_fence", |encoder| unsafe {
ffi::am_compute_command_encoder_update_fence(encoder, fence.as_ptr());
})?;
self.core.record_fence_update(fence);
Ok(())
}
pub fn wait_for_fence(&mut self, fence: &Fence) -> Result<(), CommandBufferError> {
self.core.ensure_fence_wait_allowed(fence)?;
self.core.with_active("wait_for_fence", |encoder| unsafe {
ffi::am_compute_command_encoder_wait_for_fence(encoder, fence.as_ptr());
})
}
}
impl RenderCommandEncoder {
pub fn set_render_pipeline_state(
&mut self,
pipeline: &RenderPipelineState,
) -> Result<(), CommandBufferError> {
self.core
.with_active("set_render_pipeline_state", |encoder| unsafe {
ffi::am_render_command_encoder_set_render_pipeline_state(
encoder,
pipeline.as_ptr(),
);
})
}
pub fn set_vertex_buffer(
&mut self,
buffer: &MetalBuffer,
offset: usize,
index: usize,
) -> Result<(), CommandBufferError> {
validate_binding_index("vertex buffer", index, MAX_BUFFER_BINDINGS)?;
checked_resource_range("vertex buffer", offset, 0, buffer.length())?;
ensure_native_int(offset, "vertex buffer offset")?;
self.core
.with_active("set_vertex_buffer", |encoder| unsafe {
ffi::am_render_command_encoder_set_vertex_buffer(
encoder,
buffer.as_ptr(),
offset,
index,
);
})
}
pub fn set_fragment_sampler_state(
&mut self,
sampler: &SamplerState,
index: usize,
) -> Result<(), CommandBufferError> {
validate_binding_index("fragment sampler", index, MAX_SAMPLER_BINDINGS)?;
self.core
.with_active("set_fragment_sampler_state", |encoder| unsafe {
ffi::am_render_command_encoder_set_fragment_sampler_state(
encoder,
sampler.as_ptr(),
index,
);
})
}
pub fn set_depth_stencil_state(
&mut self,
state: &DepthStencilState,
) -> Result<(), CommandBufferError> {
self.core
.with_active("set_depth_stencil_state", |encoder| unsafe {
ffi::am_render_command_encoder_set_depth_stencil_state(encoder, state.as_ptr());
})
}
pub fn draw_primitives(
&mut self,
primitive_type: usize,
vertex_start: usize,
vertex_count: usize,
) -> Result<(), CommandBufferError> {
ensure_native_int(primitive_type, "primitive type")?;
ensure_native_int(vertex_start, "vertex start")?;
ensure_native_int(vertex_count, "vertex count")?;
vertex_start
.checked_add(vertex_count)
.filter(|end| isize::try_from(*end).is_ok())
.ok_or_else(|| CommandBufferError::IntegerOutOfRange {
field: "vertex range end",
value: vertex_start.saturating_add(vertex_count),
})?;
self.core.with_active("draw_primitives", |encoder| unsafe {
ffi::am_render_command_encoder_draw_primitives(
encoder,
primitive_type,
vertex_start,
vertex_count,
);
})
}
pub fn update_fence(&mut self, fence: &Fence) -> Result<(), CommandBufferError> {
self.core.with_active("update_fence", |encoder| unsafe {
ffi::am_render_command_encoder_update_fence(encoder, fence.as_ptr());
})?;
self.core.record_fence_update(fence);
Ok(())
}
pub fn wait_for_fence(&mut self, fence: &Fence) -> Result<(), CommandBufferError> {
self.core.ensure_fence_wait_allowed(fence)?;
self.core.with_active("wait_for_fence", |encoder| unsafe {
ffi::am_render_command_encoder_wait_for_fence(encoder, fence.as_ptr());
})
}
}
fn ensure_recording(
phase: CommandBufferPhase,
operation: &'static str,
) -> Result<(), CommandBufferError> {
if matches!(
phase,
CommandBufferPhase::Recording | CommandBufferPhase::Enqueued
) {
Ok(())
} else {
Err(invalid_state(operation, phase))
}
}
fn invalid_state(operation: &'static str, phase: CommandBufferPhase) -> CommandBufferError {
CommandBufferError::InvalidState {
operation,
state: match phase {
CommandBufferPhase::Recording => "recording",
CommandBufferPhase::Enqueued => "enqueued",
CommandBufferPhase::Committed => "committed",
CommandBufferPhase::Completed => "completed",
CommandBufferPhase::Error => "failed",
},
}
}
fn checked_resource_range(
resource: &'static str,
offset: usize,
length: usize,
resource_length: usize,
) -> Result<(), CommandBufferError> {
let end = offset
.checked_add(length)
.ok_or(CommandBufferError::RangeOutOfBounds {
resource,
offset,
length,
resource_length,
})?;
if end > resource_length {
Err(CommandBufferError::RangeOutOfBounds {
resource,
offset,
length,
resource_length,
})
} else {
Ok(())
}
}
fn validate_binding_index(
binding: &'static str,
index: usize,
limit: usize,
) -> Result<(), CommandBufferError> {
if index < limit {
Ok(())
} else {
Err(CommandBufferError::InvalidBindingIndex {
binding,
index,
limit,
})
}
}
fn ensure_native_int(value: usize, field: &'static str) -> Result<(), CommandBufferError> {
if isize::try_from(value).is_ok() {
Ok(())
} else {
Err(CommandBufferError::IntegerOutOfRange { field, value })
}
}
fn validate_size(
size: (usize, usize, usize),
field: &'static str,
) -> Result<(), CommandBufferError> {
for (axis, value) in [("width", size.0), ("height", size.1), ("depth", size.2)] {
if value == 0 {
return Err(CommandBufferError::EmptyDispatch { field: axis });
}
ensure_native_int(value, field)?;
}
Ok(())
}
fn synchronize_resource(
core: &EncoderCore,
resource: *mut c_void,
resource_storage_mode: usize,
) -> Result<(), CommandBufferError> {
if resource_storage_mode != storage_mode::MANAGED {
return Err(CommandBufferError::ManagedStorageRequired {
storage_mode: resource_storage_mode,
});
}
let accepted = core.with_active("synchronize_resource", |encoder| unsafe {
ffi::am_blit_command_encoder_synchronize_resource(encoder, resource)
})?;
if accepted {
Ok(())
} else {
Err(CommandBufferError::NativeRejected {
operation: "managed resource synchronization",
})
}
}