From 52cac9b08f2d8beba8c9ba8a0e487487be8447bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20H=C3=BCbner?= Date: Thu, 2 May 2024 18:20:16 +0100 Subject: [PATCH] Fix issues with RenderTarget leaking memory. * Fix issue with RenderTarget leaking memory by adding a drop impl, reported in issue #635 --- particles/src/lib.rs | 1 + src/camera.rs | 16 +++++---- src/texture.rs | 79 ++++++++++++++++++++++++++++++++------------ 3 files changed, 69 insertions(+), 27 deletions(-) diff --git a/particles/src/lib.rs b/particles/src/lib.rs index 963b9d8e..4092f73b 100644 --- a/particles/src/lib.rs +++ b/particles/src/lib.rs @@ -1,5 +1,6 @@ use macroquad::prelude::*; use macroquad::window::miniquad::*; +use miniquad::graphics::RenderPass; #[cfg(feature = "nanoserde")] use nanoserde::{DeJson, SerJson}; diff --git a/src/camera.rs b/src/camera.rs index 0f0f3e12..8e8c6664 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -3,6 +3,7 @@ use crate::{ get_context, math::Rect, + prelude::RenderPass, texture::RenderTarget, window::{screen_height, screen_width}, }; @@ -11,7 +12,7 @@ use glam::{vec2, vec3, Mat4, Vec2, Vec3}; pub trait Camera { fn matrix(&self) -> Mat4; fn depth_enabled(&self) -> bool; - fn render_pass(&self) -> Option; + fn render_pass(&self) -> Option; fn viewport(&self) -> Option<(i32, i32, i32, i32)>; } @@ -107,8 +108,8 @@ impl Camera for Camera2D { false } - fn render_pass(&self) -> Option { - self.render_target.as_ref().map(|rt| rt.render_pass) + fn render_pass(&self) -> Option { + self.render_target.as_ref().map(|rt| rt.render_pass.clone()) } fn viewport(&self) -> Option<(i32, i32, i32, i32)> { @@ -227,8 +228,8 @@ impl Camera for Camera3D { true } - fn render_pass(&self) -> Option { - self.render_target.as_ref().map(|rt| rt.render_pass) + fn render_pass(&self) -> Option { + self.render_target.as_ref().map(|rt| rt.render_pass.clone()) } fn viewport(&self) -> Option<(i32, i32, i32, i32)> { @@ -243,7 +244,10 @@ pub fn set_camera(camera: &dyn Camera) { // flush previous camera draw calls context.perform_render_passes(); - context.gl.render_pass(camera.render_pass()); + if let Some(render_pass) = camera.render_pass() { + context.gl.render_pass(Some(render_pass.raw_miniquad_id())); + } + context.gl.viewport(camera.viewport()); context.gl.depth_test(camera.depth_enabled()); context.camera_matrix = Some(camera.matrix()); diff --git a/src/texture.rs b/src/texture.rs index 993a62d6..40d0fcc0 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -230,24 +230,27 @@ impl Image { bytes, } } - + /// Blends this image with another image (of identical dimensions) /// Inspired by OpenCV saturated blending pub fn blend(&mut self, other: &Image) { - assert!(self.width as usize * self.height as usize == other.width as usize * other.height as usize); + assert!( + self.width as usize * self.height as usize + == other.width as usize * other.height as usize + ); for i in 0..self.bytes.len() / 4 { let c1: Color = Color { r: self.bytes[i * 4] as f32 / 255., g: self.bytes[i * 4 + 1] as f32 / 255., b: self.bytes[i * 4 + 2] as f32 / 255., - a: self.bytes[i * 4 + 3] as f32 / 255. + a: self.bytes[i * 4 + 3] as f32 / 255., }; let c2: Color = Color { r: other.bytes[i * 4] as f32 / 255., g: other.bytes[i * 4 + 1] as f32 / 255., b: other.bytes[i * 4 + 2] as f32 / 255., - a: other.bytes[i * 4 + 3] as f32 / 255. + a: other.bytes[i * 4 + 3] as f32 / 255., }; let new_color: Color = Color { r: f32::min(c1.r * c1.a + c2.r * c2.a, 1.), @@ -267,28 +270,31 @@ impl Image { /// overlaying a completely transparent image has no effect /// on the original image, though blending them would. pub fn overlay(&mut self, other: &Image) { - assert!(self.width as usize * self.height as usize == other.width as usize * other.height as usize); + assert!( + self.width as usize * self.height as usize + == other.width as usize * other.height as usize + ); for i in 0..self.bytes.len() / 4 { let c1: Color = Color { r: self.bytes[i * 4] as f32 / 255., g: self.bytes[i * 4 + 1] as f32 / 255., b: self.bytes[i * 4 + 2] as f32 / 255., - a: self.bytes[i * 4 + 3] as f32 / 255. + a: self.bytes[i * 4 + 3] as f32 / 255., }; let c2: Color = Color { r: other.bytes[i * 4] as f32 / 255., g: other.bytes[i * 4 + 1] as f32 / 255., b: other.bytes[i * 4 + 2] as f32 / 255., - a: other.bytes[i * 4 + 3] as f32 / 255. + a: other.bytes[i * 4 + 3] as f32 / 255., }; let new_color: Color = Color { r: f32::min(c1.r * (1. - c2.a) + c2.r * c2.a, 1.), g: f32::min(c1.g * (1. - c2.a) + c2.g * c2.a, 1.), b: f32::min(c1.b * (1. - c2.a) + c2.b * c2.a, 1.), - a: f32::min(c1.a + c2.a, 1.) + a: f32::min(c1.a + c2.a, 1.), }; - + self.bytes[i * 4] = (new_color.r * 255.) as u8; self.bytes[i * 4 + 1] = (new_color.g * 255.) as u8; self.bytes[i * 4 + 2] = (new_color.b * 255.) as u8; @@ -334,32 +340,64 @@ pub async fn load_texture(path: &str) -> Result { Ok(Texture2D::from_file_with_format(&bytes[..], None)) } +#[derive(Debug, Clone)] +pub struct RenderPass { + pub color_texture: Texture2D, + pub depth_texture: Option, + pub(crate) render_pass: Arc, +} + +impl RenderPass { + fn new(color_texture: Texture2D, depth_texture: Option) -> RenderPass { + let render_pass = get_quad_context().new_render_pass( + color_texture.raw_miniquad_id(), + depth_texture.as_ref().map(|t| t.raw_miniquad_id()), + ); + RenderPass { + color_texture, + depth_texture: depth_texture.map(|t| t.clone()), + render_pass: Arc::new(render_pass), + } + } + /// Returns the miniquad handle for this render pass. + pub fn raw_miniquad_id(&self) -> miniquad::RenderPass { + *self.render_pass + } +} + +impl Drop for RenderPass { + fn drop(&mut self) { + if Arc::strong_count(&self.render_pass) < 2 { + let context = get_quad_context(); + context.delete_render_pass(*self.render_pass); + } + } +} + #[derive(Clone, Debug)] pub struct RenderTarget { pub texture: Texture2D, - pub render_pass: miniquad::RenderPass, + pub render_pass: RenderPass, } -impl RenderTarget { - pub fn delete(&self) { - let context = get_quad_context(); - context.delete_texture(self.texture.raw_miniquad_id()); - context.delete_render_pass(self.render_pass); - } +fn render_pass(color_texture: Texture2D, depth_texture: Option) -> RenderPass { + RenderPass::new(color_texture, depth_texture) } pub fn render_target(width: u32, height: u32) -> RenderTarget { - let context = get_quad_context(); + let context = get_context(); - let texture = context.new_render_texture(miniquad::TextureParams { + let texture_id = get_quad_context().new_render_texture(miniquad::TextureParams { width, height, ..Default::default() }); - let render_pass = context.new_render_pass(texture, None); + let texture = Texture2D { + texture: context.textures.store_texture(texture_id), + }; - let texture = Texture2D::from_miniquad_texture(texture); + let render_pass = render_pass(texture.clone(), None); RenderTarget { texture, @@ -671,7 +709,6 @@ impl Texture2D { ctx.texture_update(self.raw_miniquad_id(), bytes); } - /// Uploads [Image] data to part of this texture. pub fn update_part( &self,