use std::ffi::c_void;
use std::ptr::NonNull;
use objc2_metal::{
MTLBuffer, MTLCommandBuffer, MTLCommandBufferStatus, MTLCommandEncoder, MTLCommandQueue,
MTLComputeCommandEncoder, MTLSize,
};
use crate::MapOperation;
use super::context::Context;
const THREADGROUP_WIDTH: usize = 256;
pub(super) fn map_pipeline_index(operation: MapOperation) -> usize {
match operation {
MapOperation::Exp => 0,
MapOperation::Ln => 1,
MapOperation::Sqrt => 2,
MapOperation::Tanh => 3,
}
}
pub(super) fn executed(
context: &Context,
operation: MapOperation,
elements: &[f32],
) -> Result<Vec<f32>, String> {
let count = elements.len();
let source_buffer = context.pool.take(&context.device, size_of_val(elements))?;
let destination_buffer = context.pool.take(&context.device, size_of_val(elements))?;
unsafe {
std::ptr::copy_nonoverlapping(
elements.as_ptr(),
source_buffer.contents().as_ptr().cast::<f32>(),
count,
);
}
let command_buffer = context
.queue
.commandBuffer()
.ok_or_else(|| "no command buffer".to_string())?;
let encoder = command_buffer
.computeCommandEncoder()
.ok_or_else(|| "no compute encoder".to_string())?;
encoder.setComputePipelineState(&context.maps[map_pipeline_index(operation)]);
let bound = count as u32;
unsafe {
encoder.setBuffer_offset_atIndex(Some(&source_buffer), 0, 0);
encoder.setBuffer_offset_atIndex(Some(&destination_buffer), 0, 1);
encoder.setBytes_length_atIndex(
NonNull::new(&bound as *const u32 as *mut c_void)
.expect("a stack reference is never null"),
size_of::<u32>(),
2,
);
}
encoder.dispatchThreadgroups_threadsPerThreadgroup(
MTLSize {
width: count.div_ceil(THREADGROUP_WIDTH),
height: 1,
depth: 1,
},
MTLSize {
width: THREADGROUP_WIDTH,
height: 1,
depth: 1,
},
);
encoder.endEncoding();
command_buffer.commit();
command_buffer.waitUntilCompleted();
if command_buffer.status() != MTLCommandBufferStatus::Completed {
let reason = command_buffer
.error()
.map(|error| error.localizedDescription().to_string())
.unwrap_or_else(|| "command buffer failed without an error".to_string());
return Err(reason);
}
let mut mapped = vec![0.0_f32; count];
unsafe {
std::ptr::copy_nonoverlapping(
destination_buffer.contents().as_ptr().cast::<f32>(),
mapped.as_mut_ptr(),
count,
);
}
context.pool.give(source_buffer);
context.pool.give(destination_buffer);
Ok(mapped)
}