diff --git a/src/draw_buffer.rs b/src/draw_buffer.rs index 4e59142..3c60327 100644 --- a/src/draw_buffer.rs +++ b/src/draw_buffer.rs @@ -1,12 +1,13 @@ use euclid::Size2D; use gleam::gl; use gleam::gl::types::{GLuint, GLenum, GLint}; +use std::mem; use std::rc::Rc; use crate::GLContext; use crate::NativeGLContextMethods; -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub enum ColorAttachmentType { Texture, Renderbuffer, @@ -27,21 +28,43 @@ impl Default for ColorAttachmentType { #[derive(Debug)] pub enum ColorAttachment { Renderbuffer(GLuint), - Texture(GLuint), + Texture(GLuint, GLuint), } impl ColorAttachment { pub fn color_attachment_type(&self) -> ColorAttachmentType { match *self { ColorAttachment::Renderbuffer(_) => ColorAttachmentType::Renderbuffer, - ColorAttachment::Texture(_) => ColorAttachmentType::Texture, + ColorAttachment::Texture(_, _) => ColorAttachmentType::Texture, } } fn destroy(self, gl: &dyn gl::Gl) { match self { ColorAttachment::Renderbuffer(id) => gl.delete_renderbuffers(&[id]), - ColorAttachment::Texture(tex_id) => gl.delete_textures(&[tex_id]), + ColorAttachment::Texture(tex_id1, tex_id2) => gl.delete_textures(&[tex_id1, tex_id2]), + } + } + + fn active_texture(&self) -> GLuint { + match *self { + ColorAttachment::Renderbuffer(_) => panic!("no texture for renderbuffer attachment"), + ColorAttachment::Texture(active, _) => active, + } + } + + fn complete_texture(&self) -> GLuint { + match *self { + ColorAttachment::Renderbuffer(_) => panic!("no texture for renderbuffer attachment"), + ColorAttachment::Texture(_, complete) => complete, + } + } + + fn swap_textures(&mut self) { + match *self { + ColorAttachment::Renderbuffer(_) => (), + ColorAttachment::Texture(ref mut active, ref mut complete) => + mem::swap(active, complete), } } } @@ -152,10 +175,17 @@ impl DrawBuffer { } } - pub fn get_bound_texture_id(&self) -> Option { + pub fn get_complete_texture_id(&self) -> Option { match self.color_attachment.as_ref().unwrap() { &ColorAttachment::Renderbuffer(_) => None, - &ColorAttachment::Texture(id) => Some(id), + &ColorAttachment::Texture(_active, complete) => Some(complete), + } + } + + pub fn get_active_texture_id(&self) -> Option { + match self.color_attachment.as_ref().unwrap() { + &ColorAttachment::Renderbuffer(_) => None, + &ColorAttachment::Texture(active, _complete) => Some(active), } } @@ -184,26 +214,41 @@ impl DrawBuffer { // TODO(ecoal95): Allow more customization of textures ColorAttachmentType::Texture => { - let texture = self.gl().gen_textures(1)[0]; - debug_assert!(texture != 0); + let create_texture = || { + let texture = self.gl().gen_textures(1)[0]; + debug_assert!(texture != 0); + + self.gl().bind_texture(gl::TEXTURE_2D, texture); + + debug_assert_eq!(self.gl().get_error(), gl::NO_ERROR); + + self.gl().tex_image_2d(gl::TEXTURE_2D, 0, + formats.texture_internal as GLint, self.size.width, self.size.height, 0, formats.texture, formats.texture_type, None); + + debug_assert_eq!(self.gl().get_error(), gl::NO_ERROR); - self.gl().bind_texture(gl::TEXTURE_2D, texture); - self.gl().tex_image_2d(gl::TEXTURE_2D, 0, - formats.texture_internal as GLint, self.size.width, self.size.height, 0, formats.texture, formats.texture_type, None); + // Low filtering to allow rendering + self.gl().tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as GLint); + self.gl().tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as GLint); - // Low filtering to allow rendering - self.gl().tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as GLint); - self.gl().tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as GLint); + debug_assert_eq!(self.gl().get_error(), gl::NO_ERROR); - // TODO(emilio): Check if these two are neccessary, probably not - self.gl().tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as GLint); - self.gl().tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as GLint); + // TODO(emilio): Check if these two are neccessary, probably not + self.gl().tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as GLint); + self.gl().tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as GLint); - self.gl().bind_texture(gl::TEXTURE_2D, 0); + debug_assert_eq!(self.gl().get_error(), gl::NO_ERROR); - debug_assert_eq!(self.gl().get_error(), gl::NO_ERROR); + self.gl().bind_texture(gl::TEXTURE_2D, 0); - Some(ColorAttachment::Texture(texture)) + debug_assert_eq!(self.gl().get_error(), gl::NO_ERROR); + texture + }; + + let active = create_texture(); + let complete = create_texture(); + + Some(ColorAttachment::Texture(active, complete)) }, }; @@ -230,7 +275,31 @@ impl DrawBuffer { self.attach_to_framebuffer() } - fn attach_to_framebuffer(&mut self) -> Result<(), &'static str> { + /// Swap the internal read and draw textures, returning the id of the texture + /// now used for reading. + pub fn swap_framebuffer_texture(&mut self) -> Option { + let (active_texture_id, complete_texture_id) = match self.color_attachment { + Some(ref mut attachment) => { + attachment.swap_textures(); + ( + attachment.active_texture(), + attachment.complete_texture(), + ) + } + None => return None, + }; + self.gl().bind_framebuffer(gl::FRAMEBUFFER, self.framebuffer); + self.gl().framebuffer_texture_2d( + gl::FRAMEBUFFER, + gl::COLOR_ATTACHMENT0, + gl::TEXTURE_2D, + active_texture_id, + 0 + ); + Some(complete_texture_id) + } + + fn attach_to_framebuffer(&self) -> Result<(), &'static str> { self.gl().bind_framebuffer(gl::FRAMEBUFFER, self.framebuffer); // NOTE: The assertion fails if the framebuffer is not bound debug_assert_eq!(self.gl().is_framebuffer(self.framebuffer), gl::TRUE); @@ -243,7 +312,7 @@ impl DrawBuffer { color_renderbuffer); debug_assert_eq!(self.gl().is_renderbuffer(color_renderbuffer), gl::TRUE); }, - ColorAttachment::Texture(texture_id) => { + ColorAttachment::Texture(texture_id, _) => { self.gl().framebuffer_texture_2d(gl::FRAMEBUFFER, gl::COLOR_ATTACHMENT0, gl::TEXTURE_2D, diff --git a/src/gl_context.rs b/src/gl_context.rs index 487ccd2..7c7be1f 100644 --- a/src/gl_context.rs +++ b/src/gl_context.rs @@ -188,6 +188,19 @@ impl GLContext &self.limits } + /// Swap the backing textures for the draw buffer, returning the id of + /// the texture now used for reading. Resets the new active texture to an + /// appropriate initial state; + pub fn swap_draw_buffer(&mut self, clear_color: (f32, f32, f32, f32)) -> Option { + let texture_id = match self.draw_buffer { + Some(ref mut db) => db.swap_framebuffer_texture(), + None => return None, + }; + // TODO: support preserveDrawBuffer instead of clearing new frame + self.reset_draw_buffer_contents(Some(clear_color)); + texture_id + } + pub fn borrow_draw_buffer(&self) -> Option<&DrawBuffer> { self.draw_buffer.as_ref() } @@ -224,13 +237,20 @@ impl GLContext self.extensions.clone() } + fn reset_draw_buffer_contents(&self, clear_color: Option<(f32, f32, f32, f32)>) { + match clear_color { + Some((r, g, b, a)) => self.gl().clear_color(r, g, b, if !self.attributes.alpha { 1.0 } else { a }), + None => self.gl().clear_color(0.0, 0.0, 0.0, if !self.attributes.alpha { 1.0 } else { 0.0 }), + } + self.gl().clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT); + } + fn init_offscreen(&mut self, size: Size2D, color_attachment_type: ColorAttachmentType) -> Result<(), &'static str> { self.create_draw_buffer(size, color_attachment_type)?; debug_assert!(self.is_current()); - self.gl().clear_color(0.0, 0.0, 0.0, 0.0); - self.gl().clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT); + self.reset_draw_buffer_contents(None); self.gl().scissor(0, 0, size.width, size.height); self.gl().viewport(0, 0, size.width, size.height); diff --git a/src/tests.rs b/src/tests.rs index 00200f5..56deac8 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -98,7 +98,7 @@ fn test_texture_color_attachment(api_version: GLVersion) { test_gl_context(&context); // Get the bound texture and check we're painting on it - let texture_id = context.borrow_draw_buffer().unwrap().get_bound_texture_id().unwrap(); + let texture_id = context.borrow_draw_buffer().unwrap().get_active_texture_id().unwrap(); assert!(texture_id != 0); assert!(context.gl().get_error() == gl::NO_ERROR); @@ -132,7 +132,7 @@ fn test_sharing(api_version: GLVersion) { api_version, None).unwrap(); - let primary_texture_id = primary.borrow_draw_buffer().unwrap().get_bound_texture_id().unwrap(); + let primary_texture_id = primary.borrow_draw_buffer().unwrap().get_active_texture_id().unwrap(); assert!(primary_texture_id != 0); let secondary = GLContext::::new(size, @@ -147,7 +147,7 @@ fn test_sharing(api_version: GLVersion) { // Now the secondary context is bound, get the texture id, switch contexts, and check the // texture is there. - let secondary_texture_id = secondary.borrow_draw_buffer().unwrap().get_bound_texture_id().unwrap(); + let secondary_texture_id = secondary.borrow_draw_buffer().unwrap().get_active_texture_id().unwrap(); assert!(secondary_texture_id != 0); primary.make_current().unwrap(); @@ -254,7 +254,7 @@ fn test_multithread_sharing(api_version: GLVersion) { None).unwrap(); primary.make_current().unwrap(); - let primary_texture_id = primary.borrow_draw_buffer().unwrap().get_bound_texture_id().unwrap(); + let primary_texture_id = primary.borrow_draw_buffer().unwrap().get_active_texture_id().unwrap(); assert!(primary_texture_id != 0); let (tx, rx) = mpsc::channel(); @@ -277,7 +277,7 @@ fn test_multithread_sharing(api_version: GLVersion) { // Paint the second context red test_gl_context(&secondary); // Send texture_id to main thread - let texture_id = secondary.borrow_draw_buffer().unwrap().get_bound_texture_id().unwrap(); + let texture_id = secondary.borrow_draw_buffer().unwrap().get_active_texture_id().unwrap(); assert!(texture_id != 0); tx.send(SGLUint(texture_id)).unwrap(); // Avoid drop until test ends