use crate::ThreadBound;
use crate::foundation::Error;
use crate::metal::generated_struct_types::SamplePosition;
use crate::metal::generated_value_types::{DispatchType, VisibilityResultType};
use crate::metal::{Buffer, ClearColor, Texture};
use objc2::rc::Retained;
use objc2::runtime::{AnyObject, NSObjectProtocol};
use objc2::{msg_send, sel};
use objc2_metal::{
MTLClearColor, MTLDispatchType, MTLLoadAction, MTLRenderPassDescriptor, MTLResource,
MTLSamplePosition, MTLStoreAction, MTLVisibilityResultType,
};
use crate::metal::generated_object_types::metal::{
ComputePassSampleBufferAttachmentDescriptor, RasterizationRateMap,
RenderPassColorAttachmentDescriptor, RenderPassDepthAttachmentDescriptor,
RenderPassSampleBufferAttachmentDescriptor, RenderPassStencilAttachmentDescriptor,
};
const MAX_COLOR_ATTACHMENTS: usize = 8;
const MAX_COUNTER_SAMPLE_ATTACHMENTS: usize = 4;
fn validate_attachment_index(index: usize, limit: usize, kind: &str) -> Result<(), Error> {
if index >= limit {
return Err(Error::invalid_argument(format!(
"{kind} attachment index {index} exceeds Metal's limit of {limit}"
)));
}
Ok(())
}
fn require_selector(
object: &AnyObject,
selector: objc2::runtime::Sel,
operation: &str,
) -> Result<(), Error> {
let available: bool = unsafe { msg_send![object, respondsToSelector: selector] };
if !available {
return Err(Error::unsupported(format!("{operation} is unavailable")));
}
Ok(())
}
#[derive(Clone, Copy, Debug)]
pub struct RenderPassOptions {
pub default_raster_sample_count: usize,
pub imageblock_sample_length: usize,
pub render_target_array_length: usize,
pub render_target_height: usize,
pub render_target_width: usize,
pub support_color_attachment_mapping: bool,
pub threadgroup_memory_length: usize,
pub tile_height: usize,
pub tile_width: usize,
pub visibility_result_type: VisibilityResultType,
}
pub struct RenderPassDescriptor {
pub(super) inner: Retained<MTLRenderPassDescriptor>,
_thread_bound: ThreadBound,
}
impl RenderPassDescriptor {
#[must_use]
pub fn new() -> Self {
Self {
inner: MTLRenderPassDescriptor::renderPassDescriptor(),
_thread_bound: ThreadBound::new(),
}
}
pub fn set_color_attachment(
&self,
index: usize,
texture: &Texture,
clear_color: ClearColor,
) -> Result<(), Error> {
validate_attachment_index(index, MAX_COLOR_ATTACHMENTS, "color")?;
if [
clear_color.red,
clear_color.green,
clear_color.blue,
clear_color.alpha,
]
.iter()
.any(|component| !component.is_finite())
{
return Err(Error::invalid_argument(
"clear-color components must be finite",
));
}
let attachments = self.inner.colorAttachments();
let attachment = unsafe { attachments.objectAtIndexedSubscript(index) };
attachment.setTexture(Some(&texture.inner));
attachment.setLoadAction(MTLLoadAction::Clear);
attachment.setStoreAction(MTLStoreAction::Store);
attachment.setClearColor(MTLClearColor {
red: clear_color.red,
green: clear_color.green,
blue: clear_color.blue,
alpha: clear_color.alpha,
});
Ok(())
}
pub fn color_attachment(
&self,
index: usize,
) -> Result<Option<RenderPassColorAttachmentDescriptor>, Error> {
validate_attachment_index(index, MAX_COLOR_ATTACHMENTS, "color")?;
require_selector(
&self.inner,
sel!(colorAttachments),
"render-pass color attachments",
)?;
let value = unsafe {
let attachments: Retained<AnyObject> = msg_send![&self.inner, colorAttachments];
let value: Option<Retained<AnyObject>> =
msg_send![&*attachments, objectAtIndexedSubscript: index];
value
};
Ok(value.map(RenderPassColorAttachmentDescriptor::from_inner))
}
pub fn set_color_attachment_descriptor(
&self,
index: usize,
value: Option<&RenderPassColorAttachmentDescriptor>,
) -> Result<(), Error> {
validate_attachment_index(index, MAX_COLOR_ATTACHMENTS, "color")?;
require_selector(
&self.inner,
sel!(colorAttachments),
"render-pass color attachments",
)?;
unsafe {
let attachments: Retained<AnyObject> = msg_send![&self.inner, colorAttachments];
let _: () = msg_send![
&*attachments,
setObject: value.map(RenderPassColorAttachmentDescriptor::as_inner),
atIndexedSubscript: index
];
}
Ok(())
}
pub fn color_attachment_clear_color(&self, index: usize) -> Result<ClearColor, Error> {
let attachment = self
.color_attachment(index)?
.ok_or_else(|| Error::invalid_argument("color attachment is not configured"))?;
require_selector(
attachment.as_inner(),
sel!(clearColor),
"render-pass color attachment clear color",
)?;
let value: MTLClearColor = unsafe { msg_send![attachment.as_inner(), clearColor] };
Ok(ClearColor {
red: value.red,
green: value.green,
blue: value.blue,
alpha: value.alpha,
})
}
pub fn set_color_attachment_clear_color(
&self,
index: usize,
value: ClearColor,
) -> Result<(), Error> {
if [value.red, value.green, value.blue, value.alpha]
.iter()
.any(|component| !component.is_finite())
{
return Err(Error::invalid_argument(
"clear-color components must be finite",
));
}
let attachment = self
.color_attachment(index)?
.ok_or_else(|| Error::invalid_argument("color attachment is not configured"))?;
require_selector(
attachment.as_inner(),
sel!(setClearColor:),
"render-pass color attachment clear color setter",
)?;
let value = MTLClearColor {
red: value.red,
green: value.green,
blue: value.blue,
alpha: value.alpha,
};
unsafe {
let _: () = msg_send![attachment.as_inner(), setClearColor: value];
}
Ok(())
}
pub fn depth_attachment(&self) -> Result<Option<RenderPassDepthAttachmentDescriptor>, Error> {
require_selector(
&self.inner,
sel!(depthAttachment),
"render-pass depth attachment",
)?;
let value: Option<Retained<AnyObject>> = unsafe { msg_send![&self.inner, depthAttachment] };
Ok(value.map(RenderPassDepthAttachmentDescriptor::from_inner))
}
pub fn set_depth_attachment(
&self,
value: Option<&RenderPassDepthAttachmentDescriptor>,
) -> Result<(), Error> {
require_selector(
&self.inner,
sel!(setDepthAttachment:),
"render-pass depth attachment setter",
)?;
unsafe {
let _: () = msg_send![
&self.inner,
setDepthAttachment: value.map(RenderPassDepthAttachmentDescriptor::as_inner)
];
}
Ok(())
}
pub fn stencil_attachment(
&self,
) -> Result<Option<RenderPassStencilAttachmentDescriptor>, Error> {
require_selector(
&self.inner,
sel!(stencilAttachment),
"render-pass stencil attachment",
)?;
let value: Option<Retained<AnyObject>> =
unsafe { msg_send![&self.inner, stencilAttachment] };
Ok(value.map(RenderPassStencilAttachmentDescriptor::from_inner))
}
pub fn set_stencil_attachment(
&self,
value: Option<&RenderPassStencilAttachmentDescriptor>,
) -> Result<(), Error> {
require_selector(
&self.inner,
sel!(setStencilAttachment:),
"render-pass stencil attachment setter",
)?;
unsafe {
let _: () = msg_send![
&self.inner,
setStencilAttachment: value.map(RenderPassStencilAttachmentDescriptor::as_inner)
];
}
Ok(())
}
pub fn rasterization_rate_map(&self) -> Result<Option<RasterizationRateMap>, Error> {
require_selector(
&self.inner,
sel!(rasterizationRateMap),
"render-pass rasterization-rate map",
)?;
let value: Option<Retained<AnyObject>> =
unsafe { msg_send![&self.inner, rasterizationRateMap] };
Ok(value.map(RasterizationRateMap::from_inner))
}
pub fn set_rasterization_rate_map(
&self,
value: Option<&RasterizationRateMap>,
) -> Result<(), Error> {
require_selector(
&self.inner,
sel!(setRasterizationRateMap:),
"render-pass rasterization-rate map setter",
)?;
unsafe {
let _: () = msg_send![
&self.inner,
setRasterizationRateMap: value.map(RasterizationRateMap::as_inner)
];
}
Ok(())
}
pub fn sample_buffer_attachment(
&self,
index: usize,
) -> Result<Option<RenderPassSampleBufferAttachmentDescriptor>, Error> {
validate_attachment_index(
index,
MAX_COUNTER_SAMPLE_ATTACHMENTS,
"counter sample-buffer",
)?;
require_selector(
&self.inner,
sel!(sampleBufferAttachments),
"render-pass sample-buffer attachments",
)?;
let value = unsafe {
let attachments: Retained<AnyObject> = msg_send![&self.inner, sampleBufferAttachments];
let value: Option<Retained<AnyObject>> =
msg_send![&*attachments, objectAtIndexedSubscript: index];
value
};
Ok(value.map(RenderPassSampleBufferAttachmentDescriptor::from_inner))
}
pub fn set_sample_buffer_attachment(
&self,
index: usize,
value: Option<&RenderPassSampleBufferAttachmentDescriptor>,
) -> Result<(), Error> {
validate_attachment_index(
index,
MAX_COUNTER_SAMPLE_ATTACHMENTS,
"counter sample-buffer",
)?;
require_selector(
&self.inner,
sel!(sampleBufferAttachments),
"render-pass sample-buffer attachments",
)?;
unsafe {
let attachments: Retained<AnyObject> = msg_send![&self.inner, sampleBufferAttachments];
let _: () = msg_send![
&*attachments,
setObject: value.map(RenderPassSampleBufferAttachmentDescriptor::as_inner),
atIndexedSubscript: index
];
}
Ok(())
}
pub fn options(&self) -> Result<RenderPassOptions, Error> {
for (selector, property) in [
(
sel!(defaultRasterSampleCount),
"default raster sample count",
),
(sel!(imageblockSampleLength), "imageblock sample length"),
(sel!(renderTargetArrayLength), "render-target array length"),
(sel!(renderTargetHeight), "render-target height"),
(sel!(renderTargetWidth), "render-target width"),
(
sel!(supportColorAttachmentMapping),
"color attachment mapping",
),
(sel!(threadgroupMemoryLength), "threadgroup memory length"),
(sel!(tileHeight), "tile height"),
(sel!(tileWidth), "tile width"),
(sel!(visibilityResultType), "visibility result type"),
] {
if !self.inner.respondsToSelector(selector) {
return Err(Error::unsupported(format!(
"render-pass {property} is unavailable"
)));
}
}
Ok(RenderPassOptions {
default_raster_sample_count: self.inner.defaultRasterSampleCount(),
imageblock_sample_length: self.inner.imageblockSampleLength(),
render_target_array_length: self.inner.renderTargetArrayLength(),
render_target_height: self.inner.renderTargetHeight(),
render_target_width: self.inner.renderTargetWidth(),
support_color_attachment_mapping: self.inner.supportColorAttachmentMapping(),
threadgroup_memory_length: self.inner.threadgroupMemoryLength(),
tile_height: self.inner.tileHeight(),
tile_width: self.inner.tileWidth(),
visibility_result_type: VisibilityResultType::from_system_raw(
self.inner.visibilityResultType().0,
),
})
}
pub fn set_options(&self, value: &RenderPassOptions) -> Result<(), Error> {
if value.default_raster_sample_count == 0 {
return Err(Error::invalid_argument(
"default raster sample count must be non-zero",
));
}
for (selector, property) in [
(
sel!(setDefaultRasterSampleCount:),
"default raster sample count",
),
(sel!(setImageblockSampleLength:), "imageblock sample length"),
(
sel!(setRenderTargetArrayLength:),
"render-target array length",
),
(sel!(setRenderTargetHeight:), "render-target height"),
(sel!(setRenderTargetWidth:), "render-target width"),
(
sel!(setSupportColorAttachmentMapping:),
"color attachment mapping",
),
(
sel!(setThreadgroupMemoryLength:),
"threadgroup memory length",
),
(sel!(setTileHeight:), "tile height"),
(sel!(setTileWidth:), "tile width"),
(sel!(setVisibilityResultType:), "visibility result type"),
] {
if !self.inner.respondsToSelector(selector) {
return Err(Error::unsupported(format!(
"render-pass {property} is unavailable"
)));
}
}
self.inner
.setDefaultRasterSampleCount(value.default_raster_sample_count);
self.inner
.setImageblockSampleLength(value.imageblock_sample_length);
self.inner
.setRenderTargetArrayLength(value.render_target_array_length);
self.inner.setRenderTargetHeight(value.render_target_height);
self.inner.setRenderTargetWidth(value.render_target_width);
self.inner
.setSupportColorAttachmentMapping(value.support_color_attachment_mapping);
self.inner
.setThreadgroupMemoryLength(value.threadgroup_memory_length);
self.inner.setTileHeight(value.tile_height);
self.inner.setTileWidth(value.tile_width);
self.inner.setVisibilityResultType(MTLVisibilityResultType(
value.visibility_result_type.as_raw(),
));
Ok(())
}
pub fn sample_positions(&self, count: usize) -> Result<Vec<SamplePosition>, Error> {
if count > 32 {
return Err(Error::invalid_argument(
"Metal supports at most 32 programmable sample positions",
));
}
if !self
.inner
.respondsToSelector(sel!(getSamplePositions:count:))
{
return Err(Error::unsupported(
"programmable sample positions are unavailable",
));
}
let mut positions = vec![MTLSamplePosition { x: 0.0, y: 0.0 }; count];
let written = unsafe {
self.inner
.getSamplePositions_count(positions.as_mut_ptr(), count)
};
if written > count {
return Err(Error::unsupported(
"Metal returned more sample positions than requested",
));
}
positions.truncate(written);
Ok(positions
.into_iter()
.map(|position| SamplePosition {
x: position.x,
y: position.y,
})
.collect())
}
pub fn set_sample_positions(&self, positions: &[SamplePosition]) -> Result<(), Error> {
if positions.len() > 32
|| positions
.iter()
.any(|position| !position.x.is_finite() || !position.y.is_finite())
{
return Err(Error::invalid_argument(
"sample positions must be finite and contain at most 32 entries",
));
}
if !self
.inner
.respondsToSelector(sel!(setSamplePositions:count:))
{
return Err(Error::unsupported(
"programmable sample positions are unavailable",
));
}
let positions: Vec<_> = positions
.iter()
.map(|position| MTLSamplePosition {
x: position.x,
y: position.y,
})
.collect();
unsafe {
self.inner
.setSamplePositions_count(positions.as_ptr(), positions.len())
};
Ok(())
}
pub fn visibility_result_buffer(&self) -> Result<Option<Buffer>, Error> {
self.inner
.visibilityResultBuffer()
.map(|inner| {
let storage = crate::metal::StorageMode::try_from_system_raw(inner.storageMode().0)
.ok_or_else(|| Error::unsupported("Metal returned an unknown storage mode"))?;
Ok(Buffer::new(inner, storage))
})
.transpose()
}
pub fn set_visibility_result_buffer(&self, value: Option<&Buffer>) {
self.inner
.setVisibilityResultBuffer(value.map(|buffer| &*buffer.inner));
}
}
impl Default for RenderPassDescriptor {
fn default() -> Self {
Self::new()
}
}
pub struct ComputePassDescriptor {
pub(super) inner: Retained<objc2_metal::MTLComputePassDescriptor>,
_thread_bound: ThreadBound,
}
impl ComputePassDescriptor {
#[must_use]
pub fn new() -> Self {
Self {
inner: objc2_metal::MTLComputePassDescriptor::computePassDescriptor(),
_thread_bound: ThreadBound::new(),
}
}
pub fn dispatch_type(&self) -> Result<DispatchType, Error> {
require_selector(
&self.inner,
sel!(dispatchType),
"compute-pass dispatch type",
)?;
let value = DispatchType::from_system_raw(self.inner.dispatchType().0);
if !value.is_valid() {
return Err(Error::unsupported(
"Metal returned an unknown compute-pass dispatch type",
));
}
Ok(value)
}
pub fn set_dispatch_type(&self, value: DispatchType) -> Result<(), Error> {
if !value.is_valid() {
return Err(Error::invalid_argument(
"compute-pass dispatch type is not a declared Metal value",
));
}
require_selector(
&self.inner,
sel!(setDispatchType:),
"compute-pass dispatch type setter",
)?;
self.inner.setDispatchType(MTLDispatchType(value.as_raw()));
Ok(())
}
pub fn sample_buffer_attachment(
&self,
index: usize,
) -> Result<Option<ComputePassSampleBufferAttachmentDescriptor>, Error> {
validate_attachment_index(
index,
MAX_COUNTER_SAMPLE_ATTACHMENTS,
"counter sample-buffer",
)?;
require_selector(
&self.inner,
sel!(sampleBufferAttachments),
"compute-pass sample-buffer attachments",
)?;
let value = unsafe {
let attachments: Retained<AnyObject> = msg_send![&self.inner, sampleBufferAttachments];
let value: Option<Retained<AnyObject>> =
msg_send![&*attachments, objectAtIndexedSubscript: index];
value
};
Ok(value.map(ComputePassSampleBufferAttachmentDescriptor::from_inner))
}
pub fn set_sample_buffer_attachment(
&self,
index: usize,
value: Option<&ComputePassSampleBufferAttachmentDescriptor>,
) -> Result<(), Error> {
validate_attachment_index(
index,
MAX_COUNTER_SAMPLE_ATTACHMENTS,
"counter sample-buffer",
)?;
require_selector(
&self.inner,
sel!(sampleBufferAttachments),
"compute-pass sample-buffer attachments",
)?;
unsafe {
let attachments: Retained<AnyObject> = msg_send![&self.inner, sampleBufferAttachments];
let _: () = msg_send![
&*attachments,
setObject: value.map(ComputePassSampleBufferAttachmentDescriptor::as_inner),
atIndexedSubscript: index
];
}
Ok(())
}
}
impl Default for ComputePassDescriptor {
fn default() -> Self {
Self::new()
}
}