Use asynchronous routines to build Metal shaders and pipelines for MacOS.

On Mac, Metal will trigger an xpc to a process to compile shaders and
link pipeline programs. Sometimes that process can crash or hang. By using
an asynchronous call with a timeout we can at least recover in this case.

Bug: chromium:974219
Change-Id: I179daa86979b1217458e7be210fccd5edcbffdd0
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/222884
Reviewed-by: Christopher Cameron <ccameron@chromium.org>
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Jim Van Verth <jvanverth@google.com>
diff --git a/src/gpu/mtl/GrMtlUtil.mm b/src/gpu/mtl/GrMtlUtil.mm
index 007abe2..7caf2f5 100644
--- a/src/gpu/mtl/GrMtlUtil.mm
+++ b/src/gpu/mtl/GrMtlUtil.mm
@@ -178,6 +178,16 @@
 #endif
 
     MTLCompileOptions* defaultOptions = [[MTLCompileOptions alloc] init];
+#ifdef SK_BUILD_FOR_MAC
+    bool timedout;
+    id<MTLLibrary> compiledLibrary = GrMtlNewLibraryWithSource(gpu->device(), mtlCode,
+                                                               defaultOptions, &timedout);
+    if (timedout) {
+        // try again
+        compiledLibrary = GrMtlNewLibraryWithSource(gpu->device(), mtlCode,
+                                                    defaultOptions, &timedout);
+    }
+#else
     NSError* error = nil;
     id<MTLLibrary> compiledLibrary = [gpu->device() newLibraryWithSource: mtlCode
                                                                  options: defaultOptions
@@ -187,9 +197,69 @@
                  [[error localizedDescription] cStringUsingEncoding: NSASCIIStringEncoding]);
         return nil;
     }
+#endif
     return compiledLibrary;
 }
 
+id<MTLLibrary> GrMtlNewLibraryWithSource(id<MTLDevice> device, NSString* mslCode,
+                                         MTLCompileOptions* options, bool* timedout) {
+    dispatch_semaphore_t compilerSemaphore = dispatch_semaphore_create(0);
+
+    __block dispatch_semaphore_t semaphore = compilerSemaphore;
+    __block id<MTLLibrary> compiledLibrary;
+    [device newLibraryWithSource: mslCode
+                         options: options
+               completionHandler:
+        ^(id<MTLLibrary> library, NSError* error) {
+            if (error) {
+                SkDebugf("Error compiling MSL shader: %s\n",
+                    [[error localizedDescription] cStringUsingEncoding: NSASCIIStringEncoding]);
+            }
+            compiledLibrary = library;
+            dispatch_semaphore_signal(semaphore);
+        }
+    ];
+
+    // Wait 100 ms for the compiler
+    if (dispatch_semaphore_wait(compilerSemaphore, dispatch_time(DISPATCH_TIME_NOW, 100000))) {
+        SkDebugf("Timeout compiling MSL shader\n");
+        *timedout = true;
+        return nil;
+    }
+
+    *timedout = false;
+    return compiledLibrary;
+}
+
+id<MTLRenderPipelineState> GrMtlNewRenderPipelineStateWithDescriptor(
+        id<MTLDevice> device, MTLRenderPipelineDescriptor* pipelineDescriptor, bool* timedout) {
+    dispatch_semaphore_t pipelineSemaphore = dispatch_semaphore_create(0);
+
+    __block dispatch_semaphore_t semaphore = pipelineSemaphore;
+    __block id<MTLRenderPipelineState> pipelineState;
+    [device newRenderPipelineStateWithDescriptor: pipelineDescriptor
+                               completionHandler:
+        ^(id<MTLRenderPipelineState> state, NSError* error) {
+            if (error) {
+                SkDebugf("Error creating pipeline: %s\n",
+                    [[error localizedDescription] cStringUsingEncoding: NSASCIIStringEncoding]);
+            }
+            pipelineState = state;
+            dispatch_semaphore_signal(semaphore);
+        }
+     ];
+
+    // Wait 500 ms for pipeline creation
+    if (dispatch_semaphore_wait(pipelineSemaphore, dispatch_time(DISPATCH_TIME_NOW, 500000))) {
+        SkDebugf("Timeout creating pipeline.\n");
+        *timedout = true;
+        return nil;
+    }
+
+    *timedout = false;
+    return pipelineState;
+}
+
 id<MTLTexture> GrGetMTLTextureFromSurface(GrSurface* surface, bool doResolve) {
     id<MTLTexture> mtlTexture = nil;