use crate::ThreadBound;
use crate::foundation::Error;
use crate::metal::{CommandBuffer, Device};
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2::{msg_send, sel};
use objc2_foundation::NSObjectProtocol;
use objc2_metal::MTLCommandQueue;
pub struct CommandQueue {
pub(super) inner: Retained<ProtocolObject<dyn MTLCommandQueue>>,
_thread_bound: ThreadBound,
}
impl CommandQueue {
pub(super) const fn new(inner: Retained<ProtocolObject<dyn MTLCommandQueue>>) -> Self {
Self {
inner,
_thread_bound: ThreadBound::new(),
}
}
#[must_use]
pub fn device(&self) -> Device {
Device::from_inner(self.inner.device())
}
#[must_use]
pub fn label(&self) -> Option<String> {
self.inner.label().map(|label| label.to_string())
}
pub fn set_label(&self, label: Option<&str>) {
let label = label.map(objc2_foundation::NSString::from_str);
self.inner.setLabel(label.as_deref());
}
pub fn command_buffer(&self) -> Result<CommandBuffer, Error> {
self.inner
.commandBuffer()
.map(CommandBuffer::new)
.ok_or_else(|| Error::unsupported("Metal could not create a command buffer"))
}
pub fn command_buffer_with_descriptor(
&self,
descriptor: &crate::metal::generated_object_types::metal::CommandBufferDescriptor,
) -> Result<CommandBuffer, Error> {
if !descriptor.retained_references()? {
return Err(Error::invalid_argument(
"safe command buffers must retain every referenced resource",
));
}
if !self
.inner
.respondsToSelector(sel!(commandBufferWithDescriptor:))
{
return Err(Error::unsupported(
"MTLCommandQueue::commandBufferWithDescriptor is unavailable",
));
}
let inner: Option<Retained<ProtocolObject<dyn objc2_metal::MTLCommandBuffer>>> =
unsafe { msg_send![&*self.inner, commandBufferWithDescriptor: descriptor.as_inner()] };
inner.map(CommandBuffer::new).ok_or_else(|| {
Error::unsupported("Metal could not create the requested command buffer")
})
}
#[allow(deprecated)]
pub fn insert_debug_capture_boundary(&self) {
self.inner.insertDebugCaptureBoundary();
}
pub fn add_residency_sets(
&self,
sets: &[&crate::metal::generated_object_types::metal::ResidencySet],
) -> Result<(), Error> {
if !self.inner.respondsToSelector(sel!(addResidencySet:)) {
return Err(Error::unsupported(
"MTLCommandQueue::addResidencySet is unavailable",
));
}
for set in sets {
unsafe {
let _: () = msg_send![&*self.inner, addResidencySet: set.as_inner()];
}
}
Ok(())
}
pub fn remove_residency_sets(
&self,
sets: &[&crate::metal::generated_object_types::metal::ResidencySet],
) -> Result<(), Error> {
if !self.inner.respondsToSelector(sel!(removeResidencySet:)) {
return Err(Error::unsupported(
"MTLCommandQueue::removeResidencySet is unavailable",
));
}
for set in sets {
unsafe {
let _: () = msg_send![&*self.inner, removeResidencySet: set.as_inner()];
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn descriptor_cannot_disable_resource_retention() {
let Some(device) = Device::system_default() else {
return;
};
let Ok(queue) = device.new_command_queue(None) else {
return;
};
let Ok(descriptor) =
crate::metal::generated_object_types::metal::CommandBufferDescriptor::new()
else {
return;
};
if descriptor.set_retained_references(false).is_err() {
return;
}
let error = match queue.command_buffer_with_descriptor(&descriptor) {
Ok(_) => panic!("unsafe unretained command buffer must be rejected"),
Err(error) => error,
};
assert!(error.invalid_argument);
}
}