From bcab218563b215de587b68a7f6a35cd26d549762 Mon Sep 17 00:00:00 2001 From: Cobrand Date: Sun, 14 May 2017 07:18:33 +0200 Subject: [PATCH] Change the pub API behind `SDL_SetRenderTarget` * Extensive changes to Canvas * Deleted RendererTarget and TextureCanvas * Updated documentation * Updated examples as well Closes #657 --- examples/game-of-life.rs | 94 ++++++++++--- examples/renderer-target.rs | 11 +- src/sdl2/render.rs | 273 +++++++++++++++++++++--------------- 3 files changed, 239 insertions(+), 139 deletions(-) diff --git a/examples/game-of-life.rs b/examples/game-of-life.rs index 37324e59..99fd12cd 100644 --- a/examples/game-of-life.rs +++ b/examples/game-of-life.rs @@ -115,32 +115,77 @@ mod game_of_life { } } -fn dummy_texture<'a>(canvas: &mut Canvas, texture_creator: &'a TextureCreator) -> Texture<'a>{ - let mut square_texture : Texture = +fn dummy_texture<'a>(canvas: &mut Canvas, texture_creator: &'a TextureCreator) -> (Texture<'a>, Texture<'a>) { + enum TextureColor { + Yellow, + White, + }; + let mut square_texture1 : Texture = texture_creator.create_texture_target(None, SQUARE_SIZE, SQUARE_SIZE).unwrap(); + let mut square_texture2 : Texture = + texture_creator.create_texture_target(None, SQUARE_SIZE, SQUARE_SIZE).unwrap(); + // let's change the textures we just created { - // let's change the texture we just created - let mut texture_canvas = canvas.with_target(&mut square_texture).unwrap(); - texture_canvas.set_draw_color(Color::RGB(0, 0, 0)); - texture_canvas.clear(); - for i in 0..SQUARE_SIZE { - for j in 0..SQUARE_SIZE { - // drawing pixel by pixel isn't very effective, but we only do it once and store - // the texture afterwards so it's still alright! - if (i+j) % 7 == 0 { - // this doesn't mean anything, there was some trial and serror to find - // something that wasn't too ugly - texture_canvas.set_draw_color(Color::RGB(192, 192, 192)); - texture_canvas.draw_point(Point::new(i as i32, j as i32)).unwrap(); + let textures = vec![ + (&mut square_texture1, TextureColor::Yellow), + (&mut square_texture2, TextureColor::White) + ]; + canvas.with_multiple_texture_canvas(textures.iter(), |texture_canvas, user_context| { + texture_canvas.set_draw_color(Color::RGB(0, 0, 0)); + texture_canvas.clear(); + match *user_context { + TextureColor::Yellow => { + for i in 0..SQUARE_SIZE { + for j in 0..SQUARE_SIZE { + if (i+j) % 4 == 0 { + texture_canvas.set_draw_color(Color::RGB(255, 255, 0)); + texture_canvas.draw_point(Point::new(i as i32, j as i32)).unwrap(); + } + if (i+j*2) % 9 == 0 { + texture_canvas.set_draw_color(Color::RGB(200, 200, 0)); + texture_canvas.draw_point(Point::new(i as i32, j as i32)).unwrap(); + } + } + } + }, + TextureColor::White => { + for i in 0..SQUARE_SIZE { + for j in 0..SQUARE_SIZE { + // drawing pixel by pixel isn't very effective, but we only do it once and store + // the texture afterwards so it's still alright! + if (i+j) % 7 == 0 { + // this doesn't mean anything, there was some trial and error to find + // something that wasn't too ugly + texture_canvas.set_draw_color(Color::RGB(192, 192, 192)); + texture_canvas.draw_point(Point::new(i as i32, j as i32)).unwrap(); + } + if (i+j*2) % 5 == 0 { + texture_canvas.set_draw_color(Color::RGB(64, 64, 64)); + texture_canvas.draw_point(Point::new(i as i32, j as i32)).unwrap(); + } + } + } } - if (i+j*2) % 5 == 0 { - texture_canvas.set_draw_color(Color::RGB(64, 64, 64)); - texture_canvas.draw_point(Point::new(i as i32, j as i32)).unwrap(); + }; + for i in 0..SQUARE_SIZE { + for j in 0..SQUARE_SIZE { + // drawing pixel by pixel isn't very effective, but we only do it once and store + // the texture afterwards so it's still alright! + if (i+j) % 7 == 0 { + // this doesn't mean anything, there was some trial and serror to find + // something that wasn't too ugly + texture_canvas.set_draw_color(Color::RGB(192, 192, 192)); + texture_canvas.draw_point(Point::new(i as i32, j as i32)).unwrap(); + } + if (i+j*2) % 5 == 0 { + texture_canvas.set_draw_color(Color::RGB(64, 64, 64)); + texture_canvas.draw_point(Point::new(i as i32, j as i32)).unwrap(); + } } } - } + }).unwrap(); } - square_texture + (square_texture1, square_texture2) } pub fn main() { @@ -180,7 +225,7 @@ pub fn main() { let texture_creator : TextureCreator<_> = canvas.texture_creator(); // Create a "target" texture so that we can use our Renderer with it later - let square_texture = dummy_texture(&mut canvas, &texture_creator); + let (square_texture1, square_texture2) = dummy_texture(&mut canvas, &texture_creator); let mut game = game_of_life::GameOfLife::new(); let mut event_pump = sdl_context.event_pump().unwrap(); @@ -208,7 +253,7 @@ pub fn main() { } // update the game loop here - if frame >= 29 { + if frame >= 30 { game.update(); frame = 0; } @@ -217,6 +262,11 @@ pub fn main() { canvas.clear(); for (i, unit) in (&game).into_iter().enumerate() { let i = i as u32; + let square_texture = if frame >= 15 { + &square_texture1 + } else { + &square_texture2 + }; if *unit { canvas.copy(&square_texture, None, diff --git a/examples/renderer-target.rs b/examples/renderer-target.rs index a791483b..b1ff1909 100644 --- a/examples/renderer-target.rs +++ b/examples/renderer-target.rs @@ -30,12 +30,11 @@ fn main() { } } angle = (angle + 0.5) % 360.; - { - let mut target = canvas.with_target(&mut texture).unwrap(); - target.clear(); - target.set_draw_color(Color::RGBA(255, 0, 0, 255)); - target.fill_rect(Rect::new(0, 0, 400, 300)).unwrap(); - } // <- drops the `target` so that the `canvas` can be used again + canvas.with_texture_canvas(&mut texture, |texture_canvas| { + texture_canvas.clear(); + texture_canvas.set_draw_color(Color::RGBA(255, 0, 0, 255)); + texture_canvas.fill_rect(Rect::new(0, 0, 400, 300)).unwrap(); + }).unwrap(); canvas.set_draw_color(Color::RGBA(0, 0, 0, 255)); let dst = Some(Rect::new(0, 0, 400, 300)); canvas.clear(); diff --git a/src/sdl2/render.rs b/src/sdl2/render.rs index 99265328..b29a0bdb 100644 --- a/src/sdl2/render.rs +++ b/src/sdl2/render.rs @@ -254,11 +254,7 @@ impl<'s> RenderTarget for Surface<'s> { type Context = SurfaceContext<'s>; } -impl<'r, 't, TC> RenderTarget for TextureTarget<'r, 't, TC> { - type Context = TC; -} - -/// Manages and owns a target (`Surface`, `Window`, or `Texture`) and allows drawing in it. +/// Manages and owns a target (`Surface` or `Window`) and allows drawing in it. /// /// If the `Window` manipulates the shell of the Window, `Canvas` allows you to /// manipulate both the shell and the inside of the window; @@ -360,19 +356,6 @@ impl<'s> Canvas> { self.target } - /// Sets the render target to the provided texture - /// Returns a handle for rendering methods to the target - /// Returns `TargetRenderError::NotSupported` - /// if the renderer does not support the use of render targets, - /// Returns `TargetRenderError::SdlError` if SDL2 returned with an error code. - /// The texture must be created with the texture access: `sdl2::render::TextureAccess::Target`. - pub fn with_target<'r, 't, 'a> - (&'r mut self, - texture: &'t mut Texture<'a>) - -> Result>, TargetRenderError> { - self.internal_with_target(texture) - } - /// Returns a `TextureCreator` that can create Textures to be drawn on this `Canvas` /// /// This `TextureCreator` will share a reference to the renderer and target context. @@ -413,19 +396,6 @@ impl Canvas { self.target } - /// Sets the render target to the provided texture - /// Returns a handle for rendering methods to the target - /// Returns `TargetRenderError::NotSupported` - /// if the renderer does not support the use of render targets, - /// Returns `TargetRenderError::SdlError` if SDL2 returned with an error code. - /// The texture must be created with the texture access: `sdl2::render::TextureAccess::Target`. - pub fn with_target<'r, 't, 'a> - (&'r mut self, - texture: &'t mut Texture<'a>) - -> Result, TargetRenderError> { - self.internal_with_target(texture) - } - /// Returns a `TextureCreator` that can create Textures to be drawn on this `Canvas` /// /// This `TextureCreator` will share a reference to the renderer and target context. @@ -446,21 +416,154 @@ impl Canvas { unsafe { ll::SDL_RenderTargetSupported(self.context.raw) == 1 } } - fn internal_with_target<'r, 't, 'a> - (&'r mut self, - texture: &'t mut Texture<'a>) - -> Result, TargetRenderError> { + /// Temporarily sets the target of `Canvas` to a `Texture`. This effectively allows rendering + /// to a `Texture` in any way you want: you can make a `Texture` a combination of other + /// `Texture`s, be a complex geometry form with the `gfx` module, ... You can draw pixel by + /// pixel in it if you want, so you can do basically anything with that `Texture`. + /// + /// If you want to set the content of multiple `Texture` at once the most efficient way + /// possible, *don't* make a loop and call this function everytime and use + /// `with_multiple_texture_canvas` instead. Using `with_texture_canvas` is actually + /// inefficient because the target is reset to the source (the `Window` or the `Surface`) + /// at the end of this function, but using it in a loop would make this reset useless. + /// Plus, the check that render_target is actually supported on that `Canvas` is also + /// done every time, leading to useless checks. + /// + /// # Notes + /// + /// Note that the `Canvas` in the closure is exactly the same as the one you call this + /// function with, meaning that you can call every function of your original `Canvas`. + /// + /// That means you can also call `with_texture_canvas` and `with_multiple_texture_canvas` from + /// the inside of the closure. Even though this is useless and inefficient, this is totally + /// safe to do and allowed. + /// + /// Since the render target is now a Texture, some calls of Canvas might return another result + /// than if the target was to be the original source. For instance `output_size` will return + /// this size of the current `Texture` in the closure, but the size of the `Window` or + /// `Surface` outside of the closure. + /// + /// You do not need to call `present` after drawing in the Canvas in the closure, the changes + /// are appleid directly to the `Texture` instead of a hidden buffer. + /// + /// # Errors + /// + /// * returns `TargetRenderError::NotSupported` + /// if the renderer does not support the use of render targets + /// * returns `TargetRenderError::SdlError` if SDL2 returned with an error code. + /// + /// The texture *must* be created with the texture access: + /// `sdl2::render::TextureAccess::Target`. + /// Using a texture which was not created with the texture access `Target` is undefined + /// behavior. + /// + /// # Examples + /// + /// The example below changes a newly created `Texture` to be a 150-by-150 black texture with a + /// 50-by-50 red square in the middle. + /// + /// ```rust,no_run + /// # use sdl2::render::{Canvas, Texture}; + /// # use sdl2::video::Window; + /// # use sdl2::pixels::Color; + /// # use sdl2::rect::Rect; + /// # let mut canvas : Canvas = unimplemented!(); + /// let texture_creator = canvas.texture_creator(); + /// let mut texture = texture_creator + /// .create_texture_target(texture_creator.default_pixel_format(), 150, 150) + /// .unwrap(); + /// let result = canvas.with_texture_canvas(&mut texture, |texture_canvas| { + /// texture_canvas.set_draw_color(Color::RGBA(0, 0, 0, 255)); + /// texture_canvas.clear(); + /// texture_canvas.set_draw_color(Color::RGBA(255, 0, 0, 255)); + /// texture_canvas.fill_rect(Rect::new(50, 50, 50, 50)).unwrap(); + /// }); + /// ``` + /// + + pub fn with_texture_canvas(&mut self, texture: &mut Texture, mut f: F) + -> Result<(), TargetRenderError> where for<'r> F: FnMut(&'r mut Canvas,) { if self.render_target_supported() { unsafe { self.set_raw_target(texture.raw) } .map_err(|e| TargetRenderError::SdlError(e))?; - Ok(TextureCanvas { - context: self.context.clone(), - target: TextureTarget { - raw_renderer: &self.context.raw, - _texture_marker: PhantomData, - _texture_target: PhantomData, - }, - }) + f(self); + unsafe { self.set_raw_target(ptr::null_mut()) } + .map_err(|e| TargetRenderError::SdlError(e))?; + Ok(()) + } else { + Err(TargetRenderError::NotSupported) + } + } + + /// Same as `with_texture_canvas`, but allows to change multiple `Texture`s at once with the + /// least amount of overhead. It means that between every iteration the Target is not reset to + /// the source, and that the fact that the Canvas supports render target isn't checked every + /// iteration either; the check is actually only done once, at the beginning, avoiding useless + /// checks. + /// + /// The closure is run once for every `Texture` sent as parameter. + /// + /// The main changes from `with_texture_canvas` is that is takes an `Iterator` of `(&mut + /// Texture, U)`, where U is a type defined by the user. The closure takes a `&mut Canvas`, and + /// `&U` as arguments instead of a simple `&mut Canvas`. This user-defined type allows you to + /// keep track of what to do with the Canvas you have received in the closure. + /// + /// You will usually want to keep track of the number, a property, or anything that will allow + /// you to uniquely track this `Texture`, but it can also be an empty struct or `()` as well! + /// + /// # Examples + /// + /// Let's create two textures, one which will be yellow, and the other will be white + /// + /// ```rust,no_run + /// # use sdl2::pixels::Color; + /// # use sdl2::rect::Rect; + /// # use sdl2::video::Window; + /// # use sdl2::render::{Canvas, Texture}; + /// # let mut canvas : Canvas = unimplemented!(); + /// let texture_creator = canvas.texture_creator(); + /// enum TextureColor { + /// Yellow, + /// White, + /// }; + /// + /// let mut square_texture1 : Texture = + /// texture_creator.create_texture_target(None, 100, 100).unwrap(); + /// let mut square_texture2 : Texture = + /// texture_creator.create_texture_target(None, 100, 100).unwrap(); + /// let textures : Vec<(&mut Texture, TextureColor)> = vec![ + /// (&mut square_texture1, TextureColor::Yellow), + /// (&mut square_texture2, TextureColor::White) + /// ]; + /// let result : Result<(), _> = + /// canvas.with_multiple_texture_canvas(textures.iter(), |texture_canvas, user_context| { + /// match *user_context { + /// TextureColor::White => { + /// texture_canvas.set_draw_color(Color::RGB(255, 255, 255)); + /// }, + /// TextureColor::Yellow => { + /// texture_canvas.set_draw_color(Color::RGB(255, 255, 0)); + /// } + /// }; + /// texture_canvas.clear(); + /// }); + /// // square_texture1 is now Yellow and square_texture2 is now White! + /// ``` + /// + /// + pub fn with_multiple_texture_canvas<'t : 'a, 'a : 's, 's, I, F, U: 's>(&mut self, textures: I, mut f: F) + -> Result<(), TargetRenderError> + where for<'r> F: FnMut(&'r mut Canvas, &U), I: Iterator, U)> { + if self.render_target_supported() { + for &(ref texture, ref user_context) in textures { + unsafe { self.set_raw_target(texture.raw) } + .map_err(|e| TargetRenderError::SdlError(e))?; + f(self, &user_context); + } + // reset the target to its source + unsafe { self.set_raw_target(ptr::null_mut()) } + .map_err(|e| TargetRenderError::SdlError(e))?; + Ok(()) } else { Err(TargetRenderError::NotSupported) } @@ -470,8 +573,15 @@ impl Canvas { /// Creates Textures that cannot outlive the creator /// /// The `TextureCreator` does not hold a lifetime to its Canvas by design choice. +/// /// If a `Canvas` is dropped before its `TextureCreator`, it is still safe to use. -/// It is, however, useless. Any `Texture` created here can only be drawn onto the original `Canvas` +/// +/// It is, however, useless. +/// +/// Any `Texture` created here can only be drawn onto the original `Canvas`. A `Texture` used in a +/// `Canvas` must come from a `TextureCreator` coming from that same `Canvas`. Using a `Texture` to +/// render to a `Canvas` not being the parent of the `Texture`'s `TextureCreator` is undefined +/// behavior. pub struct TextureCreator { context: Rc>, default_pixel_format: PixelFormatEnum, @@ -480,7 +590,7 @@ pub struct TextureCreator { /// The type that allows you to build Window-based renderers. /// /// By default, the renderer builder will prioritize for a hardware-accelerated -/// renderer. +/// renderer, which is porbably what you want. pub struct CanvasBuilder { window: Window, index: Option, @@ -718,22 +828,13 @@ impl TextureCreator { } } -pub struct TextureTarget<'r, 't, TC> { - raw_renderer: &'r *mut ll::SDL_Renderer, - _texture_marker: PhantomData<&'t ()>, - // unfortunately there is no way to know which kind of Renderer we have here at compile time, - // so this PhantomData is here to keep track of that. - _texture_target: PhantomData, -} - -impl<'r, 't, TC> Drop for TextureTarget<'r, 't, TC> { - // `Drop` cannot be specialized. Get around this through run-time check of Target Kind - fn drop(&mut self) { - unsafe { - ll::SDL_SetRenderTarget(*self.raw_renderer, ptr::null_mut()); - } - } -} +// pub struct TextureTarget<'r, 't, TC> { +// raw_renderer: &'r *mut ll::SDL_Renderer, +// _texture_marker: PhantomData<&'t ()>, +// // unfortunately there is no way to know which kind of Renderer we have here at compile time, +// // so this PhantomData is here to keep track of that. +// _texture_target: PhantomData, +// } /// Drawing methods impl Canvas { @@ -1153,56 +1254,6 @@ impl Canvas { } } -/// A handle for getting/setting the render target of the render context. -/// -/// # Example -/// ```no_run -/// use sdl2::pixels::{Color, PixelFormatEnum}; -/// use sdl2::rect::Rect; -/// use sdl2::video::WindowContext; -/// use sdl2::render::{Texture, TextureCreator, WindowCanvas}; -/// -/// // Draw a red rectangle to a new texture -/// fn draw_to_texture<'c>(t: &'c TextureCreator, c: &mut WindowCanvas) -/// -> Texture<'c> { -/// let mut texture = t.create_texture_target(PixelFormatEnum::RGBA8888, 512, 512) -/// .unwrap(); -/// { -/// let mut target = c.with_target(&mut texture) -/// .expect("This platform doesn't support render targets"); -/// -/// // Start drawing -/// target.clear(); -/// target.set_draw_color(Color::RGB(255, 0, 0)); -/// target.fill_rect(Rect::new(100, 100, 256, 256)); -/// } -/// texture -/// } -/// ``` -pub type TextureCanvas<'r, 't, TC> = Canvas>; - -impl<'r, 't, TC> Canvas> { - /// Replace the target of the `TextureCanvas` with a different `Texture` - /// - /// Returns the new `TextureCanvas` and releases the `&mut` borrow on the old `Texture` - pub fn with_target<'nt, 'a>(mut self, - texture: &'nt mut Texture<'a>) - -> Result, SdlError> { - unsafe { self.set_raw_target(texture.raw) }?; - let context = mem::replace(&mut self.context, unsafe { mem::zeroed() }); - let raw_renderer = mem::replace(&mut self.target.raw_renderer, unsafe { mem::zeroed() }); - mem::forget(self); - Ok(TextureCanvas { - context: context, - target: TextureTarget { - raw_renderer: raw_renderer, - _texture_marker: PhantomData, - _texture_target: PhantomData, - }, - }) - } -} - #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub struct TextureQuery { pub format: pixels::PixelFormatEnum,