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
相关产品推荐
相关产品推荐

