#![allow(clippy::unwrap_used)] use lueur::prelude::*; #[repr(C)] struct Vertex { position: [f32; 2], color: [f32; 4], } struct Renderer { display_pipeline: gfx::PipelineId, display_bind: gfx::Bindings<0, 1>, offscreen_pipeline: gfx::PipelineId, offscreen_bind: gfx::Bindings<1, 0>, offscreen_pass: gfx::PassId, rx: f32, ry: f32, width: f64, height: f64, backend: T, } impl Renderer { pub fn init(mut backend: T) -> Result { let color_img = backend.texture(gfx::Texture { data: (), access: gfx::TextureAccess::RenderTarget, size: Size::new(512, 512), format: gfx::TextureFormat::Rgba8, min_filter: gfx::FilterMode::Nearest, mag_filter: gfx::FilterMode::Nearest, ..Default::default() })?; let depth_img = backend.texture(gfx::Texture { data: (), access: gfx::TextureAccess::RenderTarget, size: Size::new(512, 512), format: gfx::TextureFormat::Depth, ..Default::default() })?; let offscreen_pass = backend.pass(gfx::PassDescriptor { color_attachments: &[color_img], depth_attachment: Some(depth_img), })?; #[rustfmt::skip] let vertices: [Vertex; 3] = [ Vertex { position: [-0.5, -0.5], color: [1., 0., 1., 1.] }, Vertex { position: [ 0.5, -0.5], color: [0., 1., 1., 1.] }, Vertex { position: [ 0.0, 0.5], color: [1., 1., 0., 1.] }, ]; let vertex_buffer = backend.buffer( gfx::BufferKind::Vertex, gfx::BufferUsage::Immutable, gfx::BufferSource::slice(&vertices), )?; let index_buffer = backend.buffer( gfx::BufferKind::Index, gfx::BufferUsage::Immutable, gfx::BufferSource::slice(&[0, 1, 2]), )?; let offscreen_bind = gfx::Bindings { vertex: [vertex_buffer], index: Some(index_buffer), textures: [], }; let display_bind = gfx::Bindings { vertex: [], index: None, textures: [color_img], }; let source = gfx::ShaderSource { vertex: display_shader::VERTEX, fragment: display_shader::FRAGMENT, geometry: None, }; let display_shader = backend.shader(source, display_shader::meta()).unwrap(); let display_pipeline = backend.pipeline( display_shader, gfx::PipelineDescriptor { depth: Some(gfx::DepthState { compare: gfx::Comparison::LessOrEqual, offset: None, }), ..Default::default() }, )?; let source = gfx::ShaderSource { vertex: offscreen_shader::VERTEX, fragment: offscreen_shader::FRAGMENT, geometry: None, }; let offscreen_shader = backend.shader(source, offscreen_shader::meta()).unwrap(); let offscreen_pipeline = backend.pipeline( offscreen_shader, gfx::PipelineDescriptor { layout: &[gfx::BufferLayout { stride: Some(std::mem::size_of::()), attrs: &[ gfx::VertexAttribute::new("in_pos", gfx::VertexFormat::Float2), gfx::VertexAttribute::new("in_color", gfx::VertexFormat::Float4), ], ..Default::default() }], depth: Some(gfx::DepthState { compare: gfx::Comparison::LessOrEqual, offset: None, }), ..Default::default() }, )?; Ok(Self { display_pipeline, display_bind, offscreen_pipeline, offscreen_bind, offscreen_pass, height: 1., width: 1., rx: 0., ry: 0., backend, }) } fn window_resized(&mut self, size: platform::LogicalSize) -> Result<(), T::Error> { self.width = size.width; self.height = size.height; self.backend.window_resized(size); self.backend.pass_resize_attachments( self.offscreen_pass, Size::new(self.width as u32, self.height as u32), )?; Ok(()) } fn frame(&mut self) -> Result<(), T::Error> { let (width, height) = (self.width as f32, self.height as f32); let proj = Transform3D::perspective(60.0f32, width / height, 0.01, 30.0); let view = Transform3D::::look_at( Vector3D::new(0.0, 1.5, 9.0), Vector3D::new(0.0, 0.0, 0.0), Vector3D::new(0.0, 1.0, 0.0), ); let view_proj = proj * view; self.rx += 0.01; self.ry += 0.03; let model = Transform3D::rotation(self.ry, Vector3D::new(0., 1.0, 0.)) * Transform3D::::rotation(self.rx, Vector3D::new(1.0, 0., 0.)); let vs_params = display_shader::Uniforms { mvp: view_proj * model, }; let mut frame = self.backend.frame(); frame.begin_pass( self.offscreen_pass, gfx::PassAction::clear(gfx::color::Rgba::BLACK), ); frame.apply_pipeline(&self.offscreen_pipeline)?; frame.apply_bindings(&self.offscreen_bind)?; frame.apply_uniforms(&vs_params)?; frame.draw(0, 3, 1)?; frame.end_pass(); frame.begin_default_pass( gfx::PassAction::clear(gfx::color::Rgba::BLACK), [self.width as u32, self.height as u32], ); frame.apply_pipeline(&self.display_pipeline)?; frame.apply_bindings(&self.display_bind)?; frame.draw(0, 6, 1)?; frame.end_pass(); frame.commit(); Ok(()) } } fn main() -> Result<(), Box> { logger::init(log::Level::Debug)?; let (mut win, mut events) = platform::init("screenshot", 640, 480, &[], platform::GraphicsContext::Gl)?; let ctx = unsafe { glow::Context::from_loader_function(|p| win.get_proc_address(p)) }; let backend = gfx::gl::Context::new(ctx, win.size())?; let mut renderer = Renderer::init(backend)?; 'main: while win.is_open() { events.poll(); for event in events.flush() { match event { platform::WindowEvent::Resized(size) => { renderer.window_resized(size)?; } platform::WindowEvent::KeyboardInput(platform::KeyboardInput { key: Some(platform::Key::Escape), state: platform::InputState::Pressed, .. }) => break 'main, _ => {} } } renderer.frame()?; win.present(); } let size = win.size(); let mut pixels = vec![Rgba8::default(); size.width as usize * size.height as usize]; renderer .backend .pass_read_color_attachment(renderer.offscreen_pass, 0, &mut pixels)?; let img = gfx::Image::new(pixels, Size::new(size.width as u32, size.height as u32)); let mut file = std::fs::File::create("./screenshot.rgba")?; img.write(&mut file)?; Ok(()) } mod display_shader { use super::*; pub const VERTEX: &str = r#" #version 330 const vec2[6] POSITION = vec2[]( vec2(-1.0, -1.0), vec2( 1.0, -1.0), vec2( 1.0, 1.0), vec2(-1.0, -1.0), vec2(-1.0, 1.0), vec2( 1.0, 1.0) ); const vec2[6] UV = vec2[]( vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 1.0) ); out vec2 f_uv; void main() { f_uv = UV[gl_VertexID]; gl_Position = vec4(POSITION[gl_VertexID], 0.0, 1.0); } "#; pub const FRAGMENT: &str = r#" #version 330 uniform sampler2D framebuffer; in vec2 f_uv; out vec4 fragColor; void main() { fragColor = texture( framebuffer, vec2(f_uv.s, f_uv.t) ); } "#; pub fn meta() -> gfx::ShaderDescriptor<'static> { gfx::ShaderDescriptor { textures: &["framebuffer"], uniforms: gfx::UniformBlockLayout { uniforms: &[] }, } } #[repr(C)] pub struct Uniforms { pub mvp: Transform3D, } unsafe impl bytes::Packed for Uniforms {} } mod offscreen_shader { use super::*; pub const VERTEX: &str = r#" #version 100 attribute vec2 in_pos; attribute vec4 in_color; varying lowp vec4 color; uniform mat4 mvp; void main() { gl_Position = mvp * vec4(in_pos, 0, 1); color = in_color; } "#; pub const FRAGMENT: &str = r#" #version 100 varying lowp vec4 color; void main() { gl_FragColor = color; } "#; pub fn meta() -> gfx::ShaderDescriptor<'static> { gfx::ShaderDescriptor { textures: &[], uniforms: gfx::UniformBlockLayout { uniforms: &[gfx::UniformDescriptor { name: "mvp", kind: gfx::UniformKind::Mat4, arity: 1, }], }, } } }