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