pub struct Context {
pub(crate) device: Retained<ProtocolObject<dyn mtl::MTLDevice>>,
pub(crate) frames: u8,
pub(crate) queues: Vec<queue::StoredQueue>,
pub(crate) buffers: ResourceCollection<buffer::Buffer, graphics_hardware_interface::BaseBufferHandle, BufferHandle>,
pub(crate) images: ResourceCollection<image::Image, graphics_hardware_interface::BaseImageHandle, ImageHandle>,
pub(crate) samplers: Vec<sampler::Sampler>,
pub(crate) allocations: Vec<Allocation>,
pub(crate) pipeline_layouts: Vec<PipelineLayout>,
pub(crate) vertex_layouts: Vec<VertexLayout>,
vertex_layout_indices: HashMap<VertexLayoutKey, VertexLayoutHandle>,
pub(crate) descriptor_sets: Vec<descriptor_set::DescriptorSet>,
pub(crate) meshes: Vec<Mesh>,
pub(crate) acceleration_structures: Vec<AccelerationStructure>,
pub(crate) shaders: Vec<Shader>,
pub(crate) pipelines: Vec<Pipeline>,
pub(crate) command_buffers: Vec<StoredCommandBuffer>,
pub(crate) synchronizers: ResourceCollection<
synchronizer::Synchronizer,
graphics_hardware_interface::SynchronizerHandle,
crate::synchronizer::SynchronizerHandle,
>,
internal_upload_synchronizer: Option<graphics_hardware_interface::SynchronizerHandle>,
pub(crate) swapchains: Vec<swapchain::Swapchain>,
pub(crate) resource_to_descriptor:
HashMap<PrivateHandles, HashSet<(DescriptorSetHandle, crate::shader::ResourceSlot, u32, u8)>>,
pub(crate) descriptor_set_to_resource:
HashMap<(DescriptorSetHandle, crate::shader::ResourceSlot, u32, u8), HashSet<PrivateHandles>>,
pub settings: crate::device::Features,
pub(crate) states: HashMap<PrivateHandles, TransitionState>,
pub(crate) pending_buffer_syncs: VecDeque<BufferHandle>,
pub(crate) pending_image_syncs: VecDeque<ImageHandle>,
pub(crate) tasks: Vec<Task>,
#[cfg(debug_assertions)]
pub names: HashMap<graphics_hardware_interface::Handles, String>,
}
impl Context {
pub(super) fn create_metal_command_buffer(
&self,
queue: &ProtocolObject<dyn mtl::MTLCommandQueue>,
label: Option<&str>,
error_message: &'static str,
) -> Retained<ProtocolObject<dyn mtl::MTLCommandBuffer>> {
let descriptor = mtl::MTLCommandBufferDescriptor::new();
descriptor.setRetainedReferences(true);
descriptor.setErrorOptions(mtl::MTLCommandBufferErrorOption::EncoderExecutionStatus);
let command_buffer = queue.commandBufferWithDescriptor(&descriptor).expect(error_message);
#[cfg(debug_assertions)]
if self.settings.debug_labels {
if let Some(label) = label {
command_buffer.setLabel(Some(&NSString::from_str(label)));
}
}
command_buffer
}
pub(super) fn submit_metal_command_buffer(&self, command_buffer: &ProtocolObject<dyn mtl::MTLCommandBuffer>) {
submit_metal_command_buffer(command_buffer);
}
fn synchronizer_for_sequence(
&self,
synchronizer_handle: graphics_hardware_interface::SynchronizerHandle,
sequence_index: u8,
) -> crate::synchronizer::SynchronizerHandle {
self.synchronizers
.nth_handle(synchronizer_handle, sequence_index as usize)
.expect(
"Missing Metal synchronizer. The most likely cause is that the synchronizer handle came from another context.",
)
}
pub(crate) fn submit_metal_command_buffer_for_synchronizer(
&self,
command_buffer: Retained<ProtocolObject<dyn mtl::MTLCommandBuffer>>,
synchronizer_handle: graphics_hardware_interface::SynchronizerHandle,
sequence_index: u8,
) {
let synchronizer_handle = self.synchronizer_for_sequence(synchronizer_handle, sequence_index);
let synchronizer = self.synchronizers.resource(synchronizer_handle);
synchronizer.signal_workload(command_buffer.clone());
self.submit_metal_command_buffer(command_buffer.as_ref());
}
fn submit_internal_metal_command_buffer(
&self,
command_buffer: Retained<ProtocolObject<dyn mtl::MTLCommandBuffer>>,
sequence_index: u8,
) {
let synchronizer = self.internal_upload_synchronizer.expect(
"Metal internal upload synchronizer is missing. The most likely cause is that the context was not initialized correctly.",
);
self.submit_metal_command_buffer_for_synchronizer(command_buffer, synchronizer, sequence_index);
}
pub fn new(
settings: crate::device::Features,
device: Retained<ProtocolObject<dyn mtl::MTLDevice>>,
queues: Vec<queue::StoredQueue>,
) -> Result<Context, &'static str> {
let mut context = Context {
device,
frames: MAX_FRAMES_IN_FLIGHT as u8,
queues,
buffers: ResourceCollection::with_capacity(1024),
images: ResourceCollection::with_capacity(1024),
samplers: Vec::new(),
allocations: Vec::new(),
pipeline_layouts: Vec::new(),
vertex_layouts: Vec::new(),
vertex_layout_indices: HashMap::default(),
descriptor_sets: Vec::new(),
meshes: Vec::new(),
acceleration_structures: Vec::new(),
shaders: Vec::new(),
pipelines: Vec::new(),
command_buffers: Vec::new(),
synchronizers: ResourceCollection::with_capacity(32),
internal_upload_synchronizer: None,
swapchains: Vec::new(),
resource_to_descriptor: HashMap::default(),
descriptor_set_to_resource: HashMap::default(),
settings,
states: HashMap::default(),
pending_buffer_syncs: VecDeque::new(),
pending_image_syncs: VecDeque::new(),
tasks: Vec::new(),
#[cfg(debug_assertions)]
names: HashMap::default(),
};
context.internal_upload_synchronizer = Some(context.create_synchronizer(Some("Metal Internal Upload Sync"), true));
Ok(context)
}
pub fn create_factory(&self) -> Option<crate::metal::factory::Factory> {
Some(crate::metal::factory::Factory::new(self.device.clone(), self.settings))
}
fn create_buffer_resource(
&mut self,
name: Option<&str>,
size: usize,
resource_uses: crate::Uses,
device_accesses: crate::DeviceAccesses,
) -> buffer::Buffer {
let options = utils::resource_options_from_access(device_accesses);
let name = crate::debug_name(name);
let buffer = self
.device
.newBufferWithLength_options(size as _, options)
.expect("Metal buffer creation failed. The most likely cause is that the device is out of memory.");
let staging = if device_accesses == crate::DeviceAccesses::DeviceOnly {
Some(
self.device
.newBufferWithLength_options(size as _, mtl::MTLResourceOptions::StorageModeShared)
.expect("Metal staging buffer creation failed. The most likely cause is that the device is out of memory."),
)
} else {
None
};
#[cfg(debug_assertions)]
if self.settings.debug_labels {
if let Some(name) = name.as_deref() {
buffer.setLabel(Some(&NSString::from_str(name)));
if let Some(staging) = staging.as_ref() {
staging.setLabel(Some(&NSString::from_str(&format!("{name}_staging"))));
}
}
}
let pointer = staging
.as_ref()
.map(|staging| staging.contents().as_ptr() as *mut u8)
.unwrap_or_else(|| buffer.contents().as_ptr() as *mut u8);
let gpu_address = buffer.gpuAddress();
let staging = staging.map(|staging| {
let mut creator = self.buffers.creator();
let handle = creator.add(buffer::Buffer {
name: name.as_ref().map(|name| format!("{name}_staging")),
staging: None,
buffer: staging,
size,
gpu_address: 0,
pointer,
uses: resource_uses,
access: crate::DeviceAccesses::HostToDevice,
});
handle
});
buffer::Buffer {
name,
buffer,
staging,
size,
gpu_address,
pointer,
uses: resource_uses,
access: device_accesses,
}
}
fn create_buffer_internal(
&mut self,
previous: Option<BufferHandle>,
name: Option<&str>,
size: usize,
resource_uses: crate::Uses,
device_accesses: crate::DeviceAccesses,
) -> BufferHandle {
let buffer = self.create_buffer_resource(name, size, resource_uses, device_accesses);
if let Some(previous) = previous {
let previous_buffer = self.buffers.resource(previous);
let copy_size = previous_buffer.size.min(buffer.size);
unsafe {
std::ptr::copy_nonoverlapping(previous_buffer.pointer, buffer.pointer, copy_size);
}
}
let (_, handle) = self.buffers.add(buffer);
if let Some(previous) = previous {
self.buffers.set_next(previous, Some(handle));
}
handle
}
pub(super) fn create_image_resource(
&self,
name: Option<&str>,
extent: Extent,
format: crate::Formats,
resource_uses: crate::Uses,
device_accesses: crate::DeviceAccesses,
array_layers: u32,
) -> image::Image {
let name = crate::debug_name(name);
let descriptor = build_texture_descriptor(format, extent, resource_uses, device_accesses, array_layers, 1);
let texture = self
.device
.newTextureWithDescriptor(&descriptor)
.expect("Metal texture creation failed. The most likely cause is that the device is out of memory.");
#[cfg(debug_assertions)]
if self.settings.debug_labels {
if let Some(name) = name.as_deref() {
texture.setLabel(Some(&NSString::from_str(name)));
}
}
let staging = utils::texture_upload_layout(format, extent).map(|(_, _, bytes_per_image)| {
let depth = extent.depth().max(1) as usize;
let size = bytes_per_image * depth * array_layers as usize;
vec![0u8; size]
});
image::Image {
name,
texture,
extent,
format,
uses: resource_uses,
access: device_accesses,
array_layers,
staging,
}
}
fn create_image_internal(
&mut self,
previous: Option<ImageHandle>,
name: Option<&str>,
extent: Extent,
format: crate::Formats,
resource_uses: crate::Uses,
device_accesses: crate::DeviceAccesses,
array_layers: u32,
) -> ImageHandle {
let image = self.create_image_resource(name, extent, format, resource_uses, device_accesses, array_layers);
let (_, handle) = self.images.add(image);
if let Some(previous) = previous {
self.images.set_next(previous, Some(handle));
}
handle
}
fn upload_texture_from_staging(
&mut self,
texture: &ProtocolObject<dyn mtl::MTLTexture>,
format: crate::Formats,
extent: Extent,
array_layers: u32,
staging: &[u8],
queue_handle: Option<graphics_hardware_interface::QueueHandle>,
sequence_index: u8,
) {
let Some((bytes_per_row, row_count, bytes_per_image)) = utils::texture_upload_layout(format, extent) else {
return;
};
let aligned_bytes_per_row = bytes_per_row.next_multiple_of(256);
let aligned_bytes_per_image = aligned_bytes_per_row * row_count;
let upload_size = aligned_bytes_per_image * array_layers as usize;
let upload_buffer = self
.device
.newBufferWithLength_options(upload_size as _, mtl::MTLResourceOptions::StorageModeShared)
.expect("Metal upload buffer creation failed. The most likely cause is that the device is out of memory.");
let destination = upload_buffer.contents().as_ptr() as *mut u8;
for slice in 0..array_layers as usize {
let source_offset = slice * bytes_per_image;
let destination_offset = slice * aligned_bytes_per_image;
let Some(source_bytes) = staging.get(source_offset..source_offset + bytes_per_image) else {
break;
};
for row in 0..row_count {
let source_row_offset = row * bytes_per_row;
let destination_row_offset = destination_offset + row * aligned_bytes_per_row;
unsafe {
std::ptr::copy_nonoverlapping(
source_bytes.as_ptr().add(source_row_offset),
destination.add(destination_row_offset),
bytes_per_row,
);
}
}
}
if utils::is_block_compressed(format) {
let expected_size = bytes_per_image * array_layers as usize;
assert_eq!(
staging.len(),
expected_size,
"Metal compressed texture staging size mismatch. The most likely cause is that CPU staging was not packed as one compact BC image per slice. format={format:?}, extent={extent:?}, array_layers={array_layers}, staging_len={}, expected_size={expected_size}",
staging.len()
);
}
let queue = queue_handle
.and_then(|queue_handle| self.queues.get(queue_handle.0 as usize))
.unwrap_or_else(|| self.transfer_queue());
let command_buffer = self.create_metal_command_buffer(
queue.queue.as_ref(),
Some("Texture Upload"),
"Metal texture upload command buffer creation failed. The most likely cause is that the transfer queue did not provide a command buffer.",
);
let blit_encoder = command_buffer.blitCommandEncoder().expect(
"Metal blit command encoder creation failed. The most likely cause is that the command buffer is in an invalid state.",
);
#[cfg(debug_assertions)]
if self.settings.debug_labels {
blit_encoder.setLabel(Some(&NSString::from_str("Texture Upload")));
}
let mut source_size = utils::texture_copy_size(format, extent);
source_size.depth = 1;
let destination_origin = mtl::MTLOrigin { x: 0, y: 0, z: 0 };
for slice in 0..array_layers as usize {
unsafe {
blit_encoder.copyFromBuffer_sourceOffset_sourceBytesPerRow_sourceBytesPerImage_sourceSize_toTexture_destinationSlice_destinationLevel_destinationOrigin(
upload_buffer.as_ref(),
(slice * aligned_bytes_per_image) as _,
aligned_bytes_per_row as _,
aligned_bytes_per_image as _,
source_size,
texture,
slice,
0,
destination_origin,
);
}
}
blit_encoder.endEncoding();
self.submit_internal_metal_command_buffer(command_buffer, sequence_index);
}
pub(crate) fn update_descriptor_slot(
&mut self,
set_handle: DescriptorSetHandle,
slot: crate::shader::ResourceSlot,
descriptor: Descriptor,
frame_index: u8,
array_element: u32,
) {
let previous = self.descriptor_sets[set_handle.0 as usize]
.descriptors
.get(&slot)
.and_then(|descriptors| descriptors.get(&array_element))
.copied();
if previous == Some(descriptor) {
return;
}
self.clear_descriptor_tracking(set_handle, slot, array_element, frame_index);
let descriptor_set = &mut self.descriptor_sets[set_handle.0 as usize];
descriptor_set
.descriptors
.entry(slot)
.or_default()
.insert(array_element, descriptor);
descriptor_set.version = descriptor_set.version.wrapping_add(1);
self.register_descriptor_tracking(set_handle, slot, descriptor, array_element, frame_index);
}
fn clear_descriptor_tracking(
&mut self,
set_handle: DescriptorSetHandle,
slot: crate::shader::ResourceSlot,
array_element: u32,
frame_index: u8,
) {
let key = (set_handle, slot, array_element, frame_index);
let Some(resources) = self.descriptor_set_to_resource.remove(&key) else {
return;
};
for resource in resources {
let should_remove = if let Some(descriptor_bindings) = self.resource_to_descriptor.get_mut(&resource) {
descriptor_bindings.remove(&(set_handle, slot, array_element, frame_index));
descriptor_bindings.is_empty()
} else {
false
};
if should_remove {
self.resource_to_descriptor.remove(&resource);
}
}
}
fn register_descriptor_tracking(
&mut self,
set_handle: DescriptorSetHandle,
slot: crate::shader::ResourceSlot,
descriptor: Descriptor,
array_element: u32,
frame_index: u8,
) {
let Some(resource) = descriptor.tracked_resource() else {
return;
};
self.descriptor_set_to_resource
.entry((set_handle, slot, array_element, frame_index))
.or_default()
.insert(resource);
self.resource_to_descriptor
.entry(resource)
.or_default()
.insert((set_handle, slot, array_element, frame_index));
}
fn resolve_descriptor_for_frame(
&self,
descriptor: crate::descriptors::WriteData,
sequence_index: u8,
frame_offset: i32,
) -> Option<Descriptor> {
let resource_frame_index = (sequence_index as i32 - frame_offset).rem_euclid(self.frames as i32) as usize;
match descriptor {
crate::descriptors::WriteData::Buffer { handle, size } => {
let handle = self.buffers.nth_handle(handle, resource_frame_index)?;
Some(Descriptor::Buffer { buffer: handle, size })
}
crate::descriptors::WriteData::Image { handle, layout } => {
let handle = self.images.nth_handle(handle, resource_frame_index)?;
Some(Descriptor::Image { image: handle, layout })
}
crate::descriptors::WriteData::CombinedImageSampler {
image_handle,
sampler_handle,
layout,
..
} => {
let handle = self.images.nth_handle(image_handle, resource_frame_index)?;
Some(Descriptor::CombinedImageSampler {
image: handle,
sampler: SamplerHandle(sampler_handle.0),
layout,
})
}
crate::descriptors::WriteData::Sampler(handle) => Some(Descriptor::Sampler {
sampler: SamplerHandle(handle.0),
}),
crate::descriptors::WriteData::StaticSamplers => None,
crate::descriptors::WriteData::CombinedImageSamplerArray => None,
crate::descriptors::WriteData::AccelerationStructure { handle } => Some(Descriptor::AccelerationStructure {
handle: TopLevelAccelerationStructureHandle(handle.0),
}),
crate::descriptors::WriteData::Swapchain(swapchain_handle) => Some(Descriptor::Swapchain {
handle: crate::swapchain::SwapchainHandle(swapchain_handle.0),
}),
}
}
fn apply_descriptor_write_for_frame(
&mut self,
set_handle: DescriptorSetHandle,
slot: crate::shader::ResourceSlot,
descriptor: crate::descriptors::WriteData,
array_element: u32,
frame_offset: i32,
sequence_index: u8,
) {
if let Some(descriptor) = self.resolve_descriptor_for_frame(descriptor, sequence_index, frame_offset) {
self.update_descriptor_slot(set_handle, slot, descriptor, sequence_index, array_element);
}
}
fn apply_descriptor_write_to_all_frames(
&mut self,
set_handle: DescriptorSetHandle,
slot: crate::shader::ResourceSlot,
descriptor: crate::descriptors::WriteData,
array_element: u32,
frame_offset: i32,
) {
let set_handles = set_handle.root(&self.descriptor_sets).get_all(&self.descriptor_sets);
for (sequence_index, &set_handle) in set_handles.iter().enumerate() {
self.apply_descriptor_write_for_frame(
set_handle,
slot,
descriptor,
array_element,
frame_offset,
sequence_index as u8,
);
}
}
pub(crate) fn rewrite_descriptors_for_handle(&mut self, handle: PrivateHandles) {
let Some(descriptor_bindings) = self.resource_to_descriptor.get(&handle).cloned() else {
return;
};
for (set_handle, ..) in descriptor_bindings {
let descriptor_set = &mut self.descriptor_sets[set_handle.0 as usize];
descriptor_set.version = descriptor_set.version.wrapping_add(1);
}
}
fn buffer_chain_handles(&self, master: graphics_hardware_interface::BaseBufferHandle) -> Vec<PrivateHandles> {
let mut handles = Vec::with_capacity(self.frames as usize);
for frame_index in 0..self.frames as usize {
let Some(handle) = self.buffers.nth_handle(master, frame_index) else {
continue;
};
let handle = PrivateHandles::Buffer(handle);
if !handles.contains(&handle) {
handles.push(handle);
}
}
handles
}
fn image_chain_handles(&self, master: graphics_hardware_interface::BaseImageHandle) -> Vec<PrivateHandles> {
let mut handles = Vec::with_capacity(self.frames as usize);
for frame_index in 0..self.frames as usize {
let Some(handle) = self.images.nth_handle(master, frame_index) else {
continue;
};
let handle = PrivateHandles::Image(handle);
if !handles.contains(&handle) {
handles.push(handle);
}
}
handles
}
fn rewrite_deferred_descriptors(&mut self, candidates: &[PrivateHandles], replacement: PrivateHandles, frame_index: u8) {
let descriptor_bindings = candidates
.iter()
.copied()
.filter(|candidate| *candidate != replacement)
.filter_map(|candidate| self.resource_to_descriptor.get(&candidate))
.flat_map(|bindings| bindings.iter().copied())
.filter(|(_, _, _, descriptor_frame_index)| *descriptor_frame_index == frame_index)
.collect::<HashSet<_>>();
for (set_handle, slot, array_element, _) in descriptor_bindings {
let Some(descriptor) = self.descriptor_sets[set_handle.0 as usize]
.descriptors
.get(&slot)
.and_then(|descriptors| descriptors.get(&array_element))
.copied()
else {
continue;
};
let descriptor = match (descriptor, replacement) {
(Descriptor::Buffer { size, .. }, PrivateHandles::Buffer(buffer)) => Descriptor::Buffer { buffer, size },
(Descriptor::Image { layout, .. }, PrivateHandles::Image(image)) => Descriptor::Image { image, layout },
(Descriptor::CombinedImageSampler { sampler, layout, .. }, PrivateHandles::Image(image)) => {
Descriptor::CombinedImageSampler { image, sampler, layout }
}
_ => continue,
};
self.update_descriptor_slot(set_handle, slot, descriptor, frame_index, array_element);
}
}
pub(crate) fn resize_swapchain_images(
&mut self,
swapchain_handle: graphics_hardware_interface::SwapchainHandle,
extent: Extent,
) {
let image_handles = self.swapchains[swapchain_handle.0 as usize].images;
let mut resized = false;
for image_handle in image_handles.into_iter().flatten() {
let (current_extent, format, uses, access, array_layers) = {
let image = self.images.resource(image_handle);
(image.extent, image.format, image.uses, image.access, image.array_layers)
};
if current_extent == extent {
continue;
}
let name = self.images.resource(image_handle).name.clone();
let replacement = self.create_image_resource(name.as_deref(), extent, format, uses, access, array_layers);
*self.images.resource_mut(image_handle) = replacement;
self.rewrite_descriptors_for_handle(PrivateHandles::Image(image_handle));
resized = true;
}
if resized {
self.rewrite_descriptors_for_handle(PrivateHandles::Swapchain(crate::swapchain::SwapchainHandle(
swapchain_handle.0,
)));
}
}
pub(crate) fn process_tasks(&mut self, sequence_index: u8) {
let mut tasks = std::mem::take(&mut self.tasks);
let mut deferred_frame_tasks = SmallVec::<[Task; 16]>::new();
tasks.retain(|task| {
if let Some(frame) = task.frame() {
if frame != sequence_index {
return true;
}
}
match task.task() {
Tasks::UpdateBufferDescriptors { handle } => {
self.rewrite_descriptors_for_handle(PrivateHandles::Buffer(*handle));
}
Tasks::UpdateImageDescriptors { handle } => {
self.rewrite_descriptors_for_handle(PrivateHandles::Image(*handle));
}
Tasks::BuildImage(builder) => {
let previous = self.images.resource(builder.previous);
let name = previous.name.clone();
let extent = previous.extent;
let format = previous.format;
let uses = previous.uses;
let access = previous.access;
let array_layers = previous.array_layers;
let handle = self.create_image_internal(
Some(builder.previous),
name.as_deref(),
extent,
format,
uses,
access,
array_layers,
);
let candidates = self.image_chain_handles(builder.master.0);
self.rewrite_deferred_descriptors(&candidates, PrivateHandles::Image(handle), sequence_index);
let next_frame = sequence_index + 1;
if next_frame < self.frames {
deferred_frame_tasks.push(Task::new(
Tasks::BuildImage(BuildImage {
previous: handle,
master: builder.master,
}),
Some(next_frame),
));
}
}
Tasks::BuildBuffer(builder) => {
let previous = self.buffers.resource(builder.previous);
let name = previous.name.clone();
let size = previous.size;
let uses = previous.uses;
let access = previous.access;
let handle = self.create_buffer_internal(Some(builder.previous), name.as_deref(), size, uses, access);
let candidates = self.buffer_chain_handles(builder.master);
self.rewrite_deferred_descriptors(&candidates, PrivateHandles::Buffer(handle), sequence_index);
let next_frame = sequence_index + 1;
if next_frame < self.frames {
deferred_frame_tasks.push(Task::new(
Tasks::BuildBuffer(BuildBuffer {
previous: handle,
master: builder.master,
}),
Some(next_frame),
));
}
}
Tasks::DeleteMetalTexture { .. } | Tasks::DeleteMetalBuffer { .. } | Tasks::ResizeImage { .. } => {}
}
false
});
tasks.extend(deferred_frame_tasks);
self.tasks = tasks;
}
}
impl Context {
#[cfg(any(debug_assertions, test))]
pub fn has_errors(&self) -> bool {
false
}
pub fn set_frames_in_flight(&mut self, frames: u8) {
self.frames = frames.max(1);
}
pub fn create_allocation(
&mut self,
size: usize,
_resource_uses: crate::Uses,
device_accesses: crate::DeviceAccesses,
) -> graphics_hardware_interface::AllocationHandle {
let options = utils::resource_options_from_access(device_accesses);
let buffer = self
.device
.newBufferWithLength_options(size as _, options)
.expect("Metal allocation failed. The most likely cause is that the device is out of memory.");
let pointer = buffer.contents().as_ptr() as *mut u8;
self.allocations.push(Allocation { buffer, pointer, size });
graphics_hardware_interface::AllocationHandle((self.allocations.len() - 1) as u64)
}
pub fn add_mesh_from_vertices_and_indices(
&mut self,
vertex_count: u32,
index_count: u32,
vertices: &[u8],
indices: &[u8],
vertex_layout: &[crate::pipelines::VertexElement],
) -> graphics_hardware_interface::MeshHandle {
let options = mtl::MTLResourceOptions::StorageModeShared;
let index_ptr = NonNull::new(indices.as_ptr() as *mut std::ffi::c_void)
.expect("Index data pointer was null. The most likely cause is an empty index slice.");
let index_buffer = unsafe {
self.device
.newBufferWithBytes_length_options(index_ptr, indices.len() as _, options)
}
.expect("Metal index buffer creation failed. The most likely cause is that the device is out of memory.");
let vertex_size = vertex_layout.iter().map(|element| element.format.size()).sum();
let max_binding = vertex_layout
.iter()
.map(|element| element.binding)
.max()
.map(|binding| binding as usize + 1)
.unwrap_or(0);
let mut binding_spans = vec![Vec::<(usize, usize, usize)>::new(); max_binding];
let mut source_offset = 0usize;
for element in vertex_layout {
let element_size = element.format.size();
let binding = element.binding as usize;
let destination_offset = binding_spans[binding]
.last()
.map(|(_, destination_offset, size)| destination_offset + size)
.unwrap_or(0);
binding_spans[binding].push((source_offset, destination_offset, element_size));
source_offset += element_size;
}
let vertex_buffers = binding_spans
.iter()
.map(|spans| {
if spans.is_empty() {
return None;
}
let binding_stride = spans
.last()
.map(|(_, destination_offset, size)| destination_offset + size)
.unwrap_or(0);
let mut binding_vertices = vec![0u8; binding_stride * vertex_count as usize];
for vertex_index in 0..vertex_count as usize {
let source_vertex_offset = vertex_index * vertex_size;
let destination_vertex_offset = vertex_index * binding_stride;
for &(span_source_offset, span_destination_offset, span_size) in spans {
let source_range =
source_vertex_offset + span_source_offset..source_vertex_offset + span_source_offset + span_size;
let destination_range = destination_vertex_offset + span_destination_offset
..destination_vertex_offset + span_destination_offset + span_size;
binding_vertices[destination_range].copy_from_slice(&vertices[source_range]);
}
}
let vertex_ptr = NonNull::new(binding_vertices.as_ptr() as *mut std::ffi::c_void)
.expect("Vertex data pointer was null. The most likely cause is an empty vertex slice.");
Some(
unsafe {
self.device
.newBufferWithBytes_length_options(vertex_ptr, binding_vertices.len() as _, options)
}
.expect("Metal vertex buffer creation failed. The most likely cause is that the device is out of memory."),
)
})
.collect::<Vec<_>>();
self.meshes.push(Mesh {
vertex_buffers,
index_buffer,
vertex_count,
index_count,
vertex_size,
});
graphics_hardware_interface::MeshHandle((self.meshes.len() - 1) as u64)
}
pub fn create_shader(
&mut self,
name: Option<&str>,
shader_source_type: crate::shader::Sources,
stage: crate::ShaderTypes,
shader_resource_descriptors: impl IntoIterator<Item = crate::shader::ShaderResourceDescriptor>,
) -> Result<graphics_hardware_interface::ShaderHandle, ()> {
let (metal_library, metal_entry_point, threadgroup_size) = match shader_source_type {
crate::shader::Sources::SPIRV(_) => {
eprintln!(
"Metal shader creation failed for {:?} shader {:?}. The most likely cause is that SPIR-V was supplied to the Metal backend without translation to MSL or MTLB.",
stage,
name.unwrap_or("<unnamed>"),
);
return Err(());
}
crate::shader::Sources::DXIL(_) | crate::shader::Sources::HLSL { .. } => return Err(()),
crate::shader::Sources::MTLB {
binary,
entry_point,
threadgroup_size,
} => {
let data = DispatchData::from_bytes(binary);
let library = self.device.newLibraryWithData_error(&data).map_err(|error| {
eprintln!("Metal shader library load failed: {}", error.localizedDescription());
})?;
(Some(library), Some(entry_point.to_owned()), threadgroup_size)
}
crate::shader::Sources::MTL { source, entry_point } => {
let threadgroup_size = match stage {
crate::ShaderTypes::Task | crate::ShaderTypes::Mesh | crate::ShaderTypes::Compute => {
parse_threadgroup_size_metadata(source)
}
_ => None,
};
let compile_options = mtl::MTLCompileOptions::new();
let source = NSString::from_str(source);
let library = self
.device
.newLibraryWithSource_options_error(&source, Some(&compile_options))
.map_err(|error| {
eprintln!("Metal shader compilation failed: {}", error.localizedDescription());
})?;
(Some(library), Some(entry_point.to_owned()), threadgroup_size)
}
};
let stages = stage.into();
self.shaders.push(Shader {
name: crate::debug_name(name),
stage: stages,
shader_resource_descriptors: shader_resource_descriptors.into_iter().collect(),
metal_library,
metal_entry_point,
threadgroup_size,
});
Ok(graphics_hardware_interface::ShaderHandle((self.shaders.len() - 1) as u64))
}
fn create_metal_function(
&self,
shader_parameter: &crate::pipelines::ShaderParameter,
) -> Option<Retained<ProtocolObject<dyn mtl::MTLFunction>>> {
let shader = &self.shaders[shader_parameter.handle.0 as usize];
let library = shader.metal_library.as_ref()?;
let entry_point = shader.metal_entry_point.as_ref()?;
let entry_point = NSString::from_str(entry_point);
let constant_values = mtl::MTLFunctionConstantValues::new();
for specialization_map_entry in shader_parameter.specialization_map {
apply_specialization_map_entry(&constant_values, specialization_map_entry);
}
library
.newFunctionWithName_constantValues_error(&entry_point, &constant_values)
.map_err(|error| {
eprintln!("Metal shader specialization failed: {}", error.localizedDescription());
})
.ok()
}
pub fn create_descriptor_set(&mut self, _name: Option<&str>) -> graphics_hardware_interface::DescriptorSetHandle {
let handle = graphics_hardware_interface::DescriptorSetHandle(self.descriptor_sets.len() as u64);
let mut previous_handle: Option<DescriptorSetHandle> = None;
for _ in 0..self.frames {
let descriptor_set_handle = DescriptorSetHandle(self.descriptor_sets.len() as u64);
self.descriptor_sets.push(descriptor_set::DescriptorSet {
next: None,
version: 0,
descriptors: HashMap::default(),
});
if let Some(previous_handle) = previous_handle {
self.descriptor_sets[previous_handle.0 as usize].next = Some(descriptor_set_handle);
}
previous_handle = Some(descriptor_set_handle);
}
handle
}
fn create_pipeline_layout(
&mut self,
shaders: &[crate::pipelines::ShaderParameter],
push_constant_ranges: &[crate::pipelines::PushConstantRange],
) -> graphics_hardware_interface::PipelineLayoutHandle {
let stage_resources = shaders
.iter()
.map(|shader_parameter| {
let shader = &self.shaders[shader_parameter.handle.0 as usize];
(shader.stage, shader.shader_resource_descriptors.clone())
})
.collect::<Vec<_>>();
let layout = build_pipeline_layout(self.device.as_ref(), &stage_resources, push_constant_ranges);
self.pipeline_layouts.push(layout);
graphics_hardware_interface::PipelineLayoutHandle((self.pipeline_layouts.len() - 1) as u64)
}
fn intern_pipeline_layout(&mut self, layout: PipelineLayout) -> graphics_hardware_interface::PipelineLayoutHandle {
self.pipeline_layouts.push(layout);
graphics_hardware_interface::PipelineLayoutHandle((self.pipeline_layouts.len() - 1) as u64)
}
fn get_or_create_vertex_layout(&mut self, vertex_elements: &[crate::pipelines::VertexElement]) -> VertexLayoutHandle {
let elements = vertex_elements
.iter()
.map(|element| VertexElementDescriptor {
name: element.name.to_owned(),
format: element.format,
binding: element.binding,
})
.collect::<Vec<_>>();
let key = VertexLayoutKey {
elements: elements.clone(),
};
if let Some(handle) = self.vertex_layout_indices.get(&key) {
return *handle;
}
let max_binding = elements
.iter()
.map(|element| element.binding)
.max()
.map(|binding| binding as usize + 1)
.unwrap_or(0);
let mut strides = vec![0; max_binding];
let vertex_descriptor = mtl::MTLVertexDescriptor::vertexDescriptor();
let mut binding_offsets = vec![0usize; max_binding];
for (attribute_index, element) in elements.iter().enumerate() {
strides[element.binding as usize] += element.format.size() as u32;
let offset = binding_offsets[element.binding as usize];
let attribute = unsafe { vertex_descriptor.attributes().objectAtIndexedSubscript(attribute_index as _) };
attribute.setFormat(utils::vertex_format(element.format));
unsafe {
attribute.setOffset(offset as _);
attribute.setBufferIndex(element.binding as _);
}
binding_offsets[element.binding as usize] += element.format.size();
}
for (binding, stride) in strides.iter().copied().enumerate() {
let layout = unsafe { vertex_descriptor.layouts().objectAtIndexedSubscript(binding as _) };
unsafe {
layout.setStride(stride as _);
layout.setStepRate(1);
}
layout.setStepFunction(mtl::MTLVertexStepFunction::PerVertex);
}
self.vertex_layouts.push(VertexLayout {
elements,
strides,
vertex_descriptor,
});
let handle = VertexLayoutHandle((self.vertex_layouts.len() - 1) as u64);
self.vertex_layout_indices.insert(key, handle);
handle
}
fn get_or_create_vertex_layout_from_prebuilt(&mut self, vertex_layout: VertexLayout) -> VertexLayoutHandle {
let key = VertexLayoutKey {
elements: vertex_layout.elements.clone(),
};
if let Some(handle) = self.vertex_layout_indices.get(&key) {
return *handle;
}
self.vertex_layouts.push(vertex_layout);
let handle = VertexLayoutHandle((self.vertex_layouts.len() - 1) as u64);
self.vertex_layout_indices.insert(key, handle);
handle
}
fn intern_pipeline(&mut self, pipeline: Pipeline) -> graphics_hardware_interface::PipelineHandle {
self.pipelines.push(pipeline);
graphics_hardware_interface::PipelineHandle((self.pipelines.len() - 1) as u64)
}
pub fn intern_raster_pipeline(
&mut self,
pipeline: crate::metal::device::Pipeline,
) -> graphics_hardware_interface::PipelineHandle {
let layout = self.intern_pipeline_layout(pipeline.layout);
let vertex_layout = pipeline
.vertex_layout
.map(|vertex_layout| self.get_or_create_vertex_layout_from_prebuilt(vertex_layout));
self.intern_pipeline(Pipeline {
pipeline: pipeline.pipeline,
depth_stencil_state: pipeline.depth_stencil_state,
layout,
vertex_layout,
shader_handles: pipeline.shader_handles,
materializations: RefCell::new(HashMap::default()),
compute_threadgroup_size: pipeline.compute_threadgroup_size,
object_threadgroup_size: pipeline.object_threadgroup_size,
mesh_threadgroup_size: pipeline.mesh_threadgroup_size,
face_winding: pipeline.face_winding,
cull_mode: pipeline.cull_mode,
})
}
pub fn intern_compute_pipeline(
&mut self,
pipeline: crate::metal::device::ComputePipeline,
) -> graphics_hardware_interface::PipelineHandle {
let layout = self.intern_pipeline_layout(pipeline.layout);
self.intern_pipeline(Pipeline {
pipeline: pipeline.pipeline,
depth_stencil_state: pipeline.depth_stencil_state,
layout,
vertex_layout: None,
shader_handles: pipeline.shader_handles,
materializations: RefCell::new(HashMap::default()),
compute_threadgroup_size: pipeline.compute_threadgroup_size,
object_threadgroup_size: pipeline.object_threadgroup_size,
mesh_threadgroup_size: pipeline.mesh_threadgroup_size,
face_winding: pipeline.face_winding,
cull_mode: pipeline.cull_mode,
})
}
pub fn intern_image(&mut self, image: crate::metal::device::Image) -> graphics_hardware_interface::ImageHandle {
let name = image.image.name.clone();
let (root_image_handle, _) = self.images.add(image.image);
let handle = graphics_hardware_interface::ImageHandle(root_image_handle);
#[cfg(debug_assertions)]
{
if let Some(name) = name {
self.names.insert(graphics_hardware_interface::Handles::Image(handle), name);
}
}
handle
}
pub fn intern_sampler(&mut self, sampler: crate::metal::device::Sampler) -> graphics_hardware_interface::SamplerHandle {
self.samplers.push(sampler.sampler);
graphics_hardware_interface::SamplerHandle((self.samplers.len() - 1) as u64)
}
pub fn create_raster_pipeline(&mut self, builder: raster_pipeline::Builder) -> graphics_hardware_interface::PipelineHandle {
let layout = self.create_pipeline_layout(builder.shaders.as_ref(), builder.push_constant_ranges.as_ref());
let has_depth_attachment = builder
.render_targets
.iter()
.any(|attachment| attachment.format.channel_layout() == crate::ChannelLayout::Depth);
let vertex_layout = self.get_or_create_vertex_layout(builder.vertex_elements.as_ref());
let mut shader_handles = HashMap::default();
let mut object_function = None;
let mut vertex_function = None;
let mut mesh_function = None;
let mut fragment_function = None;
let mut object_threadgroup_size = None;
let mut mesh_threadgroup_size = None;
for shader_parameter in builder.shaders.iter() {
let shader = &self.shaders[shader_parameter.handle.0 as usize];
shader_handles.insert(*shader_parameter.handle, [0; 32]);
match shader_parameter.stage {
crate::ShaderTypes::Task => {
object_function = self.create_metal_function(shader_parameter);
object_threadgroup_size = shader.threadgroup_size;
}
crate::ShaderTypes::Vertex => vertex_function = self.create_metal_function(shader_parameter),
crate::ShaderTypes::Mesh => {
mesh_function = self.create_metal_function(shader_parameter);
mesh_threadgroup_size = shader.threadgroup_size;
}
crate::ShaderTypes::Fragment => fragment_function = self.create_metal_function(shader_parameter),
_ => {}
}
}
let depth_stencil_state = if has_depth_attachment {
let descriptor = mtl::MTLDepthStencilDescriptor::new();
descriptor.setDepthCompareFunction(mtl::MTLCompareFunction::GreaterEqual);
descriptor.setDepthWriteEnabled(builder.depth_write);
self.device.newDepthStencilStateWithDescriptor(&descriptor)
} else {
None
};
let raster_pipeline_state = if let Some(mesh_function) = mesh_function.as_ref() {
let descriptor = mtl::MTLMeshRenderPipelineDescriptor::new();
#[cfg(debug_assertions)]
if self.settings.debug_labels {
descriptor.setLabel(Some(&NSString::from_str("mesh_pipeline")));
}
unsafe {
descriptor.setObjectFunction(object_function.as_ref().map(|function| function.as_ref()));
descriptor.setMeshFunction(Some(mesh_function.as_ref()));
descriptor.setFragmentFunction(fragment_function.as_ref().map(|function| function.as_ref()));
}
configure_mesh_render_targets(&descriptor, builder.render_targets.as_ref());
self.device
.newRenderPipelineStateWithMeshDescriptor_options_reflection_error(
&descriptor,
mtl::MTLPipelineOption::None,
None,
)
.unwrap_or_else(|error| {
panic!(
"Metal mesh raster pipeline creation failed: {}. The most likely cause is invalid shader functions or render-target state in the raster pipeline descriptor.",
error.localizedDescription(),
)
})
.into()
} else if let Some(vertex_function) = vertex_function.as_ref() {
let descriptor = mtl::MTLRenderPipelineDescriptor::new();
#[cfg(debug_assertions)]
if self.settings.debug_labels {
descriptor.setLabel(Some(&NSString::from_str("raster_pipeline")));
}
descriptor.setVertexFunction(Some(vertex_function.as_ref()));
descriptor.setFragmentFunction(fragment_function.as_ref().map(|function| function.as_ref()));
descriptor.setVertexDescriptor(Some(&self.vertex_layouts[vertex_layout.0 as usize].vertex_descriptor));
configure_render_targets(&descriptor, builder.render_targets.as_ref());
self.device
.newRenderPipelineStateWithDescriptor_error(&descriptor)
.unwrap_or_else(|error| {
panic!(
"Metal raster pipeline creation failed: {}. The most likely cause is invalid shader functions or render-target state in the raster pipeline descriptor.",
error.localizedDescription(),
)
})
.into()
} else {
let shader_names = builder
.shaders
.iter()
.map(|shader_parameter| {
let shader = &self.shaders[shader_parameter.handle.0 as usize];
format!(
"{:?} {:?}",
shader_parameter.stage,
shader.name.as_deref().unwrap_or("<unnamed>")
)
})
.collect::<Vec<_>>()
.join(", ");
panic!(
"Metal raster pipeline creation failed because no vertex or mesh shader function was available. The most likely cause is shader creation failed or SPIR-V was supplied to the Metal backend without translation to MSL or MTLB. Shaders: {shader_names}",
);
};
self.pipelines.push(Pipeline {
pipeline: PipelineState::Raster(raster_pipeline_state),
depth_stencil_state,
layout,
vertex_layout: Some(vertex_layout),
shader_handles,
materializations: RefCell::new(HashMap::default()),
compute_threadgroup_size: None,
object_threadgroup_size,
mesh_threadgroup_size,
face_winding: builder.face_winding,
cull_mode: builder.cull_mode,
});
graphics_hardware_interface::PipelineHandle((self.pipelines.len() - 1) as u64)
}
pub fn create_compute_pipeline(
&mut self,
builder: crate::pipelines::compute::Builder,
) -> graphics_hardware_interface::PipelineHandle {
let layout = self.create_pipeline_layout(std::slice::from_ref(&builder.shader), builder.push_constant_ranges);
let shader_handle = *builder.shader.handle;
let compute_pipeline_state = {
let shader_parameter = &builder.shader;
let shader = &self.shaders[shader_handle.0 as usize];
assert!(
shader.stage == crate::Stages::COMPUTE,
"Metal compute pipeline creation requires a compute shader. The most likely cause is that a non-compute shader was passed to compute::Builder.",
);
let function = self.create_metal_function(shader_parameter).expect(
"Metal compute pipeline creation requires a Metal shader function. The most likely cause is that this compute shader was created from SPIR-V, which this backend does not translate to MSL.",
);
Some(
self.device
.newComputePipelineStateWithFunction_error(&function)
.expect("Metal compute pipeline creation failed. The most likely cause is that the shader function was invalid for compute pipeline creation."),
)
};
let mut shader_handles = HashMap::default();
shader_handles.insert(shader_handle, [0; 32]);
let compute_threadgroup_size = self.shaders[shader_handle.0 as usize].threadgroup_size;
self.pipelines.push(Pipeline {
pipeline: PipelineState::Compute(compute_pipeline_state),
depth_stencil_state: None,
layout,
vertex_layout: None,
shader_handles,
materializations: RefCell::new(HashMap::default()),
compute_threadgroup_size,
object_threadgroup_size: None,
mesh_threadgroup_size: None,
face_winding: crate::pipelines::raster::FaceWinding::Clockwise,
cull_mode: crate::pipelines::raster::CullMode::Back,
});
graphics_hardware_interface::PipelineHandle((self.pipelines.len() - 1) as u64)
}
pub fn create_ray_tracing_pipeline(
&mut self,
builder: crate::pipelines::ray_tracing::Builder,
) -> graphics_hardware_interface::PipelineHandle {
let layout = self.create_pipeline_layout(builder.shaders.as_ref(), builder.push_constant_ranges.as_ref());
self.pipelines.push(Pipeline {
pipeline: PipelineState::RayTracing,
depth_stencil_state: None,
layout,
vertex_layout: None,
shader_handles: HashMap::default(),
materializations: RefCell::new(HashMap::default()),
compute_threadgroup_size: None,
object_threadgroup_size: None,
mesh_threadgroup_size: None,
face_winding: crate::pipelines::raster::FaceWinding::Clockwise,
cull_mode: crate::pipelines::raster::CullMode::Back,
});
graphics_hardware_interface::PipelineHandle((self.pipelines.len() - 1) as u64)
}
pub(crate) fn create_command_buffer(
&mut self,
name: Option<&str>,
queue_handle: graphics_hardware_interface::QueueHandle,
) -> graphics_hardware_interface::CommandBufferHandle {
self.command_buffers.push(StoredCommandBuffer {
queue_handle,
name: crate::debug_name(name),
});
graphics_hardware_interface::CommandBufferHandle((self.command_buffers.len() - 1) as u64)
}
pub(crate) fn create_command_buffer_recording<'a>(
&'a mut self,
command_buffer_handle: graphics_hardware_interface::CommandBufferHandle,
) -> super::CommandBufferRecording<'a> {
self.create_command_buffer_recording_with_frame_key(command_buffer_handle, None)
}
pub(crate) fn create_command_buffer_recording_with_frame_key<'a>(
&'a mut self,
command_buffer_handle: graphics_hardware_interface::CommandBufferHandle,
frame_key: Option<graphics_hardware_interface::FrameKey>,
) -> super::CommandBufferRecording<'a> {
let autorelease_pool = frame_key.is_none().then(|| unsafe { NSAutoreleasePool::new() });
let sequence_index = frame_key.map(|key| key.sequence_index).unwrap_or(0);
let (queue_handle, command_buffer_name) = {
let command_buffer = &self.command_buffers[command_buffer_handle.0 as usize];
let name = self.settings.debug_labels.then(|| command_buffer.name.clone()).flatten();
(command_buffer.queue_handle, name)
};
self.flush_pending_uploads(Some(queue_handle), sequence_index);
let queue = &self.queues[queue_handle.0 as usize];
let mtl_command_buffer = self.create_metal_command_buffer(
queue.queue.as_ref(),
command_buffer_name.as_deref(),
"Metal command buffer creation failed. The most likely cause is that the command queue did not provide a command buffer.",
);
let recording_device = super::command_buffer::RecordingDevice {
metal_device: self.device.as_ref(),
buffers: &self.buffers,
images: &self.images,
samplers: &self.samplers,
acceleration_structures: &self.acceleration_structures,
pipeline_layouts: &self.pipeline_layouts,
descriptor_sets: &self.descriptor_sets,
meshes: &self.meshes,
pipelines: &self.pipelines,
swapchains: &self.swapchains,
debug_labels: self.settings.debug_labels,
};
let commit = super::command_buffer::RecordingCommit {
states: &mut self.states,
synchronizers: &mut self.synchronizers,
};
super::CommandBufferRecording::new(
recording_device,
Some(commit),
command_buffer_handle,
mtl_command_buffer,
frame_key,
SmallVec::new(),
autorelease_pool,
)
}
pub fn build_buffer<T: Copy>(&mut self, builder: buffer_builder::Builder) -> graphics_hardware_interface::BufferHandle<T> {
let size = std::mem::size_of::<T>();
let handle = self.create_buffer_internal(None, builder.name, size, builder.resource_uses, builder.device_accesses);
graphics_hardware_interface::BufferHandle::<T>(
graphics_hardware_interface::BaseBufferHandle::new(handle.0),
std::marker::PhantomData,
)
}
pub fn build_dynamic_buffer<T: Copy>(
&mut self,
builder: buffer_builder::Builder,
) -> graphics_hardware_interface::DynamicBufferHandle<T> {
let size = std::mem::size_of::<T>();
let root = self.create_buffer_internal(None, builder.name, size, builder.resource_uses, builder.device_accesses);
let master = graphics_hardware_interface::BaseBufferHandle::new(root.0);
if self.frames > 1 {
self.tasks
.push(Task::new(Tasks::BuildBuffer(BuildBuffer { previous: root, master }), Some(1)));
}
graphics_hardware_interface::DynamicBufferHandle::<T>(master, std::marker::PhantomData)
}
pub fn queue(&mut self, queue_handle: graphics_hardware_interface::QueueHandle) -> queue::Queue {
queue::Queue {
device: std::ptr::NonNull::from(self),
queue_handle,
}
}
pub fn queue_reference<'a>(
&'a mut self,
queue_handle: graphics_hardware_interface::QueueHandle,
) -> queue::QueueReference<'a> {
queue::QueueReference {
device: self,
queue_handle,
}
}
fn transfer_queue(&self) -> &queue::StoredQueue {
self.queues
.iter()
.find(|queue| queue.workloads.intersects(crate::WorkloadTypes::TRANSFER))
.or_else(|| self.queues.first())
.expect(
"Metal transfer queue lookup failed. The most likely cause is that the device was created without any command queues.",
)
}
pub fn command_buffer<'a>(
&'a mut self,
command_buffer_handle: graphics_hardware_interface::CommandBufferHandle,
) -> super::CommandBuffer<'a> {
super::CommandBuffer {
device: self,
command_buffer_handle,
}
}
pub fn build_dynamic_image(&mut self, builder: image_builder::Builder) -> graphics_hardware_interface::DynamicImageHandle {
let layers = builder.array_layers.map(|l| l.get()).unwrap_or(1);
let root = self.create_image_internal(
None,
builder.get_name(),
builder.extent,
builder.format,
builder.resource_uses,
builder.device_accesses,
layers,
);
let master = graphics_hardware_interface::BaseImageHandle::new(root.0);
if self.frames > 1 {
self.tasks.push(Task::new(
Tasks::BuildImage(BuildImage {
previous: root,
master: graphics_hardware_interface::ImageHandle(master),
}),
Some(1),
));
}
graphics_hardware_interface::DynamicImageHandle(master)
}
pub fn get_buffer_address(&self, buffer_handle: graphics_hardware_interface::BaseBufferHandle) -> u64 {
self.buffers.get_single(buffer_handle).unwrap().gpu_address
}
pub fn get_buffer_slice<T: Copy>(&mut self, buffer_handle: graphics_hardware_interface::BufferHandle<T>) -> &T {
let buffer = self.buffers.get_single(buffer_handle.into()).unwrap();
let buffer = buffer
.staging
.map(|staging_handle| self.buffers.resource(staging_handle))
.unwrap_or(buffer);
unsafe { &*(buffer.pointer as *const T) }
}
pub fn get_mut_buffer_slice<T: Copy>(&self, buffer_handle: graphics_hardware_interface::BufferHandle<T>) -> &'static mut T {
let buffer = self.buffers.get_single(buffer_handle.into()).unwrap();
let buffer = buffer
.staging
.map(|staging_handle| self.buffers.resource(staging_handle))
.unwrap_or(buffer);
unsafe { &mut *(buffer.pointer as *mut T) }
}
pub fn sync_buffer(&mut self, buffer_handle: impl Into<graphics_hardware_interface::BaseBufferHandle>) {
let handle = self.buffers.nth_handle(buffer_handle.into(), 0).unwrap();
let buffer = self.buffers.resource(handle);
if buffer.staging.is_some() {
self.pending_buffer_syncs.push_back(handle);
}
}
fn upload_buffer_from_staging(
&mut self,
buffer_handle: BufferHandle,
queue_handle: Option<graphics_hardware_interface::QueueHandle>,
sequence_index: u8,
) {
let buffer = self.buffers.resource(buffer_handle);
let Some(staging_handle) = buffer.staging else {
return;
};
let staging = self.buffers.resource(staging_handle);
let queue = queue_handle
.and_then(|queue_handle| self.queues.get(queue_handle.0 as usize))
.unwrap_or_else(|| self.transfer_queue());
let command_buffer = self.create_metal_command_buffer(
queue.queue.as_ref(),
Some("Buffer Upload"),
"Metal command buffer creation failed. The most likely cause is that the transfer queue did not provide a command buffer.",
);
let blit_encoder = command_buffer.blitCommandEncoder().expect(
"Metal blit command encoder creation failed. The most likely cause is that the command buffer is in an invalid state.",
);
#[cfg(debug_assertions)]
if self.settings.debug_labels {
blit_encoder.setLabel(Some(&NSString::from_str("Buffer Upload")));
}
unsafe {
blit_encoder.copyFromBuffer_sourceOffset_toBuffer_destinationOffset_size(
staging.buffer.as_ref(),
0,
buffer.buffer.as_ref(),
0,
buffer.size as _,
);
}
blit_encoder.endEncoding();
self.submit_internal_metal_command_buffer(command_buffer, sequence_index);
}
fn upload_image_from_staging(
&mut self,
image_handle: ImageHandle,
queue_handle: Option<graphics_hardware_interface::QueueHandle>,
sequence_index: u8,
) {
let image = self.images.resource_mut(image_handle);
let Some(staging) = image.staging.as_ref() else {
return;
};
let texture = image.texture.clone();
let format = image.format;
let extent = image.extent;
let array_layers = image.array_layers;
let staging = staging.to_vec();
self.upload_texture_from_staging(
texture.as_ref(),
format,
extent,
array_layers,
&staging,
queue_handle,
sequence_index,
);
}
fn flush_pending_uploads(&mut self, queue_handle: Option<graphics_hardware_interface::QueueHandle>, sequence_index: u8) {
while let Some(buffer_handle) = self.pending_buffer_syncs.pop_front() {
self.upload_buffer_from_staging(buffer_handle, queue_handle, sequence_index);
}
while let Some(image_handle) = self.pending_image_syncs.pop_front() {
self.upload_image_from_staging(image_handle, queue_handle, sequence_index);
}
}
pub fn get_texture_slice_mut(&self, texture_handle: graphics_hardware_interface::ImageHandle) -> &'static mut [u8] {
let image = self.images.get_single(texture_handle.0).unwrap();
let Some(staging) = image.staging.as_ref() else {
return &mut [];
};
unsafe { std::slice::from_raw_parts_mut(staging.as_ptr() as *mut u8, staging.len()) }
}
pub fn write_texture(&mut self, texture_handle: graphics_hardware_interface::ImageHandle, f: impl FnOnce(&mut [u8])) {
let image = self.images.resource_mut(self.images.nth_handle(texture_handle.0, 0).unwrap());
let Some(staging) = image.staging.as_mut() else {
return;
};
f(staging);
let texture = image.texture.clone();
let format = image.format;
let extent = image.extent;
let array_layers = image.array_layers;
let staging = staging.to_vec();
self.upload_texture_from_staging(texture.as_ref(), format, extent, array_layers, &staging, None, 0);
}
pub fn sync_texture(&mut self, image_handle: graphics_hardware_interface::ImageHandle) {
let handle = self.images.nth_handle(image_handle.0, 0).unwrap();
self.pending_image_syncs.push_back(handle);
}
pub fn build_image(&mut self, builder: image_builder::Builder) -> graphics_hardware_interface::ImageHandle {
let layers = builder.array_layers.map(|l| l.get()).unwrap_or(1);
let image_handle = self.create_image_internal(
None,
builder.get_name(),
builder.extent,
builder.format,
builder.resource_uses,
builder.device_accesses,
layers,
);
graphics_hardware_interface::ImageHandle(graphics_hardware_interface::BaseImageHandle::new(image_handle.0))
}
pub fn build_sampler(&mut self, builder: sampler_builder::Builder) -> graphics_hardware_interface::SamplerHandle {
let descriptor = build_sampler_descriptor(&builder);
let sampler_state = self
.device
.newSamplerStateWithDescriptor(&descriptor)
.expect("Metal sampler creation failed. The most likely cause is that the device is out of sampler resources.");
self.samplers.push(super::sampler::Sampler { sampler: sampler_state });
graphics_hardware_interface::SamplerHandle((self.samplers.len() - 1) as u64)
}
pub fn create_acceleration_structure_instance_buffer(
&mut self,
name: Option<&str>,
max_instance_count: u32,
) -> graphics_hardware_interface::BaseBufferHandle {
let size = max_instance_count as usize * std::mem::size_of::<mtl::MTLAccelerationStructureInstanceDescriptor>();
let buffer = self.create_buffer_resource(
name,
size,
crate::Uses::AccelerationStructure,
crate::DeviceAccesses::DeviceOnly,
);
let mut creator = self.buffers.creator();
creator.add(buffer);
creator.into()
}
pub fn create_top_level_acceleration_structure(
&mut self,
_name: Option<&str>,
_max_instance_count: u32,
) -> graphics_hardware_interface::TopLevelAccelerationStructureHandle {
self.acceleration_structures.push(AccelerationStructure {
structure: None,
buffer: None,
});
graphics_hardware_interface::TopLevelAccelerationStructureHandle((self.acceleration_structures.len() - 1) as u64)
}
pub fn create_bottom_level_acceleration_structure(
&mut self,
_description: &graphics_hardware_interface::BottomLevelAccelerationStructure,
) -> graphics_hardware_interface::BottomLevelAccelerationStructureHandle {
self.acceleration_structures.push(AccelerationStructure {
structure: None,
buffer: None,
});
graphics_hardware_interface::BottomLevelAccelerationStructureHandle((self.acceleration_structures.len() - 1) as u64)
}
pub fn write(&mut self, descriptor_set_writes: &[crate::descriptors::DescriptorWrite]) {
for write in descriptor_set_writes {
self.apply_descriptor_write_to_all_frames(
DescriptorSetHandle(write.descriptor_set.0),
write.slot,
write.descriptor,
write.array_element,
write.frame_offset.unwrap_or(0),
);
}
}
pub fn write_instance(
&mut self,
_instances_buffer_handle: graphics_hardware_interface::BaseBufferHandle,
_instance_index: usize,
_transform: [[f32; 4]; 3],
_custom_index: u16,
_mask: u8,
_sbt_record_offset: usize,
_acceleration_structure: graphics_hardware_interface::BottomLevelAccelerationStructureHandle,
) {
}
pub fn write_sbt_entry(
&mut self,
_sbt_buffer_handle: graphics_hardware_interface::BaseBufferHandle,
_sbt_record_offset: usize,
_pipeline_handle: graphics_hardware_interface::PipelineHandle,
_shader_handle: graphics_hardware_interface::ShaderHandle,
) {
}
pub fn bind_to_window(
&mut self,
window_os_handles: &window::Handles,
_presentation_mode: graphics_hardware_interface::PresentationModes,
_fallback_extent: Extent,
uses: crate::Uses,
) -> graphics_hardware_interface::SwapchainHandle {
let layer = CAMetalLayer::new();
layer.setDevice(Some(&self.device));
layer.setPixelFormat(mtl::MTLPixelFormat::BGRA8Unorm);
layer.setFramebufferOnly(false);
window_os_handles.view.setWantsLayer(true);
window_os_handles.view.setLayer(Some(layer.as_super()));
let extent = get_layer_extent(&layer, &window_os_handles.view);
let format = mtl::MTLPixelFormat::BGRA8Unorm;
let needs_proxies = {
true };
let format = match format {
mtl::MTLPixelFormat::BGRA8Unorm => crate::Formats::BGRAu8,
mtl::MTLPixelFormat::BGRA8Unorm_sRGB => crate::Formats::BGRAsRGB,
_ => panic!(
"Unsupported Metal swapchain pixel format. The most likely cause is that the layer pixel format does not have a matching GHI format."
),
};
let mut images = [None; super::MAX_SWAPCHAIN_IMAGES];
if needs_proxies {
for image_index in 0..super::MAX_SWAPCHAIN_IMAGES {
let proxy = self.create_image_resource(
Some("Swapchain Proxy Image"),
extent,
format,
uses | Uses::BlitSource,
DeviceAccesses::DeviceOnly,
1,
);
let image_handle = self.images.add(proxy);
images[image_index] = Some(image_handle.1);
}
}
let handle = graphics_hardware_interface::SwapchainHandle(self.swapchains.len() as u64);
self.swapchains.push(Swapchain {
layer,
view: window_os_handles.view.clone(),
extent,
images,
});
handle
}
pub fn get_image_data(&mut self, texture_copy_handle: graphics_hardware_interface::TextureCopyHandle) -> &[u8] {
let image = self.images.resource_mut(ImageHandle(texture_copy_handle.0));
let Some(staging) = image.staging.as_mut() else {
return &[];
};
let Some((bytes_per_row, ..)) = utils::texture_upload_layout(image.format, image.extent) else {
return &[];
};
let data_ptr = NonNull::new(staging.as_mut_ptr() as *mut std::ffi::c_void)
.expect("Texture readback buffer was null. The most likely cause is an empty image staging allocation.");
let mut region_size = utils::texture_copy_size(image.format, image.extent);
region_size.depth = 1;
let region = mtl::MTLRegion {
origin: mtl::MTLOrigin { x: 0, y: 0, z: 0 },
size: region_size,
};
unsafe {
image
.texture
.getBytes_bytesPerRow_fromRegion_mipmapLevel(data_ptr, bytes_per_row as _, region, 0);
}
staging
}
pub fn create_synchronizer(
&mut self,
_name: Option<&str>,
signaled: bool,
) -> graphics_hardware_interface::SynchronizerHandle {
let (master, mut previous) = self.synchronizers.add(synchronizer::Synchronizer::new(signaled));
for _ in 1..self.frames {
let handle = self
.synchronizers
.add_with_master(synchronizer::Synchronizer::new(signaled), master);
self.synchronizers.set_next(previous, Some(handle));
previous = handle;
}
master
}
pub fn reset_synchronizer(&mut self, synchronizer_handle: graphics_hardware_interface::SynchronizerHandle) {
for frame_index in 0..self.frames as usize {
let synchronizer_handle = self.synchronizer_for_sequence(synchronizer_handle, frame_index as u8);
self.synchronizers.resource(synchronizer_handle).reset();
}
}
pub fn wait_for_synchronizer(&self, synchronizer_handle: graphics_hardware_interface::SynchronizerHandle) {
for frame_index in 0..self.frames as usize {
let synchronizer_handle = self.synchronizer_for_sequence(synchronizer_handle, frame_index as u8);
self.synchronizers.resource(synchronizer_handle).wait();
}
}
pub(crate) fn start_frame<'a>(
&'a mut self,
index: u32,
synchronizer_handle: graphics_hardware_interface::SynchronizerHandle,
) -> crate::queue::StartedFrame<super::Frame<'a>> {
let frame_key = graphics_hardware_interface::FrameKey {
frame_index: index,
sequence_index: (index % self.frames as u32) as u8,
};
let completed_frame = crate::queue::completed_frame_key(index, self.frames);
let synchronizer_handle = self.synchronizer_for_sequence(synchronizer_handle, frame_key.sequence_index);
self.synchronizers.resource(synchronizer_handle).wait();
self.process_tasks(frame_key.sequence_index);
crate::queue::StartedFrame::new(super::Frame::new(self, frame_key), completed_frame)
}
pub fn resize_buffer<T: Copy>(&mut self, buffer_handle: graphics_hardware_interface::DynamicBufferHandle<T>, size: usize) {
let buffer_handle = buffer_handle.into();
let buffer = self.buffers.get_single(buffer_handle).unwrap();
if buffer.size >= size {
return;
}
let uses = buffer.uses;
let access = buffer.access;
let name = buffer.name.clone();
let replacement = self.create_buffer_resource(name.as_deref(), size, uses, access);
let handle = self.buffers.nth_handle(buffer_handle, 0).unwrap();
*self.buffers.resource_mut(handle) = replacement;
self.rewrite_descriptors_for_handle(PrivateHandles::Buffer(handle));
}
pub fn start_frame_capture(&self) {
}
pub fn end_frame_capture(&self) {
}
pub fn wait(&self) {
for synchronizer in self.synchronizers.iter() {
synchronizer.wait();
}
}
}
impl crate::context::Context for Context {
type Queue = crate::metal::queue::Queue;
type QueueReference<'a> = crate::metal::queue::QueueReference<'a>;
type CommandBuffer<'a> = crate::metal::CommandBuffer<'a>;
#[cfg(any(debug_assertions, test))]
fn has_errors(&self) -> bool {
Context::has_errors(self)
}
fn supports_bc_texture_compression(&self) -> bool {
true
}
fn queue(&mut self, queue_handle: graphics_hardware_interface::QueueHandle) -> Self::Queue {
Context::queue(self, queue_handle)
}
fn queue_reference<'a>(&'a mut self, queue_handle: graphics_hardware_interface::QueueHandle) -> Self::QueueReference<'a> {
Context::queue_reference(self, queue_handle)
}
fn command_buffer<'a>(
&'a mut self,
command_buffer_handle: graphics_hardware_interface::CommandBufferHandle,
) -> Self::CommandBuffer<'a> {
Context::command_buffer(self, command_buffer_handle)
}
fn get_buffer_address(&self, buffer_handle: graphics_hardware_interface::BaseBufferHandle) -> u64 {
Context::get_buffer_address(self, buffer_handle)
}
fn get_buffer_slice<T: Copy>(&mut self, buffer_handle: graphics_hardware_interface::BufferHandle<T>) -> &T {
Context::get_buffer_slice(self, buffer_handle)
}
fn get_mut_buffer_slice<T: Copy>(&self, buffer_handle: graphics_hardware_interface::BufferHandle<T>) -> &'static mut T {
Context::get_mut_buffer_slice(self, buffer_handle)
}
fn sync_buffer(&mut self, buffer_handle: impl Into<graphics_hardware_interface::BaseBufferHandle>) {
Context::sync_buffer(self, buffer_handle);
}
fn get_texture_slice_mut(&self, texture_handle: graphics_hardware_interface::ImageHandle) -> &'static mut [u8] {
Context::get_texture_slice_mut(self, texture_handle)
}
fn sync_texture(&mut self, image_handle: graphics_hardware_interface::ImageHandle) {
Context::sync_texture(self, image_handle);
}
fn write_texture(&mut self, texture_handle: graphics_hardware_interface::ImageHandle, f: impl FnOnce(&mut [u8])) {
Context::write_texture(self, texture_handle, f);
}
fn write(&mut self, descriptor_set_writes: &[crate::descriptors::DescriptorWrite]) {
Context::write(self, descriptor_set_writes);
}
fn write_instance(
&mut self,
instances_buffer_handle: graphics_hardware_interface::BaseBufferHandle,
instance_index: usize,
transform: [[f32; 4]; 3],
custom_index: u16,
mask: u8,
sbt_record_offset: usize,
acceleration_structure: graphics_hardware_interface::BottomLevelAccelerationStructureHandle,
) {
Context::write_instance(
self,
instances_buffer_handle,
instance_index,
transform,
custom_index,
mask,
sbt_record_offset,
acceleration_structure,
);
}
fn write_sbt_entry(
&mut self,
sbt_buffer_handle: graphics_hardware_interface::BaseBufferHandle,
sbt_record_offset: usize,
pipeline_handle: graphics_hardware_interface::PipelineHandle,
shader_handle: graphics_hardware_interface::ShaderHandle,
) {
Context::write_sbt_entry(self, sbt_buffer_handle, sbt_record_offset, pipeline_handle, shader_handle);
}
fn bind_to_window(
&mut self,
window_os_handles: &window::Handles,
presentation_mode: graphics_hardware_interface::PresentationModes,
fallback_extent: Extent,
uses: crate::Uses,
) -> graphics_hardware_interface::SwapchainHandle {
Context::bind_to_window(self, window_os_handles, presentation_mode, fallback_extent, uses)
}
fn get_image_data(&mut self, texture_copy_handle: graphics_hardware_interface::TextureCopyHandle) -> &[u8] {
Context::get_image_data(self, texture_copy_handle)
}
fn resize_buffer<T: Copy>(&mut self, buffer_handle: graphics_hardware_interface::DynamicBufferHandle<T>, size: usize) {
Context::resize_buffer(self, buffer_handle, size);
}
fn start_frame_capture(&mut self) {
Context::start_frame_capture(self);
}
fn end_frame_capture(&mut self) {
Context::end_frame_capture(self);
}
fn wait(&self) {
Context::wait(self);
}
fn set_frames_in_flight(&mut self, frames: u8) {
Context::set_frames_in_flight(self, frames);
}
}
impl crate::context::ContextCreate for Context {
fn create_allocation(
&mut self,
size: usize,
resource_uses: crate::Uses,
resource_device_accesses: crate::DeviceAccesses,
) -> graphics_hardware_interface::AllocationHandle {
Context::create_allocation(self, size, resource_uses, resource_device_accesses)
}
fn add_mesh_from_vertices_and_indices(
&mut self,
vertex_count: u32,
index_count: u32,
vertices: &[u8],
indices: &[u8],
vertex_layout: &[crate::pipelines::VertexElement],
) -> graphics_hardware_interface::MeshHandle {
Context::add_mesh_from_vertices_and_indices(self, vertex_count, index_count, vertices, indices, vertex_layout)
}
fn create_shader(
&mut self,
name: Option<&str>,
shader_source_type: crate::shader::Sources,
stage: crate::ShaderTypes,
shader_resource_descriptors: impl IntoIterator<Item = crate::shader::ShaderResourceDescriptor>,
) -> Result<graphics_hardware_interface::ShaderHandle, ()> {
Context::create_shader(self, name, shader_source_type, stage, shader_resource_descriptors)
}
fn create_descriptor_set(&mut self, name: Option<&str>) -> graphics_hardware_interface::DescriptorSetHandle {
Context::create_descriptor_set(self, name)
}
fn create_raster_pipeline(
&mut self,
builder: crate::pipelines::raster::Builder,
) -> graphics_hardware_interface::PipelineHandle {
Context::create_raster_pipeline(self, builder)
}
fn create_compute_pipeline(
&mut self,
builder: crate::pipelines::compute::Builder,
) -> graphics_hardware_interface::PipelineHandle {
Context::create_compute_pipeline(self, builder)
}
fn create_ray_tracing_pipeline(
&mut self,
builder: crate::pipelines::ray_tracing::Builder,
) -> graphics_hardware_interface::PipelineHandle {
Context::create_ray_tracing_pipeline(self, builder)
}
fn build_buffer<T: Copy>(&mut self, builder: crate::buffer::Builder) -> graphics_hardware_interface::BufferHandle<T> {
Context::build_buffer(self, builder)
}
fn build_dynamic_buffer<T: Copy>(
&mut self,
builder: crate::buffer::Builder,
) -> graphics_hardware_interface::DynamicBufferHandle<T> {
Context::build_dynamic_buffer(self, builder)
}
fn build_dynamic_image(&mut self, builder: crate::image::Builder) -> graphics_hardware_interface::DynamicImageHandle {
Context::build_dynamic_image(self, builder)
}
fn build_image(&mut self, builder: crate::image::Builder) -> graphics_hardware_interface::ImageHandle {
Context::build_image(self, builder)
}
fn build_sampler(&mut self, builder: crate::sampler::Builder) -> graphics_hardware_interface::SamplerHandle {
Context::build_sampler(self, builder)
}
fn create_acceleration_structure_instance_buffer(
&mut self,
name: Option<&str>,
max_instance_count: u32,
) -> graphics_hardware_interface::BaseBufferHandle {
Context::create_acceleration_structure_instance_buffer(self, name, max_instance_count)
}
fn create_top_level_acceleration_structure(
&mut self,
name: Option<&str>,
max_instance_count: u32,
) -> graphics_hardware_interface::TopLevelAccelerationStructureHandle {
Context::create_top_level_acceleration_structure(self, name, max_instance_count)
}
fn create_bottom_level_acceleration_structure(
&mut self,
description: &graphics_hardware_interface::BottomLevelAccelerationStructure,
) -> graphics_hardware_interface::BottomLevelAccelerationStructureHandle {
Context::create_bottom_level_acceleration_structure(self, description)
}
fn create_synchronizer(&mut self, name: Option<&str>, signaled: bool) -> graphics_hardware_interface::SynchronizerHandle {
Context::create_synchronizer(self, name, signaled)
}
}
use std::cell::RefCell;
use std::collections::VecDeque;
use std::ptr::NonNull;
use ::utils::hash::{HashMap, HashSet};
use dispatch2::DispatchData;
use objc2::runtime::ProtocolObject;
use objc2::ClassType;
use objc2_foundation::{NSAutoreleasePool, NSString};
use objc2_metal::{
MTLBlitCommandEncoder, MTLBuffer, MTLCommandBuffer, MTLCommandEncoder, MTLCommandQueue, MTLDevice, MTLLibrary, MTLResource,
MTLTexture,
};
use smallvec::SmallVec;
use super::*;
use crate::implementation::device::submit_metal_command_buffer;
use crate::{
buffer::{self as buffer_builder, BufferHandle},
descriptors::DescriptorSetHandle,
image::{self as image_builder, ImageHandle},
metal::swapchain::Swapchain,
metal::utils::parse_threadgroup_size_metadata,
pipelines::raster as raster_pipeline,
sampler::{self as sampler_builder, SamplerHandle},
window, DeviceAccesses, HandleLike as _, MasterHandle as _, ResourceCollection, Uses,
};