D3D12: Avoid re-binding sampler tables.

Skip re-binding samplers since sampler heap
allocations are cached and the pipeline would
redundantly get set every draw. This is
particularly important for draw heavy scenarios.

After the change:
+3% in RendererPerfTest/1.* via SkiaDawn
+100% for Forest Demo FPS (55ms vs 28ms)

BUG=dawn:479

Change-Id: I93cbca4e365d6ff89ec86fc15eaccf70b49ea916
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/24161
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Bryan Bernhart <bryan.bernhart@intel.com>
diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
index a9c0249..0b8b124 100644
--- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp
@@ -83,6 +83,8 @@
     }  // anonymous namespace
 
     class BindGroupStateTracker : public BindGroupAndStorageBarrierTrackerBase<false, uint64_t> {
+        using Base = BindGroupAndStorageBarrierTrackerBase;
+
       public:
         BindGroupStateTracker(Device* device)
             : BindGroupAndStorageBarrierTrackerBase(),
@@ -95,6 +97,17 @@
             mInCompute = inCompute_;
         }
 
+        void OnSetPipeline(PipelineBase* pipeline) {
+            // Invalidate the root sampler tables previously set in the root signature.
+            // This is because changing the pipeline layout also changes the root signature.
+            const PipelineLayout* pipelineLayout = ToBackend(pipeline->GetLayout());
+            if (mLastAppliedPipelineLayout != pipelineLayout) {
+                mBoundRootSamplerTables = {};
+            }
+
+            Base::OnSetPipeline(pipeline);
+        }
+
         MaybeError Apply(CommandRecordingContext* commandContext) {
             // Bindgroups are allocated in shader-visible descriptor heaps which are managed by a
             // ringbuffer. There can be a single shader-visible descriptor heap of each type bound
@@ -309,10 +322,16 @@
                 uint32_t parameterIndex = pipelineLayout->GetSamplerRootParameterIndex(index);
                 const D3D12_GPU_DESCRIPTOR_HANDLE baseDescriptor =
                     group->GetBaseSamplerDescriptor();
-                if (mInCompute) {
-                    commandList->SetComputeRootDescriptorTable(parameterIndex, baseDescriptor);
-                } else {
-                    commandList->SetGraphicsRootDescriptorTable(parameterIndex, baseDescriptor);
+                // Check if the group requires its sampler table to be set in the pipeline.
+                // This because sampler heap allocations could be cached and use the same table.
+                if (mBoundRootSamplerTables[index].ptr != baseDescriptor.ptr) {
+                    if (mInCompute) {
+                        commandList->SetComputeRootDescriptorTable(parameterIndex, baseDescriptor);
+                    } else {
+                        commandList->SetGraphicsRootDescriptorTable(parameterIndex, baseDescriptor);
+                    }
+
+                    mBoundRootSamplerTables[index] = baseDescriptor;
                 }
             }
         }
@@ -321,6 +340,9 @@
 
         bool mInCompute = false;
 
+        ityp::array<BindGroupIndex, D3D12_GPU_DESCRIPTOR_HANDLE, kMaxBindGroups>
+            mBoundRootSamplerTables = {};
+
         ShaderVisibleDescriptorAllocator* mViewAllocator;
         ShaderVisibleDescriptorAllocator* mSamplerAllocator;
     };