[metal] Audit and document device guards for Create* functions.

- As a part of ML analysis for CPU bottlenecks, a detailed report
  identified that lock contention in Metal accounted for ~6.6% of
  CPU time. The major one is the one for CreateBindGroup which we
  unfortunately need to conditionally gate for now since the
  ArgumentBuffer implementation is WIP. Ideally, as we implement
  that, it would be best if we could put in place the synchronizations
  such that we don't need to regress to needing the lock.

Bug: 363031535
Change-Id: I33045d01ae1013f8ab05a2174d40b1475b871734
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/352915
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Loko Kung <lokokung@google.com>
diff --git a/src/dawn/native/metal/DeviceMTL.h b/src/dawn/native/metal/DeviceMTL.h
index f4213d1..e6765c4 100644
--- a/src/dawn/native/metal/DeviceMTL.h
+++ b/src/dawn/native/metal/DeviceMTL.h
@@ -85,6 +85,12 @@
 
     bool BackendWillValidateMultiDraw() const override;
 
+    std::optional<DeviceGuard> UseGuardForCreateBindGroup() override;
+    std::optional<DeviceGuard> UseGuardForCreateBindGroupLayout() override;
+    std::optional<DeviceGuard> UseGuardForCreateBuffer() override;
+    std::optional<DeviceGuard> UseGuardForCreateSampler() override;
+    std::optional<DeviceGuard> UseGuardForCreateTexture() override;
+
     // Get a MTLBuffer that can be used as a mock in a no-op blit encoder based on filling this
     // single-byte buffer
     id<MTLBuffer> GetMockBlitMtlBuffer();
diff --git a/src/dawn/native/metal/DeviceMTL.mm b/src/dawn/native/metal/DeviceMTL.mm
index 47bc253..0385087 100644
--- a/src/dawn/native/metal/DeviceMTL.mm
+++ b/src/dawn/native/metal/DeviceMTL.mm
@@ -412,6 +412,53 @@
     return true;
 }
 
+std::optional<DeviceGuard> Device::UseGuardForCreateBindGroup() {
+    // TODO(crbug.com/363031535): When argument buffers are enabled, BindGroup::InitializeImpl()
+    // encodes into a shared MTLArgumentEncoder stored on the BindGroupLayout without
+    // synchronization. We can remove this guard if we synchronize the shared encoder's state in the
+    // future.
+    if (IsToggleEnabled(Toggle::MetalUseArgumentBuffers)) {
+        return GetGuard();
+    }
+    return std::nullopt;
+}
+
+std::optional<DeviceGuard> Device::UseGuardForCreateBindGroupLayout() {
+    return std::nullopt;
+}
+
+std::optional<DeviceGuard> Device::UseGuardForCreateBuffer() {
+    // Buffer::Initialize() clears padding bytes (when LazyClearResourceOnFirstUse is enabled) or
+    // initializes the buffer to non-zero (when NonzeroClearResourcesOnCreationForTesting is
+    // enabled) using the queue's shared CommandRecordingContext, which is not thread-safe. If we
+    // defer padding byte clearing to first use on the GPU in the future, we may be able to remove
+    // the LazyClearResourceOnFirstUse check here.
+    if (IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse) ||
+        IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting)) {
+        return GetGuard();
+    }
+    return std::nullopt;
+}
+
+std::optional<DeviceGuard> Device::UseGuardForCreateSampler() {
+    return std::nullopt;
+}
+
+std::optional<DeviceGuard> Device::UseGuardForCreateTexture() {
+    // Texture::Create() clears textures at creation time when ShouldKeepInitialized() is true
+    // (for multi-subresource depth/stencil textures when LazyClearResourceOnFirstUse and
+    // MetalKeepMultisubresourceDepthStencilTexturesInitialized are enabled) or when
+    // NonzeroClearResourcesOnCreationForTesting is enabled. Clearing uses the queue's shared
+    // CommandRecordingContext and DynamicUploader, which are not thread-safe without the device
+    // lock.
+    if (IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting) ||
+        (IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse) &&
+         IsToggleEnabled(Toggle::MetalKeepMultisubresourceDepthStencilTexturesInitialized))) {
+        return GetGuard();
+    }
+    return std::nullopt;
+}
+
 id<MTLBuffer> Device::GetMockBlitMtlBuffer() {
     if (mMockBlitMtlBuffer == nullptr) {
         mMockBlitMtlBuffer.Acquire(