diff --git a/Source/Core/VideoBackends/D3D12/PerfQuery.cpp b/Source/Core/VideoBackends/D3D12/PerfQuery.cpp
index f5231aad76..4b2ec079ee 100644
--- a/Source/Core/VideoBackends/D3D12/PerfQuery.cpp
+++ b/Source/Core/VideoBackends/D3D12/PerfQuery.cpp
@@ -57,6 +57,12 @@ void PerfQuery::EnableQuery(PerfQueryGroup type)
     PartialFlush(do_resolve, blocking);
   }
 
+  // Ensure all state is applied before beginning the query.
+  // This is because we can't leave a query open when submitting a command list, and the draw
+  // call itself may need to execute a command list if we run out of descriptors. Note that
+  // this assumes that the caller has bound all required state prior to enabling the query.
+  Renderer::GetInstance()->ApplyState();
+
   if (type == PQG_ZCOMP_ZCOMPLOC || type == PQG_ZCOMP)
   {
     ActiveQuery& entry = m_query_buffer[m_query_next_pos];
diff --git a/Source/Core/VideoBackends/D3D12/Renderer.h b/Source/Core/VideoBackends/D3D12/Renderer.h
index 60e0000cff..1979833048 100644
--- a/Source/Core/VideoBackends/D3D12/Renderer.h
+++ b/Source/Core/VideoBackends/D3D12/Renderer.h
@@ -89,6 +89,9 @@ public:
   void SetVertexBuffer(D3D12_GPU_VIRTUAL_ADDRESS address, u32 stride, u32 size);
   void SetIndexBuffer(D3D12_GPU_VIRTUAL_ADDRESS address, u32 size, DXGI_FORMAT format);
 
+  // Binds all dirty state
+  bool ApplyState();
+
 protected:
   void OnConfigChanged(u32 bits) override;
 
@@ -131,8 +134,6 @@ private:
 
   void CheckForSwapChainChanges();
 
-  // Binds all dirty state
-  bool ApplyState();
   void BindFramebuffer(DXFramebuffer* fb);
   void SetRootSignatures();
   void SetDescriptorHeaps();
diff --git a/Source/Core/VideoCommon/PerfQueryBase.h b/Source/Core/VideoCommon/PerfQueryBase.h
index 449b86cc1b..de99b215c9 100644
--- a/Source/Core/VideoCommon/PerfQueryBase.h
+++ b/Source/Core/VideoCommon/PerfQueryBase.h
@@ -31,22 +31,31 @@ class PerfQueryBase
 public:
   PerfQueryBase() : m_query_count(0) {}
   virtual ~PerfQueryBase() {}
+
   // Checks if performance queries are enabled in the gameini configuration.
   // NOTE: Called from CPU+GPU thread
   static bool ShouldEmulate();
 
   // Begin querying the specified value for the following host GPU commands
+  // The call to EnableQuery() should be placed immediately before the draw command, otherwise
+  // there is a risk of GPU resets if the query is left open and the buffer is submitted during
+  // resource binding (D3D12/Vulkan).
   virtual void EnableQuery(PerfQueryGroup type) {}
+
   // Stop querying the specified value for the following host GPU commands
   virtual void DisableQuery(PerfQueryGroup type) {}
+
   // Reset query counters to zero and drop any pending queries
   virtual void ResetQuery() {}
+
   // Return the measured value for the specified query type
   // NOTE: Called from CPU thread
   virtual u32 GetQueryResult(PerfQueryType type) { return 0; }
+
   // Request the value of any pending queries - causes a pipeline flush and thus should be used
   // carefully!
   virtual void FlushResults() {}
+
   // True if there are no further pending query results
   // NOTE: Called from CPU thread
   virtual bool IsFlushed() const { return true; }