[dawn][native] Split DynamicArrayState to its own class.

The logic of the dynamic array is very different from the one for static
bindings of a BindGroup. Tracking of pinned / unpinned resources and
metadata buffer updates will get non-trivial so it is better to separate
the classes in different files.

Bug: 435317394
Change-Id: I8b4514f56b2eb7a50ed86e5be9623f719ce684f0
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/259194
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn/native/BUILD.gn b/src/dawn/native/BUILD.gn
index ab6ad62..1b1d8a1 100644
--- a/src/dawn/native/BUILD.gn
+++ b/src/dawn/native/BUILD.gn
@@ -278,6 +278,8 @@
     "Device.h",
     "DeviceGuard.cpp",
     "DeviceGuard.h",
+    "DynamicArrayState.cpp",
+    "DynamicArrayState.h",
     "DynamicUploader.cpp",
     "DynamicUploader.h",
     "EncodingContext.cpp",
diff --git a/src/dawn/native/BindGroup.cpp b/src/dawn/native/BindGroup.cpp
index 73bd805..0bd5f3a 100644
--- a/src/dawn/native/BindGroup.cpp
+++ b/src/dawn/native/BindGroup.cpp
@@ -42,6 +42,7 @@
 #include "dawn/native/Buffer.h"
 #include "dawn/native/CommandValidation.h"
 #include "dawn/native/Device.h"
+#include "dawn/native/DynamicArrayState.h"
 #include "dawn/native/ExternalTexture.h"
 #include "dawn/native/ObjectBase.h"
 #include "dawn/native/ObjectType_autogen.h"
@@ -814,9 +815,9 @@
 
     // Gather dynamic binding entries in a second loop to put the handling off the critical path.
     if (auto* dynamic = descriptor.Get<BindGroupDynamicBindingArray>()) {
-        mDynamicArray = std::make_unique<DynamicArrayState>(
-            GetDevice(), BindingIndex(dynamic->dynamicArraySize));
-        DAWN_TRY(mDynamicArray->Initialize());
+        mDynamicArray =
+            std::make_unique<DynamicArrayState>(BindingIndex(dynamic->dynamicArraySize));
+        DAWN_TRY(mDynamicArray->Initialize(GetDevice()));
 
         for (uint32_t i = 0; i < descriptor->entryCount; ++i) {
             UnpackedPtr<BindGroupEntry> entry = Unpack(&descriptor->entries[i]);
@@ -967,12 +968,6 @@
     return mDynamicArray != nullptr;
 }
 
-BindingIndex BindGroupBase::GetDynamicArraySize() const {
-    DAWN_ASSERT(!IsError());
-    DAWN_ASSERT(HasDynamicArray());
-    return mDynamicArray->GetSize();
-}
-
 ityp::span<BindingIndex, const Ref<TextureViewBase>> BindGroupBase::GetDynamicArrayBindings()
     const {
     DAWN_ASSERT(!IsError());
@@ -997,61 +992,4 @@
     return {};
 }
 
-BindGroupBase::DynamicArrayState::DynamicArrayState(DeviceBase* device, BindingIndex size)
-    : mDevice(device) {
-    mBindings.resize(size);
-}
-
-MaybeError BindGroupBase::DynamicArrayState::Initialize() {
-    // Create a storage buffer that will hold the shader-visible metadata for the dynamic array.
-    BufferDescriptor metadataDesc{
-        .label = "binding array metadata",
-        .usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst,
-        .size = 4,
-        .mappedAtCreation = true,
-    };
-    DAWN_TRY_ASSIGN(mMetadataBuffer, mDevice->CreateBuffer(&metadataDesc));
-
-    // TODO(https://crbug.com/439522242): For now it only contains the size but we also need to add
-    // type information for each entry in the future.
-    uint32_t* data = static_cast<uint32_t*>(mMetadataBuffer->GetMappedRange(0, metadataDesc.size));
-    *data = uint32_t(mBindings.size());
-    DAWN_TRY(mMetadataBuffer->Unmap());
-
-    return {};
-}
-
-BindingIndex BindGroupBase::DynamicArrayState::GetSize() const {
-    DAWN_ASSERT(!mDestroyed);
-    return mBindings.size();
-}
-
-ityp::span<BindingIndex, const Ref<TextureViewBase>> BindGroupBase::DynamicArrayState::GetBindings()
-    const {
-    DAWN_ASSERT(!mDestroyed);
-    return {mBindings.data(), mBindings.size()};
-}
-
-BufferBase* BindGroupBase::DynamicArrayState::GetMetadataBuffer() const {
-    DAWN_ASSERT(!mDestroyed);
-    return mMetadataBuffer.Get();
-}
-
-bool BindGroupBase::DynamicArrayState::IsDestroyed() const {
-    return mDestroyed;
-}
-
-void BindGroupBase::DynamicArrayState::Update(BindingIndex i, TextureViewBase* view) {
-    DAWN_ASSERT(!mDestroyed);
-    mBindings[i] = view;
-}
-
-void BindGroupBase::DynamicArrayState::Destroy() {
-    DAWN_ASSERT(!mDestroyed);
-    mBindings.clear();
-    mMetadataBuffer->Destroy();
-    mMetadataBuffer = nullptr;
-    mDestroyed = true;
-}
-
 }  // namespace dawn::native
diff --git a/src/dawn/native/BindGroup.h b/src/dawn/native/BindGroup.h
index 7fd07cf..77bcafc 100644
--- a/src/dawn/native/BindGroup.h
+++ b/src/dawn/native/BindGroup.h
@@ -35,7 +35,6 @@
 #include "dawn/common/Constants.h"
 #include "dawn/common/Math.h"
 #include "dawn/common/ityp_span.h"
-#include "dawn/common/ityp_vector.h"
 #include "dawn/native/BindGroupLayout.h"
 #include "dawn/native/ChainUtils.h"
 #include "dawn/native/Error.h"
@@ -48,6 +47,7 @@
 namespace dawn::native {
 
 class DeviceBase;
+class DynamicArrayState;
 
 ResultOrError<UnpackedPtr<BindGroupDescriptor>> ValidateBindGroupDescriptor(
     DeviceBase* device,
@@ -86,9 +86,9 @@
 
     void ForEachUnverifiedBufferBindingIndex(std::function<void(BindingIndex, uint32_t)> fn) const;
 
-    // Getters and operations on the dynamic array part.
+    // Getters and operations on the dynamic array part for code that doesn't need to directly
+    // modify the state.
     bool HasDynamicArray() const;
-    BindingIndex GetDynamicArraySize() const;
     ityp::span<BindingIndex, const Ref<TextureViewBase>> GetDynamicArrayBindings() const;
     MaybeError ValidateCanUseOnQueueNow() const;
 
@@ -133,26 +133,6 @@
 
     // The dynamic array is separate so as to not bloat the size and destructor of bind groups
     // without them.
-    class DynamicArrayState {
-      public:
-        explicit DynamicArrayState(DeviceBase* device, BindingIndex size);
-
-        MaybeError Initialize();
-
-        BindingIndex GetSize() const;
-        ityp::span<BindingIndex, const Ref<TextureViewBase>> GetBindings() const;
-        BufferBase* GetMetadataBuffer() const;
-        bool IsDestroyed() const;
-
-        void Update(BindingIndex i, TextureViewBase* view);
-        void Destroy();
-
-      private:
-        bool mDestroyed = false;
-        ityp::vector<BindingIndex, Ref<TextureViewBase>> mBindings;
-        raw_ptr<DeviceBase> mDevice;
-        Ref<BufferBase> mMetadataBuffer;
-    };
     std::unique_ptr<DynamicArrayState> mDynamicArray;
 };
 
diff --git a/src/dawn/native/CMakeLists.txt b/src/dawn/native/CMakeLists.txt
index 62eb7f4..354577a 100644
--- a/src/dawn/native/CMakeLists.txt
+++ b/src/dawn/native/CMakeLists.txt
@@ -80,6 +80,7 @@
     "dawn_platform.h"
     "Device.h"
     "DeviceGuard.h"
+    "DynamicArrayState.h"
     "DynamicUploader.h"
     "EncodingContext.h"
     "EnumClassBitmasks.h"
@@ -201,6 +202,7 @@
     "CreatePipelineAsyncEvent.cpp"
     "Device.cpp"
     "DeviceGuard.cpp"
+    "DynamicArrayState.cpp"
     "DynamicUploader.cpp"
     "EncodingContext.cpp"
     "Error.cpp"
diff --git a/src/dawn/native/DynamicArrayState.cpp b/src/dawn/native/DynamicArrayState.cpp
new file mode 100644
index 0000000..d5902d6
--- /dev/null
+++ b/src/dawn/native/DynamicArrayState.cpp
@@ -0,0 +1,90 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "dawn/native/DynamicArrayState.h"
+
+#include "dawn/native/Buffer.h"
+#include "dawn/native/Device.h"
+
+namespace dawn::native {
+
+DynamicArrayState::DynamicArrayState(BindingIndex size) {
+    mBindings.resize(size);
+}
+
+MaybeError DynamicArrayState::Initialize(DeviceBase* device) {
+    // Create a storage buffer that will hold the shader-visible metadata for the dynamic array.
+    BufferDescriptor metadataDesc{
+        .label = "binding array metadata",
+        .usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst,
+        .size = 4,
+        .mappedAtCreation = true,
+    };
+    DAWN_TRY_ASSIGN(mMetadataBuffer, device->CreateBuffer(&metadataDesc));
+
+    // TODO(https://crbug.com/439522242): For now it only contains the size but we also need to add
+    // type information for each entry in the future.
+    uint32_t* data = static_cast<uint32_t*>(mMetadataBuffer->GetMappedRange(0, metadataDesc.size));
+    *data = uint32_t(mBindings.size());
+    DAWN_TRY(mMetadataBuffer->Unmap());
+
+    return {};
+}
+
+BindingIndex DynamicArrayState::GetSize() const {
+    DAWN_ASSERT(!mDestroyed);
+    return mBindings.size();
+}
+
+ityp::span<BindingIndex, const Ref<TextureViewBase>> DynamicArrayState::GetBindings() const {
+    DAWN_ASSERT(!mDestroyed);
+    return {mBindings.data(), mBindings.size()};
+}
+
+BufferBase* DynamicArrayState::GetMetadataBuffer() const {
+    DAWN_ASSERT(!mDestroyed);
+    return mMetadataBuffer.Get();
+}
+
+bool DynamicArrayState::IsDestroyed() const {
+    return mDestroyed;
+}
+
+void DynamicArrayState::Update(BindingIndex i, TextureViewBase* view) {
+    DAWN_ASSERT(!mDestroyed);
+    mBindings[i] = view;
+}
+
+void DynamicArrayState::Destroy() {
+    DAWN_ASSERT(!mDestroyed);
+    mBindings.clear();
+    mMetadataBuffer->Destroy();
+    mMetadataBuffer = nullptr;
+    mDestroyed = true;
+}
+
+}  // namespace dawn::native
diff --git a/src/dawn/native/DynamicArrayState.h b/src/dawn/native/DynamicArrayState.h
new file mode 100644
index 0000000..1ddeae7
--- /dev/null
+++ b/src/dawn/native/DynamicArrayState.h
@@ -0,0 +1,70 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_DAWN_NATIVE_DYNAMICARRAYSTATE_H_
+#define SRC_DAWN_NATIVE_DYNAMICARRAYSTATE_H_
+
+#include <vector>
+
+#include "dawn/common/Ref.h"
+#include "dawn/common/ityp_span.h"
+#include "dawn/common/ityp_vector.h"
+#include "dawn/native/Error.h"
+#include "dawn/native/Forward.h"
+#include "dawn/native/IntegerTypes.h"
+#include "dawn/native/dawn_platform.h"
+#include "partition_alloc/pointers/raw_ptr.h"
+
+namespace dawn::native {
+
+// An optional component of a BindGroup that's used to track the resources that are in the dynamic
+// binding array part. It helps maintain the metadata buffer that's used in shaders to know if it is
+// valid to access an entry of the dynamic binding array with a given type (note that the writing of
+// the updates to the buffer are done by the backends).
+class DynamicArrayState {
+  public:
+    explicit DynamicArrayState(BindingIndex size);
+
+    MaybeError Initialize(DeviceBase* device);
+
+    BindingIndex GetSize() const;
+    ityp::span<BindingIndex, const Ref<TextureViewBase>> GetBindings() const;
+    BufferBase* GetMetadataBuffer() const;
+    bool IsDestroyed() const;
+
+    void Update(BindingIndex i, TextureViewBase* view);
+    void Destroy();
+
+  private:
+    bool mDestroyed = false;
+    ityp::vector<BindingIndex, Ref<TextureViewBase>> mBindings;
+    Ref<BufferBase> mMetadataBuffer;
+};
+
+}  // namespace dawn::native
+
+#endif  // SRC_DAWN_NATIVE_DYNAMICARRAYSTATE_H_