gfx-rs / gfx

[maintenance mode] A low-overhead Vulkan-like GPU API for Rust.
http://gfx-rs.github.io/
Apache License 2.0
5.35k stars 549 forks source link

Memory leak when creating texture with opengl backend #1395

Closed icefoxen closed 7 years ago

icefoxen commented 7 years ago

Creating texture seems to leak ~250 bytes per texture, even when cleanup() is called appropriately. Following example code reproduces the behavior on Linux using Intel graphics, using the latest gfx-rs master with memory usage tracked via watch 'ps aux | grep leak'. Sorry the repro is ugly; it's just the triangle example with a texture-loading function crudely called each frame. See the "BUGGO" comment for what I've added.

#[macro_use]
extern crate gfx;
extern crate gfx_window_glutin;
extern crate glutin;
extern crate image;

use gfx::{Adapter, CommandQueue, Factory, FrameSync, GraphicsPoolExt,
          Surface, SwapChain, SwapChainExt, WindowExt};
use gfx::format::Formatted;
use gfx::traits::FactoryExt;
use gfx::format::{Rgba8};

pub type ColorFormat = gfx::format::Rgba8;
pub type DepthFormat = gfx::format::DepthStencil;

gfx_defines!{
    vertex Vertex {
        pos: [f32; 2] = "a_Pos",
        color: [f32; 3] = "a_Color",
    }

    pipeline pipe {
        vbuf: gfx::VertexBuffer<Vertex> = (),
        out: gfx::RenderTarget<ColorFormat> = "Target0",
    }
}

const TRIANGLE: [Vertex; 3] = [
    Vertex { pos: [ -0.5, -0.5 ], color: [1.0, 0.0, 0.0] },
    Vertex { pos: [  0.5, -0.5 ], color: [0.0, 1.0, 0.0] },
    Vertex { pos: [  0.0,  0.5 ], color: [0.0, 0.0, 1.0] }
];

const CLEAR_COLOR: [f32; 4] = [0.1, 0.2, 0.3, 1.0];

fn load_texture<R, F>(factory: &mut F, data: &[u8])
                -> Result<gfx::handle::ShaderResourceView<R, [f32; 4]>, String> where
                R: gfx::Resources, F: gfx::Factory<R> {
    use std::io::Cursor;
    use gfx::texture as t;
    let img = image::load(Cursor::new(data), image::PNG).unwrap().to_rgba();
    let (width, height) = img.dimensions();
    let kind = t::Kind::D2(width as t::Size, height as t::Size, t::AaMode::Single);
    let (_, view) = factory.create_texture_immutable_u8::<Rgba8>(kind, &[&img]).unwrap();
    Ok(view)
}

pub fn main() {
    // Create window
    let mut events_loop = glutin::EventsLoop::new();
    let wb = glutin::WindowBuilder::new()
        .with_title("Triangle example".to_string())
        .with_dimensions(1024, 768);
    let gl_builder = glutin::ContextBuilder::new().with_vsync(true);
    // let gl_builder = gfx_window_glutin::config_context(gl_builder, ColorFormat::get_format(), DepthFormat::get_format());
    let window = glutin::GlWindow::new(wb, gl_builder, &events_loop).unwrap();

    // Acquire surface and adapters
    let (mut surface, adapters) = gfx_window_glutin::Window::new(window).get_surface_and_adapters();

    // Open device (factory and queues)
    let gfx::Device { mut factory, mut graphics_queues, .. } =
        adapters[0].open_with(|family, ty| {
            ((ty.supports_graphics() && surface.supports_queue(&family)) as u32, gfx::QueueType::Graphics)
        });
    let mut graphics_queue = graphics_queues.pop().expect("Unable to find a graphics queue.");

    // Create swapchain
    let config = gfx::SwapchainConfig::new()
                    .with_color::<ColorFormat>();
    let mut swap_chain = surface.build_swapchain(config, &graphics_queue);
    let views = swap_chain.create_color_views(&mut factory);

    let pso = factory.create_pipeline_simple(
        include_bytes!("shader/triangle_150.glslv"),
        include_bytes!("shader/triangle_150.glslf"),
        pipe::new()
    ).unwrap();
    let (vertex_buffer, slice) = factory.create_vertex_buffer_with_slice(&TRIANGLE, ());
    let mut graphics_pool = graphics_queue.create_graphics_pool(1);
    let frame_semaphore = factory.create_semaphore();
    let draw_semaphore = factory.create_semaphore();
    let frame_fence = factory.create_fence(false);

    let mut data = pipe::Data {
        vbuf: vertex_buffer,
        out: views[0].clone(),
    };

    // main loop
    let mut running = true;
    while running {
        // fetch events
        events_loop.poll_events(|event| {
            if let glutin::Event::WindowEvent { event, .. } = event {
                match event {
                    glutin::WindowEvent::Closed => running = false,
                    glutin::WindowEvent::KeyboardInput {
                        input: glutin::KeyboardInput {
                            virtual_keycode: Some(glutin::VirtualKeyCode::Escape), ..
                        }, ..
                    } => return,
                    glutin::WindowEvent::Resized(_width, _height) => {
                        // TODO
                    },
                    _ => (),
                }
            }
        });

        // Get next frame
        let frame = swap_chain.acquire_frame(FrameSync::Semaphore(&frame_semaphore));
        data.out = views[frame.id()].clone();

        // draw a frame
        // wait for frame -> draw -> signal -> present
        {
            let mut encoder = graphics_pool.acquire_graphics_encoder();
            encoder.clear(&data.out, CLEAR_COLOR);
            encoder.draw(&slice, &pso, &data);
            encoder.synced_flush(&mut graphics_queue, &[&frame_semaphore], &[&draw_semaphore], Some(&frame_fence));
        }

        // BUGGO: TEXTURE LOADED HERE EACH FRAME
        let _ = load_texture(&mut factory, &include_bytes!("../blend/image/lena.png")[..]);

        swap_chain.present(&mut graphics_queue, &[&draw_semaphore]);
        factory.wait_for_fences(&[&frame_fence], gfx::WaitFor::All, 1_000_000);
        graphics_queue.cleanup();
        graphics_pool.reset();
    }
}

Original bug report here: https://github.com/ggez/ggez/issues/111 , there's a little bit of discussion there about potential causes.

Thanks in advance!

kvark commented 7 years ago

Fixed by #1397

sector-f commented 7 years ago

Can you push this fix to crates.io?

kvark commented 7 years ago

@sector-f https://crates.io/crates/gfx_device_gl/0.14.3