Fix: Implement shared Metal Command queue to resolve sync bugs #108223
|
@ -138,7 +138,6 @@ class GHOST_ContextCGL : public GHOST_Context {
|
||||||
bool m_useMetalForRendering = false;
|
bool m_useMetalForRendering = false;
|
||||||
NSView *m_metalView;
|
NSView *m_metalView;
|
||||||
CAMetalLayer *m_metalLayer;
|
CAMetalLayer *m_metalLayer;
|
||||||
MTLCommandQueue *m_metalCmdQueue;
|
|
||||||
MTLRenderPipelineState *m_metalRenderPipeline;
|
MTLRenderPipelineState *m_metalRenderPipeline;
|
||||||
bool m_ownsMetalDevice;
|
bool m_ownsMetalDevice;
|
||||||
|
|
||||||
|
@ -182,6 +181,9 @@ class GHOST_ContextCGL : public GHOST_Context {
|
||||||
static NSOpenGLContext *s_sharedOpenGLContext;
|
static NSOpenGLContext *s_sharedOpenGLContext;
|
||||||
static int s_sharedCount;
|
static int s_sharedCount;
|
||||||
|
|
||||||
|
/* Single device queue for multiple contexts. */
|
||||||
|
static MTLCommandQueue *s_sharedMetalCommandQueue;
|
||||||
|
|
||||||
/* Metal functions */
|
/* Metal functions */
|
||||||
void metalInit();
|
void metalInit();
|
||||||
void metalFree();
|
void metalFree();
|
||||||
|
|
|
@ -43,6 +43,7 @@ static void ghost_fatal_error_dialog(const char *msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
NSOpenGLContext *GHOST_ContextCGL::s_sharedOpenGLContext = nil;
|
NSOpenGLContext *GHOST_ContextCGL::s_sharedOpenGLContext = nil;
|
||||||
|
MTLCommandQueue *GHOST_ContextCGL::s_sharedMetalCommandQueue = nil;
|
||||||
int GHOST_ContextCGL::s_sharedCount = 0;
|
int GHOST_ContextCGL::s_sharedCount = 0;
|
||||||
|
|
||||||
GHOST_ContextCGL::GHOST_ContextCGL(bool stereoVisual,
|
GHOST_ContextCGL::GHOST_ContextCGL(bool stereoVisual,
|
||||||
|
@ -54,7 +55,6 @@ GHOST_ContextCGL::GHOST_ContextCGL(bool stereoVisual,
|
||||||
m_useMetalForRendering(type == GHOST_kDrawingContextTypeMetal),
|
m_useMetalForRendering(type == GHOST_kDrawingContextTypeMetal),
|
||||||
m_metalView(metalView),
|
m_metalView(metalView),
|
||||||
m_metalLayer(metalLayer),
|
m_metalLayer(metalLayer),
|
||||||
m_metalCmdQueue(nil),
|
|
||||||
m_metalRenderPipeline(nil),
|
m_metalRenderPipeline(nil),
|
||||||
m_openGLView(openGLView),
|
m_openGLView(openGLView),
|
||||||
m_openGLContext(nil),
|
m_openGLContext(nil),
|
||||||
|
@ -318,7 +318,7 @@ id<MTLTexture> GHOST_ContextCGL::metalOverlayTexture()
|
||||||
|
|
||||||
MTLCommandQueue *GHOST_ContextCGL::metalCommandQueue()
|
MTLCommandQueue *GHOST_ContextCGL::metalCommandQueue()
|
||||||
{
|
{
|
||||||
return m_metalCmdQueue;
|
return s_sharedMetalCommandQueue;
|
||||||
}
|
}
|
||||||
MTLDevice *GHOST_ContextCGL::metalDevice()
|
MTLDevice *GHOST_ContextCGL::metalDevice()
|
||||||
{
|
{
|
||||||
|
@ -530,10 +530,14 @@ void GHOST_ContextCGL::metalInit()
|
||||||
/* clang-format on */
|
/* clang-format on */
|
||||||
id<MTLDevice> device = m_metalLayer.device;
|
id<MTLDevice> device = m_metalLayer.device;
|
||||||
|
|
||||||
/* Create a command queue for blit/present operation. */
|
/* Create a command queue for blit/present operation. Note: All context should share a single
|
||||||
m_metalCmdQueue = (MTLCommandQueue *)[device
|
* command queue to ensure correct ordering of work submitted from multiple contexts. */
|
||||||
newCommandQueueWithMaxCommandBufferCount:GHOST_ContextCGL::max_command_buffer_count];
|
if (s_sharedMetalCommandQueue == nil) {
|
||||||
[m_metalCmdQueue retain];
|
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. */
|
/* Create shaders for blit operation. */
|
||||||
NSString *source = @R"msl(
|
NSString *source = @R"msl(
|
||||||
|
@ -616,9 +620,6 @@ void GHOST_ContextCGL::metalInit()
|
||||||
|
|
||||||
void GHOST_ContextCGL::metalFree()
|
void GHOST_ContextCGL::metalFree()
|
||||||
{
|
{
|
||||||
if (m_metalCmdQueue) {
|
|
||||||
[m_metalCmdQueue release];
|
|
||||||
}
|
|
||||||
if (m_metalRenderPipeline) {
|
if (m_metalRenderPipeline) {
|
||||||
[m_metalRenderPipeline release];
|
[m_metalRenderPipeline release];
|
||||||
}
|
}
|
||||||
|
@ -789,7 +790,7 @@ void GHOST_ContextCGL::metalUpdateFramebuffer()
|
||||||
overlayTex; //[(MTLTexture *)overlayTex retain];
|
overlayTex; //[(MTLTexture *)overlayTex retain];
|
||||||
|
|
||||||
/* Clear texture on create */
|
/* Clear texture on create */
|
||||||
id<MTLCommandBuffer> cmdBuffer = [m_metalCmdQueue commandBuffer];
|
id<MTLCommandBuffer> cmdBuffer = [s_sharedMetalCommandQueue commandBuffer];
|
||||||
MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
|
MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
|
||||||
{
|
{
|
||||||
auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0];
|
auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0];
|
||||||
|
@ -854,7 +855,7 @@ void GHOST_ContextCGL::metalSwapBuffers()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_useMetalForRendering) {
|
if (!m_useMetalForRendering) {
|
||||||
id<MTLCommandBuffer> cmdBuffer = [m_metalCmdQueue commandBuffer];
|
id<MTLCommandBuffer> cmdBuffer = [s_sharedMetalCommandQueue commandBuffer];
|
||||||
{
|
{
|
||||||
assert(m_defaultFramebufferMetalTexture[current_swapchain_index].texture != nil);
|
assert(m_defaultFramebufferMetalTexture[current_swapchain_index].texture != nil);
|
||||||
id<MTLRenderCommandEncoder> enc = [cmdBuffer
|
id<MTLRenderCommandEncoder> enc = [cmdBuffer
|
||||||
|
@ -896,7 +897,7 @@ void GHOST_ContextCGL::initClear()
|
||||||
#if WITH_METAL
|
#if WITH_METAL
|
||||||
// TODO (mg_gpusw_apple) this path is never taken, this is legacy left from inital integration
|
// 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
|
// 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];
|
MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
|
||||||
{
|
{
|
||||||
auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0];
|
auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0];
|
||||||
|
|
|
@ -17,13 +17,6 @@ using namespace blender::gpu;
|
||||||
|
|
||||||
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. */
|
/* Counter for active command buffers. */
|
||||||
int MTLCommandBufferManager::num_active_cmd_bufs = 0;
|
int MTLCommandBufferManager::num_active_cmd_bufs = 0;
|
||||||
|
|
||||||
|
@ -76,11 +69,6 @@ id<MTLCommandBuffer> MTLCommandBufferManager::ensure_begin()
|
||||||
[active_command_buffer_ retain];
|
[active_command_buffer_ retain];
|
||||||
MTLCommandBufferManager::num_active_cmd_bufs++;
|
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. */
|
/* Ensure we begin new Scratch Buffer if we are on a new frame. */
|
||||||
MTLScratchBufferManager &mem = context_.memory_manager;
|
MTLScratchBufferManager &mem = context_.memory_manager;
|
||||||
mem.ensure_increment_scratch_buffer();
|
mem.ensure_increment_scratch_buffer();
|
||||||
|
@ -108,20 +96,6 @@ bool MTLCommandBufferManager::submit(bool wait)
|
||||||
context_.memory_manager.flush_active_scratch_buffer();
|
context_.memory_manager.flush_active_scratch_buffer();
|
||||||
|
|
||||||
/*** Submit Command 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. */
|
/* Command buffer lifetime tracking. */
|
||||||
/* Increment current MTLSafeFreeList reference counter to flag MTLBuffers freed within
|
/* Increment current MTLSafeFreeList reference counter to flag MTLBuffers freed within
|
||||||
* the current command buffer lifetime as used.
|
* the current command buffer lifetime as used.
|
||||||
|
|
|
@ -536,10 +536,6 @@ class MTLCommandBufferManager {
|
||||||
friend class MTLContext;
|
friend class MTLContext;
|
||||||
|
|
||||||
public:
|
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. */
|
/* Counter for active command buffers. */
|
||||||
static int num_active_cmd_bufs;
|
static int num_active_cmd_bufs;
|
||||||
|
|
||||||
|
|
|
@ -608,19 +608,6 @@ void MTLFence::wait()
|
||||||
return;
|
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_) {
|
if (signalled_) {
|
||||||
MTLContext *ctx = MTLContext::get();
|
MTLContext *ctx = MTLContext::get();
|
||||||
BLI_assert(ctx);
|
BLI_assert(ctx);
|
||||||
|
|
Loading…
Reference in New Issue