You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于ImGui+GLFW+Metal的基础程序无法渲染、内存泄漏并崩溃

问题描述

我尝试使用ImGui、GLFW和Metal API创建最基础的示例程序,程序可创建可交互窗口,但窗口始终黑屏,内存泄漏严重(3秒内占用3GB),帧率在300-3000FPS之间,运行数秒后会在layer_private_present_impl(_CAMetalLayerPrivate*, CAMetalDrawable*, double, unsigned int)函数中崩溃。以下是我的代码:

// main.mm

#include <iostream>

#include <GLFW/glfw3.h>
#define GLFW_EXPOSE_NATIVE_COCOA
#include <GLFW/glfw3native.h>
#include <imgui.h>
#include <backends/imgui_impl_osx.h>
#include <backends/imgui_impl_metal.h>
#include <Metal/Metal.h>
#include <QuartzCore/CAMetalLayer.h>

int main() {
    if (!glfwInit()) {
        std::cout << "Failed to init GLFW\n";
        return 1;
    }
    auto* W = glfwCreateWindow(1000, 800, "My Window", nullptr, nullptr);
    if (!W) {
        std::cout << "Failed to create window\n";
        return 1;
    }
    int width = 0, height = 0;
    glfwGetWindowSize(W, &width, &height);
    ImGui::CreateContext();
    NSWindow* NSW = glfwGetCocoaWindow(W);
    ImGui_ImplOSX_Init(NSW.contentView);
    id<MTLDevice> device = MTLCreateSystemDefaultDevice();
    ImGui_ImplMetal_Init(device);
    CAMetalLayer* layer = [[CAMetalLayer alloc] init];
    layer.device = device;
    layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
    layer.drawableSize = CGSize{ 2.0 * width, 2.0 * height };
    NSW.contentView.wantsLayer = true;
    [NSW.contentView setLayer:layer];
    id commandQueue = [device newCommandQueue];
    ImGui_ImplMetal_CreateFontsTexture(device);
    while (!glfwWindowShouldClose(W)) {
        ImGuiIO& io = ImGui::GetIO();
        io.DisplaySize = ImVec2(width, height);
        ImGui_ImplOSX_NewFrame(NSW.contentView);
        ImGui::NewFrame();
        
        // User code
        ImGui::ShowDemoWindow();
        
        id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
        MTLRenderPassDescriptor* renderPassDescriptor = [[MTLRenderPassDescriptor alloc] init];
        MTLRenderPassColorAttachmentDescriptor* caDesc = [[MTLRenderPassColorAttachmentDescriptor alloc] init];
        id<CAMetalDrawable> drawable = layer.nextDrawable;
        caDesc.texture = drawable.texture;
        caDesc.loadAction = MTLLoadActionClear;
        caDesc.storeAction = MTLStoreActionStore;
        [renderPassDescriptor.colorAttachments setObject: caDesc
                                      atIndexedSubscript: 0];
        ImGui_ImplMetal_NewFrame(renderPassDescriptor);
        
        // Rendering
        ImGui::Render();
        ImDrawData* drawData = ImGui::GetDrawData();
        renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(1, 0, 1, 1);
        id <MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
        [renderEncoder pushDebugGroup:@"Dear ImGui rendering"];
        ImGui_ImplMetal_RenderDrawData(drawData, commandBuffer, renderEncoder);
        [renderEncoder popDebugGroup];
        [renderEncoder endEncoding];
        // Present
        [commandBuffer presentDrawable: drawable];
        [commandBuffer commit];
        [drawable present];
        
        glfwPollEvents();
    }
    ImGui_ImplMetal_Shutdown();
    ImGui_ImplOSX_Shutdown();
}

问题原因与修复方案

1. 重复提交present动作导致崩溃与资源泄漏

同时调用[commandBuffer presentDrawable: drawable]和[drawable present]是错误操作,Metal只需要通过commandBuffer提交present动作即可。重复调用会导致drawable资源无法正确回收,引发内存泄漏和崩溃。
修复: 删除[drawable present];这一行。

2. Objective-C临时对象未及时回收导致内存泄漏

循环内重复创建的MTLRenderPassDescriptor和MTLRenderPassColorAttachmentDescriptor属于ARC管理对象,但未通过自动释放池及时回收,导致对象堆积快速消耗内存。
修复: 在循环内包裹@autoreleasepool块,让ARC及时回收临时对象。

3. 未适配高DPI与窗口尺寸变化

初始获取窗口尺寸后未在循环中更新,且未设置io.DisplayFramebufferScale,导致ImGui渲染内容与窗口实际尺寸不匹配,引发黑屏。
修复: 在循环内重新获取窗口尺寸与屏幕缩放因子,同步更新io.DisplayFramebufferScale和MetalLayer的drawableSize。

4. 无帧率限制导致资源耗尽

未开启垂直同步,循环以CPU最高速度运行,既浪费资源又加速内存泄漏。
修复: 调用glfwSwapInterval(1)开启垂直同步限制帧率。

修复后的完整代码

// main.mm

#include <iostream>

#include <GLFW/glfw3.h>
#define GLFW_EXPOSE_NATIVE_COCOA
#include <GLFW/glfw3native.h>
#include <imgui.h>
#include <backends/imgui_impl_osx.h>
#include <backends/imgui_impl_metal.h>
#include <Metal/Metal.h>
#include <QuartzCore/CAMetalLayer.h>

int main() {
    if (!glfwInit()) {
        std::cout << "Failed to init GLFW\n";
        return 1;
    }
    auto* W = glfwCreateWindow(1000, 800, "My Window", nullptr, nullptr);
    if (!W) {
        std::cout << "Failed to create window\n";
        return 1;
    }
    // 开启垂直同步限制帧率
    glfwSwapInterval(1);
    
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO();
    NSWindow* NSW = glfwGetCocoaWindow(W);
    ImGui_ImplOSX_Init(NSW.contentView);
    id<MTLDevice> device = MTLCreateSystemDefaultDevice();
    ImGui_ImplMetal_Init(device);
    
    CAMetalLayer* layer = [[CAMetalLayer alloc] init];
    layer.device = device;
    layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
    NSW.contentView.wantsLayer = true;
    [NSW.contentView setLayer:layer];
    
    id<MTLCommandQueue> commandQueue = [device newCommandQueue];
    ImGui_ImplMetal_CreateFontsTexture(device);
    
    while (!glfwWindowShouldClose(W)) {
        @autoreleasepool {
            int width = 0, height = 0;
            glfwGetWindowSize(W, &width, &height);
            // 获取屏幕缩放因子,适配高DPI
            float scaleFactor = [NSW.contentView backingScaleFactor];
            io.DisplaySize = ImVec2(width, height);
            io.DisplayFramebufferScale = ImVec2(scaleFactor, scaleFactor);
            layer.drawableSize = CGSize{ width * scaleFactor, height * scaleFactor };
            
            ImGui_ImplOSX_NewFrame(NSW.contentView);
            ImGui::NewFrame();
            
            // User code
            ImGui::ShowDemoWindow();
            
            id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
            MTLRenderPassDescriptor* renderPassDescriptor = [[MTLRenderPassDescriptor alloc] init];
            MTLRenderPassColorAttachmentDescriptor* caDesc = [[MTLRenderPassColorAttachmentDescriptor alloc] init];
            id<CAMetalDrawable> drawable = layer.nextDrawable;
            
            caDesc.texture = drawable.texture;
            caDesc.loadAction = MTLLoadActionClear;
            caDesc.storeAction = MTLStoreActionStore;
            caDesc.clearColor = MTLClearColorMake(1, 0, 1, 1);
            [renderPassDescriptor.colorAttachments setObject:caDesc atIndexedSubscript:0];
            
            ImGui_ImplMetal_NewFrame(renderPassDescriptor);
            ImGui::Render();
            ImDrawData* drawData = ImGui::GetDrawData();
            
            id <MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
            [renderEncoder pushDebugGroup:@"Dear ImGui rendering"];
            ImGui_ImplMetal_RenderDrawData(drawData, commandBuffer, renderEncoder);
            [renderEncoder popDebugGroup];
            [renderEncoder endEncoding];
            
            // 仅通过commandBuffer提交present动作
            [commandBuffer presentDrawable:drawable];
            [commandBuffer commit];
            
            glfwPollEvents();
        }
    }
    
    ImGui_ImplMetal_Shutdown();
    ImGui_ImplOSX_Shutdown();
    ImGui::DestroyContext();
    glfwDestroyWindow(W);
    glfwTerminate();
    
    return 0;
}

内容的提问来源于stack exchange,提问作者chrysante

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 22:09:56