ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
61.37k stars 10.33k forks source link

Metal (metal-cpp) backend leaking memory #8166

Open selimsandal opened 1 week ago

selimsandal commented 1 week ago

Version/Branch of Dear ImGui:

master

Back-ends:

imgui_impl_sdl3.h + imgui_impl_metal.h

Compiler, OS:

macOS 15.2, clang version 19.1.3

Full config/build information:

Dear ImGui 1.91.5 (19150)
--------------------------------
sizeof(size_t): 8, sizeof(ImDrawIdx): 2, sizeof(ImDrawVert): 20
define: __cplusplus=202400
define: __APPLE__
define: __GNUC__=4
define: __clang_version__=16.0.0 (clang-1600.0.26.5)
--------------------------------
io.BackendPlatformName: imgui_impl_sdl3
io.BackendRendererName: imgui_impl_metal
io.ConfigFlags: 0x00000000
io.ConfigMacOSXBehaviors
io.ConfigNavCaptureKeyboard
io.ConfigInputTextCursorBlink
io.ConfigWindowsResizeFromEdges
io.ConfigMemoryCompactTimer = 60.0
io.BackendFlags: 0x0000000E
 HasMouseCursors
 HasSetMousePos
 RendererHasVtxOffset
--------------------------------
io.Fonts: 1 fonts, Flags: 0x00000000, TexSize: 1024,1024
io.DisplaySize: 1200.00,700.00
io.DisplayFramebufferScale: 2.00,2.00
--------------------------------
style.WindowPadding: 8.00,8.00
style.WindowBorderSize: 1.00
style.FramePadding: 4.00,3.00
style.FrameRounding: 0.00
style.FrameBorderSize: 0.00
style.ItemSpacing: 8.00,4.00
style.ItemInnerSpacing: 4.00,4.00

Details:

Hi, I am trying to use SDL3 + Imgui with metal-cpp. I noticed that memory usage is going up rapidly. I profiled the app with Instruments' Leaks template and it seems that Imgui_ImplMetal is leaking the FrameBufferDescriptors. I tried to port from the Metal SDL2 sample as much as possible.

I can also provide my CMake config linking metal-cpp and sdl3 if needed.

Screenshots/Video:

image image

Minimal, Complete and Verifiable Example code:

#include <SDL3/SDL.h>
#include <imgui.h>
#include <backends/imgui_impl_sdl3.h>
#include <backends/imgui_impl_metal.h>
#include <Metal/Metal.hpp>
#include <Foundation/Foundation.hpp>
#include <QuartzCore/QuartzCore.hpp>

int main(int argc, char *args[]) {
    NS::AutoreleasePool* pAutoreleasePool = NS::AutoreleasePool::alloc()->init();

    // Initialize ImGui
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();

    // Initialize SDL
    SDL_Init(SDL_INIT_VIDEO);

    // Create window with Metal flag
    SDL_Window* window = SDL_CreateWindow(
        "SDL Window",
        1200, 700,
        SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY
    );

    // Create Metal device
    MTL::Device* metaldevice = MTL::CreateSystemDefaultDevice();

    // Get SDL's Metal view and attach our layer
    SDL_MetalView metalView = SDL_Metal_CreateView(window);
    auto* sdlLayer = static_cast<CA::MetalLayer*>(SDL_Metal_GetLayer(metalView));
    sdlLayer->setDevice(metaldevice);
    sdlLayer->setPixelFormat(MTL::PixelFormatBGRA10_XR);

    // Initialize ImGui Metal implementation
    ImGui_ImplMetal_Init(metaldevice);
    ImGui_ImplSDL3_InitForMetal(window);

    // Create command queue
    MTL::CommandQueue* commandQueue = metaldevice->newCommandQueue();

    // Create render pass descriptor
    MTL::RenderPassDescriptor* renderPassDescriptor = MTL::RenderPassDescriptor::alloc()->init();

    float clear_color[4] = {0.45f, 0.55f, 0.60f, 1.00f};
    bool done = false;

    while (!done) {
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            ImGui_ImplSDL3_ProcessEvent(&event);
            if (event.type == SDL_EVENT_QUIT)
                done = true;
        }

        // Get current window size and update metal layer
        int width, height;
        SDL_GetWindowSizeInPixels(window, &width, &height);
        sdlLayer->setDrawableSize(CGSizeMake(width, height));

        // Get next drawable
        CA::MetalDrawable* drawable = sdlLayer->nextDrawable();
        if (!drawable) {
            continue;
        }

        // Create command buffer and render pass
        auto commandBuffer = commandQueue->commandBuffer();

        renderPassDescriptor->colorAttachments()->object(0)->setTexture(drawable->texture());
        renderPassDescriptor->colorAttachments()->object(0)->setClearColor(
            MTL::ClearColor(
                clear_color[0] * clear_color[3],
                clear_color[1] * clear_color[3],
                clear_color[2] * clear_color[3],
                clear_color[3]
            )
        );
        renderPassDescriptor->colorAttachments()->object(0)->setLoadAction(MTL::LoadActionClear);
        renderPassDescriptor->colorAttachments()->object(0)->setStoreAction(MTL::StoreActionStore);

        MTL::RenderCommandEncoder* renderEncoder =
            commandBuffer->renderCommandEncoder(renderPassDescriptor);

        // ImGui frame
        ImGui_ImplMetal_NewFrame(renderPassDescriptor);
        ImGui_ImplSDL3_NewFrame();
        ImGui::NewFrame();

        ImGui::ShowDemoWindow();
        ImGui::Render();
        ImGui_ImplMetal_RenderDrawData(ImGui::GetDrawData(), commandBuffer, renderEncoder);

        renderEncoder->endEncoding();
        commandBuffer->presentDrawable(drawable);
        commandBuffer->commit();
    }

    // Cleanup
    commandQueue->release();
    renderPassDescriptor->release();
    metaldevice->release();

    ImGui_ImplMetal_Shutdown();
    ImGui_ImplSDL3_Shutdown();
    ImGui::DestroyContext();

    SDL_Metal_DestroyView(metalView);
    SDL_DestroyWindow(window);
    SDL_Quit();

    pAutoreleasePool->release();

    return 0;
}