Create ring buffer for managing D3D uniforms.

* Adds a base class for the ring buffer (to be used by Metal as well),
  which tracks the current available space. APIs will need to
  implement creation of the buffer in the subclass.
* The API implementation will need to store SubmitData on command buffer
  submit, and then pass it to finishSubmit when the command buffer
  finishes.

Change-Id: I4cc5e4a72d259ee9d15dac0e964819d4562da3d7
Bug: skia:9935
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/291936
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Jim Van Verth <jvanverth@google.com>
diff --git a/gn/gpu.gni b/gn/gpu.gni
index e81d03e..ab27ce4 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -179,6 +179,8 @@
   "$_src/gpu/GrResourceProvider.cpp",
   "$_src/gpu/GrResourceProvider.h",
   "$_src/gpu/GrResourceProviderPriv.h",
+  "$_src/gpu/GrRingBuffer.cpp",
+  "$_src/gpu/GrRingBuffer.h",
   "$_src/gpu/GrSPIRVUniformHandler.cpp",
   "$_src/gpu/GrSPIRVUniformHandler.h",
   "$_src/gpu/GrSPIRVVaryingHandler.cpp",
@@ -755,6 +757,8 @@
   "$_src/gpu/d3d/GrD3DCaps.h",
   "$_src/gpu/d3d/GrD3DCommandList.cpp",
   "$_src/gpu/d3d/GrD3DCommandList.h",
+  "$_src/gpu/d3d/GrD3DConstantRingBuffer.cpp",
+  "$_src/gpu/d3d/GrD3DConstantRingBuffer.h",
   "$_src/gpu/d3d/GrD3DCpuDescriptorManager.cpp",
   "$_src/gpu/d3d/GrD3DCpuDescriptorManager.h",
   "$_src/gpu/d3d/GrD3DDescriptorHeap.cpp",
diff --git a/src/gpu/GrRingBuffer.cpp b/src/gpu/GrRingBuffer.cpp
new file mode 100644
index 0000000..c901428
--- /dev/null
+++ b/src/gpu/GrRingBuffer.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/gpu/GrRingBuffer.h"
+
+// Get offset into buffer that has enough space for size
+// Returns fTotalSize if no space
+size_t GrRingBuffer::getAllocationOffset(size_t size) {
+    // capture current state locally (because fTail could be overwritten by the completion handler)
+    size_t head, tail;
+    SkAutoSpinlock lock(fMutex);
+    head = fHead;
+    tail = fTail;
+
+    // The head and tail indices increment without bound, wrapping with overflow,
+    // so we need to mod them down to the actual bounds of the allocation to determine
+    // which blocks are available.
+    size_t modHead = head & (fTotalSize - 1);
+    size_t modTail = tail & (fTotalSize - 1);
+
+    bool full = (head != tail && modHead == modTail);
+
+    if (full) {
+        return fTotalSize;
+    }
+
+    // case 1: free space lies at the beginning and/or the end of the buffer
+    if (modHead >= modTail) {
+        // check for room at the end
+        if (fTotalSize - modHead < size) {
+            // no room at the end, check the beginning
+            if (modTail < size) {
+                // no room at the beginning
+                return fTotalSize;
+            }
+            // we are going to allocate from the beginning, adjust head to '0' position
+            head += fTotalSize - modHead;
+            modHead = 0;
+        }
+        // case 2: free space lies in the middle of the buffer, check for room there
+    } else if (modTail - modHead < size) {
+        // no room in the middle
+        return fTotalSize;
+    }
+
+    fHead = GrAlignTo(head + size, fAlignment);
+    return modHead;
+}
+
+GrRingBuffer::Slice GrRingBuffer::suballocate(size_t size) {
+    size_t offset = this->getAllocationOffset(size);
+    if (offset < fTotalSize) {
+        return { fBuffer, offset };
+    }
+
+    // Try to grow allocation (old allocation will age out).
+    fTotalSize *= 2;
+    fBuffer = this->createBuffer(fTotalSize);
+    SkASSERT(fBuffer);
+    SkAutoSpinlock lock(fMutex);
+    fHead = 0;
+    fTail = 0;
+    fGenID++;
+    offset = this->getAllocationOffset(size);
+    SkASSERT(offset < fTotalSize);
+    return { fBuffer, offset };
+}
+
+// used when current command buffer/command list is submitted
+GrRingBuffer::SubmitData GrRingBuffer::startSubmit() {
+    SubmitData submitData;
+    SkAutoSpinlock lock(fMutex);
+    submitData.fBuffer = fBuffer;
+    submitData.fLastHead = fHead;
+    submitData.fGenID = fGenID;
+    return submitData;
+}
+
+// used when current command buffer/command list is completed
+void GrRingBuffer::finishSubmit(const GrRingBuffer::SubmitData& submitData) {
+    SkAutoSpinlock lock(fMutex);
+    if (submitData.fGenID == fGenID) {
+        fTail = submitData.fLastHead;
+    }
+}
diff --git a/src/gpu/GrRingBuffer.h b/src/gpu/GrRingBuffer.h
new file mode 100644
index 0000000..f816922
--- /dev/null
+++ b/src/gpu/GrRingBuffer.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrRingBuffer_DEFINED
+#define GrRingBuffer_DEFINED
+
+#include "src/gpu/GrGpuBuffer.h"
+
+#include "include/private/SkSpinlock.h"
+
+/**
+ * A wrapper for a GPU buffer that allocates slices in a continuous ring
+ */
+class GrRingBuffer : public SkRefCnt {
+public:
+    GrRingBuffer(sk_sp<GrGpuBuffer> buffer, size_t size, size_t alignment)
+        : fBuffer(std::move(buffer))
+        , fTotalSize(size)
+        , fAlignment(alignment)
+        , fHead(0)
+        , fTail(0)
+        , fGenID(0) {
+        // We increment fHead and fTail without bound and let overflow handle any wrapping.
+        // Because of this, size needs to be a power of two.
+        SkASSERT(SkIsPow2(size));
+    }
+    virtual ~GrRingBuffer() = default;
+
+    struct Slice {
+        sk_sp<GrGpuBuffer> fBuffer;
+        size_t fOffset;
+    };
+
+    Slice suballocate(size_t size);
+
+    class SubmitData {
+    public:
+        GrGpuBuffer* buffer() const { return fBuffer.get(); }
+    private:
+        friend class GrRingBuffer;
+        sk_sp<GrGpuBuffer> fBuffer;
+        size_t fLastHead;
+        size_t fGenID;
+    };
+    // Backends should call startSubmit() at submit time, and finishSubmit() when the
+    // command buffer/list finishes.
+    SubmitData startSubmit();
+    void finishSubmit(const SubmitData&);
+
+    size_t size() const { return fTotalSize; }
+
+private:
+    virtual sk_sp<GrGpuBuffer> createBuffer(size_t size) = 0;
+    size_t getAllocationOffset(size_t size);
+
+    sk_sp<GrGpuBuffer> fBuffer;
+    size_t fTotalSize;
+    size_t fAlignment;
+    size_t fHead SK_GUARDED_BY(fMutex);     // where we start allocating
+    size_t fTail SK_GUARDED_BY(fMutex);     // where we start deallocating
+    uint64_t fGenID SK_GUARDED_BY(fMutex);  // incremented when createBuffer is called
+    SkSpinlock fMutex;
+};
+
+#endif
diff --git a/src/gpu/d3d/GrD3DCommandList.cpp b/src/gpu/d3d/GrD3DCommandList.cpp
index 541b973..4703bbb 100644
--- a/src/gpu/d3d/GrD3DCommandList.cpp
+++ b/src/gpu/d3d/GrD3DCommandList.cpp
@@ -200,7 +200,8 @@
     , fCurrentVertexStride(0)
     , fCurrentInstanceBuffer(nullptr)
     , fCurrentInstanceStride(0)
-    , fCurrentIndexBuffer(nullptr) {
+    , fCurrentIndexBuffer(nullptr)
+    , fCurrentConstantRingBuffer(nullptr) {
 }
 
 void GrD3DDirectCommandList::onReset() {
@@ -210,6 +211,10 @@
     fCurrentInstanceBuffer = nullptr;
     fCurrentInstanceStride = 0;
     fCurrentIndexBuffer = nullptr;
+    if (fCurrentConstantRingBuffer) {
+        fCurrentConstantRingBuffer->finishSubmit(fConstantRingBufferSubmitData);
+        fCurrentConstantRingBuffer = nullptr;
+    }
 }
 
 void GrD3DDirectCommandList::setPipelineState(sk_sp<GrD3DPipelineState> pipelineState) {
@@ -218,6 +223,16 @@
     this->addResource(std::move(pipelineState));
 }
 
+void GrD3DDirectCommandList::setCurrentConstantBuffer(
+        const sk_sp<GrD3DConstantRingBuffer>& constantBuffer) {
+    fCurrentConstantRingBuffer = constantBuffer.get();
+    if (fCurrentConstantRingBuffer) {
+        fConstantRingBufferSubmitData = constantBuffer->startSubmit();
+        this->addResource(
+                static_cast<GrD3DBuffer*>(fConstantRingBufferSubmitData.buffer())->resource());
+    }
+}
+
 void GrD3DDirectCommandList::setStencilRef(unsigned int stencilRef) {
     SkASSERT(fIsActive);
     fCommandList->OMSetStencilRef(stencilRef);
diff --git a/src/gpu/d3d/GrD3DCommandList.h b/src/gpu/d3d/GrD3DCommandList.h
index e662437..86d51da7 100644
--- a/src/gpu/d3d/GrD3DCommandList.h
+++ b/src/gpu/d3d/GrD3DCommandList.h
@@ -12,11 +12,13 @@
 #include "include/gpu/d3d/GrD3DTypes.h"
 #include "include/private/SkColorData.h"
 #include "src/gpu/GrManagedResource.h"
+#include "src/gpu/d3d/GrD3DConstantRingBuffer.h"
 
 #include <memory>
 
 class GrD3DGpu;
 class GrD3DBuffer;
+class GrD3DConstantRingBuffer;
 class GrD3DPipelineState;
 class GrD3DRenderTarget;
 class GrD3DRootSignature;
@@ -122,6 +124,8 @@
 
     void setPipelineState(sk_sp<GrD3DPipelineState> pipelineState);
 
+    void setCurrentConstantBuffer(const sk_sp<GrD3DConstantRingBuffer>& constantBuffer);
+
     void setStencilRef(unsigned int stencilRef);
     void setBlendFactor(const float blendFactor[4]);
     void setPrimitiveTopology(D3D12_PRIMITIVE_TOPOLOGY primitiveTopology);
@@ -154,6 +158,9 @@
     const GrD3DBuffer* fCurrentInstanceBuffer;
     size_t fCurrentInstanceStride;
     const GrD3DBuffer* fCurrentIndexBuffer;
+
+    GrD3DConstantRingBuffer* fCurrentConstantRingBuffer;
+    GrD3DConstantRingBuffer::SubmitData fConstantRingBufferSubmitData;
 };
 
 class GrD3DCopyCommandList : public GrD3DCommandList {
diff --git a/src/gpu/d3d/GrD3DConstantRingBuffer.cpp b/src/gpu/d3d/GrD3DConstantRingBuffer.cpp
new file mode 100644
index 0000000..cf719c7
--- /dev/null
+++ b/src/gpu/d3d/GrD3DConstantRingBuffer.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/gpu/d3d/GrD3DConstantRingBuffer.h"
+
+#include "src/gpu/d3d/GrD3DBuffer.h"
+#include "src/gpu/d3d/GrD3DGpu.h"
+
+sk_sp<GrD3DConstantRingBuffer> GrD3DConstantRingBuffer::Make(GrD3DGpu* gpu, size_t size,
+                                                             size_t alignment) {
+    sk_sp<GrGpuBuffer> buffer = GrD3DBuffer::Make(gpu, size, GrGpuBufferType::kVertex,
+                                                  kDynamic_GrAccessPattern);
+    if (!buffer) {
+        return nullptr;
+    }
+
+    return sk_sp<GrD3DConstantRingBuffer>(new GrD3DConstantRingBuffer(std::move(buffer), size,
+                                                                      alignment, gpu));
+}
+
+sk_sp<GrGpuBuffer> GrD3DConstantRingBuffer::createBuffer(size_t size) {
+    // Make sure the old buffer is added to the current command list
+    fGpu->resourceProvider().prepForSubmit();
+
+    return GrD3DBuffer::Make(fGpu, size, GrGpuBufferType::kVertex, kDynamic_GrAccessPattern);
+}
diff --git a/src/gpu/d3d/GrD3DConstantRingBuffer.h b/src/gpu/d3d/GrD3DConstantRingBuffer.h
new file mode 100644
index 0000000..0a4bbf6
--- /dev/null
+++ b/src/gpu/d3d/GrD3DConstantRingBuffer.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrD3DConstantRingBuffer_DEFINED
+
+#define GrD3DConstantRingBuffer_DEFINED
+
+#include "src/gpu/GrRingBuffer.h"
+
+class GrD3DGpu;
+
+class GrD3DConstantRingBuffer : public GrRingBuffer {
+public:
+    static sk_sp<GrD3DConstantRingBuffer> Make(GrD3DGpu* gpu, size_t size, size_t alignment);
+
+private:
+    GrD3DConstantRingBuffer(sk_sp<GrGpuBuffer> buffer, size_t size, size_t alignment, GrD3DGpu* gpu)
+        : INHERITED(std::move(buffer), size, alignment)
+        , fGpu(gpu) {}
+    ~GrD3DConstantRingBuffer() override = default;
+
+    sk_sp<GrGpuBuffer> createBuffer(size_t size) override;
+
+    GrD3DGpu* fGpu;
+
+    typedef GrRingBuffer INHERITED;
+};
+
+#endif
diff --git a/src/gpu/d3d/GrD3DGpu.cpp b/src/gpu/d3d/GrD3DGpu.cpp
index c41ae08..7a50f01 100644
--- a/src/gpu/d3d/GrD3DGpu.cpp
+++ b/src/gpu/d3d/GrD3DGpu.cpp
@@ -111,6 +111,8 @@
 bool GrD3DGpu::submitDirectCommandList(SyncQueue sync) {
     SkASSERT(fCurrentDirectCommandList);
 
+    fResourceProvider.prepForSubmit();
+
     GrD3DDirectCommandList::SubmitResult result = fCurrentDirectCommandList->submit(fQueue.get());
     if (result == GrD3DDirectCommandList::SubmitResult::kFailure) {
         return false;
diff --git a/src/gpu/d3d/GrD3DOpsRenderPass.cpp b/src/gpu/d3d/GrD3DOpsRenderPass.cpp
index 45315e0..7280d58 100644
--- a/src/gpu/d3d/GrD3DOpsRenderPass.cpp
+++ b/src/gpu/d3d/GrD3DOpsRenderPass.cpp
@@ -160,7 +160,7 @@
         return false;
     }
 
-    fCurrentPipelineState->setData(fRenderTarget, info);
+    fCurrentPipelineState->setData(fGpu, fRenderTarget, info);
     fGpu->currentCommandList()->setGraphicsRootSignature(fCurrentPipelineState->rootSignature());
     fGpu->currentCommandList()->setPipelineState(fCurrentPipelineState);
 
diff --git a/src/gpu/d3d/GrD3DPipelineState.cpp b/src/gpu/d3d/GrD3DPipelineState.cpp
index c140e7f..f025fed 100644
--- a/src/gpu/d3d/GrD3DPipelineState.cpp
+++ b/src/gpu/d3d/GrD3DPipelineState.cpp
@@ -42,7 +42,7 @@
     , fVertexStride(vertexStride)
     , fInstanceStride(instanceStride) {}
 
-void GrD3DPipelineState::setData(const GrRenderTarget* renderTarget,
+void GrD3DPipelineState::setData(GrD3DGpu* gpu, const GrRenderTarget* renderTarget,
                                  const GrProgramInfo& programInfo) {
     this->setRenderTargetState(renderTarget, programInfo.origin());
 
@@ -62,6 +62,9 @@
         fXferProcessor->setData(fDataManager, programInfo.pipeline().getXferProcessor(),
                                 dstTexture, offset);
     }
+
+    // TODO: use returned virtual address to create a CBV and set in command list
+    (void) fDataManager.uploadConstants(gpu);
 }
 
 void GrD3DPipelineState::setRenderTargetState(const GrRenderTarget* rt, GrSurfaceOrigin origin) {
diff --git a/src/gpu/d3d/GrD3DPipelineState.h b/src/gpu/d3d/GrD3DPipelineState.h
index ecd8a3b..a62d445 100644
--- a/src/gpu/d3d/GrD3DPipelineState.h
+++ b/src/gpu/d3d/GrD3DPipelineState.h
@@ -52,7 +52,7 @@
     ID3D12PipelineState* pipelineState() const { return fPipelineState.get(); }
     const sk_sp<GrD3DRootSignature>& rootSignature() const { return fRootSignature; }
 
-    void setData(const GrRenderTarget* renderTarget, const GrProgramInfo& programInfo);
+    void setData(GrD3DGpu*, const GrRenderTarget* renderTarget, const GrProgramInfo& programInfo);
 
     void setAndBindTextures(const GrPrimitiveProcessor& primProc,
                             const GrSurfaceProxy* const primProcTextures[],
diff --git a/src/gpu/d3d/GrD3DPipelineStateDataManager.cpp b/src/gpu/d3d/GrD3DPipelineStateDataManager.cpp
index 9326099..a611d7c 100644
--- a/src/gpu/d3d/GrD3DPipelineStateDataManager.cpp
+++ b/src/gpu/d3d/GrD3DPipelineStateDataManager.cpp
@@ -7,6 +7,9 @@
 
 #include "src/gpu/d3d/GrD3DPipelineStateDataManager.h"
 
+#include "src/gpu/d3d/GrD3DGpu.h"
+#include "src/gpu/d3d/GrD3DResourceProvider.h"
+
 GrD3DPipelineStateDataManager::GrD3DPipelineStateDataManager(const UniformInfoArray& uniforms,
                                                              uint32_t uniformSize)
     : INHERITED(uniforms.count(), uniformSize) {
@@ -26,3 +29,13 @@
         ++i;
     }
 }
+
+D3D12_GPU_VIRTUAL_ADDRESS GrD3DPipelineStateDataManager::uploadConstants(GrD3DGpu* gpu) {
+    if (fUniformsDirty) {
+        fConstantBufferAddress = gpu->resourceProvider().uploadConstantData(fUniformData.get(),
+                                                                         fUniformSize);
+        fUniformsDirty = false;
+    }
+
+    return fConstantBufferAddress;
+}
diff --git a/src/gpu/d3d/GrD3DPipelineStateDataManager.h b/src/gpu/d3d/GrD3DPipelineStateDataManager.h
index 14d9c2e..15a41ab 100644
--- a/src/gpu/d3d/GrD3DPipelineStateDataManager.h
+++ b/src/gpu/d3d/GrD3DPipelineStateDataManager.h
@@ -13,6 +13,9 @@
 #include "include/gpu/d3d/GrD3DTypes.h"
 #include "src/gpu/GrSPIRVUniformHandler.h"
 
+class GrD3DConstantRingBuffer;
+class GrD3DGpu;
+
 class GrD3DPipelineStateDataManager : public GrUniformDataManager {
 public:
     typedef GrSPIRVUniformHandler::UniformInfoArray UniformInfoArray;
@@ -20,9 +23,11 @@
     GrD3DPipelineStateDataManager(const UniformInfoArray&,
                                   uint32_t uniformSize);
 
-    // TODO: upload to uniform buffer
+    D3D12_GPU_VIRTUAL_ADDRESS uploadConstants(GrD3DGpu* gpu);
 
 private:
+    D3D12_GPU_VIRTUAL_ADDRESS fConstantBufferAddress;
+
     typedef GrUniformDataManager INHERITED;
 };
 
diff --git a/src/gpu/d3d/GrD3DResourceProvider.cpp b/src/gpu/d3d/GrD3DResourceProvider.cpp
index f7b5543..f6810de 100644
--- a/src/gpu/d3d/GrD3DResourceProvider.cpp
+++ b/src/gpu/d3d/GrD3DResourceProvider.cpp
@@ -9,6 +9,7 @@
 
 #include "include/gpu/GrContextOptions.h"
 #include "src/gpu/GrContextPriv.h"
+#include "src/gpu/d3d/GrD3DBuffer.h"
 #include "src/gpu/d3d/GrD3DCommandList.h"
 #include "src/gpu/d3d/GrD3DGpu.h"
 #include "src/gpu/d3d/GrD3DPipelineState.h"
@@ -17,8 +18,7 @@
 GrD3DResourceProvider::GrD3DResourceProvider(GrD3DGpu* gpu)
         : fGpu(gpu)
         , fCpuDescriptorManager(gpu)
-        , fPipelineStateCache(new PipelineStateCache(gpu)) {
-}
+        , fPipelineStateCache(new PipelineStateCache(gpu)) {}
 
 std::unique_ptr<GrD3DDirectCommandList> GrD3DResourceProvider::findOrCreateDirectCommandList() {
     if (fAvailableDirectCommandLists.count()) {
@@ -99,6 +99,33 @@
     return fPipelineStateCache->refPipelineState(rt, info);
 }
 
+D3D12_GPU_VIRTUAL_ADDRESS GrD3DResourceProvider::uploadConstantData(void* data, size_t size) {
+    // constant size has to be aligned to 256
+    constexpr int kConstantAlignment = 256;
+
+    // Due to dependency on the resource cache we can't initialize this in the constructor, so
+    // we do so it here.
+    if (!fConstantBuffer) {
+        fConstantBuffer = GrD3DConstantRingBuffer::Make(fGpu, 128 * 1024, kConstantAlignment);
+        SkASSERT(fConstantBuffer);
+    }
+
+    // upload the data
+    size_t paddedSize = GrAlignTo(size, kConstantAlignment);
+    GrRingBuffer::Slice slice = fConstantBuffer->suballocate(paddedSize);
+    char* destPtr = static_cast<char*>(slice.fBuffer->map()) + slice.fOffset;
+    memcpy(destPtr, data, size);
+
+    // create the associated constant buffer view descriptor
+    GrD3DBuffer* d3dBuffer = static_cast<GrD3DBuffer*>(slice.fBuffer.get());
+    D3D12_GPU_VIRTUAL_ADDRESS gpuAddress = d3dBuffer->d3dResource()->GetGPUVirtualAddress();
+    return gpuAddress + slice.fOffset;
+}
+
+void GrD3DResourceProvider::prepForSubmit() {
+    fGpu->currentCommandList()->setCurrentConstantBuffer(fConstantBuffer);
+}
+
 ////////////////////////////////////////////////////////////////////////////////////////////////
 
 #ifdef GR_PIPELINE_STATE_CACHE_STATS
diff --git a/src/gpu/d3d/GrD3DResourceProvider.h b/src/gpu/d3d/GrD3DResourceProvider.h
index 5e7db62..0d7b828 100644
--- a/src/gpu/d3d/GrD3DResourceProvider.h
+++ b/src/gpu/d3d/GrD3DResourceProvider.h
@@ -12,6 +12,7 @@
 #include "include/private/SkTArray.h"
 #include "src/core/SkLRUCache.h"
 #include "src/gpu/GrProgramDesc.h"
+#include "src/gpu/d3d/GrD3DConstantRingBuffer.h"
 #include "src/gpu/d3d/GrD3DCpuDescriptorManager.h"
 #include "src/gpu/d3d/GrD3DRootSignature.h"
 
@@ -51,6 +52,9 @@
    sk_sp<GrD3DPipelineState> findOrCreateCompatiblePipelineState(GrRenderTarget*,
                                                                  const GrProgramInfo&);
 
+   D3D12_GPU_VIRTUAL_ADDRESS uploadConstantData(void* data, size_t size);
+   void prepForSubmit();
+
 private:
 #ifdef SK_DEBUG
 #define GR_PIPELINE_STATE_CACHE_STATS
@@ -89,6 +93,8 @@
 
     GrD3DCpuDescriptorManager fCpuDescriptorManager;
 
+    sk_sp<GrD3DConstantRingBuffer> fConstantBuffer;
+
     std::unique_ptr<PipelineStateCache> fPipelineStateCache;
 };