Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

memory leak (probably command encoders not getting cleaned up) #1128

Open
Dumdidldum opened this issue Nov 1, 2022 · 15 comments
Open

memory leak (probably command encoders not getting cleaned up) #1128

Dumdidldum opened this issue Nov 1, 2022 · 15 comments

Comments

@Dumdidldum
Copy link

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
@jazzfool
Copy link
Contributor

jazzfool commented Nov 2, 2022

I can't seem to repro. note that the logger is spamming the terminal with info logs
Meshes themselves definitely do not leak (is your GPU memory going up or your system memory?)

@Dumdidldum
Copy link
Author

It seems like the system memory is going up (the games process that is)
The GPU does not seem to be affected actually, at least not in nvidia-smi

@nobbele nobbele added this to the 0.8.2 milestone Nov 2, 2022
@jazzfool
Copy link
Contributor

jazzfool commented Nov 2, 2022

Can you remove the logger and see if it still happens?

@Dumdidldum
Copy link
Author

Hi, yes even without the logger the (system) memory leak still exists

@PSteinhaus
Copy link
Member

I think I can repro on Windows 10.
But in fact system memory continously increases on all of the examples, sadly. Though some (blend_modes.rs for example) are worse than others.
But even minimalistic ones like the logging example seem to be leaking memory, though this example doesn't use any meshes at all...

@nobbele
Copy link
Member

nobbele commented Nov 4, 2022

can confirm on linux
image

it's exactly 132 bytes at some interval

@nobbele
Copy link
Member

nobbele commented Nov 4, 2022

possibly related to gfx-rs/wgpu#2553

@nobbele nobbele removed this from the 0.8.2 milestone Nov 4, 2022
@PSteinhaus PSteinhaus changed the title Meshes not cleaned up on GPU - memory leak? memory leak (probably command encoders not getting cleaned up) Nov 10, 2022
@qwitwa
Copy link
Contributor

qwitwa commented Nov 29, 2022

image

This will grow continuously, right? (src/graphics/gpu/growing.rs)

@nobbele
Copy link
Member

nobbele commented Nov 29, 2022

didn't check how that works exactly but the buffers can probably be deleted post-frame when strong = 1

@jazzfool
Copy link
Contributor

Growing arena doesn't cause this issue - deleting grown buffers every frame doesn't help (e.g., for all the examples, the arena reaches a stable point after frame 1)

@qwitwa
Copy link
Contributor

qwitwa commented Nov 29, 2022

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.

@qwitwa
Copy link
Contributor

qwitwa commented Nov 29, 2022

The output is long with possible losses but here's the stuff that valgrind thinks is definitely lost

  ==171440==
==171440== 4,248 bytes in 59 blocks are possibly lost in loss record 3,000 of 3,049
==171440==    at 0x484DA83: calloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==171440==    by 0x4ABE671: ??? (in /usr/lib/x86_64-linux-gnu/libasound.so.2.0.0)
==171440==    by 0x4AE2C74: ??? (in /usr/lib/x86_64-linux-gnu/libasound.so.2.0.0)
==171440==    by 0x4AE2FD1: ??? (in /usr/lib/x86_64-linux-gnu/libasound.so.2.0.0)
==171440==    by 0x4AE341C: ??? (in /usr/lib/x86_64-linux-gnu/libasound.so.2.0.0)
==171440==    by 0x4AE341C: ??? (in /usr/lib/x86_64-linux-gnu/libasound.so.2.0.0)
==171440==    by 0x4AE38E1: ??? (in /usr/lib/x86_64-linux-gnu/libasound.so.2.0.0)
==171440==    by 0x4AEE1DE: snd_config_update_r (in /usr/lib/x86_64-linux-gnu/libasound.so.2.0.0)
==171440==    by 0x4AEE876: snd_config_update_ref (in /usr/lib/x86_64-linux-gnu/libasound.so.2.0.0)
==171440==    by 0x4B14CA9: snd_pcm_open (in /usr/lib/x86_64-linux-gnu/libasound.so.2.0.0)
==171440==    by 0x15F998B: alsa::pcm::PCM::open (pcm.rs:131)
==171440==    by 0x15F9863: alsa::pcm::PCM::new (pcm.rs:120)
==171440==
==171440== 14,016 bytes in 438 blocks are definitely lost in loss record 3,032 of 3,049
==171440==    at 0x484DA83: calloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==171440==    by 0x496AE46: ??? (in /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.12.0)
==171440==    by 0x496CA20: FcCharSetMerge (in /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.12.0)
==171440==    by 0x122633A: crossfont::ft::fc::char_set::CharSetRef::merge (char_set.rs:62)
==171440==    by 0x1216CBF: crossfont::ft::FreeTypeRasterizer::get_face::{{closure}} (mod.rs:409)
==171440==    by 0x1213C87: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &mut F>::call_once (function.rs:306)
==171440==    by 0x122DA4C: core::option::Option<T>::map (option.rs:929)
==171440==    by 0x121E9AC: <core::iter::adapters::map::Map<I,F> as core::iter::traits::iterator::Iterator>::next (map.rs:103)
==171440==    by 0x1224BA6: alloc::vec::Vec<T,A>::extend_desugared (mod.rs:2749)
==171440==    by 0x12252CC: <alloc::vec::Vec<T,A> as alloc::vec::spec_extend::SpecExtend<T,I>>::spec_extend (spec_extend.rs:18)
==171440==    by 0x1223C27: <alloc::vec::Vec<T> as alloc::vec::spec_from_iter_nested::SpecFromIterNested<T,I>>::from_iter (spec_from_iter_nested.rs:43)
==171440==    by 0x122530C: <alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (spec_from_iter.rs:33)
==171440==
==171440== 15,648 bytes in 489 blocks are definitely lost in loss record 3,035 of 3,049
==171440==    at 0x484DA83: calloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==171440==    by 0x496AE46: ??? (in /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.12.0)
==171440==    by 0x496CA20: FcCharSetMerge (in /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.12.0)
==171440==    by 0x4983362: FcFontSetSort (in /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.12.0)
==171440==    by 0x4983641: FcFontSort (in /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.12.0)
==171440==    by 0x12263E0: crossfont::ft::fc::font_sort (mod.rs:66)
==171440==    by 0x12161E0: crossfont::ft::FreeTypeRasterizer::get_face (mod.rs:373)
==171440==    by 0x1214C9A: <crossfont::ft::FreeTypeRasterizer as crossfont::Rasterize>::load_font (mod.rs:200)
==171440==    by 0x1203004: sctk_adwaita::title::crossfont_renderer::CrossfontTitleText::new (crossfont_renderer.rs:47)
==171440==    by 0x11EFA2C: sctk_adwaita::title::TitleText::new (title.rs:20)
==171440==    by 0x11E6022: <sctk_adwaita::AdwaitaFrame as smithay_client_toolkit::window::Frame>::init (lib.rs:218)
==171440==    by 0x464595: smithay_client_toolkit::window::Window<F>::init_with_decorations (mod.rs:216)
==171440==
==171440== 139,443 (46,080 direct, 93,363 indirect) bytes in 60 blocks are definitely lost in loss record 3,049 of 3,049
==171440==    at 0x484DCD3: realloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==171440==    by 0x497FAC0: ??? (in /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.12.0)
==171440==    by 0x498EE52: ??? (in /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.12.0)
==171440==    by 0x49882BC: FcFontRenderPrepare (in /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.12.0)
==171440==    by 0x122BA29: crossfont::ft::fc::pattern::PatternRef::render_prepare (pattern.rs:539)
==171440==    by 0x1216C1A: crossfont::ft::FreeTypeRasterizer::get_face::{{closure}} (mod.rs:406)
==171440==    by 0x1213C87: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &mut F>::call_once (function.rs:306)
==171440==    by 0x122DA4C: core::option::Option<T>::map (option.rs:929)
==171440==    by 0x121E9AC: <core::iter::adapters::map::Map<I,F> as core::iter::traits::iterator::Iterator>::next (map.rs:103)
==171440==    by 0x1224BA6: alloc::vec::Vec<T,A>::extend_desugared (mod.rs:2749)
==171440==    by 0x12252CC: <alloc::vec::Vec<T,A> as alloc::vec::spec_extend::SpecExtend<T,I>>::spec_extend (spec_extend.rs:18)
==171440==    by 0x1223C27: <alloc::vec::Vec<T> as alloc::vec::spec_from_iter_nested::SpecFromIterNested<T,I>>::from_iter (spec_from_iter_nested.rs:43)

@kybarg
Copy link

kybarg commented Dec 16, 2022

Same leak i my project, except I'm not useing Mesh.
This is can be reproduced when rendering sub canvas.

@Vixeliz
Copy link
Contributor

Vixeliz commented Jun 29, 2023

Can anyone confirm this is still happening on devel with latest rust compiler nightly? I'm having trouble reproducing.

This was referenced Jul 7, 2023
@Dumdidldum
Copy link
Author

@Vixeliz i am using the latest nightly and the devel branch of this repo

rustc 1.74.0-nightly (a991861ec 2023-09-05)
ggez = { git = "https://github.com/ggez/ggez", rev = "74d2d0e342dd8453ade229ca398d1ada6190287b" }

I can still see the increase in system memory usage
Code used:

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

struct MainState {
    rectangle: graphics::Mesh,
}

impl MainState {
    fn new(ctx: &mut Context) -> GameResult<MainState> {
        let rectangle = graphics::Mesh::new_rectangle(
            ctx,
            graphics::DrawMode::fill(),
            Rect::new(0., 0., 500., 500.),
            Color::YELLOW,
        )?;
        Ok(MainState { rectangle })
    }
}

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

    fn draw(&mut self, ctx: &mut Context) -> GameResult {
        let mut screen_canvas = graphics::Canvas::from_frame(ctx, Color::MAGENTA);

        screen_canvas.draw(&self.rectangle, Vec2::ZERO);

        screen_canvas.finish(ctx)?;

        Ok(())
    }
}

pub fn main() -> GameResult {
    let cb = ggez::ContextBuilder::new("super_simple", "ggez");
    let (mut ctx, event_loop) = cb.build()?;
    let state = MainState::new(&mut ctx)?;
    event::run(ctx, event_loop, state)
}

I am using linux with x11 and nvidia proprietary drivers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants