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

Apple Metal屏幕撕裂与窗口卡顿问题排查(SDL2/metal-cpp环境)

M1 MacBook Air上SDL2+metal-cpp渲染项目的问题与分析

核心问题

  • 禁用VSync后,Metal渲染会出现严重的屏幕撕裂现象,但使用OpenGL渲染时,无论是否开启VSync都不会出现撕裂;
  • 开启VSync时,拖动窗口会先出现明显卡顿才开始移动,Metal、OpenGL渲染模式下均存在此问题,甚至使用Rust的winit库也会出现,但Xcode创建的默认Swift/Metal项目无此现象。

Xcode Swift/Metal项目的测试异常

在Xcode中创建Swift/Metal项目测试时发现:

  • 禁用VSync后,帧率仍被限制在60且无屏幕撕裂;
  • 禁用CAMetalLayer的presentWithTransaction选项后,会出现屏幕撕裂,但帧率依旧保持60。

项目代码示例

#include <iostream>
#include <imgui.h>
#include <SDL2/SDL.h>
#include <SDL2/Sdl_metal.h>
#include <SDL2/SDL_render.h>
#include <simd/simd.h>

#define NS_PRIVATE_IMPLEMENTATION
#define CA_PRIVATE_IMPLEMENTATION
#define MTL_PRIVATE_IMPLEMENTATION
#include <QuartzCore/QuartzCore.hpp>
#include <Foundation/Foundation.hpp>
#include <Metal/Metal.hpp>

std::vector<simd::float3> get_triangle();

int main() {
    // DEVICE
    auto device = NS::TransferPtr(MTL::CreateSystemDefaultDevice());
    // COMMAND QUEUE
    auto commandQueue = NS::TransferPtr(device->newCommandQueue());
    
    if(SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO) < 0) {
        SDL_Log("sdl init failed: %s", SDL_GetError());
    };
    
    // WINDOW
    auto window = SDL_CreateWindow(
                                   "Window",
                                   SDL_WINDOWPOS_UNDEFINED,
                                   SDL_WINDOWPOS_UNDEFINED,
                                   400, 400,
                                   SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
    if(!window) { SDL_Log("window creation failed: %s", SDL_GetError()); }
    
    SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal");
    SDL_SetHint(SDL_HINT_RENDER_VSYNC, "0");
    auto renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    
    auto layer = (CA::MetalLayer*)SDL_RenderGetMetalLayer(renderer);
    if(!layer) { SDL_Log("failed to init metal layer: %s", SDL_GetError()); }
    
    layer->setPixelFormat(MTL::PixelFormatBGRA8Unorm);
    layer->setDevice(device.get());
    
    // set triangle buffer
    auto triangle = get_triangle();
    auto triangle_buffer = NS::TransferPtr(device->newBuffer(&triangle[0], triangle.size() * sizeof(simd::float3), MTL::ResourceStorageModeShared));
    
    // DEFAULT LIBRARY
    auto library = device->newDefaultLibrary();
    if(!library) { SDL_Log("failed to load default library"); std::exit(-1); }
    
    // RENDER PIPELINE
    auto vertexShader = library->newFunction(NS::String::string("vertexShader", NS::ASCIIStringEncoding));
    auto fragmentShader = library->newFunction(NS::String::string("fragmentShader", NS::ASCIIStringEncoding));
    auto renderPipelineDescriptor = MTL::RenderPipelineDescriptor::alloc()->init();
    renderPipelineDescriptor->setLabel(NS::String::string("triangle rendering", NS::ASCIIStringEncoding));
    renderPipelineDescriptor->setVertexFunction(vertexShader);
    renderPipelineDescriptor->setFragmentFunction(fragmentShader);
    vertexShader->release();
    fragmentShader->release();
    renderPipelineDescriptor->colorAttachments()->object(0)->setPixelFormat(layer->pixelFormat());
    NS::Error* err;
    auto renderPipelineState = device->newRenderPipelineState(renderPipelineDescriptor, &err);
    if(!renderPipelineState) { SDL_Log("render pipeline state creation failed"); std::exit(-1); }
    renderPipelineDescriptor->release();
    
    bool quit = false;
    SDL_Event event;
    while(!quit) {
        
        while(SDL_PollEvent(&event)) {
            if(event.type == SDL_QUIT) {
                quit = true;
            }
        }
        
        auto autoreleasePool = NS::TransferPtr(NS::AutoreleasePool::alloc()->init());
        
        auto commandBuffer = commandQueue->commandBuffer();
        auto drawable = layer->nextDrawable();
        
        // RENDER PASS DESCRIPTOR
        auto renderPassDescriptor = NS::TransferPtr(MTL::RenderPassDescriptor::alloc()->init());
        renderPassDescriptor->colorAttachments()->object(0)->setTexture(drawable->texture());
        renderPassDescriptor->colorAttachments()->object(0)->setLoadAction(MTL::LoadActionClear);
        renderPassDescriptor->colorAttachments()->object(0)->setClearColor(MTL::ClearColor(1, 0, 0, 1));
        renderPassDescriptor->colorAttachments()->object(0)->setStoreAction(MTL::StoreActionStore);
        
        // RENDER COMMAND ENCODER
        auto renderCommandEncoder = commandBuffer->renderCommandEncoder(renderPassDescriptor.get());
        renderCommandEncoder->setRenderPipelineState(renderPipelineState);
        renderCommandEncoder->setVertexBuffer(triangle_buffer.get(), 0, 0);
        renderCommandEncoder->drawPrimitives(MTL::PrimitiveTypeTriangle, NS::UInteger(0), NS::UInteger(3));
        
        renderCommandEncoder->popDebugGroup();
        renderCommandEncoder->endEncoding();
        
        commandBuffer->presentDrawable(drawable);
        commandBuffer->commit();
        commandBuffer->waitUntilCompleted();
    }
    
    SDL_DestroyWindow(window);
    SDL_Quit();
}

std::vector<simd::float3> get_triangle() {
    return std::vector<simd::float3>{
        simd::float3{-0.5f, -0.5f, 0.0f},
        simd::float3{ 0.5f, -0.5f, 0.0f},
        simd::float3{ 0.0f,  0.5f, 0.0f}
    };
}

补充:窗口拖动卡顿问题已解决

开启VSync时SDL窗口拖动卡顿的问题由窗口管理应用Magnet导致,社区讨论显示该应用的窗口捕捉逻辑会干扰SDL、winit等跨平台窗口库的事件响应,关闭Magnet后即可解决此问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 09:22:05