1 use std::mem;
2 use std::path::PathBuf;
3 
4 use std::fs::File;
5 use std::io::BufWriter;
6 
7 use metal::{
8     Buffer, Device, DeviceRef, LibraryRef, MTLClearColor, MTLLoadAction, MTLOrigin, MTLPixelFormat,
9     MTLPrimitiveType, MTLRegion, MTLResourceOptions, MTLSize, MTLStoreAction, RenderPassDescriptor,
10     RenderPassDescriptorRef, RenderPipelineDescriptor, RenderPipelineState, Texture,
11     TextureDescriptor, TextureRef,
12 };
13 use png::ColorType;
14 
15 const VIEW_WIDTH: u64 = 512;
16 const VIEW_HEIGHT: u64 = 512;
17 const TOTAL_BYTES: usize = (VIEW_WIDTH * VIEW_HEIGHT * 4) as usize;
18 
19 const VERTEX_SHADER: &'static str = "triangle_vertex";
20 const FRAGMENT_SHADER: &'static str = "triangle_fragment";
21 
22 // [2 bytes position, 3 bytes color] * 3
23 #[rustfmt::skip]
24 const VERTEX_ATTRIBS: [f32; 15] = [
25     0.0, 0.5, 1.0, 0.0, 0.0,
26     -0.5, -0.5, 0.0, 1.0, 0.0,
27     0.5, -0.5, 0.0, 0.0, 1.0,
28 ];
29 
30 /// This example shows how to render headlessly by:
31 ///
32 /// 1. Rendering a triangle to an MtlDrawable
33 ///
34 /// 2. Waiting for the render to complete and the color texture to be synchronized with the CPU
35 ///    by using a blit command encoder
36 ///
37 /// 3. Reading the texture bytes from the MtlTexture
38 ///
39 /// 4. Saving the texture to a PNG file
main()40 fn main() {
41     let device = Device::system_default().expect("No device found");
42 
43     let texture = create_texture(&device);
44 
45     let library_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
46         .join("examples/window/shaders.metallib");
47 
48     let library = device.new_library_with_file(library_path).unwrap();
49 
50     let pipeline_state = prepare_pipeline_state(&device, &library);
51 
52     let command_queue = device.new_command_queue();
53 
54     let vertex_buffer = create_vertex_buffer(&device);
55 
56     let render_pass_descriptor = RenderPassDescriptor::new();
57     initialize_color_attachment(&render_pass_descriptor, &texture);
58 
59     let command_buffer = command_queue.new_command_buffer();
60     let rc_encoder = command_buffer.new_render_command_encoder(&render_pass_descriptor);
61     rc_encoder.set_render_pipeline_state(&pipeline_state);
62     rc_encoder.set_vertex_buffer(0, Some(&vertex_buffer), 0);
63     rc_encoder.draw_primitives(MTLPrimitiveType::Triangle, 0, 3);
64     rc_encoder.end_encoding();
65 
66     render_pass_descriptor
67         .color_attachments()
68         .object_at(0)
69         .unwrap()
70         .set_load_action(MTLLoadAction::DontCare);
71 
72     let blit_encoder = command_buffer.new_blit_command_encoder();
73     blit_encoder.synchronize_resource(&texture);
74     blit_encoder.end_encoding();
75 
76     command_buffer.commit();
77 
78     command_buffer.wait_until_completed();
79 
80     save_image(&texture);
81 }
82 
save_image(texture: &TextureRef)83 fn save_image(texture: &TextureRef) {
84     let mut image = vec![0; TOTAL_BYTES];
85 
86     texture.get_bytes(
87         image.as_mut_ptr() as *mut std::ffi::c_void,
88         VIEW_WIDTH * 4,
89         MTLRegion {
90             origin: MTLOrigin { x: 0, y: 0, z: 0 },
91             size: MTLSize {
92                 width: VIEW_WIDTH,
93                 height: VIEW_HEIGHT,
94                 depth: 1,
95             },
96         },
97         0,
98     );
99 
100     let out_file =
101         PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples/headless-render/out.png");
102     let file = File::create(&out_file).unwrap();
103     let ref mut w = BufWriter::new(file);
104 
105     let mut encoder = png::Encoder::new(w, VIEW_WIDTH as u32, VIEW_HEIGHT as u32);
106     encoder.set_color(ColorType::RGBA);
107     encoder.set_depth(png::BitDepth::Eight);
108     let mut writer = encoder.write_header().unwrap();
109 
110     writer.write_image_data(&image).unwrap();
111 
112     println!("Image saved to {:?}", out_file);
113 }
114 
create_texture(device: &Device) -> Texture115 fn create_texture(device: &Device) -> Texture {
116     let texture = TextureDescriptor::new();
117     texture.set_width(VIEW_WIDTH);
118     texture.set_height(VIEW_HEIGHT);
119     texture.set_pixel_format(MTLPixelFormat::RGBA8Unorm);
120 
121     device.new_texture(&texture)
122 }
123 
prepare_pipeline_state(device: &DeviceRef, library: &LibraryRef) -> RenderPipelineState124 fn prepare_pipeline_state(device: &DeviceRef, library: &LibraryRef) -> RenderPipelineState {
125     let vert = library.get_function(VERTEX_SHADER, None).unwrap();
126     let frag = library.get_function(FRAGMENT_SHADER, None).unwrap();
127 
128     let pipeline_state_descriptor = RenderPipelineDescriptor::new();
129 
130     pipeline_state_descriptor.set_vertex_function(Some(&vert));
131     pipeline_state_descriptor.set_fragment_function(Some(&frag));
132 
133     pipeline_state_descriptor
134         .color_attachments()
135         .object_at(0)
136         .unwrap()
137         .set_pixel_format(MTLPixelFormat::RGBA8Unorm);
138 
139     device
140         .new_render_pipeline_state(&pipeline_state_descriptor)
141         .unwrap()
142 }
143 
create_vertex_buffer(device: &DeviceRef) -> Buffer144 fn create_vertex_buffer(device: &DeviceRef) -> Buffer {
145     device.new_buffer_with_data(
146         VERTEX_ATTRIBS.as_ptr() as *const _,
147         (VERTEX_ATTRIBS.len() * mem::size_of::<f32>()) as u64,
148         MTLResourceOptions::CPUCacheModeDefaultCache | MTLResourceOptions::StorageModeManaged,
149     )
150 }
151 
initialize_color_attachment(descriptor: &RenderPassDescriptorRef, texture: &TextureRef)152 fn initialize_color_attachment(descriptor: &RenderPassDescriptorRef, texture: &TextureRef) {
153     let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
154 
155     color_attachment.set_texture(Some(texture));
156     color_attachment.set_load_action(MTLLoadAction::Clear);
157     color_attachment.set_clear_color(MTLClearColor::new(0.5, 0.2, 0.2, 1.0));
158     color_attachment.set_store_action(MTLStoreAction::Store);
159 }
160