Metal: Handle failure to allocate an MTLCommandBuffer

This requires restructuring the logic around MTLCommandBuffer allocation
so that GetPendingCommandContext is guaranteed to never fail. Logic in
the Metal backend is now similar to the Vulkan backend: the
MTLCommandBuffer is prepared at device initialization time, or after a
submission, such that it is always valid.

A new mUsed boolean is added to CommandRecordingContext to say whether
any commands have been recording. Previously mCommandBuffer was used for
that purpose, but it is now always non-null.

Bug: dawn:801

Change-Id: I5dc6747d1e6d538054010cc50533a03a49af921a
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/58720
Auto-Submit: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Stephen White <senorblanco@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/metal/CommandRecordingContext.mm b/src/dawn_native/metal/CommandRecordingContext.mm
index decb650..f07c48c 100644
--- a/src/dawn_native/metal/CommandRecordingContext.mm
+++ b/src/dawn_native/metal/CommandRecordingContext.mm
@@ -20,19 +20,6 @@
 
     CommandRecordingContext::CommandRecordingContext() = default;
 
-    CommandRecordingContext::CommandRecordingContext(NSPRef<id<MTLCommandBuffer>> commands)
-        : mCommands(std::move(commands)) {
-    }
-
-    CommandRecordingContext::CommandRecordingContext(CommandRecordingContext&& rhs)
-        : mCommands(rhs.AcquireCommands()) {
-    }
-
-    CommandRecordingContext& CommandRecordingContext::operator=(CommandRecordingContext&& rhs) {
-        mCommands = rhs.AcquireCommands();
-        return *this;
-    }
-
     CommandRecordingContext::~CommandRecordingContext() {
         // Commands must be acquired.
         ASSERT(mCommands == nullptr);
@@ -42,6 +29,28 @@
         return mCommands.Get();
     }
 
+    void CommandRecordingContext::MarkUsed() {
+        mUsed = true;
+    }
+    bool CommandRecordingContext::WasUsed() const {
+        return mUsed;
+    }
+
+    MaybeError CommandRecordingContext::PrepareNextCommandBuffer(id<MTLCommandQueue> queue) {
+        ASSERT(mCommands == nil);
+        ASSERT(!mUsed);
+
+        // The MTLCommandBuffer will be autoreleased by default.
+        // The autorelease pool may drain before the command buffer is submitted. Retain so it stays
+        // alive.
+        mCommands = AcquireNSPRef([[queue commandBuffer] retain]);
+        if (mCommands == nil) {
+            return DAWN_INTERNAL_ERROR("Failed to allocate an MTLCommandBuffer");
+        }
+
+        return {};
+    }
+
     NSPRef<id<MTLCommandBuffer>> CommandRecordingContext::AcquireCommands() {
         // A blit encoder can be left open from WriteBuffer, make sure we close it.
         if (mCommands != nullptr) {
@@ -49,6 +58,7 @@
         }
 
         ASSERT(!mInEncoder);
+        mUsed = false;
         return std::move(mCommands);
     }