Remove auto lock from CreateTexture/Sampler API calls

Add "no autolock" to Device::CreateTexture in dawn.json to prevent
automatic locking at the API boundary.

Introduce virtual method UseGuardForCreateTexture() that returns
std::optional<DeviceGuard>. The default implementation returns a guard,
but D3D11 overrides to return nullopt since its CreateTextureImpl is
already thread-safe.

Lock acquisition must happen at the API level, not within Impl
methods, to avoid potential lock inversion issues when Impl methods
are called by other internal functions.

This CL also does similar things for Device::CreateSampler.

Bug: 474265307
Bug: 475520968
Change-Id: I1f3df3e525c447abceeea9ec56e59791e071835f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/284276
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Quyen Le <lehoangquyen@chromium.org>
diff --git a/src/dawn/dawn.json b/src/dawn/dawn.json
index 2a5c3e0..33f4810 100644
--- a/src/dawn/dawn.json
+++ b/src/dawn/dawn.json
@@ -1360,6 +1360,7 @@
             },
             {
                 "name": "create sampler",
+                "no autolock": true,
                 "returns": "sampler",
                 "args": [
                     {"name": "descriptor", "type": "sampler descriptor", "annotation": "const*", "optional": true}
@@ -1384,6 +1385,7 @@
             },
             {
                 "name": "create texture",
+                "no autolock": true,
                 "returns": "texture",
                 "args": [
                     {"name": "descriptor", "type": "texture descriptor", "annotation": "const*"}
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index 7087ca9..05e6838 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -1459,6 +1459,7 @@
     return ReturnToAPI(std::move(result));
 }
 SamplerBase* DeviceBase::APICreateSampler(const SamplerDescriptor* descriptor) {
+    auto deviceGuard = UseGuardForCreateSampler();
     Ref<SamplerBase> result;
     if (ConsumedError(CreateSampler(descriptor), &result, "calling %s.CreateSampler(%s).", this,
                       descriptor)) {
@@ -1592,6 +1593,7 @@
     return ReturnToAPI(std::move(result));
 }
 TextureBase* DeviceBase::APICreateTexture(const TextureDescriptor* descriptor) {
+    auto deviceGuard = UseGuardForCreateTexture();
     Ref<TextureBase> result;
     if (ConsumedError(CreateTexture(descriptor), &result, InternalErrorType::OutOfMemory,
                       "calling %s.CreateTexture(%s).", this, descriptor)) {
@@ -2543,6 +2545,18 @@
 
 void DeviceBase::PerformIdleTasksImpl() {}
 
+std::optional<DeviceGuard> DeviceBase::UseGuardForCreateTexture() {
+    // Backends with thread-safe CreateTextureImpl() can override this to return nullopt.
+    // TODO(crbug.com/475530346): Even with a thread-safe CreateTextureImpl(), there's still a
+    // potential race between Device::Destroy() and APICreateTexture() without the device lock. We
+    // assume callers are responsible for synchronizing Destroy() calls with texture creation.
+    return GetGuard();
+}
+
+std::optional<DeviceGuard> DeviceBase::UseGuardForCreateSampler() {
+    return GetGuard();
+}
+
 bool DeviceBase::ShouldDuplicateNumWorkgroupsForDispatchIndirect(
     ComputePipelineBase* computePipeline) const {
     return false;
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index c3e8b72..1f099f4 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -32,6 +32,7 @@
 
 #include <atomic>
 #include <memory>
+#include <optional>
 #include <string>
 #include <utility>
 #include <vector>
@@ -559,6 +560,11 @@
     virtual bool ReduceMemoryUsageImpl();
     virtual void PerformIdleTasksImpl();
 
+    // TODO(crbug.com/475520968): Remove this once all backends' Create*Impl methods are
+    // thread-safe
+    virtual std::optional<DeviceGuard> UseGuardForCreateTexture();
+    virtual std::optional<DeviceGuard> UseGuardForCreateSampler();
+
     virtual MaybeError TickImpl() = 0;
     void FlushCallbackTaskQueue();
 
diff --git a/src/dawn/native/d3d11/DeviceD3D11.cpp b/src/dawn/native/d3d11/DeviceD3D11.cpp
index 6ee0c73..4b9b47e 100644
--- a/src/dawn/native/d3d11/DeviceD3D11.cpp
+++ b/src/dawn/native/d3d11/DeviceD3D11.cpp
@@ -525,6 +525,14 @@
     return false;
 }
 
+std::optional<DeviceGuard> Device::UseGuardForCreateTexture() {
+    return std::nullopt;
+}
+
+std::optional<DeviceGuard> Device::UseGuardForCreateSampler() {
+    return std::nullopt;
+}
+
 bool Device::MayRequireDuplicationOfIndirectParameters() const {
     return true;
 }
diff --git a/src/dawn/native/d3d11/DeviceD3D11.h b/src/dawn/native/d3d11/DeviceD3D11.h
index 2e48f50..952f521 100644
--- a/src/dawn/native/d3d11/DeviceD3D11.h
+++ b/src/dawn/native/d3d11/DeviceD3D11.h
@@ -97,6 +97,9 @@
 
     bool ReduceMemoryUsageImpl() override;
 
+    std::optional<DeviceGuard> UseGuardForCreateTexture() override;
+    std::optional<DeviceGuard> UseGuardForCreateSampler() override;
+
     uint32_t GetUAVSlotCount() const;
 
     ResultOrError<TextureViewBase*> GetOrCreateCachedImplicitPixelLocalStorageAttachment(