screenshot/
screenshot.rs

1use ombre::prelude::*;
2
3#[repr(C)]
4struct Vertex {
5    position: [f32; 2],
6    color: [f32; 4],
7}
8
9struct Renderer<T> {
10    display_pipeline: gfx::PipelineId,
11    display_bind: gfx::Bindings,
12    offscreen_pipeline: gfx::PipelineId,
13    offscreen_bind: gfx::Bindings,
14    offscreen_pass: gfx::PassId,
15    rx: f32,
16    ry: f32,
17    width: f64,
18    height: f64,
19    backend: T,
20}
21
22impl<T: gfx::RenderBackend> Renderer<T> {
23    pub fn init(mut backend: T) -> Result<Self, T::Error> {
24        let color_img = backend.texture(gfx::Texture {
25            data: (),
26            access: gfx::TextureAccess::RenderTarget,
27            size: Size::new(512, 512),
28            format: gfx::TextureFormat::Rgba8,
29            min_filter: gfx::FilterMode::Nearest,
30            mag_filter: gfx::FilterMode::Nearest,
31            ..Default::default()
32        })?;
33        let depth_img = backend.texture(gfx::Texture {
34            data: (),
35            access: gfx::TextureAccess::RenderTarget,
36            size: Size::new(512, 512),
37            format: gfx::TextureFormat::Depth,
38            ..Default::default()
39        })?;
40
41        let offscreen_pass = backend.pass(gfx::PassDescriptor {
42            color_attachment: Some(color_img),
43            depth_attachment: Some(depth_img),
44        })?;
45
46        #[rustfmt::skip]
47        let vertices: [Vertex; 3] = [
48            Vertex { position: [-0.5, -0.5], color: [1., 0., 1., 1.] },
49            Vertex { position: [ 0.5, -0.5], color: [0., 1., 1., 1.] },
50            Vertex { position: [ 0.0,  0.5], color: [1., 1., 0., 1.] },
51        ];
52        let vertex_buffer = backend.buffer(
53            gfx::BufferKind::Vertex,
54            gfx::BufferUsage::Immutable,
55            gfx::BufferSource::slice(&vertices),
56        )?;
57        let index_buffer = backend.buffer(
58            gfx::BufferKind::Index,
59            gfx::BufferUsage::Immutable,
60            gfx::BufferSource::slice(&[0, 1, 2]),
61        )?;
62
63        let offscreen_bind = gfx::Bindings {
64            vertex: vec![vertex_buffer],
65            index: Some(index_buffer),
66            textures: vec![],
67        };
68
69        let display_bind = gfx::Bindings {
70            vertex: vec![],
71            index: None,
72            textures: vec![color_img],
73        };
74
75        let source = gfx::ShaderSource {
76            vertex: display_shader::VERTEX,
77            fragment: display_shader::FRAGMENT,
78        };
79        let display_shader = backend.shader(source, display_shader::meta()).unwrap();
80        let display_pipeline = backend.pipeline(
81            display_shader,
82            gfx::PipelineDescriptor {
83                depth: Some(gfx::DepthState {
84                    compare: gfx::Comparison::LessOrEqual,
85                    offset: None,
86                }),
87                ..Default::default()
88            },
89        );
90
91        let source = gfx::ShaderSource {
92            vertex: offscreen_shader::VERTEX,
93            fragment: offscreen_shader::FRAGMENT,
94        };
95        let offscreen_shader = backend.shader(source, offscreen_shader::meta()).unwrap();
96
97        let offscreen_pipeline = backend.pipeline(
98            offscreen_shader,
99            gfx::PipelineDescriptor {
100                layout: &[gfx::BufferLayout {
101                    stride: Some(std::mem::size_of::<Vertex>() as i32),
102                    attrs: &[
103                        gfx::VertexAttribute::new("in_pos", gfx::VertexFormat::Float2),
104                        gfx::VertexAttribute::new("in_color", gfx::VertexFormat::Float4),
105                    ],
106                    ..Default::default()
107                }],
108                depth: Some(gfx::DepthState {
109                    compare: gfx::Comparison::LessOrEqual,
110                    offset: None,
111                }),
112                ..Default::default()
113            },
114        );
115
116        Ok(Self {
117            display_pipeline,
118            display_bind,
119            offscreen_pipeline,
120            offscreen_bind,
121            offscreen_pass,
122            height: 1.,
123            width: 1.,
124            rx: 0.,
125            ry: 0.,
126            backend,
127        })
128    }
129
130    fn window_resized(&mut self, size: platform::LogicalSize) -> Result<(), T::Error> {
131        self.width = size.width;
132        self.height = size.height;
133        self.backend.window_resized(size);
134
135        let color_img = self.backend.texture(gfx::Texture {
136            data: (),
137            access: gfx::TextureAccess::RenderTarget,
138            size: Size::new(size.width as u32, size.height as u32),
139            format: gfx::TextureFormat::Rgba8,
140            min_filter: gfx::FilterMode::Nearest,
141            mag_filter: gfx::FilterMode::Nearest,
142            ..Default::default()
143        })?;
144        let depth_img = self.backend.texture(gfx::Texture {
145            data: (),
146            access: gfx::TextureAccess::RenderTarget,
147            size: Size::new(size.width as u32, size.height as u32),
148            format: gfx::TextureFormat::Depth,
149            ..Default::default()
150        })?;
151        self.offscreen_pass = self.backend.pass(gfx::PassDescriptor {
152            color_attachment: Some(color_img),
153            depth_attachment: Some(depth_img),
154        })?;
155        self.display_bind = gfx::Bindings {
156            vertex: vec![],
157            index: None,
158            textures: vec![color_img],
159        };
160        Ok(())
161    }
162
163    fn frame(&mut self) -> Result<(), T::Error> {
164        let (width, height) = (self.width as f32, self.height as f32);
165        let proj = Transform3D::perspective(60.0f32, width / height, 0.01, 30.0);
166        let view = Transform3D::<f32>::look_at(
167            Vector3D::new(0.0, 1.5, 9.0),
168            Vector3D::new(0.0, 0.0, 0.0),
169            Vector3D::new(0.0, 1.0, 0.0),
170        );
171        let view_proj = proj * view;
172
173        self.rx += 0.01;
174        self.ry += 0.03;
175
176        let model = Transform3D::rotation(self.ry, Vector3D::new(0., 1.0, 0.))
177            * Transform3D::<f32>::rotation(self.rx, Vector3D::new(1.0, 0., 0.));
178
179        let vs_params = display_shader::Uniforms {
180            mvp: view_proj * model,
181        };
182        let mut frame = self.backend.frame();
183
184        frame.begin_pass(
185            self.offscreen_pass,
186            gfx::PassAction::clear(gfx::color::Rgba::BLACK),
187        );
188        frame.apply_pipeline(&self.offscreen_pipeline)?;
189        frame.apply_bindings(&self.offscreen_bind)?;
190        frame.apply_uniforms(&vs_params)?;
191        frame.draw(0, 3, 1)?;
192        frame.end_pass();
193
194        frame.begin_default_pass(gfx::PassAction::clear(gfx::color::Rgba::BLACK));
195        frame.apply_pipeline(&self.display_pipeline)?;
196        frame.apply_bindings(&self.display_bind)?;
197        frame.draw(0, 6, 1)?;
198        frame.end_pass();
199        frame.commit();
200
201        Ok(())
202    }
203}
204
205fn main() -> Result<(), Box<dyn std::error::Error>> {
206    logger::init(log::Level::Debug)?;
207
208    let (mut win, mut events) =
209        platform::init("screenshot", 640, 480, &[], platform::GraphicsContext::Gl)?;
210    let ctx = unsafe { glow::Context::from_loader_function(|p| win.get_proc_address(p)) };
211    let backend = gfx::gl::Context::new(ctx, win.size())?;
212    let mut renderer = Renderer::init(backend)?;
213
214    'main: while win.is_open() {
215        events.poll();
216
217        for event in events.flush() {
218            match event {
219                platform::WindowEvent::Resized(size) => {
220                    renderer.window_resized(size)?;
221                }
222                platform::WindowEvent::KeyboardInput(platform::KeyboardInput {
223                    key: Some(platform::Key::Escape),
224                    state: platform::InputState::Pressed,
225                    ..
226                }) => break 'main,
227                _ => {}
228            }
229        }
230        renderer.frame()?;
231        win.present();
232    }
233
234    let size = win.size();
235    let mut pixels = vec![Rgba8::default(); size.width as usize * size.height as usize];
236    renderer
237        .backend
238        .pass_read_color_attachment(renderer.offscreen_pass, &mut pixels)?;
239
240    let img = gfx::Image::new(pixels, Size::new(size.width as u32, size.height as u32));
241    let mut file = std::fs::File::create("./screenshot.rgba")?;
242    img.write(&mut file)?;
243
244    Ok(())
245}
246
247mod display_shader {
248    use super::*;
249
250    pub const VERTEX: &str = r#"
251    #version 330
252
253    const vec2[6] POSITION = vec2[](
254        vec2(-1.0, -1.0),
255        vec2( 1.0, -1.0),
256        vec2( 1.0,  1.0),
257        vec2(-1.0, -1.0),
258        vec2(-1.0,  1.0),
259        vec2( 1.0,  1.0)
260    );
261    const vec2[6] UV = vec2[](
262        vec2(0.0, 0.0),
263        vec2(1.0, 0.0),
264        vec2(1.0, 1.0),
265        vec2(0.0, 0.0),
266        vec2(0.0, 1.0),
267        vec2(1.0, 1.0)
268    );
269
270    out vec2 f_uv;
271
272    void main() {
273        f_uv = UV[gl_VertexID];
274        gl_Position = vec4(POSITION[gl_VertexID], 0.0, 1.0);
275    }
276    "#;
277
278    pub const FRAGMENT: &str = r#"
279    #version 330
280
281    uniform sampler2D framebuffer;
282
283    in  vec2 f_uv;
284    out vec4 fragColor;
285
286    void main() {
287        fragColor = texture(
288            framebuffer,
289            vec2(f_uv.s, f_uv.t)
290        );
291    }
292    "#;
293
294    pub fn meta() -> gfx::ShaderDescriptor {
295        gfx::ShaderDescriptor {
296            textures: vec!["framebuffer".to_string()],
297            uniforms: gfx::UniformBlockLayout { uniforms: vec![] },
298        }
299    }
300
301    #[repr(C)]
302    pub struct Uniforms {
303        pub mvp: Transform3D,
304    }
305    unsafe impl bytes::Packed for Uniforms {}
306}
307
308mod offscreen_shader {
309    use super::*;
310
311    pub const VERTEX: &str = r#"
312    #version 100
313
314    attribute vec2 in_pos;
315    attribute vec4 in_color;
316
317    varying lowp vec4 color;
318
319    uniform mat4 mvp;
320
321    void main() {
322        gl_Position = mvp * vec4(in_pos, 0, 1);
323        color = in_color;
324    }
325    "#;
326
327    pub const FRAGMENT: &str = r#"
328    #version 100
329
330    varying lowp vec4 color;
331
332    void main() {
333        gl_FragColor = color;
334    }
335    "#;
336
337    pub fn meta() -> gfx::ShaderDescriptor {
338        gfx::ShaderDescriptor {
339            textures: vec![],
340            uniforms: gfx::UniformBlockLayout {
341                uniforms: vec![gfx::UniformDescriptor::new("mvp", gfx::UniformKind::Mat4)],
342            },
343        }
344    }
345}