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 with short lived Buffers #5397

Closed
rickvanprim opened this issue Mar 15, 2024 · 2 comments · Fixed by #5413
Closed

Memory Leak with short lived Buffers #5397

rickvanprim opened this issue Mar 15, 2024 · 2 comments · Fixed by #5413
Labels
type: bug Something isn't working

Comments

@rickvanprim
Copy link

Description
When creating and immediately dropping Buffers with mapped_at_creation: true, they don't seem to properly get cleaned up resulting in a memory leak.

Repro steps
Run the following program and observe an ever increasing amount of memory being used.

[package]
name = "scratch"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0.81"
pollster = "0.3.0"
wgpu = { git = "https://github.com/gfx-rs/wgpu.git" }
winit = "0.29.15"
use anyhow::Context;
use anyhow::Result;
use pollster::FutureExt;
use wgpu::Color;
use wgpu::InstanceDescriptor;
use wgpu::InstanceFlags;
use wgpu::LoadOp;
use wgpu::MaintainBase::Poll;
use wgpu::Operations;
use wgpu::RenderPassColorAttachment;
use wgpu::RenderPassDescriptor;
use wgpu::StoreOp;
use wgpu::{
    BufferUsages, CommandEncoderDescriptor, DeviceDescriptor, Features, Instance, Limits,
    PowerPreference, PresentMode, RequestAdapterOptions, SurfaceConfiguration, SurfaceTargetUnsafe,
    TextureUsages, TextureViewDescriptor,
};
use winit::{
    dpi::PhysicalSize,
    event::{Event, WindowEvent},
    event_loop::{ControlFlow, EventLoopBuilder},
    window::WindowBuilder,
};

fn main() -> Result<()> {
    let event_loop = EventLoopBuilder::new().build()?;
    event_loop.set_control_flow(ControlFlow::Poll);

    let window = WindowBuilder::new()
        .with_title("Memory Leak")
        .with_inner_size(PhysicalSize::new(1280, 720))
        .with_active(true)
        .build(&event_loop)?;

    let instance = Instance::new(InstanceDescriptor {
        flags: InstanceFlags::empty(),
        ..Default::default()
    });

    let surface = unsafe {
        instance
            .create_surface_unsafe(SurfaceTargetUnsafe::from_window(&window).unwrap())
            .unwrap()
    };

    let adapter = instance
        .request_adapter(&RequestAdapterOptions {
            power_preference: PowerPreference::default(),
            force_fallback_adapter: false,
            compatible_surface: Some(&surface),
        })
        .block_on()
        .context("Failed to find an appropriate adapter")?;

    let (device, queue) = adapter
        .request_device(
            &DeviceDescriptor {
                label: None,
                required_features: Features::empty(),
                required_limits: Limits::downlevel_webgl2_defaults()
                    .using_resolution(adapter.limits()),
            },
            None,
        )
        .block_on()
        .context("Failed to create device")?;

    let size = window.inner_size();
    let capabilities = surface.get_capabilities(&adapter);
    let texture_format = capabilities
        .formats
        .get(0)
        .context("Incompatible `surface`")?;
    let composite_alpha_mode = capabilities
        .alpha_modes
        .get(0)
        .context("Incompatible `surface`")?;

    let mut surface_configuration = SurfaceConfiguration {
        usage: TextureUsages::RENDER_ATTACHMENT,
        format: *texture_format,
        width: size.width.max(1),
        height: size.height.max(1),
        present_mode: PresentMode::Fifo,
        desired_maximum_frame_latency: 2,
        alpha_mode: *composite_alpha_mode,
        view_formats: vec![],
    };

    surface.configure(&device, &surface_configuration);

    event_loop.run(|event, event_loop_window_target| match event {
        Event::WindowEvent { event, .. } => match event {
            WindowEvent::RedrawRequested => {
                device.poll(Poll);

                let frame = match surface.get_current_texture() {
                    Ok(frame) => frame,
                    Err(_) => return,
                };
                let view = frame.texture.create_view(&TextureViewDescriptor::default());

                let clear = {
                    let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor {
                        label: Some("Clear Encoder"),
                    });
                    encoder.begin_render_pass(&RenderPassDescriptor {
                        label: Some("Clear RenderPass"),
                        color_attachments: &[Some(RenderPassColorAttachment {
                            view: &view,
                            resolve_target: None,
                            ops: Operations {
                                load: LoadOp::Clear(Color::BLACK),
                                store: StoreOp::Store,
                            },
                        })],
                        depth_stencil_attachment: None,
                        timestamp_writes: None,
                        occlusion_query_set: None,
                    });
                    encoder.finish()
                };

                let bytes: &[u8] = &[255u8; 64];
                for _ in 0..1000 {
                    let buffer = device.create_buffer(&wgpu::BufferDescriptor {
                        label: Some("Vertex Buffer"),
                        size: bytes.len() as u64,
                        usage: BufferUsages::VERTEX,
                        mapped_at_creation: true,
                    });
                    buffer.unmap();
                }

                queue.submit([clear]);

                window.pre_present_notify();

                frame.present();
            }

            WindowEvent::Resized(size) => {
                surface_configuration.width = size.width.max(1);
                surface_configuration.height = size.height.max(1);
                surface.configure(&device, &surface_configuration);
            }
            WindowEvent::CloseRequested => event_loop_window_target.exit(),
            _ => (),
        },
        Event::AboutToWait => window.request_redraw(),
        _ => (),
    })?;

    Ok(())
}

Ideally, a runnable example we can check out.

Expected vs observed behavior
I would expect process memory usage to stay steady given all of the Buffers are immediately freed.

Platform
Tested on Windows 10 with Vulkan (default) and DX12 backend using wgpu, wgpu-core, and wgpu-hal version 0.19.3 and against trunk (as shown with the Git reference in the Cargo.toml above).

@rickvanprim
Copy link
Author

Possibly related: #4274

@robtfm
Copy link
Contributor

robtfm commented Mar 16, 2024

i suspect the problem is the take(1) here.

@Wumpf Wumpf added the type: bug Something isn't working label Mar 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants