diff options
Diffstat (limited to 'third_party/rust/metal/examples/headless-render/main.rs')
-rw-r--r-- | third_party/rust/metal/examples/headless-render/main.rs | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/third_party/rust/metal/examples/headless-render/main.rs b/third_party/rust/metal/examples/headless-render/main.rs new file mode 100644 index 0000000000..ed68da1a53 --- /dev/null +++ b/third_party/rust/metal/examples/headless-render/main.rs @@ -0,0 +1,159 @@ +use std::mem; +use std::path::PathBuf; + +use std::fs::File; +use std::io::BufWriter; + +use metal::{ + Buffer, Device, DeviceRef, LibraryRef, MTLClearColor, MTLLoadAction, MTLOrigin, MTLPixelFormat, + MTLPrimitiveType, MTLRegion, MTLResourceOptions, MTLSize, MTLStoreAction, RenderPassDescriptor, + RenderPassDescriptorRef, RenderPipelineDescriptor, RenderPipelineState, Texture, + TextureDescriptor, TextureRef, +}; +use png::ColorType; + +const VIEW_WIDTH: u64 = 512; +const VIEW_HEIGHT: u64 = 512; +const TOTAL_BYTES: usize = (VIEW_WIDTH * VIEW_HEIGHT * 4) as usize; + +const VERTEX_SHADER: &'static str = "triangle_vertex"; +const FRAGMENT_SHADER: &'static str = "triangle_fragment"; + +// [2 bytes position, 3 bytes color] * 3 +#[rustfmt::skip] +const VERTEX_ATTRIBS: [f32; 15] = [ + 0.0, 0.5, 1.0, 0.0, 0.0, + -0.5, -0.5, 0.0, 1.0, 0.0, + 0.5, -0.5, 0.0, 0.0, 1.0, +]; + +/// This example shows how to render headlessly by: +/// +/// 1. Rendering a triangle to an MtlDrawable +/// +/// 2. Waiting for the render to complete and the color texture to be synchronized with the CPU +/// by using a blit command encoder +/// +/// 3. Reading the texture bytes from the MtlTexture +/// +/// 4. Saving the texture to a PNG file +fn main() { + let device = Device::system_default().expect("No device found"); + + let texture = create_texture(&device); + + let library_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("examples/window/shaders.metallib"); + + let library = device.new_library_with_file(library_path).unwrap(); + + let pipeline_state = prepare_pipeline_state(&device, &library); + + let command_queue = device.new_command_queue(); + + let vertex_buffer = create_vertex_buffer(&device); + + let render_pass_descriptor = RenderPassDescriptor::new(); + initialize_color_attachment(&render_pass_descriptor, &texture); + + let command_buffer = command_queue.new_command_buffer(); + let rc_encoder = command_buffer.new_render_command_encoder(&render_pass_descriptor); + rc_encoder.set_render_pipeline_state(&pipeline_state); + rc_encoder.set_vertex_buffer(0, Some(&vertex_buffer), 0); + rc_encoder.draw_primitives(MTLPrimitiveType::Triangle, 0, 3); + rc_encoder.end_encoding(); + + render_pass_descriptor + .color_attachments() + .object_at(0) + .unwrap() + .set_load_action(MTLLoadAction::DontCare); + + let blit_encoder = command_buffer.new_blit_command_encoder(); + blit_encoder.synchronize_resource(&texture); + blit_encoder.end_encoding(); + + command_buffer.commit(); + + command_buffer.wait_until_completed(); + + save_image(&texture); +} + +fn save_image(texture: &TextureRef) { + let mut image = vec![0; TOTAL_BYTES]; + + texture.get_bytes( + image.as_mut_ptr() as *mut std::ffi::c_void, + VIEW_WIDTH * 4, + MTLRegion { + origin: MTLOrigin { x: 0, y: 0, z: 0 }, + size: MTLSize { + width: VIEW_WIDTH, + height: VIEW_HEIGHT, + depth: 1, + }, + }, + 0, + ); + + let out_file = + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples/headless-render/out.png"); + let file = File::create(&out_file).unwrap(); + let ref mut w = BufWriter::new(file); + + let mut encoder = png::Encoder::new(w, VIEW_WIDTH as u32, VIEW_HEIGHT as u32); + encoder.set_color(ColorType::RGBA); + encoder.set_depth(png::BitDepth::Eight); + let mut writer = encoder.write_header().unwrap(); + + writer.write_image_data(&image).unwrap(); + + println!("Image saved to {:?}", out_file); +} + +fn create_texture(device: &Device) -> Texture { + let texture = TextureDescriptor::new(); + texture.set_width(VIEW_WIDTH); + texture.set_height(VIEW_HEIGHT); + texture.set_pixel_format(MTLPixelFormat::RGBA8Unorm); + + device.new_texture(&texture) +} + +fn prepare_pipeline_state(device: &DeviceRef, library: &LibraryRef) -> RenderPipelineState { + let vert = library.get_function(VERTEX_SHADER, None).unwrap(); + let frag = library.get_function(FRAGMENT_SHADER, None).unwrap(); + + let pipeline_state_descriptor = RenderPipelineDescriptor::new(); + + pipeline_state_descriptor.set_vertex_function(Some(&vert)); + pipeline_state_descriptor.set_fragment_function(Some(&frag)); + + pipeline_state_descriptor + .color_attachments() + .object_at(0) + .unwrap() + .set_pixel_format(MTLPixelFormat::RGBA8Unorm); + + device + .new_render_pipeline_state(&pipeline_state_descriptor) + .unwrap() +} + +fn create_vertex_buffer(device: &DeviceRef) -> Buffer { + device.new_buffer_with_data( + VERTEX_ATTRIBS.as_ptr() as *const _, + (VERTEX_ATTRIBS.len() * mem::size_of::<f32>()) as u64, + MTLResourceOptions::CPUCacheModeDefaultCache | MTLResourceOptions::StorageModeManaged, + ) +} + +fn initialize_color_attachment(descriptor: &RenderPassDescriptorRef, texture: &TextureRef) { + let color_attachment = descriptor.color_attachments().object_at(0).unwrap(); + + color_attachment.set_texture(Some(texture)); + color_attachment.set_load_action(MTLLoadAction::Clear); + color_attachment.set_clear_color(MTLClearColor::new(0.5, 0.2, 0.2, 1.0)); + color_attachment.set_store_action(MTLStoreAction::Store); +} |