Fix: Implement shared Metal Command queue to resolve sync bugs
Previously, each GHOST Context instantiated its own Metal device queue. Commands are only synchronized within a queue, this was the root cause of a number of flickering issues which had previously been worked-around with synchronization primitives. New solution uses a shared queue to simplify dependencies and alleviate possibility of stalls and bugs when resources are modified or shared across separate GPU command queues. Authored by Apple: Michael Parkin-White Pull Request: https://projects.blender.org/blender/blender/pulls/108223
This commit is contained in:
parent
ccd85688ed
commit
9e83211b8d
|
@ -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();
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue