Skip to content

Commit

Permalink
Fix issues with RenderTarget leaking memory.
Browse files Browse the repository at this point in the history
* Fix issue with RenderTarget leaking memory by adding a drop impl, reported in issue #635
  • Loading branch information
profan authored and not-fl3 committed May 3, 2024
1 parent ec3a655 commit 52cac9b
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 27 deletions.
1 change: 1 addition & 0 deletions particles/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use macroquad::prelude::*;
use macroquad::window::miniquad::*;
use miniquad::graphics::RenderPass;

#[cfg(feature = "nanoserde")]
use nanoserde::{DeJson, SerJson};
Expand Down
16 changes: 10 additions & 6 deletions src/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use crate::{
get_context,
math::Rect,
prelude::RenderPass,
texture::RenderTarget,
window::{screen_height, screen_width},
};
Expand All @@ -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<miniquad::RenderPass>;
fn render_pass(&self) -> Option<RenderPass>;
fn viewport(&self) -> Option<(i32, i32, i32, i32)>;
}

Expand Down Expand Up @@ -107,8 +108,8 @@ impl Camera for Camera2D {
false
}

fn render_pass(&self) -> Option<miniquad::RenderPass> {
self.render_target.as_ref().map(|rt| rt.render_pass)
fn render_pass(&self) -> Option<RenderPass> {
self.render_target.as_ref().map(|rt| rt.render_pass.clone())
}

fn viewport(&self) -> Option<(i32, i32, i32, i32)> {
Expand Down Expand Up @@ -227,8 +228,8 @@ impl Camera for Camera3D {
true
}

fn render_pass(&self) -> Option<miniquad::RenderPass> {
self.render_target.as_ref().map(|rt| rt.render_pass)
fn render_pass(&self) -> Option<RenderPass> {
self.render_target.as_ref().map(|rt| rt.render_pass.clone())
}

fn viewport(&self) -> Option<(i32, i32, i32, i32)> {
Expand All @@ -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());
Expand Down
79 changes: 58 additions & 21 deletions src/texture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.),
Expand All @@ -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;
Expand Down Expand Up @@ -334,32 +340,64 @@ pub async fn load_texture(path: &str) -> Result<Texture2D, Error> {
Ok(Texture2D::from_file_with_format(&bytes[..], None))
}

#[derive(Debug, Clone)]
pub struct RenderPass {
pub color_texture: Texture2D,
pub depth_texture: Option<Texture2D>,
pub(crate) render_pass: Arc<miniquad::RenderPass>,
}

impl RenderPass {
fn new(color_texture: Texture2D, depth_texture: Option<Texture2D>) -> 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<Texture2D>) -> 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,
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 52cac9b

Please sign in to comment.