diff --git a/librashader-reflect/src/back/cross.rs b/librashader-reflect/src/back/cross.rs index 32cad71..8560af8 100644 --- a/librashader-reflect/src/back/cross.rs +++ b/librashader-reflect/src/back/cross.rs @@ -7,7 +7,7 @@ use crate::reflect::ReflectShader; pub type GlVersion = spirv_cross::glsl::Version; pub struct GlslangGlslContext { - pub texture_fixups: Vec, + pub sampler_bindings: Vec, pub compiler: CompiledAst } impl FromCompilation for GLSL { diff --git a/librashader-reflect/src/reflect/cross.rs b/librashader-reflect/src/reflect/cross.rs index 28f3a49..6b841c6 100644 --- a/librashader-reflect/src/reflect/cross.rs +++ b/librashader-reflect/src/reflect/cross.rs @@ -810,7 +810,7 @@ impl CompileShader for CrossReflect { vertex: self.vertex.compile()?, fragment: self.fragment.compile()?, context: GlslangGlslContext { - texture_fixups, + sampler_bindings: texture_fixups, compiler: CompiledAst { vertex: self.vertex, fragment: self.fragment diff --git a/librashader-runtime-gl/src/filter_pass.rs b/librashader-runtime-gl/src/filter_pass.rs index 883e3c5..d5ab0a7 100644 --- a/librashader-runtime-gl/src/filter_pass.rs +++ b/librashader-runtime-gl/src/filter_pass.rs @@ -24,7 +24,6 @@ pub struct FilterPass { pub uniform_buffer: Box<[u8]>, pub push_buffer: Box<[u8]>, pub variable_bindings: FxHashMap, - pub framebuffer: Framebuffer, pub feedback_framebuffer: Framebuffer, pub source: ShaderSource, pub config: ShaderPassConfig @@ -86,8 +85,8 @@ impl FilterPass { fn bind_texture(binding: &TextureImage, texture: &Texture) { unsafe { - // eprintln!("binding {} = texture {}", binding.binding, texture.image.handle); - gl::ActiveTexture((gl::TEXTURE0 + binding.binding) as GLenum); + // eprintln!("setting {} to texunit {}", texture.image.handle, binding.binding); + gl::ActiveTexture(gl::TEXTURE0 + binding.binding); gl::BindTexture(gl::TEXTURE_2D, texture.image.handle); gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, GLenum::from(texture.filter) as GLint); @@ -97,87 +96,25 @@ impl FilterPass { } } - fn scale_framebuffer(&mut self, format: ShaderFormat, viewport: &Viewport, original: &Texture, source: &Texture) -> Size { - let mut width = 0f32; - let mut height = 0f32; - - match self.config.scaling.x { - Scaling { - scale_type: ScaleType::Input, - factor - } => { - width = source.image.size.width * factor - }, - Scaling { - scale_type: ScaleType::Absolute, - factor - } => { - width = factor.into() - } - Scaling { - scale_type: ScaleType::Viewport, - factor - } => { - width = viewport.size.width * factor - } - }; - - match self.config.scaling.y { - Scaling { - scale_type: ScaleType::Input, - factor - } => { - height = source.image.size.height * factor - }, - Scaling { - scale_type: ScaleType::Absolute, - factor - } => { - height = factor.into() - } - Scaling { - scale_type: ScaleType::Viewport, - factor - } => { - height = viewport.size.height * factor - } - }; - - let size = Size { - width: width.round() as u32, - height: height.round() as u32 - }; - - if self.framebuffer.size != size { - self.framebuffer.size = size; - - self.framebuffer.init(size,if format == ShaderFormat::Unknown { - ShaderFormat::R8G8B8A8Unorm - } else { - format - }); - } - size - } - - // todo: fix rendertargets (i.e. non-final pass is internal, final pass is user provided fbo) - pub fn build_commands(&mut self, parent: &FilterCommon, mvp: Option<&[f32]>, frame_count: u32, frame_direction: i32, viewport: &Viewport, original: &Texture, source: &Texture) { + pub fn get_format(&self) -> ShaderFormat { let mut fb_format = ShaderFormat::R8G8B8A8Unorm; if self.config.srgb_framebuffer { fb_format = ShaderFormat::R8G8B8A8Srgb; } else if self.config.float_framebuffer { fb_format = ShaderFormat::R16G16B16A16Sfloat; } + fb_format + } - let fb_size = self.scale_framebuffer(fb_format, viewport, original, source); - - // println!("[frame] Using framebuffer {}, image {}", self.framebuffer.framebuffer, self.framebuffer.image); + // todo: fix rendertargets (i.e. non-final pass is internal, final pass is user provided fbo) + pub fn draw(&mut self, parent: &FilterCommon, mvp: Option<&[f32]>, frame_count: u32, + frame_direction: i32, viewport: &Viewport, original: &Texture, source: &Texture, output: &Framebuffer) { unsafe { - gl::BindFramebuffer(gl::FRAMEBUFFER, self.framebuffer.framebuffer); + gl::BindFramebuffer(gl::FRAMEBUFFER, output.framebuffer); gl::UseProgram(self.program); } - self.build_semantics(parent, mvp, frame_count, frame_direction, fb_size, viewport, original, source); + self.build_semantics(parent, mvp, frame_count, frame_direction, output.size, viewport, original, source); // shader_gl3:1514 if self.ubo_location.vertex != gl::INVALID_INDEX && self.ubo_location.fragment != gl::INVALID_INDEX { @@ -205,14 +142,14 @@ impl FilterPass { // todo: final pass? unsafe { - gl::BindFramebuffer(gl::FRAMEBUFFER, self.framebuffer.framebuffer); + gl::BindFramebuffer(gl::FRAMEBUFFER, output.framebuffer); gl::ColorMask(gl::TRUE, gl::TRUE, gl::TRUE, gl::TRUE); gl::ClearColor(0.0f32, 0.0f32, 0.0f32, 0.0f32); gl::Clear(gl::COLOR_BUFFER_BIT); // - gl::Viewport(0, 0, fb_size.width as GLsizei, fb_size.height as GLsizei); + gl::Viewport(0, 0, output.size.width as GLsizei, output.size.height as GLsizei); - if self.framebuffer.format == gl::SRGB8_ALPHA8 { + if output.format == gl::SRGB8_ALPHA8 { gl::Enable(gl::FRAMEBUFFER_SRGB); } else { gl::Disable(gl::FRAMEBUFFER_SRGB); @@ -280,7 +217,7 @@ impl FilterPass { MemberOffset::Ubo(offset) => (&mut self.uniform_buffer, *offset), MemberOffset::PushConstant(offset) => (&mut self.push_buffer, *offset) }; - FilterPass::build_vec4(location.location(), &mut buffer[offset..][..4], viewport.size) + FilterPass::build_vec4(location.location(), &mut buffer[offset..][..4], viewport.output.size) } if let Some((location, offset)) = self.variable_bindings.get(&VariableSemantics::FrameCount.into()) { diff --git a/librashader-runtime-gl/src/framebuffer.rs b/librashader-runtime-gl/src/framebuffer.rs index d28f277..5774f44 100644 --- a/librashader-runtime-gl/src/framebuffer.rs +++ b/librashader-runtime-gl/src/framebuffer.rs @@ -1,8 +1,10 @@ use gl::types::{GLenum, GLsizei, GLuint}; -use librashader::ShaderFormat; +use librashader::{FilterMode, ShaderFormat, WrapMode}; +use librashader_presets::{Scale2D, ScaleType, Scaling}; use crate::util; -use crate::util::Size; +use crate::util::{GlImage, Size, Texture, Viewport}; +#[derive(Debug)] pub struct Framebuffer { pub image: GLuint, pub size: Size, @@ -33,7 +35,96 @@ impl Framebuffer { } } - pub(crate) fn init(&mut self, mut size: Size, mut format: impl Into) { + pub fn new_from_raw(texture: GLuint, handle: GLuint, format: GLenum, size: Size, miplevels: u32) -> Framebuffer { + Framebuffer { + image: texture, + size, + format, + max_levels: miplevels, + levels: miplevels, + framebuffer: handle, + init: true + } + } + + pub fn as_texture(&self, filter: FilterMode, wrap_mode: WrapMode) -> Texture { + Texture { + image: GlImage { + handle: self.image, + format: self.format, + size: self.size, + padded_size: Default::default() + }, + filter, + mip_filter: filter, + wrap_mode + } + } + + pub fn scale(&mut self, scaling: Scale2D, format: ShaderFormat, viewport: &Viewport, original: &Texture, source: &Texture) -> Size { + let mut width = 0f32; + let mut height = 0f32; + + match scaling.x { + Scaling { + scale_type: ScaleType::Input, + factor + } => { + width = source.image.size.width * factor + }, + Scaling { + scale_type: ScaleType::Absolute, + factor + } => { + width = factor.into() + } + Scaling { + scale_type: ScaleType::Viewport, + factor + } => { + width = viewport.output.size.width * factor + } + }; + + match scaling.y { + Scaling { + scale_type: ScaleType::Input, + factor + } => { + height = source.image.size.height * factor + }, + Scaling { + scale_type: ScaleType::Absolute, + factor + } => { + height = factor.into() + } + Scaling { + scale_type: ScaleType::Viewport, + factor + } => { + height = viewport.output.size.height * factor + } + }; + + let size = Size { + width: width.round() as u32, + height: height.round() as u32 + }; + + if self.size != size { + self.size = size; + + self.init(size,if format == ShaderFormat::Unknown { + ShaderFormat::R8G8B8A8Unorm + } else { + format + }); + } + size + } + + fn init(&mut self, mut size: Size, mut format: impl Into) { self.format = format.into(); self.size = size; diff --git a/librashader-runtime-gl/src/hello_triangle.rs b/librashader-runtime-gl/src/hello_triangle.rs index 70f3b25..2655c28 100644 --- a/librashader-runtime-gl/src/hello_triangle.rs +++ b/librashader-runtime-gl/src/hello_triangle.rs @@ -8,6 +8,7 @@ use gl; use gl::types::{GLchar, GLenum, GLint, GLsizei, GLuint}; use glfw::Key::P; use crate::FilterChain; +use crate::framebuffer::Framebuffer; use crate::util::{GlImage, Size, Viewport}; const WIDTH: u32 = 900; @@ -145,6 +146,10 @@ void main() }"; let shader_program = compile_program(VERT_SHADER, FRAG_SHADER); + unsafe { + gl::ObjectLabel(gl::SHADER, shader_program, -1, b"color_shader\0".as_ptr().cast()); + } + let vertices = &[ // positions // colors 0.5f32, -0.5, 0.0, 1.0, 0.0, 0.0, // bottom right @@ -154,6 +159,8 @@ void main() let mut vbo: gl::types::GLuint = 0; unsafe { gl::GenBuffers(1, &mut vbo); + gl::ObjectLabel(gl::BUFFER, vbo, -1, b"triangle_vbo\0".as_ptr().cast()); + } unsafe { @@ -172,6 +179,8 @@ void main() let mut vao: gl::types::GLuint = 0; unsafe { gl::GenVertexArrays(1, &mut vao); + gl::ObjectLabel(gl::VERTEX_ARRAY, vao, -1, b"triangle_vao\0".as_ptr().cast()); + } unsafe { @@ -216,20 +225,28 @@ void main() (glfw, window, events, shader_program, vao) } -pub fn do_loop(mut glfw: Glfw, mut window: Window, events: Receiver<(f64, WindowEvent)>, triangle_program: GLuint, vao: GLuint, filter: &mut FilterChain) { - let mut framebuffer_handle = 0; +pub fn do_loop(mut glfw: Glfw, mut window: Window, events: Receiver<(f64, WindowEvent)>, triangle_program: GLuint, triangle_vao: GLuint, filter: &mut FilterChain) { + let mut rendered_framebuffer = 0; let mut rendered_texture = 0; let mut quad_vbuf = 0; + let mut output_texture = 0; + let mut output_framebuffer_handle = 0; + let mut output_quad_vbuf = 0; + unsafe { // do frmaebuffer - gl::GenFramebuffers(1, &mut framebuffer_handle); - gl::BindFramebuffer(gl::FRAMEBUFFER, framebuffer_handle); + gl::GenFramebuffers(1, &mut rendered_framebuffer); + gl::BindFramebuffer(gl::FRAMEBUFFER, rendered_framebuffer); + + gl::ObjectLabel(gl::FRAMEBUFFER, rendered_framebuffer, -1, b"rendered_framebuffer\0".as_ptr().cast()); // make tetxure gl::GenTextures(1, &mut rendered_texture); gl::BindTexture(gl::TEXTURE_2D, rendered_texture); + gl::ObjectLabel(gl::TEXTURE, rendered_texture, -1, b"rendered_texture\0".as_ptr().cast()); + // empty image gl::TexStorage2D(gl::TEXTURE_2D, 1, gl::RGBA8, WIDTH as GLsizei, HEIGHT as GLsizei); @@ -267,6 +284,56 @@ pub fn do_loop(mut glfw: Glfw, mut window: Window, events: Receiver<(f64, Window ); } + unsafe { + // do frmaebuffer + gl::GenFramebuffers(1, &mut output_framebuffer_handle); + gl::BindFramebuffer(gl::FRAMEBUFFER, output_framebuffer_handle); + + gl::ObjectLabel(gl::FRAMEBUFFER, output_framebuffer_handle, -1, b"output_framebuffer\0".as_ptr().cast()); + + // make tetxure + gl::GenTextures(1, &mut output_texture); + gl::BindTexture(gl::TEXTURE_2D, output_texture); + + gl::ObjectLabel(gl::TEXTURE, output_texture, -1, b"output_texture\0".as_ptr().cast()); + + // empty image + gl::TexStorage2D(gl::TEXTURE_2D, 1, gl::RGBA8, WIDTH as GLsizei, HEIGHT as GLsizei); + + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as GLint); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as GLint); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as GLint); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as GLint); + + // set color attachment + gl::FramebufferTexture2D(gl::FRAMEBUFFER, gl::COLOR_ATTACHMENT0, gl::TEXTURE_2D, output_texture, 0); + + let buffers = [gl::COLOR_ATTACHMENT0]; + gl::DrawBuffers(1, buffers.as_ptr()); + + if gl::CheckFramebufferStatus(gl::FRAMEBUFFER) != gl::FRAMEBUFFER_COMPLETE { + panic!("failed to create fbo") + } + + let fullscreen_fbo = [ + -1.0f32, -1.0, 0.0, + 1.0, -1.0, 0.0, + -1.0, 1.0, 0.0, + -1.0, 1.0, 0.0, + 1.0, -1.0, 0.0, + 1.0, 1.0, 0.0, + ]; + + gl::GenBuffers(1, &mut output_quad_vbuf); + gl::BindBuffer(gl::ARRAY_BUFFER, output_quad_vbuf); + gl::BufferData( + gl::ARRAY_BUFFER, // target + (fullscreen_fbo.len() * std::mem::size_of::()) as gl::types::GLsizeiptr, // size of data in bytes + fullscreen_fbo.as_ptr() as *const gl::types::GLvoid, // pointer to data + gl::STATIC_DRAW, // usage + ); + } + const VERT_SHADER: &str = r"#version 150 core out vec2 v_tex; @@ -299,6 +366,11 @@ void main() gl::GenVertexArrays(1, &mut quad_vao); } + let fb = Framebuffer::new_from_raw(output_texture, output_framebuffer_handle, gl::RGBA8, Size { + width: WIDTH, + height: HEIGHT + }, 1); + while !window.should_close() { glfw.poll_events(); for (_, event) in glfw::flush_messages(&events) { @@ -307,7 +379,7 @@ void main() unsafe { // render to fb - gl::BindFramebuffer(gl::FRAMEBUFFER, framebuffer_handle); + gl::BindFramebuffer(gl::FRAMEBUFFER, rendered_framebuffer); // gl::BindFramebuffer(gl::FRAMEBUFFER, 0); @@ -321,7 +393,7 @@ void main() // do the drawing gl::UseProgram(triangle_program); // select vertices - gl::BindVertexArray(vao); + gl::BindVertexArray(triangle_vao); // draw to bound target gl::DrawArrays(gl::TRIANGLES, 0, 3); @@ -337,14 +409,16 @@ void main() // eprintln!("[core] rendered texture is {rendered_texture}"); // do offscreen passes + + // unsafe { + // gl::ActiveTexture(gl::TEXTURE0); + // gl::BindTexture(gl::TEXTURE_2D, rendered_texture); + // } unsafe { filter.frame(0, &Viewport { x: 0, y: 0, - size: Size { - width: WIDTH, - height: HEIGHT - } + output: &fb }, GlImage { handle: rendered_texture, format: gl::RGBA8, @@ -363,14 +437,16 @@ void main() // map quad to screen gl::BindFramebuffer(gl::FRAMEBUFFER, 0); + gl::UseProgram(quad_programid); + + gl::ActiveTexture(gl::TEXTURE0); - gl::BindTexture(gl::TEXTURE_2D, rendered_texture); + gl::BindTexture(gl::TEXTURE_2D, output_texture); - gl::UseProgram(quad_programid); gl::BindVertexArray(quad_vao); - gl::DrawArrays(gl::TRIANGLE_STRIP, 0, 4) + gl::DrawArrays(gl::TRIANGLE_STRIP, 0, 4); } window.swap_buffers(); diff --git a/librashader-runtime-gl/src/lib.rs b/librashader-runtime-gl/src/lib.rs index 4b103ff..d7cfc1d 100644 --- a/librashader-runtime-gl/src/lib.rs +++ b/librashader-runtime-gl/src/lib.rs @@ -10,6 +10,7 @@ mod binding; use std::collections::HashMap; use std::error::Error; use std::iter::Filter; +use std::ops::Deref; use std::path::Path; use gl::types::{GLenum, GLint, GLsizei, GLsizeiptr, GLuint}; use glfw::Key::P; @@ -37,7 +38,6 @@ unsafe fn gl_compile_shader(stage: GLenum, source: &str) -> GLuint { let shader = gl::CreateShader(stage); gl::ShaderSource(shader, 1, &source.as_bytes().as_ptr().cast(), std::ptr::null()); gl::CompileShader(shader); - let mut compile_status = 0; gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut compile_status); @@ -137,8 +137,8 @@ pub struct FilterCommon { history: Vec, feedback: Vec, luts: FxHashMap, + outputs: Vec, pub quad_vbo: GLuint, - pub input_framebuffer: Framebuffer, } impl FilterChain { @@ -192,6 +192,7 @@ impl FilterChain { }; let mut filters = Vec::new(); + let mut output_framebuffers = Vec::new(); // initialize passes for (index, (config, source, mut reflect)) in passes.into_iter().enumerate() { @@ -227,23 +228,22 @@ impl FilterChain { panic!("failed to link program") } - for binding in &glsl.context.texture_fixups { + gl::UseProgram(program); + + for binding in &glsl.context.sampler_bindings { let loc_name = format!("LIBRA_TEXTURE_{}\0", *binding); - unsafe { - let location = gl::GetUniformLocation(program, loc_name.as_str().as_ptr().cast()); - if location >= 0 { - gl::Uniform1i(location, *binding as GLint); - } + let location = gl::GetUniformLocation(program, loc_name.as_str().as_ptr().cast()); + if location >= 0 { + // eprintln!("setting sampler {location} to sample from {binding}"); + gl::Uniform1i(location, *binding as GLint); } } - unsafe { - gl::UseProgram(0); - (program, UniformLocation { - vertex: gl::GetUniformBlockIndex(program, b"LIBRA_UBO_VERTEX\0".as_ptr().cast()), - fragment: gl::GetUniformBlockIndex(program, b"LIBRA_UBO_FRAGMENT\0".as_ptr().cast()), - }) - } + gl::UseProgram(0); + (program, UniformLocation { + vertex: gl::GetUniformBlockIndex(program, b"LIBRA_UBO_VERTEX\0".as_ptr().cast()), + fragment: gl::GetUniformBlockIndex(program, b"LIBRA_UBO_FRAGMENT\0".as_ptr().cast()), + }) }; let ubo_ring = if let Some(ubo) = &reflection.ubo { @@ -282,12 +282,13 @@ impl FilterChain { (FilterChain::reflect_uniform_location(program, param), param.offset)); } - + // need output framebuffers. + output_framebuffers.push(Framebuffer::new(1)); // eprintln!("{:#?}", semantics); - eprintln!("{:#?}", reflection.meta); - eprintln!("{:#?}", locations); - eprintln!("{:#?}", reflection.push_constant); + // eprintln!("{:#?}", reflection.meta); + // eprintln!("{:#?}", locations); + // eprintln!("{:#?}", reflection.push_constant); // eprintln!("====fragment===="); // eprintln!("{:#}", glsl.fragment); // eprintln!("====vertex===="); @@ -305,32 +306,11 @@ impl FilterChain { source, // no idea if this works. // retroarch checks if feedback frames are used but we'll just init it tbh. - framebuffer: Framebuffer::new(1), feedback_framebuffer: Framebuffer::new(1), config: config.clone() }); } - eprintln!("{:?}", filters.iter().map(|f| f.program).collect::>()); - // let mut glprogram: Vec = Vec::new(); - // for compilation in &compiled { - // // compilation.context.compiler.vertex - // } - - // eprintln!("{:#?}", reflections); - - // eprintln!("{:#?}", compiled./); - // eprintln!("{:?}", preset); - // eprintln!("{:?}", reflect.reflect(&ReflectOptions { - // pass_number: i as u32, - // uniform_semantics, - // non_uniform_semantics: Default::default(), - // })); - - // todo: apply shader pass - // gl3.cpp: 1942 - - // load luts let mut luts = FxHashMap::default(); @@ -415,7 +395,6 @@ impl FilterChain { gl::GenVertexArrays(1, &mut quad_vao); } - // todo: split params Ok(FilterChain { passes: filters, quad_vao, @@ -426,8 +405,8 @@ impl FilterChain { history: vec![], feedback: vec![], luts, + outputs: output_framebuffers, quad_vbo, - input_framebuffer: Framebuffer::new(1) } }) } @@ -455,37 +434,34 @@ impl FilterChain { let mut source = original.clone(); - for passes in &mut self.passes { - passes.build_commands(&self.common, None, count, 1, vp, &original, &source); + let passes_len = self.passes.len(); + let (pass, last) = self.passes.split_at_mut(passes_len - 1); + + for (index, pass) in pass.iter_mut().enumerate() { + { + let target = &mut self.common.outputs[index]; + let framebuffer_size = target.scale(pass.config.scaling.clone(), pass.get_format(), vp, &original, &source); + } + let target = &self.common.outputs[index]; + pass.draw(&self.common, None, count, 1, vp, &original, &source, &target); + let target = target.as_texture(pass.config.filter, pass.config.wrap_mode); + + // todo: update-pass-outputs + source = target; // passes.build_semantics(&self, None, count, 1, vp, &original, &source); } + assert_eq!(last.len(), 1); + for pass in last { + source.filter = pass.config.filter; + source.mip_filter = pass.config.filter; + pass.draw(&self.common, None, count, 1, vp, &original, &source, &vp.output); + } unsafe { gl::BindFramebuffer(gl::FRAMEBUFFER, 0); gl::BindVertexArray(0); } - // todo: deal with the mess that is frame history - } - - pub fn do_final_pass(&mut self, count: u64, vp: &Viewport, input: GlImage, clear: bool, mvp: &[f32]) { - - // todo: make copy - - // todo: get filter info from pass data. - let filter = self.common.preset.shaders.first().map(|f| f.filter).unwrap_or_default(); - let wrap_mode = self.common.preset.shaders.first().map(|f| f.wrap_mode).unwrap_or_default(); - let original = Texture { - image: input, - filter, - mip_filter: filter, - wrap_mode - }; - - - - - // todo: deal with the mess that is frame history } } diff --git a/librashader-runtime-gl/src/util.rs b/librashader-runtime-gl/src/util.rs index 5f67bcd..160fdc4 100644 --- a/librashader-runtime-gl/src/util.rs +++ b/librashader-runtime-gl/src/util.rs @@ -1,5 +1,6 @@ use gl::types::{GLenum, GLint, GLuint}; use librashader::{FilterMode, WrapMode}; +use crate::framebuffer::Framebuffer; pub fn calc_miplevel(width: u32, height: u32) -> u32 { let mut size = std::cmp::max(width, height); @@ -21,10 +22,10 @@ pub struct Texture { } #[derive(Debug, Copy, Clone)] -pub struct Viewport { +pub struct Viewport<'a> { pub x: i32, pub y: i32, - pub size: Size, + pub output: &'a Framebuffer, } #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]