[dawn][native] Validate ExternalTextures used in RenderBundle

Validation was missing that an ExternalTexture used in a bundle was
alive when used in a submit.

 - Move the code to handle merging the sync scope usages of render
   bundles from ExecuteBundles to a new helper function in
   PassResourceUsageTracker.
 - Add handling of ExternalTexture merging to that function.
 - Add tests.

Bug: None
Change-Id: Ief15a7a85a085f58c6c71b6e541a1436ce60a9d7
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/258294
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Loko Kung <lokokung@google.com>
Auto-Submit: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn/native/PassResourceUsageTracker.cpp b/src/dawn/native/PassResourceUsageTracker.cpp
index 7be797f..18e3a48 100644
--- a/src/dawn/native/PassResourceUsageTracker.cpp
+++ b/src/dawn/native/PassResourceUsageTracker.cpp
@@ -84,18 +84,17 @@
         });
 }
 
-void SyncScopeUsageTracker::AddRenderBundleTextureUsage(
-    TextureBase* texture,
-    const TextureSubresourceSyncInfo& textureSyncInfo) {
+void SyncScopeUsageTracker::MergeTextureUsage(TextureBase* texture,
+                                              const TextureSubresourceSyncInfo& textureSyncInfo) {
     // Get or create a new TextureSubresourceSyncInfo for that texture (initially filled with
     // wgpu::TextureUsage::None and WGPUShaderStage_None)
     auto it = mTextureSyncInfos.try_emplace(
         texture, texture->GetFormat().aspects, texture->GetArrayLayers(),
         texture->GetNumMipLevels(),
         TextureSyncInfo{wgpu::TextureUsage::None, wgpu::ShaderStage::None});
-    TextureSubresourceSyncInfo* passTextureSyncInfo = &it.first->second;
+    TextureSubresourceSyncInfo& passTextureSyncInfo = it.first->second;
 
-    passTextureSyncInfo->Merge(
+    passTextureSyncInfo.Merge(
         textureSyncInfo, [](const SubresourceRange&, TextureSyncInfo* storedSyncInfo,
                             const TextureSyncInfo& addedSyncInfo) {
             DAWN_ASSERT((addedSyncInfo.usage & wgpu::TextureUsage::RenderAttachment) == 0);
@@ -104,6 +103,21 @@
         });
 }
 
+void SyncScopeUsageTracker::MergeResourceUsages(const SyncScopeResourceUsage& usages) {
+    for (uint32_t i = 0; i < usages.buffers.size(); ++i) {
+        BufferUsedAs(usages.buffers[i], usages.bufferSyncInfos[i].usage,
+                     usages.bufferSyncInfos[i].shaderStages);
+    }
+
+    for (uint32_t i = 0; i < usages.textures.size(); ++i) {
+        MergeTextureUsage(usages.textures[i], usages.textureSyncInfos[i]);
+    }
+
+    for (ExternalTextureBase* t : usages.externalTextures) {
+        mExternalTextureUsages.insert(t);
+    }
+}
+
 void SyncScopeUsageTracker::AddBindGroup(BindGroupBase* group) {
     const auto* layout = group->GetLayout();
 
@@ -196,18 +210,17 @@
         result.buffers.push_back(buffer);
         result.bufferSyncInfos.push_back(std::move(syncInfo));
     }
+    mBufferSyncInfos.clear();
 
     for (auto& [texture, syncInfo] : mTextureSyncInfos) {
         result.textures.push_back(texture);
         result.textureSyncInfos.push_back(std::move(syncInfo));
     }
+    mTextureSyncInfos.clear();
 
     for (auto* const it : mExternalTextureUsages) {
         result.externalTextures.push_back(it);
     }
-
-    mBufferSyncInfos.clear();
-    mTextureSyncInfos.clear();
     mExternalTextureUsages.clear();
 
     return result;
diff --git a/src/dawn/native/PassResourceUsageTracker.h b/src/dawn/native/PassResourceUsageTracker.h
index 905f797..d143526 100644
--- a/src/dawn/native/PassResourceUsageTracker.h
+++ b/src/dawn/native/PassResourceUsageTracker.h
@@ -65,8 +65,9 @@
                             const SubresourceRange& range,
                             wgpu::TextureUsage usage,
                             wgpu::ShaderStage shaderStages = wgpu::ShaderStage::None);
-    void AddRenderBundleTextureUsage(TextureBase* texture,
-                                     const TextureSubresourceSyncInfo& textureSyncInfo);
+
+    // Add all usages referenced to this tracker.
+    void MergeResourceUsages(const SyncScopeResourceUsage& usages);
 
     // Walks the bind groups and tracks all its resources.
     void AddBindGroup(BindGroupBase* group);
@@ -75,6 +76,8 @@
     SyncScopeResourceUsage AcquireSyncScopeUsage();
 
   private:
+    void MergeTextureUsage(TextureBase* texture, const TextureSubresourceSyncInfo& textureSyncInfo);
+
     absl::flat_hash_map<BufferBase*, BufferSyncInfo> mBufferSyncInfos;
     absl::flat_hash_map<TextureBase*, TextureSubresourceSyncInfo> mTextureSyncInfos;
     absl::flat_hash_set<ExternalTextureBase*> mExternalTextureUsages;
diff --git a/src/dawn/native/RenderPassEncoder.cpp b/src/dawn/native/RenderPassEncoder.cpp
index 4aef4bd..6aedb91 100644
--- a/src/dawn/native/RenderPassEncoder.cpp
+++ b/src/dawn/native/RenderPassEncoder.cpp
@@ -376,17 +376,7 @@
             for (uint32_t i = 0; i < count; ++i) {
                 bundles[i] = renderBundles[i];
 
-                const RenderPassResourceUsage& usages = bundles[i]->GetResourceUsage();
-                for (uint32_t j = 0; j < usages.buffers.size(); ++j) {
-                    mUsageTracker.BufferUsedAs(usages.buffers[j], usages.bufferSyncInfos[j].usage,
-                                               usages.bufferSyncInfos[j].shaderStages);
-                }
-
-                for (uint32_t j = 0; j < usages.textures.size(); ++j) {
-                    mUsageTracker.AddRenderBundleTextureUsage(usages.textures[j],
-                                                              usages.textureSyncInfos[j]);
-                }
-
+                mUsageTracker.MergeResourceUsages(bundles[i]->GetResourceUsage());
                 if (IsValidationEnabled()) {
                     mIndirectDrawMetadata.AddBundle(renderBundles[i]);
                 }
diff --git a/src/dawn/tests/unittests/validation/ExternalTextureTests.cpp b/src/dawn/tests/unittests/validation/ExternalTextureTests.cpp
index 5e95c2b..6b0eb89 100644
--- a/src/dawn/tests/unittests/validation/ExternalTextureTests.cpp
+++ b/src/dawn/tests/unittests/validation/ExternalTextureTests.cpp
@@ -82,6 +82,42 @@
         }
     }
 
+    void SubmitExternalTextureInDefaultRenderBundle(wgpu::ExternalTexture externalTexture,
+                                                    bool success) {
+        // Create a bind group that contains the external texture.
+        wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
+            device, {{0, wgpu::ShaderStage::Fragment, &utils::kExternalTextureBindingLayout}});
+        wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, bgl, {{0, externalTexture}});
+
+        // Create another texture to use as a color attachment.
+        wgpu::TextureDescriptor renderTextureDescriptor = CreateTextureDescriptor();
+        wgpu::Texture renderTexture = device.CreateTexture(&renderTextureDescriptor);
+        wgpu::TextureView renderView = renderTexture.CreateView();
+
+        // Create a RenderBundle using the bindgroup and doing nothing else.
+        wgpu::RenderBundleEncoderDescriptor rbDesc;
+        rbDesc.colorFormatCount = 1;
+        rbDesc.colorFormats = &renderTextureDescriptor.format;
+
+        wgpu::RenderBundleEncoder rbEncoder = device.CreateRenderBundleEncoder(&rbDesc);
+        rbEncoder.SetBindGroup(0, bindGroup);
+        wgpu::RenderBundle bundle = rbEncoder.Finish();
+
+        // Use that render bundle in the render pass.
+        utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr);
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
+        pass.ExecuteBundles(1, &bundle);
+        pass.End();
+        wgpu::CommandBuffer commands = encoder.Finish();
+
+        if (success) {
+            queue.Submit(1, &commands);
+        } else {
+            ASSERT_DEVICE_ERROR(queue.Submit(1, &commands));
+        }
+    }
+
     void SubmitExternalTextureInDefaultComputePass(wgpu::ExternalTexture externalTexture,
                                                    bool success) {
         // Create a bind group that contains the external texture.
@@ -515,13 +551,34 @@
     SubmitExternalTextureInDefaultComputePass(externalTexture, false /* success = false */);
 }
 
-// Test that submitting a compute pass that contains an active external texture should success.
+// Test that submitting a compute pass that contains a destroyed external texture should success.
 TEST_F(ExternalTextureTest, SubmitDestroyedExternalTextureInComputePass) {
     wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture();
     externalTexture.Destroy();
     SubmitExternalTextureInDefaultComputePass(externalTexture, false /* success = false */);
 }
 
+// Test that submitting a render bundle that contains an active external.
+TEST_F(ExternalTextureTest, SubmitActiveExternalTextureInRenderBundle) {
+    wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture();
+    SubmitExternalTextureInDefaultRenderBundle(externalTexture, true /* success = true */);
+}
+
+// Test that submitting a render bundle that contains an expired external texture results in an
+// error.
+TEST_F(ExternalTextureTest, SubmitExpiredExternalTextureInRenderBundle) {
+    wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture();
+    externalTexture.Expire();
+    SubmitExternalTextureInDefaultRenderBundle(externalTexture, false /* success = false */);
+}
+
+// Test that submitting a render bundle that contains a destroyed external texture should success.
+TEST_F(ExternalTextureTest, SubmitDestroyedExternalTextureInRenderBundle) {
+    wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture();
+    externalTexture.Destroy();
+    SubmitExternalTextureInDefaultRenderBundle(externalTexture, false /* success = false */);
+}
+
 // Test that refresh an expired external texture and submit a compute pass with it.
 TEST_F(ExternalTextureTest, RefreshExpiredExternalTexture) {
     wgpu::ExternalTexture externalTexture = CreateDefaultExternalTexture();