Fix: Implement shared Metal Command queue to resolve sync bugs #108223

Merged
Jeroen Bakker merged 1 commits from Jason-Fielder/blender:MetalSharedCommandQueue_2 into blender-v3.6-release 2023-05-26 16:00:27 +02:00
5 changed files with 16 additions and 56 deletions

View File

@ -138,7 +138,6 @@ class GHOST_ContextCGL : public GHOST_Context {
bool m_useMetalForRendering = false;
NSView *m_metalView;
CAMetalLayer *m_metalLayer;
MTLCommandQueue *m_metalCmdQueue;
MTLRenderPipelineState *m_metalRenderPipeline;
bool m_ownsMetalDevice;
@ -182,6 +181,9 @@ class GHOST_ContextCGL : public GHOST_Context {
static NSOpenGLContext *s_sharedOpenGLContext;
static int s_sharedCount;
/* Single device queue for multiple contexts. */
static MTLCommandQueue *s_sharedMetalCommandQueue;
/* Metal functions */
void metalInit();
void metalFree();

View File

@ -43,6 +43,7 @@ static void ghost_fatal_error_dialog(const char *msg)
}
NSOpenGLContext *GHOST_ContextCGL::s_sharedOpenGLContext = nil;
MTLCommandQueue *GHOST_ContextCGL::s_sharedMetalCommandQueue = nil;
int GHOST_ContextCGL::s_sharedCount = 0;
GHOST_ContextCGL::GHOST_ContextCGL(bool stereoVisual,
@ -54,7 +55,6 @@ GHOST_ContextCGL::GHOST_ContextCGL(bool stereoVisual,
m_useMetalForRendering(type == GHOST_kDrawingContextTypeMetal),
m_metalView(metalView),
m_metalLayer(metalLayer),
m_metalCmdQueue(nil),
m_metalRenderPipeline(nil),
m_openGLView(openGLView),
m_openGLContext(nil),
@ -318,7 +318,7 @@ id<MTLTexture> GHOST_ContextCGL::metalOverlayTexture()
MTLCommandQueue *GHOST_ContextCGL::metalCommandQueue()
{
return m_metalCmdQueue;
return s_sharedMetalCommandQueue;
}
MTLDevice *GHOST_ContextCGL::metalDevice()
{
@ -530,10 +530,14 @@ void GHOST_ContextCGL::metalInit()
/* clang-format on */
id<MTLDevice> device = m_metalLayer.device;
/* Create a command queue for blit/present operation. */
m_metalCmdQueue = (MTLCommandQueue *)[device
newCommandQueueWithMaxCommandBufferCount:GHOST_ContextCGL::max_command_buffer_count];
[m_metalCmdQueue retain];
/* Create a command queue for blit/present operation. Note: All context should share a single
* command queue to ensure correct ordering of work submitted from multiple contexts. */
if (s_sharedMetalCommandQueue == nil) {
s_sharedMetalCommandQueue = (MTLCommandQueue *)[device
newCommandQueueWithMaxCommandBufferCount:GHOST_ContextCGL::max_command_buffer_count];
}
/* Ensure active GHOSTContext retains a reference to the shared context. */
[s_sharedMetalCommandQueue retain];
/* Create shaders for blit operation. */
NSString *source = @R"msl(
@ -616,9 +620,6 @@ void GHOST_ContextCGL::metalInit()
void GHOST_ContextCGL::metalFree()
{
if (m_metalCmdQueue) {
[m_metalCmdQueue release];
}
if (m_metalRenderPipeline) {
[m_metalRenderPipeline release];
}
@ -789,7 +790,7 @@ void GHOST_ContextCGL::metalUpdateFramebuffer()
overlayTex; //[(MTLTexture *)overlayTex retain];
/* Clear texture on create */
id<MTLCommandBuffer> cmdBuffer = [m_metalCmdQueue commandBuffer];
id<MTLCommandBuffer> cmdBuffer = [s_sharedMetalCommandQueue commandBuffer];
MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
{
auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0];
@ -854,7 +855,7 @@ void GHOST_ContextCGL::metalSwapBuffers()
}
if (!m_useMetalForRendering) {
id<MTLCommandBuffer> cmdBuffer = [m_metalCmdQueue commandBuffer];
id<MTLCommandBuffer> cmdBuffer = [s_sharedMetalCommandQueue commandBuffer];
{
assert(m_defaultFramebufferMetalTexture[current_swapchain_index].texture != nil);
id<MTLRenderCommandEncoder> enc = [cmdBuffer
@ -896,7 +897,7 @@ void GHOST_ContextCGL::initClear()
#if WITH_METAL
// TODO (mg_gpusw_apple) this path is never taken, this is legacy left from inital integration
// of metal and gl, the whole file should be cleaned up and stripped of the legacy path
id<MTLCommandBuffer> cmdBuffer = [m_metalCmdQueue commandBuffer];
id<MTLCommandBuffer> cmdBuffer = [s_sharedMetalCommandQueue commandBuffer];
MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
{
auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0];

View File

@ -17,13 +17,6 @@ using namespace blender::gpu;
namespace blender::gpu {
/* Global sync event used across MTLContext's.
* This resolves flickering artifacts from command buffer
* dependencies not being honored for work submitted between
* different GPUContext's. */
id<MTLEvent> MTLCommandBufferManager::sync_event = nil;
uint64_t MTLCommandBufferManager::event_signal_val = 0;
/* Counter for active command buffers. */
int MTLCommandBufferManager::num_active_cmd_bufs = 0;
@ -76,11 +69,6 @@ id<MTLCommandBuffer> MTLCommandBufferManager::ensure_begin()
[active_command_buffer_ retain];
MTLCommandBufferManager::num_active_cmd_bufs++;
/* Ensure command buffers execute in submission order across multiple MTLContext's. */
if (this->sync_event != nil) {
[active_command_buffer_ encodeWaitForEvent:this->sync_event value:this->event_signal_val];
}
/* Ensure we begin new Scratch Buffer if we are on a new frame. */
MTLScratchBufferManager &mem = context_.memory_manager;
mem.ensure_increment_scratch_buffer();
@ -108,20 +96,6 @@ bool MTLCommandBufferManager::submit(bool wait)
context_.memory_manager.flush_active_scratch_buffer();
/*** Submit Command Buffer. ***/
/* Strict ordering ensures command buffers are guaranteed to execute after a previous
* one has completed. Resolves flickering when command buffers are submitted from
* different MTLContext's. */
if (MTLCommandBufferManager::sync_event == nil) {
MTLCommandBufferManager::sync_event = [context_.device newEvent];
BLI_assert(MTLCommandBufferManager::sync_event);
[MTLCommandBufferManager::sync_event retain];
}
BLI_assert(MTLCommandBufferManager::sync_event != nil);
MTLCommandBufferManager::event_signal_val++;
[active_command_buffer_ encodeSignalEvent:MTLCommandBufferManager::sync_event
value:MTLCommandBufferManager::event_signal_val];
/* Command buffer lifetime tracking. */
/* Increment current MTLSafeFreeList reference counter to flag MTLBuffers freed within
* the current command buffer lifetime as used.

View File

@ -536,10 +536,6 @@ class MTLCommandBufferManager {
friend class MTLContext;
public:
/* Event to coordinate sequential execution across all "main" command buffers. */
static id<MTLEvent> sync_event;
static uint64_t event_signal_val;
/* Counter for active command buffers. */
static int num_active_cmd_bufs;

View File

@ -608,19 +608,6 @@ void MTLFence::wait()
return;
}
/* Note(#106431 #106704): `sync_event` is a global cross-context synchronization primitive used
* to ensure GPU workloads execute in the correct order across contexts.
*
* To prevent unexpected GPU stalls, this needs to be reset when used along side explicit
* synchronization. Previously this was handled during frame boundaries, however, to eliminate
* situational flickering (#106704), only reset this during the cases where we are waiting on
* synchronization primitives. */
if (MTLCommandBufferManager::sync_event != nil) {
[MTLCommandBufferManager::sync_event release];
MTLCommandBufferManager::sync_event = nil;
MTLCommandBufferManager::event_signal_val = 0;
}
if (signalled_) {
MTLContext *ctx = MTLContext::get();
BLI_assert(ctx);