use crate::device::Gpu;
use crate::ffi::*;
use crate::render::pipeline::{
DepthStencil, DepthStencilState, RenderPipeline, RenderPipelineSpec,
};
use crate::shader::Shader;
use crate::texture::Texture;
use crate::GpuError;
use std::ffi::c_void;
impl Gpu {
pub fn render_target(
&self,
width: u32,
height: u32,
format: NSUInteger,
) -> Result<Texture, GpuError> {
self.render_target_ms(width, height, format, 1)
}
pub fn render_target_ms(
&self,
width: u32,
height: u32,
format: NSUInteger,
samples: u32,
) -> Result<Texture, GpuError> {
if width == 0 || height == 0 {
return Err(GpuError::TextureCreationFailed(
"width/height must be > 0".into(),
));
}
unsafe {
let desc = build_texture_descriptor(
format,
width,
height,
MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead,
samples,
)?;
let tex = self.texture(desc);
release(desc);
tex
}
}
pub fn depth_target(
&self,
width: u32,
height: u32,
format: NSUInteger,
) -> Result<Texture, GpuError> {
self.depth_target_ms(width, height, format, 1)
}
pub fn depth_target_ms(
&self,
width: u32,
height: u32,
format: NSUInteger,
samples: u32,
) -> Result<Texture, GpuError> {
if width == 0 || height == 0 {
return Err(GpuError::TextureCreationFailed(
"width/height must be > 0".into(),
));
}
unsafe {
let desc = build_texture_descriptor(
format,
width,
height,
MTLTextureUsageRenderTarget,
samples,
)?;
let tex = self.texture(desc);
release(desc);
tex
}
}
pub fn render_pipeline(
&self,
vertex: &Shader,
fragment: &Shader,
spec: &RenderPipelineSpec,
) -> Result<RenderPipeline, GpuError> {
if spec.color_attachments.is_empty() {
return Err(GpuError::PipelineCreationFailed(
"at least one color attachment required".into(),
));
}
unsafe {
let cls = objc_getClass(c"MTLRenderPipelineDescriptor".as_ptr()) as ObjcId;
let desc = msg0(cls, sel_registerName(c"new".as_ptr()));
if desc.is_null() {
return Err(GpuError::PipelineCreationFailed(
"descriptor alloc failed".into(),
));
}
msg1_void(desc, SEL_setVertexFunction(), vertex.as_raw());
msg1_void(desc, SEL_setFragmentFunction(), fragment.as_raw());
configure_color_attachments(desc, spec);
if let Some(df) = spec.depth_format {
msg1_uint_void(desc, SEL_setDepthAttachmentPixelFormat(), df);
}
if let Some(sf) = spec.stencil_format {
msg1_uint_void(desc, SEL_setStencilAttachmentPixelFormat(), sf);
}
if spec.sample_count > 1 {
msg1_uint_void(
desc,
SEL_setRasterSampleCount(),
spec.sample_count as NSUInteger,
);
}
if let Some(vd) = &spec.vertex_descriptor {
let vdesc = vd.build_objc();
msg1_void(desc, SEL_setVertexDescriptor(), vdesc);
release(vdesc);
}
let mut error: ObjcId = std::ptr::null_mut();
type F = unsafe extern "C" fn(ObjcId, ObjcSel, ObjcId, *mut ObjcId) -> ObjcId;
let f: F = std::mem::transmute(objc_msgSend as *const c_void);
let raw = f(
self.as_raw(),
SEL_newRenderPipelineStateWithDescriptor_error(),
desc,
&mut error,
);
release(desc);
if raw.is_null() {
let msg = nserror_string(error).unwrap_or_else(|| "unknown error".into());
return Err(GpuError::PipelineCreationFailed(msg));
}
Ok(RenderPipeline::from_raw(
raw,
spec.color_attachments.len(),
spec.sample_count,
))
}
}
pub fn depth_stencil_state(&self, ds: DepthStencil) -> Result<DepthStencilState, GpuError> {
unsafe {
let cls = objc_getClass(c"MTLDepthStencilDescriptor".as_ptr()) as ObjcId;
let desc = msg0(cls, sel_registerName(c"new".as_ptr()));
if desc.is_null() {
return Err(GpuError::PipelineCreationFailed(
"depth-stencil descriptor alloc failed".into(),
));
}
msg1_uint_void(
desc,
SEL_setDepthCompareFunction(),
ds.compare as NSUInteger,
);
msg1_bool_void(desc, SEL_setDepthWriteEnabled(), ds.write_enabled);
let raw = msg1_id_id(
self.as_raw(),
SEL_newDepthStencilStateWithDescriptor(),
desc,
);
release(desc);
if raw.is_null() {
return Err(GpuError::PipelineCreationFailed(
"depth-stencil state creation failed".into(),
));
}
Ok(DepthStencilState::from_raw(raw))
}
}
}
unsafe fn build_texture_descriptor(
format: NSUInteger,
width: u32,
height: u32,
usage: NSUInteger,
samples: u32,
) -> Result<ObjcId, GpuError> {
let cls = objc_getClass(c"MTLTextureDescriptor".as_ptr()) as ObjcId;
let desc = msg0(cls, sel_registerName(c"new".as_ptr()));
if desc.is_null() {
return Err(GpuError::TextureCreationFailed(
"descriptor alloc failed".into(),
));
}
let tex_type = if samples > 1 {
MTLTextureType2DMultisample
} else {
MTLTextureType2D
};
msg1_uint_void(desc, SEL_setTextureType(), tex_type);
msg1_uint_void(desc, SEL_setPixelFormat(), format);
msg1_uint_void(desc, SEL_setWidth(), width as NSUInteger);
msg1_uint_void(desc, SEL_setHeight(), height as NSUInteger);
msg1_uint_void(desc, SEL_setUsage(), usage);
msg1_uint_void(desc, SEL_setStorageMode(), 0x2); if samples > 1 {
msg1_uint_void(desc, SEL_setSampleCount(), samples as NSUInteger);
}
Ok(desc)
}
unsafe fn configure_color_attachments(desc: ObjcId, spec: &RenderPipelineSpec) {
let color_array = msg0(desc, SEL_colorAttachments());
for (i, ca) in spec.color_attachments.iter().enumerate() {
let slot = msg1_uint_id(color_array, SEL_objectAtIndexedSubscript(), i as NSUInteger);
msg1_uint_void(slot, SEL_setPixelFormat(), ca.format);
msg1_uint_void(slot, SEL_setWriteMask(), ca.write_mask as NSUInteger);
if let Some(b) = &ca.blend {
msg1_bool_void(slot, SEL_setBlendingEnabled(), true);
msg1_uint_void(slot, SEL_setRgbBlendOperation(), b.rgb_op as NSUInteger);
msg1_uint_void(slot, SEL_setAlphaBlendOperation(), b.alpha_op as NSUInteger);
msg1_uint_void(slot, SEL_setSourceRGBBlendFactor(), b.src_rgb as NSUInteger);
msg1_uint_void(
slot,
SEL_setDestinationRGBBlendFactor(),
b.dst_rgb as NSUInteger,
);
msg1_uint_void(
slot,
SEL_setSourceAlphaBlendFactor(),
b.src_alpha as NSUInteger,
);
msg1_uint_void(
slot,
SEL_setDestinationAlphaBlendFactor(),
b.dst_alpha as NSUInteger,
);
} else {
msg1_bool_void(slot, SEL_setBlendingEnabled(), false);
}
}
}