Dawn Native: Add ImmediateConstantsTrackers

ImmediateConstantsTrackers are introduced to track immediate constants
including immediate data exposed to external usage and some internal
constants.

It uses dirty bits to track immediate constants states between pipelines
and records latest constant values for uploading to GPU.

Bug: 366291600
Change-Id: Ib9355081a34e81f978115755690e9d0469e50b3b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/219634
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Shaobo Yan <shaoboyan@microsoft.com>
Reviewed-by: Loko Kung <lokokung@google.com>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
diff --git a/src/dawn/common/Constants.h b/src/dawn/common/Constants.h
index 64f732b..ad5af6a 100644
--- a/src/dawn/common/Constants.h
+++ b/src/dawn/common/Constants.h
@@ -47,6 +47,17 @@
 static constexpr uint64_t kAssumedMaxBufferSize =
     0x80000000u;  // Use 2 GB when the limit is unavailable
 
+// All Immediate constants are 32 bit
+static constexpr uint32_t kImmediateConstantElementByteSize = sizeof(uint32_t);
+
+// Known as 'Immediate Data'. User could update them through APIs.
+static constexpr uint32_t kMaxExternalImmediateConstantsPerPipeline = 4u;
+
+// Vulkan requires min-max push constant bytes is 128 byte, which is
+// equals to 32 32bit constants. D3D12 requires 64 32bit constants limits.
+// Pick 32 here.
+static constexpr uint32_t kMaxImmediateConstantsPerPipeline = 32u;
+
 // Per stage maximum limits used to optimized Dawn internals.
 static constexpr uint32_t kMaxSampledTexturesPerShaderStage = 16;
 static constexpr uint32_t kMaxSamplersPerShaderStage = 16;
diff --git a/src/dawn/native/BUILD.gn b/src/dawn/native/BUILD.gn
index a418a8f..553ea8b 100644
--- a/src/dawn/native/BUILD.gn
+++ b/src/dawn/native/BUILD.gn
@@ -296,6 +296,10 @@
     "Format.cpp",
     "Format.h",
     "Forward.h",
+    "ImmediateConstantsLayout.cpp",
+    "ImmediateConstantsLayout.h",
+    "ImmediateConstantsTracker.cpp",
+    "ImmediateConstantsTracker.h",
     "IndirectDrawMetadata.cpp",
     "IndirectDrawMetadata.h",
     "IndirectDrawValidationEncoder.cpp",
diff --git a/src/dawn/native/CMakeLists.txt b/src/dawn/native/CMakeLists.txt
index a14450a..eb61456 100644
--- a/src/dawn/native/CMakeLists.txt
+++ b/src/dawn/native/CMakeLists.txt
@@ -92,6 +92,8 @@
     "Features.h"
     "Format.h"
     "Forward.h"
+    "ImmediateConstantsLayout.h"
+    "ImmediateConstantsTracker.h"
     "IndirectDrawMetadata.h"
     "IndirectDrawValidationEncoder.h"
     "Instance.h"
@@ -200,6 +202,8 @@
     "ExternalTexture.cpp"
     "Features.cpp"
     "Format.cpp"
+    "ImmediateConstantsLayout.cpp"
+    "ImmediateConstantsTracker.cpp"
     "IndirectDrawMetadata.cpp"
     "IndirectDrawValidationEncoder.cpp"
     "Instance.cpp"
diff --git a/src/dawn/native/ImmediateConstantsLayout.cpp b/src/dawn/native/ImmediateConstantsLayout.cpp
new file mode 100644
index 0000000..20440e0
--- /dev/null
+++ b/src/dawn/native/ImmediateConstantsLayout.cpp
@@ -0,0 +1,41 @@
+// 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/ImmediateConstantsLayout.h"
+
+#include <utility>
+
+namespace dawn::native {
+
+ImmediateConstantMask GetImmediateConstantBlockBits(size_t byteOffset, size_t byteSize) {
+    size_t firstIndex = byteOffset / kImmediateConstantElementByteSize;
+    size_t constantCount = byteSize / kImmediateConstantElementByteSize;
+
+    return ((1 << constantCount) - 1) << firstIndex;
+}
+
+}  // namespace dawn::native
diff --git a/src/dawn/native/ImmediateConstantsLayout.h b/src/dawn/native/ImmediateConstantsLayout.h
new file mode 100644
index 0000000..26f2dd3
--- /dev/null
+++ b/src/dawn/native/ImmediateConstantsLayout.h
@@ -0,0 +1,85 @@
+// 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_IMMEDIATECONSTANTSLAYOUT_H_
+#define SRC_DAWN_NATIVE_IMMEDIATECONSTANTSLAYOUT_H_
+
+#include <unordered_map>
+
+#include "dawn/common/ityp_bitset.h"
+#include "dawn/native/IntegerTypes.h"
+
+namespace dawn::native {
+
+// Define common immediate data layout. Append members to expand layouts.
+// NOTE: 'offsetof' doesn't support non-standard-layout structs. So use
+// aggregate instead of inheritance for RenderImmediateConstants and
+// ComputeImmediateConstants.
+struct UserImmediateConstants {
+    uint32_t userImmediateData[kMaxExternalImmediateConstantsPerPipeline];
+};
+
+struct ClampFragDepthArgs {
+    float minClampFragDepth;
+    float maxClampFragDepth;
+};
+
+// Define render pipeline immediate data layout. Append members to
+// expand the layout.
+struct RenderImmediateConstants {
+    UserImmediateConstants userConstants;
+
+    ClampFragDepthArgs clampFragDepth;
+
+    // first index offset
+    uint32_t firstVertex;
+    uint32_t firstInstance;
+};
+static_assert(sizeof(RenderImmediateConstants) == 32u);
+
+struct NumWorkgroupsDimensions {
+    uint32_t numWorkgroupsX;
+    uint32_t numWorkgroupsY;
+    uint32_t numWorkgroupsZ;
+};
+
+// Define compute pipeline immediate data layout. Append members to
+// expand the layout.
+struct ComputeImmediateConstants {
+    UserImmediateConstants userConstants;
+
+    NumWorkgroupsDimensions numWorkgroups;
+};
+static_assert(sizeof(ComputeImmediateConstants) == 28u);
+
+// Convert byte sizes and offsets into immediate constant indices and offsets
+// (dividing everything by kImmediateConstantElementByteSize)
+ImmediateConstantMask GetImmediateConstantBlockBits(size_t byteOffset, size_t byteSize);
+
+}  // namespace dawn::native
+
+#endif  // SRC_DAWN_NATIVE_IMMEDIATECONSTANTSLAYOUT_H_
diff --git a/src/dawn/native/ImmediateConstantsTracker.cpp b/src/dawn/native/ImmediateConstantsTracker.cpp
new file mode 100644
index 0000000..8cdc75c
--- /dev/null
+++ b/src/dawn/native/ImmediateConstantsTracker.cpp
@@ -0,0 +1,74 @@
+// 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/ImmediateConstantsTracker.h"
+#include "ImmediateConstantsLayout.h"
+
+namespace dawn::native {
+RenderImmediateConstantsTrackerBase::RenderImmediateConstantsTrackerBase()
+    : UserImmediateConstantsTrackerBase() {}
+
+void RenderImmediateConstantsTrackerBase::SetClampFragDepth(float minClampFragDepth,
+                                                            float maxClampFragDepth) {
+    // Put the data in the right layout to match the RenderImmediateConstants struct
+    ClampFragDepthArgs fragDepthArgs;
+    fragDepthArgs.minClampFragDepth = minClampFragDepth;
+    fragDepthArgs.maxClampFragDepth = maxClampFragDepth;
+
+    UpdateImmediateConstants(offsetof(RenderImmediateConstants, clampFragDepth), fragDepthArgs);
+}
+
+void RenderImmediateConstantsTrackerBase::SetFirstIndexOffset(uint32_t firstVertex,
+                                                              uint32_t firstInstance) {
+    this->SetFirstVertex(firstVertex);
+    this->SetFirstInstance(firstInstance);
+}
+
+void RenderImmediateConstantsTrackerBase::SetFirstVertex(uint32_t firstVertex) {
+    UpdateImmediateConstants(offsetof(RenderImmediateConstants, firstVertex), firstVertex);
+}
+
+void RenderImmediateConstantsTrackerBase::SetFirstInstance(uint32_t firstInstance) {
+    UpdateImmediateConstants(offsetof(RenderImmediateConstants, firstInstance), firstInstance);
+}
+
+ComputeImmediateConstantsTrackerBase::ComputeImmediateConstantsTrackerBase()
+    : UserImmediateConstantsTrackerBase() {}
+void ComputeImmediateConstantsTrackerBase::SetNumWorkgroups(uint32_t numWorkgroupX,
+                                                            uint32_t numWorkgroupY,
+                                                            uint32_t numWorkgroupZ) {
+    // Put the data in the right layout to match the ComputeImmediateConstants struct
+    NumWorkgroupsDimensions numWorkgroupsDimensions;
+    numWorkgroupsDimensions.numWorkgroupsX = numWorkgroupX;
+    numWorkgroupsDimensions.numWorkgroupsY = numWorkgroupY;
+    numWorkgroupsDimensions.numWorkgroupsZ = numWorkgroupZ;
+
+    UpdateImmediateConstants(offsetof(ComputeImmediateConstants, numWorkgroups),
+                             numWorkgroupsDimensions);
+}
+
+}  // namespace dawn::native
diff --git a/src/dawn/native/ImmediateConstantsTracker.h b/src/dawn/native/ImmediateConstantsTracker.h
new file mode 100644
index 0000000..c62840f
--- /dev/null
+++ b/src/dawn/native/ImmediateConstantsTracker.h
@@ -0,0 +1,129 @@
+// 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_IMMEDIATECONSTANTSTRACKER_H_
+#define SRC_DAWN_NATIVE_IMMEDIATECONSTANTSTRACKER_H_
+
+#include <algorithm>
+#include <array>
+#include <bitset>
+#include <cstddef>
+
+#include "ImmediateConstantsLayout.h"
+#include "dawn/common/Constants.h"
+#include "dawn/common/ityp_bitset.h"
+#include "dawn/common/ityp_span.h"
+#include "dawn/native/Device.h"
+#include "dawn/native/ImmediateConstantsLayout.h"
+#include "dawn/native/IntegerTypes.h"
+#include "dawn/native/Pipeline.h"
+
+namespace dawn::native {
+
+template <typename T>
+struct ImmediateDataContent {
+  public:
+    const T* operator->() const { return reinterpret_cast<const T*>(&mData); }
+    T* operator->() { return reinterpret_cast<T*>(&mData); }
+
+    const unsigned char* data() const { return mData; }
+
+    template <typename Out>
+    const Out* Get(uint32_t offset) const {
+        DAWN_ASSERT(sizeof(Out) + offset <= sizeof(T));
+        return reinterpret_cast<const Out*>(&mData[offset]);
+    }
+
+    template <typename Out>
+    Out* Get(uint32_t offset) {
+        DAWN_ASSERT(sizeof(Out) + offset <= sizeof(T));
+        return reinterpret_cast<Out*>(&mData[offset]);
+    }
+
+  private:
+    alignas(T) unsigned char mData[sizeof(T)] = {0};
+};
+
+template <typename T>
+class UserImmediateConstantsTrackerBase {
+  public:
+    UserImmediateConstantsTrackerBase() {}
+    // Setters
+    void SetImmediateData(uint32_t immediateDataRangeOffset, uint32_t* values, uint32_t count) {
+        uint32_t* destData = mContent.template Get<uint32_t>(offsetof(T, userConstants) +
+                                                             immediateDataRangeOffset *
+                                                                 kImmediateConstantElementByteSize);
+        size_t dataSize = count * kImmediateConstantElementByteSize;
+        if (memcmp(destData, values, dataSize) != 0) {
+            memcpy(destData, values, dataSize);
+            mDirty |= GetImmediateConstantBlockBits(offsetof(T, userConstants),
+                                                    sizeof(UserImmediateConstants));
+        }
+    }
+
+    // Getters
+    const ImmediateConstantMask& GetDirtyBits() const { return mDirty; }
+
+    const ImmediateDataContent<T>& GetContent() const { return mContent; }
+
+    void SetDirtyBitsForTesting(ImmediateConstantMask dirtyBits) { mDirty = dirtyBits; }
+
+  protected:
+    template <typename U>
+    void UpdateImmediateConstants(size_t dataOffset, const U& data) {
+        constexpr size_t dataSize = sizeof(U);
+        U* destData = mContent.template Get<U>(dataOffset);
+        if (memcmp(destData, &data, dataSize) != 0) {
+            memcpy(destData, &data, dataSize);
+            mDirty |= GetImmediateConstantBlockBits(dataOffset, dataSize);
+        }
+    }
+
+    ImmediateDataContent<T> mContent;
+    ImmediateConstantMask mDirty = ImmediateConstantMask(0);
+};
+
+class RenderImmediateConstantsTrackerBase
+    : public UserImmediateConstantsTrackerBase<RenderImmediateConstants> {
+  public:
+    RenderImmediateConstantsTrackerBase();
+    void SetClampFragDepth(float minClampFragDepth, float maxClampFragDepth);
+    void SetFirstIndexOffset(uint32_t firstVertex, uint32_t firstInstance);
+    void SetFirstVertex(uint32_t firstVertex);
+    void SetFirstInstance(uint32_t firstInstance);
+};
+
+class ComputeImmediateConstantsTrackerBase
+    : public UserImmediateConstantsTrackerBase<ComputeImmediateConstants> {
+  public:
+    ComputeImmediateConstantsTrackerBase();
+    void SetNumWorkgroups(uint32_t numWorkgroupX, uint32_t numWorkgroupY, uint32_t numWorkgroupZ);
+};
+
+}  // namespace dawn::native
+
+#endif  // SRC_DAWN_NATIVE_IMMEDIATECONSTANTSTRACKER_H_
diff --git a/src/dawn/native/IntegerTypes.h b/src/dawn/native/IntegerTypes.h
index bb39c6f..ef4bc07 100644
--- a/src/dawn/native/IntegerTypes.h
+++ b/src/dawn/native/IntegerTypes.h
@@ -67,6 +67,14 @@
 template <typename Value>
 using PerBindGroup = ityp::array<BindGroupIndex, Value, kMaxBindGroups>;
 
+// Immediate data constant index get mapped to a packed range of indices
+using ImmediateConstantIndex = TypedInteger<struct ImmediateConstantIndexT, uint32_t>;
+constexpr ImmediateConstantIndex kMaxImmediateConstantIndexTyped =
+    ImmediateConstantIndex(kMaxImmediateConstantsPerPipeline);
+
+using ImmediateConstantMask =
+    ityp::bitset<ImmediateConstantIndex, kMaxImmediateConstantsPerPipeline>;
+
 // Color attachment indices represent the index in the wgpu::FragmentState::targets array, the
 // wgpu::RenderPassDescriptor::colorAttachments arry and other similar arrays.
 using ColorAttachmentIndex = TypedInteger<struct ColorAttachmentIndexT, uint8_t>;
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index 4a597c4..24a8e91 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -369,6 +369,7 @@
     "unittests/native/DestroyObjectTests.cpp",
     "unittests/native/DeviceAsyncTaskTests.cpp",
     "unittests/native/DeviceCreationTests.cpp",
+    "unittests/native/ImmediateConstantsTrackerTests.cpp",
     "unittests/native/LimitsTests.cpp",
     "unittests/native/MemoryInstrumentationTests.cpp",
     "unittests/native/ObjectContentHasherTests.cpp",
diff --git a/src/dawn/tests/unittests/native/ImmediateConstantsTrackerTests.cpp b/src/dawn/tests/unittests/native/ImmediateConstantsTrackerTests.cpp
new file mode 100644
index 0000000..3d9df13
--- /dev/null
+++ b/src/dawn/tests/unittests/native/ImmediateConstantsTrackerTests.cpp
@@ -0,0 +1,204 @@
+// 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/common/Assert.h"
+#include "dawn/native/ComputePipeline.h"
+#include "dawn/native/ImmediateConstantsLayout.h"
+#include "dawn/native/ImmediateConstantsTracker.h"
+#include "dawn/native/RenderPipeline.h"
+#include "dawn/tests/DawnNativeTest.h"
+#include "dawn/utils/WGPUHelpers.h"
+
+namespace dawn::native {
+namespace {
+class ImmediateConstantsTrackerTest : public DawnNativeTest {};
+
+class RenderImmediateConstantsTrackerTest : public ImmediateConstantsTrackerTest {};
+
+class ComputeImmediateConstantsTrackerTest : public ImmediateConstantsTrackerTest {};
+
+// Test immediate setting update dirty bits and contents correctly.
+TEST_F(ImmediateConstantsTrackerTest, SetImmediateData) {
+    static constexpr uint32_t rangeOffset = 1u;
+    static constexpr uint32_t dataOffset = 2u;
+    static constexpr uint32_t userImmediateDataCount = 2u;
+    ImmediateConstantMask expected =
+        GetImmediateConstantBlockBits(0u, sizeof(UserImmediateConstants));
+
+    size_t userImmediateDataStartByteOffset = 0u;
+    // RenderImmediateConstantsTracker
+    {
+        RenderImmediateConstantsTrackerBase tracker;
+        int32_t userImmediateData[] = {2, 4, -6, 8};
+        tracker.SetImmediateData(rangeOffset,
+                                 reinterpret_cast<uint32_t*>(&userImmediateData[dataOffset]),
+                                 userImmediateDataCount);
+        EXPECT_TRUE(tracker.GetDirtyBits() == expected);
+
+        uint32_t userImmediateDataRangeOffset =
+            userImmediateDataStartByteOffset + rangeOffset * kImmediateConstantElementByteSize;
+        EXPECT_TRUE(memcmp(tracker.GetContent().Get<int32_t>(userImmediateDataRangeOffset),
+                           &userImmediateData[dataOffset],
+                           sizeof(int32_t) * userImmediateDataCount) == 0);
+    }
+
+    // ComputeImmediateConstantsTracker
+    {
+        ComputeImmediateConstantsTrackerBase tracker;
+        int32_t userImmediateData[] = {2, 4, -6, 8};
+        tracker.SetImmediateData(rangeOffset,
+                                 reinterpret_cast<uint32_t*>(&userImmediateData[dataOffset]),
+                                 userImmediateDataCount);
+        EXPECT_TRUE(tracker.GetDirtyBits() == expected);
+
+        uint32_t userImmediateDataRangeOffset =
+            userImmediateDataStartByteOffset + rangeOffset * kImmediateConstantElementByteSize;
+        EXPECT_TRUE(memcmp(tracker.GetContent().Get<int32_t>(userImmediateDataRangeOffset),
+                           &userImmediateData[dataOffset],
+                           sizeof(int32_t) * userImmediateDataCount) == 0);
+    }
+
+    device.Destroy();
+}
+
+// Test setting clamp frag depth args with float value updates dirty bits and contents correctly.
+TEST_F(RenderImmediateConstantsTrackerTest, SetClampFragDepth) {
+    RenderImmediateConstantsTrackerBase tracker;
+    float minClampFragDepth = 0.1;
+    float maxClampFragDepth = 0.95;
+    tracker.SetClampFragDepth(minClampFragDepth, maxClampFragDepth);
+
+    ImmediateConstantMask expected;
+    // Hard coded to verify dirty bit.
+    expected |= 1u << 4u;
+    expected |= 1u << 5u;
+    EXPECT_TRUE(tracker.GetDirtyBits() == expected);
+
+    // Compare bits instead of values here to ensure bits level equality.
+    size_t clampFragDepthStartOffsetBytes = offsetof(RenderImmediateConstants, clampFragDepth);
+    size_t minClampFragDepthOffsetBytes =
+        clampFragDepthStartOffsetBytes + offsetof(ClampFragDepthArgs, minClampFragDepth);
+    size_t maxClampFragDepthOffsetBytes =
+        clampFragDepthStartOffsetBytes + offsetof(ClampFragDepthArgs, maxClampFragDepth);
+    EXPECT_TRUE(memcmp(tracker.GetContent().Get<float>(minClampFragDepthOffsetBytes),
+                       &minClampFragDepth, sizeof(float)) == 0);
+    EXPECT_TRUE(memcmp(tracker.GetContent().Get<float>(maxClampFragDepthOffsetBytes),
+                       &maxClampFragDepth, sizeof(float)) == 0);
+
+    device.Destroy();
+}
+
+// Test setting first index offset args updates dirty bits and contents correctly.
+TEST_F(RenderImmediateConstantsTrackerTest, SetFirstIndexOffset) {
+    size_t firstVertexByteOffset = offsetof(RenderImmediateConstants, firstVertex);
+    size_t firstInstanceByteOffset = offsetof(RenderImmediateConstants, firstInstance);
+    // SetFirstIndexOffset()
+    {
+        RenderImmediateConstantsTrackerBase tracker;
+        uint32_t firstVertex = 1;
+        uint32_t firstInstance = 2;
+        tracker.SetFirstIndexOffset(firstVertex, firstInstance);
+
+        ImmediateConstantMask expected;
+        // Hard coded to verify dirty bit.
+        expected |= 1u << 6u;
+        expected |= 1u << 7u;
+        EXPECT_TRUE(tracker.GetDirtyBits() == expected);
+
+        EXPECT_TRUE(memcmp(tracker.GetContent().Get<uint32_t>(firstVertexByteOffset), &firstVertex,
+                           sizeof(uint32_t)) == 0);
+        EXPECT_TRUE(memcmp(tracker.GetContent().Get<uint32_t>(firstInstanceByteOffset),
+                           &firstInstance, sizeof(uint32_t)) == 0);
+    }
+
+    // SetFirstVertex()
+    {
+        RenderImmediateConstantsTrackerBase tracker;
+        uint32_t firstVertex = 1;
+        tracker.SetFirstVertex(firstVertex);
+
+        ImmediateConstantMask expected;
+        // Hard coded to verify dirty bit.
+        expected |= 1u << 6u;
+        EXPECT_TRUE(tracker.GetDirtyBits() == expected);
+
+        EXPECT_TRUE(memcmp(tracker.GetContent().Get<uint32_t>(firstVertexByteOffset), &firstVertex,
+                           sizeof(uint32_t)) == 0);
+    }
+
+    // SetFirstInstance()
+    {
+        RenderImmediateConstantsTrackerBase tracker;
+        uint32_t firstInstance = 2;
+        tracker.SetFirstInstance(firstInstance);
+
+        ImmediateConstantMask expected;
+        // Hard coded to verify dirty bit.
+        expected |= 1u << 7u;
+        EXPECT_TRUE(tracker.GetDirtyBits() == expected);
+
+        EXPECT_TRUE(memcmp(tracker.GetContent().Get<uint32_t>(firstInstanceByteOffset),
+                           &firstInstance, sizeof(uint32_t)) == 0);
+    }
+
+    device.Destroy();
+}
+
+// Test setting num workgroups dimensions update dirty bits and contents correctly.
+TEST_F(ComputeImmediateConstantsTrackerTest, SetNumWorkgroupDimensions) {
+    ComputeImmediateConstantsTrackerBase tracker;
+    uint32_t numWorkgroupsX = 256;
+    uint32_t numWorkgroupsY = 128;
+    uint32_t numWorkgroupsZ = 64;
+    tracker.SetNumWorkgroups(256, 128, 64);
+
+    ImmediateConstantMask expected;
+    // Hard coded to verify dirty bit.
+    expected |= 1u << 4u;
+    expected |= 1u << 5u;
+    expected |= 1u << 6u;
+    EXPECT_TRUE(tracker.GetDirtyBits() == expected);
+
+    size_t numWorkgroupsStartByteOffset = offsetof(ComputeImmediateConstants, numWorkgroups);
+    size_t numWorkgroupXByteOffset =
+        numWorkgroupsStartByteOffset + offsetof(NumWorkgroupsDimensions, numWorkgroupsX);
+    size_t numWorkgroupYByteOffset =
+        numWorkgroupsStartByteOffset + offsetof(NumWorkgroupsDimensions, numWorkgroupsY);
+    size_t numWorkgroupZByteOffset =
+        numWorkgroupsStartByteOffset + offsetof(NumWorkgroupsDimensions, numWorkgroupsZ);
+    EXPECT_TRUE(memcmp(tracker.GetContent().Get<uint32_t>(numWorkgroupXByteOffset), &numWorkgroupsX,
+                       sizeof(uint32_t)) == 0);
+
+    EXPECT_TRUE(memcmp(tracker.GetContent().Get<uint32_t>(numWorkgroupYByteOffset), &numWorkgroupsY,
+                       sizeof(uint32_t)) == 0);
+    EXPECT_TRUE(memcmp(tracker.GetContent().Get<uint32_t>(numWorkgroupZByteOffset), &numWorkgroupsZ,
+                       sizeof(uint32_t)) == 0);
+    device.Destroy();
+}
+
+}  // anonymous namespace
+}  // namespace dawn::native