ggez: memory leak (probably command encoders not getting cleaned up)

Describe the bug Meshes are not cleaned up after they go out of scope

To Reproduce Create a Mesh and drop it

Expected behavior The mesh should be dropped on the GPU

Screenshots or pasted code Adapted from the “super_simple” example

use ggez::{
    event,
    glam::*,
    graphics::{self, Color},
    Context, GameResult,
};
use log::LevelFilter;
use simplelog::{ColorChoice, TermLogger, TerminalMode};

struct MainState {}

impl event::EventHandler<ggez::GameError> for MainState {
    fn update(&mut self, _ctx: &mut Context) -> GameResult {
        Ok(())
    }

    fn draw(&mut self, ctx: &mut Context) -> GameResult {
        let mut canvas =
            graphics::Canvas::from_frame(ctx, graphics::Color::from([0.1, 0.2, 0.3, 1.0]));

        let circle = graphics::Mesh::new_circle(
            ctx,
            graphics::DrawMode::fill(),
            vec2(0., 0.),
            100.0,
            2.0,
            Color::WHITE,
        )?;

        canvas.draw(&circle, Vec2::new(0., 380.0));

        canvas.finish(ctx)?;

        Ok(())
    }
}

pub fn main() -> GameResult {
    TermLogger::init(
        LevelFilter::Info,
        simplelog::Config::default(),
        TerminalMode::Stdout,
        ColorChoice::Auto,
    )
    .unwrap();

    let cb = ggez::ContextBuilder::new("super_simple", "ggez");
    let (ctx, event_loop) = cb.build()?;
    let state = MainState {};
    event::run(ctx, event_loop, state)
}

Hardware and Software:

  • ggez version: 0.8.1
  • OS: Archlinux
  • Graphics card: Nvidia RTX 3070
  • Graphics card drivers: nvidia proprietary driver, version 520.56.06, from nvidia-all

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Comments: 15 (11 by maintainers)

Most upvoted comments

use std::env;
use std::str::FromStr;

use ggez::{
    event,
    glam::*,
    graphics::{self, Color},
    Context, GameResult,
};


struct MainState {
    count: u64,
    circles_per_draw: u64,
}

impl event::EventHandler<ggez::GameError> for MainState {
    fn update(&mut self, _ctx: &mut Context) -> GameResult {
        Ok(())
    }

    fn draw(&mut self, ctx: &mut Context) -> GameResult {
        self.count += 1;
        let mut canvas =
            graphics::Canvas::from_frame(ctx, graphics::Color::from([0.1, 0.2, 0.3, 1.0]));

        for _ in 0..self.circles_per_draw {
            let circle = graphics::Mesh::new_circle(
                ctx,
                graphics::DrawMode::fill(),
                vec2(0., 0.),
                100.0,
                2.0,
                Color::WHITE,
            )?;
            canvas.draw(&circle, Vec2::new(0., 380.0));
        }


        canvas.finish(ctx)?;

        if self.count > (60 * 60) { // run for 1m, assuming 60fps
            ctx.request_quit();
        }
        Ok(())
    }
}

pub fn main() -> GameResult {
    let mut numbers = Vec::new();
    for arg in env::args().skip(1) {
        numbers.push(u64::from_str(&arg)
        .expect("error parsing argument"));
    }
    if numbers.len() == 0 {
        eprintln!("Usage: no arg given");
        std::process::exit(1);
    }
    let cb = ggez::ContextBuilder::new("super_simple", "ggez");
    let (ctx, event_loop) = cb.build()?;
    let state = MainState {count: 0, circles_per_draw: numbers[0]};
    event::run(ctx, event_loop, state)
}

I ran this, passing in 10 and 100 as an argument, with valgrind, which returned the following

With 10

==170894== LEAK SUMMARY:
==170894==    definitely lost: 89,208 bytes in 1,048 blocks
==170894==    indirectly lost: 95,015 bytes in 3,543 blocks
==170894==      possibly lost: 68,981 bytes in 1,900 blocks
==170894==    still reachable: 1,037,739 bytes in 11,888 blocks
==170894==         suppressed: 0 bytes in 0 blocks
==170894== Reachable blocks (those to which a pointer was found) are not shown.
==170894== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==170894==
==170894== For lists of detected and suppressed errors, rerun with: -s
==170894== ERROR SUMMARY: 141 errors from 141 contexts (suppressed: 0 from 0)

With 100

==171440== LEAK SUMMARY:
==171440==    definitely lost: 89,208 bytes in 1,048 blocks
==171440==    indirectly lost: 95,015 bytes in 3,543 blocks
==171440==      possibly lost: 68,981 bytes in 1,900 blocks
==171440==    still reachable: 1,041,019 bytes in 11,920 blocks
==171440==         suppressed: 0 bytes in 0 blocks
==171440== Reachable blocks (those to which a pointer was found) are not shown.
==171440== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==171440==
==171440== For lists of detected and suppressed errors, rerun with: -s
==171440== ERROR SUMMARY: 141 errors from 141 contexts (suppressed: 0 from 0)

So whatever’s going on doesn’t seem to scale with the number of meshes dropped, at least on my machine.