text: /*
   * 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/GrD3DGpu.h"
  
  #include "include/gpu/GrBackendSurface.h"
  #include "include/gpu/d3d/GrD3DBackendContext.h"
  #include "src/core/SkConvertPixels.h"
  #include "src/core/SkMipmap.h"
  #include "src/gpu/GrBackendUtils.h"
  #include "src/gpu/GrDataUtils.h"
  #include "src/gpu/GrTexture.h"
  #include "src/gpu/GrThreadSafePipelineBuilder.h"
  #include "src/gpu/d3d/GrD3DAMDMemoryAllocator.h"
  #include "src/gpu/d3d/GrD3DAttachment.h"
  #include "src/gpu/d3d/GrD3DBuffer.h"
  #include "src/gpu/d3d/GrD3DCaps.h"
  #include "src/gpu/d3d/GrD3DOpsRenderPass.h"
  #include "src/gpu/d3d/GrD3DSemaphore.h"
  #include "src/gpu/d3d/GrD3DTexture.h"
  #include "src/gpu/d3d/GrD3DTextureRenderTarget.h"
  #include "src/gpu/d3d/GrD3DUtil.h"
  #include "src/sksl/SkSLCompiler.h"
  
  #if GR_TEST_UTILS
  #include <DXProgrammableCapture.h>
  #endif
  
  GrThreadSafePipelineBuilder* GrD3DGpu::pipelineBuilder() {
      return nullptr;
  }
  
  sk_sp<GrThreadSafePipelineBuilder> GrD3DGpu::refPipelineBuilder() {
      return nullptr;
  }
  
  
  sk_sp<GrGpu> GrD3DGpu::Make(const GrD3DBackendContext& backendContext,
                              const GrContextOptions& contextOptions, GrDirectContext* direct) {
      sk_sp<GrD3DMemoryAllocator> memoryAllocator = backendContext.fMemoryAllocator;
      if (!memoryAllocator) {
          // We were not given a memory allocator at creation
          memoryAllocator = GrD3DAMDMemoryAllocator::Make(
                  backendContext.fAdapter.get(), backendContext.fDevice.get());
      }
      if (!memoryAllocator) {
          SkDEBUGFAIL("No supplied Direct3D memory allocator and unable to create one internally.");
          return nullptr;
      }
  
      return sk_sp<GrGpu>(new GrD3DGpu(direct, contextOptions, backendContext, memoryAllocator));
  }
  
  // This constant determines how many OutstandingCommandLists are allocated together as a block in
  // the deque. As such it needs to balance allocating too much memory vs. incurring
  // allocation/deallocation thrashing. It should roughly correspond to the max number of outstanding
  // command lists we expect to see.
  static const int kDefaultOutstandingAllocCnt = 8;
  
  // constants have to be aligned to 256
  constexpr int kConstantAlignment = 256;
  
  GrD3DGpu::GrD3DGpu(GrDirectContext* direct, const GrContextOptions& contextOptions,
                     const GrD3DBackendContext& backendContext,
                     sk_sp<GrD3DMemoryAllocator> allocator)
          : INHERITED(direct)
          , fDevice(backendContext.fDevice)
          , fQueue(backendContext.fQueue)
          , fMemoryAllocator(std::move(allocator))
          , fResourceProvider(this)
          , fStagingBufferManager(this)
          , fConstantsRingBuffer(this, 128 * 1024, kConstantAlignment, GrGpuBufferType::kVertex)
          , fOutstandingCommandLists(sizeof(OutstandingCommandList), kDefaultOutstandingAllocCnt) {
      this->initCapsAndCompiler(sk_make_sp<GrD3DCaps>(contextOptions,
                                                      backendContext.fAdapter.get(),
                                                      backendContext.fDevice.get()));
  
      fCurrentDirectCommandList = fResourceProvider.findOrCreateDirectCommandList();
      SkASSERT(fCurrentDirectCommandList);
  
      SkASSERT(fCurrentFenceValue == 0);
      GR_D3D_CALL_ERRCHECK(fDevice->CreateFence(fCurrentFenceValue, D3D12_FENCE_FLAG_NONE,
                                                IID_PPV_ARGS(&fFence)));
  
  #if GR_TEST_UTILS
      HRESULT getAnalysis = DXGIGetDebugInterface1(0, IID_PPV_ARGS(&fGraphicsAnalysis));
      if (FAILED(getAnalysis)) {
          fGraphicsAnalysis = nullptr;
      }
  #endif
  }
  
  GrD3DGpu::~GrD3DGpu() {
      this->destroyResources();
  }
  
  void GrD3DGpu::destroyResources() {
      if (fCurrentDirectCommandList) {
          fCurrentDirectCommandList->close();
          fCurrentDirectCommandList->reset();
      }
  
      // We need to make sure everything has finished on the queue.
      this->waitForQueueCompletion();
  
      SkDEBUGCODE(uint64_t fenceValue = fFence->GetCompletedValue();)
  
      // We used a placement new for each object in fOutstandingCommandLists, so we're responsible
      // for calling the destructor on each of them as well.
      while (!fOutstandingCommandLists.empty()) {
          OutstandingCommandList* list = (OutstandingCommandList*)fOutstandingCommandLists.front();
          SkASSERT(list->fFenceValue <= fenceValue);
          // No reason to recycle the command lists since we are destroying all resources anyways.
          list->~OutstandingCommandList();
          fOutstandingCommandLists.pop_front();
      }
  
      fStagingBufferManager.reset();
  
      fResourceProvider.destroyResources();
  }
  
  GrOpsRenderPass* GrD3DGpu::onGetOpsRenderPass(
          GrRenderTarget* rt,
          bool /*useMSAASurface*/,
          GrAttachment*,
          GrSurfaceOrigin origin,
          const SkIRect& bounds,
          const GrOpsRenderPass::LoadAndStoreInfo& colorInfo,
          const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilInfo,
          const SkTArray<GrSurfaceProxy*, true>& sampledProxies,
          GrXferBarrierFlags renderPassXferBarriers) {
      if (!fCachedOpsRenderPass) {
          fCachedOpsRenderPass.reset(new GrD3DOpsRenderPass(this));
      }
  
      if (!fCachedOpsRenderPass->set(rt, origin, bounds, colorInfo, stencilInfo, sampledProxies)) {
          return nullptr;
      }
      return fCachedOpsRenderPass.get();
  }
  
  bool GrD3DGpu::submitDirectCommandList(SyncQueue sync) {
      SkASSERT(fCurrentDirectCommandList);
  
      fResourceProvider.prepForSubmit();
  
      GrD3DDirectCommandList::SubmitResult result = fCurrentDirectCommandList->submit(fQueue.get());
      if (result == GrD3DDirectCommandList::SubmitResult::kFailure) {
          return false;
      } else if (result == GrD3DDirectCommandList::SubmitResult::kNoWork) {
          if (sync == SyncQueue::kForce) {
              this->waitForQueueCompletion();
              this->checkForFinishedCommandLists();
          }
          return true;
      }
  
      // We just submitted the command list so make sure all GrD3DPipelineState's mark their cached
      // uniform data as dirty.
      fResourceProvider.markPipelineStateUniformsDirty();
  
      GrFence fence = this->insertFence();
      new (fOutstandingCommandLists.push_back()) OutstandingCommandList(
              std::move(fCurrentDirectCommandList), fence);
  
      if (sync == SyncQueue::kForce) {
          this->waitForQueueCompletion();
      }
  
      fCurrentDirectCommandList = fResourceProvider.findOrCreateDirectCommandList();
  
      // This should be done after we have a new command list in case the freeing of any resources
      // held by a finished command list causes us send a new command to the gpu (like changing the
      // resource state.
      this->checkForFinishedCommandLists();
  
      SkASSERT(fCurrentDirectCommandList);
      return true;
  }
  
  void GrD3DGpu::checkForFinishedCommandLists() {
      uint64_t currentFenceValue = fFence->GetCompletedValue();
  
      // Iterate over all the outstanding command lists to see if any have finished. The commands
      // lists are in order from oldest to newest, so we start at the front to check if their fence
      // value is less than the last signaled value. If so we pop it off and move onto the next.
      // Repeat till we find a command list that has not finished yet (and all others afterwards are
      // also guaranteed to not have finished).
      OutstandingCommandList* front = (OutstandingCommandList*)fOutstandingCommandLists.front();
      while (front && front->fFenceValue <= currentFenceValue) {
          std::unique_ptr<GrD3DDirectCommandList> currList(std::move(front->fCommandList));
          // Since we used placement new we are responsible for calling the destructor manually.
          front->~OutstandingCommandList();
          fOutstandingCommandLists.pop_front();
          fResourceProvider.recycleDirectCommandList(std::move(currList));
          front = (OutstandingCommandList*)fOutstandingCommandLists.front();
      }
  }
  
  void GrD3DGpu::waitForQueueCompletion() {
      if (fFence->GetCompletedValue() < fCurrentFenceValue) {
          HANDLE fenceEvent;
          fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
          SkASSERT(fenceEvent);
          GR_D3D_CALL_ERRCHECK(fFence->SetEventOnCompletion(fCurrentFenceValue, fenceEvent));
          WaitForSingleObject(fenceEvent, INFINITE);
          CloseHandle(fenceEvent);
      }
  }
  
  void GrD3DGpu::submit(GrOpsRenderPass* renderPass) {
      SkASSERT(fCachedOpsRenderPass.get() == renderPass);
  
      // TODO: actually submit something here
      fCachedOpsRenderPass.reset();
  }
  
  void GrD3DGpu::addFinishedProc(GrGpuFinishedProc finishedProc,
                                 GrGpuFinishedContext finishedContext) {
      SkASSERT(finishedProc);
      this->addFinishedCallback(GrRefCntedCallback::Make(finishedProc, finishedContext));
  }
  
  void GrD3DGpu::addFinishedCallback(sk_sp<GrRefCntedCallback> finishedCallback) {
      SkASSERT(finishedCallback);
      // Besides the current command list, we also add the finishedCallback to the newest outstanding
      // command list. Our contract for calling the proc is that all previous submitted command lists
      // have finished when we call it. However, if our current command list has no work when it is
      // flushed it will drop its ref to the callback immediately. But the previous work may not have
      // finished. It is safe to only add the proc to the newest outstanding commandlist cause that
      // must finish after all previously submitted command lists.
      OutstandingCommandList* back = (OutstandingCommandList*)fOutstandingCommandLists.back();
      if (back) {
          back->fCommandList->addFinishedCallback(finishedCallback);
      }
      fCurrentDirectCommandList->addFinishedCallback(std::move(finishedCallback));
  }
  
  sk_sp<GrD3DTexture> GrD3DGpu::createD3DTexture(SkISize dimensions,
                                                 DXGI_FORMAT dxgiFormat,
                                                 GrRenderable renderable,
                                                 int renderTargetSampleCnt,
                                                 SkBudgeted budgeted,
                                                 GrProtected isProtected,
                                                 int mipLevelCount,
                                                 GrMipmapStatus mipmapStatus) {
      D3D12_RESOURCE_FLAGS usageFlags = D3D12_RESOURCE_FLAG_NONE;
      if (renderable == GrRenderable::kYes) {
          usageFlags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
      }
  
      // This desc refers to a texture that will be read by the client. Thus even if msaa is
      // requested, this describes the resolved texture. Therefore we always have samples set
      // to 1.
      SkASSERT(mipLevelCount > 0);
      D3D12_RESOURCE_DESC resourceDesc = {};
      resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
      // TODO: will use 4MB alignment for MSAA textures and 64KB for everything else
      //       might want to manually set alignment to 4KB for smaller textures
      resourceDesc.Alignment = 0;
      resourceDesc.Width = dimensions.fWidth;
      resourceDesc.Height = dimensions.fHeight;
      resourceDesc.DepthOrArraySize = 1;
      resourceDesc.MipLevels = mipLevelCount;
      resourceDesc.Format = dxgiFormat;
      resourceDesc.SampleDesc.Count = 1;
      resourceDesc.SampleDesc.Quality = DXGI_STANDARD_MULTISAMPLE_QUALITY_PATTERN;
      resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;  // use driver-selected swizzle
      resourceDesc.Flags = usageFlags;
  
      if (renderable == GrRenderable::kYes) {
          return GrD3DTextureRenderTarget::MakeNewTextureRenderTarget(
                  this, budgeted, dimensions, renderTargetSampleCnt, resourceDesc, isProtected,
                  mipmapStatus);
      } else {
          return GrD3DTexture::MakeNewTexture(this, budgeted, dimensions, resourceDesc, isProtected,
                                              mipmapStatus);
      }
  }
  
  sk_sp<GrTexture> GrD3DGpu::onCreateTexture(SkISize dimensions,
                                             const GrBackendFormat& format,
                                             GrRenderable renderable,
                                             int renderTargetSampleCnt,
                                             SkBudgeted budgeted,
                                             GrProtected isProtected,
                                             int mipLevelCount,
                                             uint32_t levelClearMask) {
      DXGI_FORMAT dxgiFormat;
      SkAssertResult(format.asDxgiFormat(&dxgiFormat));
      SkASSERT(!GrDxgiFormatIsCompressed(dxgiFormat));
  
      GrMipmapStatus mipmapStatus = mipLevelCount > 1 ? GrMipmapStatus::kDirty
                                                      : GrMipmapStatus::kNotAllocated;
  
      sk_sp<GrD3DTexture> tex = this->createD3DTexture(dimensions, dxgiFormat, renderable,
                                                       renderTargetSampleCnt, budgeted, isProtected,
                                                       mipLevelCount, mipmapStatus);
      if (!tex) {
          return nullptr;
      }
  
      if (levelClearMask) {
          // TODO
      }
  
      return std::move(tex);
  }
  
  static void copy_compressed_data(char* mapPtr, DXGI_FORMAT dxgiFormat,
                                   D3D12_PLACED_SUBRESOURCE_FOOTPRINT* placedFootprints,
                                   UINT* numRows, UINT64* rowSizeInBytes,
                                   const void* compressedData, int numMipLevels) {
      SkASSERT(compressedData && numMipLevels);
      SkASSERT(GrDxgiFormatIsCompressed(dxgiFormat));
      SkASSERT(mapPtr);
  
      const char* src = static_cast<const char*>(compressedData);
      for (int currentMipLevel = 0; currentMipLevel < numMipLevels; currentMipLevel++) {
          // copy data into the buffer, skipping any trailing bytes
          char* dst = mapPtr + placedFootprints[currentMipLevel].Offset;
          SkRectMemcpy(dst, placedFootprints[currentMipLevel].Footprint.RowPitch,
                       src, rowSizeInBytes[currentMipLevel], rowSizeInBytes[currentMipLevel],
                       numRows[currentMipLevel]);
          src += numRows[currentMipLevel] * rowSizeInBytes[currentMipLevel];
      }
  }
  
  sk_sp<GrTexture> GrD3DGpu::onCreateCompressedTexture(SkISize dimensions,
                                                       const GrBackendFormat& format,
                                                       SkBudgeted budgeted,
                                                       GrMipmapped mipMapped,
                                                       GrProtected isProtected,
                                                       const void* data, size_t dataSize) {
      DXGI_FORMAT dxgiFormat;
      SkAssertResult(format.asDxgiFormat(&dxgiFormat));
      SkASSERT(GrDxgiFormatIsCompressed(dxgiFormat));
  
      SkDEBUGCODE(SkImage::CompressionType compression = GrBackendFormatToCompressionType(format));
      SkASSERT(dataSize == SkCompressedFormatDataSize(compression, dimensions,
                                                      mipMapped == GrMipmapped::kYes));
  
      int mipLevelCount = 1;
      if (mipMapped == GrMipmapped::kYes) {
          mipLevelCount = SkMipmap::ComputeLevelCount(dimensions.width(), dimensions.height()) + 1;
      }
      GrMipmapStatus mipmapStatus = mipLevelCount > 1 ? GrMipmapStatus::kValid
                                                      : GrMipmapStatus::kNotAllocated;
  
      sk_sp<GrD3DTexture> d3dTex = this->createD3DTexture(dimensions, dxgiFormat, GrRenderable::kNo,
                                                       1, budgeted, isProtected,
                                                       mipLevelCount, mipmapStatus);
      if (!d3dTex) {
          return nullptr;
      }
  
      ID3D12Resource* d3dResource = d3dTex->d3dResource();
      SkASSERT(d3dResource);
      D3D12_RESOURCE_DESC desc = d3dResource->GetDesc();
      // Either upload only the first miplevel or all miplevels
      SkASSERT(1 == mipLevelCount || mipLevelCount == (int)desc.MipLevels);
  
      SkAutoTMalloc<D3D12_PLACED_SUBRESOURCE_FOOTPRINT> placedFootprints(mipLevelCount);
      SkAutoTMalloc<UINT> numRows(mipLevelCount);
      SkAutoTMalloc<UINT64> rowSizeInBytes(mipLevelCount);
      UINT64 combinedBufferSize;
      // We reset the width and height in the description to match our subrectangle size
      // so we don't end up allocating more space than we need.
      desc.Width = dimensions.width();
      desc.Height = dimensions.height();
      fDevice->GetCopyableFootprints(&desc, 0, mipLevelCount, 0, placedFootprints.get(),
                                     numRows.get(), rowSizeInBytes.get(), &combinedBufferSize);
      SkASSERT(combinedBufferSize);
  
      GrStagingBufferManager::Slice slice = fStagingBufferManager.allocateStagingBufferSlice(
              combinedBufferSize, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT);
      if (!slice.fBuffer) {
          return false;
      }
  
      char* bufferData = (char*)slice.fOffsetMapPtr;
  
      copy_compressed_data(bufferData, desc.Format, placedFootprints.get(), numRows.get(),
                           rowSizeInBytes.get(), data, mipLevelCount);
  
      // Update the offsets in the footprints to be relative to the slice's offset
      for (int i = 0; i < mipLevelCount; ++i) {
          placedFootprints[i].Offset += slice.fOffset;
      }
  
      ID3D12Resource* d3dBuffer = static_cast<GrD3DBuffer*>(slice.fBuffer)->d3dResource();
      fCurrentDirectCommandList->copyBufferToTexture(d3dBuffer, d3dTex.get(), mipLevelCount,
                                                     placedFootprints.get(), 0, 0);
  
      return std::move(d3dTex);
  }
  
  static int get_surface_sample_cnt(GrSurface* surf) {
      if (const GrRenderTarget* rt = surf->asRenderTarget()) {
          return rt->numSamples();
      }
      return 0;
  }
  
  bool GrD3DGpu::onCopySurface(GrSurface* dst, GrSurface* src, const SkIRect& srcRect,
                     const SkIPoint& dstPoint) {
  
      if (src->isProtected() && !dst->isProtected()) {
          SkDebugf("Can't copy from protected memory to non-protected");
          return false;
      }
  
      int dstSampleCnt = get_surface_sample_cnt(dst);
      int srcSampleCnt = get_surface_sample_cnt(src);
  
      GrD3DTextureResource* dstTexResource;
      GrD3DTextureResource* srcTexResource;
      GrRenderTarget* dstRT = dst->asRenderTarget();
      if (dstRT) {
          GrD3DRenderTarget* d3dRT = static_cast<GrD3DRenderTarget*>(dstRT);
          dstTexResource = d3dRT->numSamples() > 1 ? d3dRT->msaaTextureResource() : d3dRT;
      } else {
          SkASSERT(dst->asTexture());
          dstTexResource = static_cast<GrD3DTexture*>(dst->asTexture());
      }
      GrRenderTarget* srcRT = src->asRenderTarget();
      if (srcRT) {
          GrD3DRenderTarget* d3dRT = static_cast<GrD3DRenderTarget*>(srcRT);
          srcTexResource = d3dRT->numSamples() > 1 ? d3dRT->msaaTextureResource() : d3dRT;
      } else {
          SkASSERT(src->asTexture());
          srcTexResource = static_cast<GrD3DTexture*>(src->asTexture());
      }
  
      DXGI_FORMAT dstFormat = dstTexResource->dxgiFormat();
      DXGI_FORMAT srcFormat = srcTexResource->dxgiFormat();
  
      if (this->d3dCaps().canCopyAsResolve(dstFormat, dstSampleCnt, srcFormat, srcSampleCnt)) {
          this->copySurfaceAsResolve(dst, src, srcRect, dstPoint);
          return true;
      }
  
      if (this->d3dCaps().canCopyTexture(dstFormat, dstSampleCnt, srcFormat, srcSampleCnt)) {
          this->copySurfaceAsCopyTexture(dst, src, dstTexResource, srcTexResource, srcRect, dstPoint);
          return true;
      }
  
      return false;
  }
  
  void GrD3DGpu::copySurfaceAsCopyTexture(GrSurface* dst, GrSurface* src,
                                          GrD3DTextureResource* dstResource,
                                          GrD3DTextureResource* srcResource,
                                          const SkIRect& srcRect, const SkIPoint& dstPoint) {
  #ifdef SK_DEBUG
      int dstSampleCnt = get_surface_sample_cnt(dst);
      int srcSampleCnt = get_surface_sample_cnt(src);
      DXGI_FORMAT dstFormat = dstResource->dxgiFormat();
      DXGI_FORMAT srcFormat;
      SkAssertResult(dst->backendFormat().asDxgiFormat(&srcFormat));
      SkASSERT(this->d3dCaps().canCopyTexture(dstFormat, dstSampleCnt, srcFormat, srcSampleCnt));
  #endif
      if (src->isProtected() && !dst->isProtected()) {
          SkDebugf("Can't copy from protected memory to non-protected");
          return;
      }
  
      dstResource->setResourceState(this, D3D12_RESOURCE_STATE_COPY_DEST);
      srcResource->setResourceState(this, D3D12_RESOURCE_STATE_COPY_SOURCE);
  
      D3D12_TEXTURE_COPY_LOCATION dstLocation = {};
      dstLocation.pResource = dstResource->d3dResource();
      dstLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
      dstLocation.SubresourceIndex = 0;
  
      D3D12_TEXTURE_COPY_LOCATION srcLocation = {};
      srcLocation.pResource = srcResource->d3dResource();
      srcLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
      srcLocation.SubresourceIndex = 0;
  
      D3D12_BOX srcBox = {};
      srcBox.left = srcRect.fLeft;
      srcBox.top = srcRect.fTop;
      srcBox.right = srcRect.fRight;
      srcBox.bottom = srcRect.fBottom;
      srcBox.front = 0;
      srcBox.back = 1;
      // TODO: use copyResource if copying full resource and sizes match
      fCurrentDirectCommandList->copyTextureRegionToTexture(dstResource->resource(),
                                                            &dstLocation,
                                                            dstPoint.fX, dstPoint.fY,
                                                            srcResource->resource(),
                                                            &srcLocation,
                                                            &srcBox);
  
      SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.fX, dstPoint.fY,
                                          srcRect.width(), srcRect.height());
      // The rect is already in device space so we pass in kTopLeft so no flip is done.
      this->didWriteToSurface(dst, kTopLeft_GrSurfaceOrigin, &dstRect);
  }
  
  void GrD3DGpu::copySurfaceAsResolve(GrSurface* dst, GrSurface* src, const SkIRect& srcRect,
                                      const SkIPoint& dstPoint) {
      GrD3DRenderTarget* srcRT = static_cast<GrD3DRenderTarget*>(src->asRenderTarget());
      SkASSERT(srcRT);
  
      this->resolveTexture(dst, dstPoint.fX, dstPoint.fY, srcRT, srcRect);
  }
  
  void GrD3DGpu::resolveTexture(GrSurface* dst, int32_t dstX, int32_t dstY,
                                GrD3DRenderTarget* src, const SkIRect& srcIRect) {
      SkASSERT(dst);
      SkASSERT(src && src->numSamples() > 1 && src->msaaTextureResource());
  
      D3D12_RECT srcRect = { srcIRect.fLeft, srcIRect.fTop, srcIRect.fRight, srcIRect.fBottom };
  
      GrD3DTextureResource* dstTextureResource;
      GrRenderTarget* dstRT = dst->asRenderTarget();
      if (dstRT) {
          dstTextureResource = static_cast<GrD3DRenderTarget*>(dstRT);
      } else {
          SkASSERT(dst->asTexture());
          dstTextureResource = static_cast<GrD3DTexture*>(dst->asTexture());
      }
  
      dstTextureResource->setResourceState(this, D3D12_RESOURCE_STATE_RESOLVE_DEST);
      src->msaaTextureResource()->setResourceState(this, D3D12_RESOURCE_STATE_RESOLVE_SOURCE);
  
      fCurrentDirectCommandList->resolveSubresourceRegion(dstTextureResource, dstX, dstY,
                                                          src->msaaTextureResource(), &srcRect);
  }
  
  void GrD3DGpu::onResolveRenderTarget(GrRenderTarget* target, const SkIRect& resolveRect) {
      SkASSERT(target->numSamples() > 1);
      GrD3DRenderTarget* rt = static_cast<GrD3DRenderTarget*>(target);
      SkASSERT(rt->msaaTextureResource() && rt != rt->msaaTextureResource());
  
      this->resolveTexture(target, resolveRect.fLeft, resolveRect.fTop, rt, resolveRect);
  }
  
  bool GrD3DGpu::onReadPixels(GrSurface* surface, int left, int top, int width, int height,
                              GrColorType surfaceColorType, GrColorType dstColorType, void* buffer,
                              size_t rowBytes) {
      SkASSERT(surface);
  
      if (surfaceColorType != dstColorType) {
          return false;
      }
  
      // Set up src location and box
      GrD3DTextureResource* texResource = nullptr;
      GrD3DRenderTarget* rt = static_cast<GrD3DRenderTarget*>(surface->asRenderTarget());
      if (rt) {
          texResource = rt;
      } else {
          texResource = static_cast<GrD3DTexture*>(surface->asTexture());
      }
  
      if (!texResource) {
          return false;
      }
  
      D3D12_TEXTURE_COPY_LOCATION srcLocation = {};
      srcLocation.pResource = texResource->d3dResource();
      SkASSERT(srcLocation.pResource);
      srcLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
      srcLocation.SubresourceIndex = 0;
  
      D3D12_BOX srcBox = {};
      srcBox.left = left;
      srcBox.top = top;
      srcBox.right = left + width;
      srcBox.bottom = top + height;
      srcBox.front = 0;
      srcBox.back = 1;
  
      // Set up dst location and create transfer buffer
      D3D12_TEXTURE_COPY_LOCATION dstLocation = {};
      dstLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
      UINT64 transferTotalBytes;
      const UINT64 baseOffset = 0;
      D3D12_RESOURCE_DESC desc = srcLocation.pResource->GetDesc();
      fDevice->GetCopyableFootprints(&desc, 0, 1, baseOffset, &dstLocation.PlacedFootprint,
                                     nullptr, nullptr, &transferTotalBytes);
      SkASSERT(transferTotalBytes);
      size_t bpp = GrColorTypeBytesPerPixel(dstColorType);
      if (GrDxgiFormatBytesPerBlock(texResource->dxgiFormat()) != bpp) {
          return false;
      }
      size_t tightRowBytes = bpp * width;
  
      // TODO: implement some way of reusing buffers instead of making a new one every time.
      sk_sp<GrGpuBuffer> transferBuffer = this->createBuffer(transferTotalBytes,
                                                             GrGpuBufferType::kXferGpuToCpu,
                                                             kDynamic_GrAccessPattern);
      GrD3DBuffer* d3dBuf = static_cast<GrD3DBuffer*>(transferBuffer.get());
      dstLocation.pResource = d3dBuf->d3dResource();
  
      // Need to change the resource state to COPY_SOURCE in order to download from it
      texResource->setResourceState(this, D3D12_RESOURCE_STATE_COPY_SOURCE);
  
      fCurrentDirectCommandList->copyTextureRegionToBuffer(transferBuffer, &dstLocation, 0, 0,
                                                           texResource->resource(), &srcLocation,
                                                           &srcBox);
      this->submitDirectCommandList(SyncQueue::kForce);
  
      const void* mappedMemory = transferBuffer->map();
  
      SkRectMemcpy(buffer, rowBytes, mappedMemory, dstLocation.PlacedFootprint.Footprint.RowPitch,
                   tightRowBytes, height);
  
      transferBuffer->unmap();
  
      return true;
  }
  
  bool GrD3DGpu::onWritePixels(GrSurface* surface, int left, int top, int width, int height,
                               GrColorType surfaceColorType, GrColorType srcColorType,
                               const GrMipLevel texels[], int mipLevelCount,
                               bool prepForTexSampling) {
      GrD3DTexture* d3dTex = static_cast<GrD3DTexture*>(surface->asTexture());
      if (!d3dTex) {
          return false;
      }
  
      // Make sure we have at least the base level
      if (!mipLevelCount || !texels[0].fPixels) {
          return false;
      }
  
      SkASSERT(!GrDxgiFormatIsCompressed(d3dTex->dxgiFormat()));
      bool success = false;
  
      // Need to change the resource state to COPY_DEST in order to upload to it
      d3dTex->setResourceState(this, D3D12_RESOURCE_STATE_COPY_DEST);
  
      SkASSERT(mipLevelCount <= d3dTex->maxMipmapLevel() + 1);
      success = this->uploadToTexture(d3dTex, left, top, width, height, srcColorType, texels,
                                      mipLevelCount);
  
      if (prepForTexSampling) {
          d3dTex->setResourceState(this, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
      }
  
      return success;
  }
  
  bool GrD3DGpu::uploadToTexture(GrD3DTexture* tex, int left, int top, int width, int height,
                                 GrColorType colorType, const GrMipLevel* texels, int mipLevelCount) {
      SkASSERT(this->caps()->isFormatTexturable(tex->backendFormat()));
      // The assumption is either that we have no mipmaps, or that our rect is the entire texture
      SkASSERT(1 == mipLevelCount ||
               (0 == left && 0 == top && width == tex->width() && height == tex->height()));
  
      // We assume that if the texture has mip levels, we either upload to all the levels or just the
      // first.
      SkASSERT(1 == mipLevelCount || mipLevelCount == (tex->maxMipmapLevel() + 1));
  
      if (width == 0 || height == 0) {
          return false;
      }
  
      SkASSERT(this->d3dCaps().surfaceSupportsWritePixels(tex));
      SkASSERT(this->d3dCaps().areColorTypeAndFormatCompatible(colorType, tex->backendFormat()));
  
      ID3D12Resource* d3dResource = tex->d3dResource();
      SkASSERT(d3dResource);
      D3D12_RESOURCE_DESC desc = d3dResource->GetDesc();
      // Either upload only the first miplevel or all miplevels
      SkASSERT(1 == mipLevelCount || mipLevelCount == (int)desc.MipLevels);
  
      if (1 == mipLevelCount && !texels[0].fPixels) {
          return true;   // no data to upload
      }
  
      for (int i = 0; i < mipLevelCount; ++i) {
          // We do not allow any gaps in the mip data
          if (!texels[i].fPixels) {
              return false;
          }
      }
  
      SkAutoTMalloc<D3D12_PLACED_SUBRESOURCE_FOOTPRINT> placedFootprints(mipLevelCount);
      UINT64 combinedBufferSize;
      // We reset the width and height in the description to match our subrectangle size
      // so we don't end up allocating more space than we need.
      desc.Width = width;
      desc.Height = height;
      fDevice->GetCopyableFootprints(&desc, 0, mipLevelCount, 0, placedFootprints.get(),
                                     nullptr, nullptr, &combinedBufferSize);
      size_t bpp = GrColorTypeBytesPerPixel(colorType);
      SkASSERT(combinedBufferSize);
  
      GrStagingBufferManager::Slice slice = fStagingBufferManager.allocateStagingBufferSlice(
              combinedBufferSize, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT);
      if (!slice.fBuffer) {
          return false;
      }
  
      char* bufferData = (char*)slice.fOffsetMapPtr;
  
      int currentWidth = width;
      int currentHeight = height;
      int layerHeight = tex->height();
  
      for (int currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) {
          if (texels[currentMipLevel].fPixels) {
              SkASSERT(1 == mipLevelCount || currentHeight == layerHeight);
  
              const size_t trimRowBytes = currentWidth * bpp;
              const size_t srcRowBytes = texels[currentMipLevel].fRowBytes;
  
              char* dst = bufferData + placedFootprints[currentMipLevel].Offset;
  
              // copy data into the buffer, skipping any trailing bytes
              const char* src = (const char*)texels[currentMipLevel].fPixels;
              SkRectMemcpy(dst, placedFootprints[currentMipLevel].Footprint.RowPitch,
                           src, srcRowBytes, trimRowBytes, currentHeight);
          }
          currentWidth = std::max(1, currentWidth / 2);
          currentHeight = std::max(1, currentHeight / 2);
          layerHeight = currentHeight;
      }
  
      // Update the offsets in the footprints to be relative to the slice's offset
      for (int i = 0; i < mipLevelCount; ++i) {
          placedFootprints[i].Offset += slice.fOffset;
      }
  
      ID3D12Resource* d3dBuffer = static_cast<GrD3DBuffer*>(slice.fBuffer)->d3dResource();
      fCurrentDirectCommandList->copyBufferToTexture(d3dBuffer, tex, mipLevelCount,
                                                     placedFootprints.get(), left, top);
  
      if (mipLevelCount < (int)desc.MipLevels) {
          tex->markMipmapsDirty();
      }
  
      return true;
  }
  
  static bool check_resource_info(const GrD3DTextureResourceInfo& info) {
      if (!info.fResource.get()) {
          return false;
      }
      return true;
  }
  
  static bool check_tex_resource_info(const GrD3DCaps& caps, const GrD3DTextureResourceInfo& info) {
      if (!caps.isFormatTexturable(info.fFormat)) {
          return false;
      }
      // We don't support sampling from multisampled textures.
      if (info.fSampleCount != 1) {
          return false;
      }
      return true;
  }
  
  static bool check_rt_resource_info(const GrD3DCaps& caps, const GrD3DTextureResourceInfo& info,
                                  int sampleCnt) {
      if (!caps.isFormatRenderable(info.fFormat, sampleCnt)) {
          return false;
      }
      return true;
  }
  
  sk_sp<GrTexture> GrD3DGpu::onWrapBackendTexture(const GrBackendTexture& tex,
                                                  GrWrapOwnership,
                                                  GrWrapCacheable wrapType,
                                                  GrIOType ioType) {
      GrD3DTextureResourceInfo textureInfo;
      if (!tex.getD3DTextureResourceInfo(&textureInfo)) {
          return nullptr;
      }
  
      if (!check_resource_info(textureInfo)) {
          return nullptr;
      }
  
      if (!check_tex_resource_info(this->d3dCaps(), textureInfo)) {
          return nullptr;
      }
  
      // TODO: support protected context
      if (tex.isProtected()) {
          return nullptr;
      }
  
      sk_sp<GrD3DResourceState> state = tex.getGrD3DResourceState();
      SkASSERT(state);
      return GrD3DTexture::MakeWrappedTexture(this, tex.dimensions(), wrapType, ioType, textureInfo,
                                              std::move(state));
  }
  
  sk_sp<GrTexture> GrD3DGpu::onWrapCompressedBackendTexture(const GrBackendTexture& tex,
                                                            GrWrapOwnership ownership,
                                                            GrWrapCacheable wrapType) {
      return this->onWrapBackendTexture(tex, ownership, wrapType, kRead_GrIOType);
  }
  
  sk_sp<GrTexture> GrD3DGpu::onWrapRenderableBackendTexture(const GrBackendTexture& tex,
                                                            int sampleCnt,
                                                            GrWrapOwnership ownership,
                                                            GrWrapCacheable cacheable) {
      GrD3DTextureResourceInfo textureInfo;
      if (!tex.getD3DTextureResourceInfo(&textureInfo)) {
          return nullptr;
      }
  
      if (!check_resource_info(textureInfo)) {
          return nullptr;
      }
  
      if (!check_tex_resource_info(this->d3dCaps(), textureInfo)) {
          return nullptr;
      }
      if (!check_rt_resource_info(this->d3dCaps(), textureInfo, sampleCnt)) {
          return nullptr;
      }
  
      // TODO: support protected context
      if (tex.isProtected()) {
          return nullptr;
      }
  
      sampleCnt = this->d3dCaps().getRenderTargetSampleCount(sampleCnt, textureInfo.fFormat);
  
      sk_sp<GrD3DResourceState> state = tex.getGrD3DResourceState();
      SkASSERT(state);
  
      return GrD3DTextureRenderTarget::MakeWrappedTextureRenderTarget(this, tex.dimensions(),
                                                                      sampleCnt, cacheable,
                                                                      textureInfo, std::move(state));
  }
  
  sk_sp<GrRenderTarget> GrD3DGpu::onWrapBackendRenderTarget(const GrBackendRenderTarget& rt) {
      GrD3DTextureResourceInfo info;
      if (!rt.getD3DTextureResourceInfo(&info)) {
          return nullptr;
      }
  
      if (!check_resource_info(info)) {
          return nullptr;
      }
  
      if (!check_rt_resource_info(this->d3dCaps(), info, rt.sampleCnt())) {
          return nullptr;
      }
  
      // TODO: support protected context
      if (rt.isProtected()) {
          return nullptr;
      }
  
      sk_sp<GrD3DResourceState> state = rt.getGrD3DResourceState();
  
      sk_sp<GrD3DRenderTarget> tgt = GrD3DRenderTarget::MakeWrappedRenderTarget(
              this, rt.dimensions(), rt.sampleCnt(), info, std::move(state));
  
      // We don't allow the client to supply a premade stencil buffer. We always create one if needed.
      SkASSERT(!rt.stencilBits());
      if (tgt) {
          SkASSERT(tgt->canAttemptStencilAttachment(tgt->numSamples() > 1));
      }
  
      return std::move(tgt);
  }
  
  sk_sp<GrGpuBuffer> GrD3DGpu::onCreateBuffer(size_t sizeInBytes, GrGpuBufferType type,
                                               GrAccessPattern accessPattern, const void* data) {
      sk_sp<GrD3DBuffer> buffer = GrD3DBuffer::Make(this, sizeInBytes, type, accessPattern);
      if (data && buffer) {
          buffer->updateData(data, sizeInBytes);
      }
  
      return std::move(buffer);
  }
  
  sk_sp<GrAttachment> GrD3DGpu::makeStencilAttachment(const GrBackendFormat& /*colorFormat*/,
                                                      SkISize dimensions, int numStencilSamples) {
      DXGI_FORMAT sFmt = this->d3dCaps().preferredStencilFormat();
  
      fStats.incStencilAttachmentCreates();
      return GrD3DAttachment::MakeStencil(this, dimensions, numStencilSamples, sFmt);
  }
  
  bool GrD3DGpu::createTextureResourceForBackendSurface(DXGI_FORMAT dxgiFormat,
                                                        SkISize dimensions,
                                                        GrTexturable texturable,
                                                        GrRenderable renderable,
                                                        GrMipmapped mipMapped,
                                                        int sampleCnt,
                                                        GrD3DTextureResourceInfo* info,
                                                        GrProtected isProtected) {
      SkASSERT(texturable == GrTexturable::kYes || renderable == GrRenderable::kYes);
  
      if (this->protectedContext() != (isProtected == GrProtected::kYes)) {
          return false;
      }
  
      if (texturable == GrTexturable::kYes && !this->d3dCaps().isFormatTexturable(dxgiFormat)) {
          return false;
      }
  
      if (renderable == GrRenderable::kYes && !this->d3dCaps().isFormatRenderable(dxgiFormat, 1)) {
          return false;
      }
  
      int numMipLevels = 1;
      if (mipMapped == GrMipmapped::kYes) {
          numMipLevels = SkMipmap::ComputeLevelCount(dimensions.width(), dimensions.height()) + 1;
      }
  
      // create the texture
      D3D12_RESOURCE_FLAGS usageFlags = D3D12_RESOURCE_FLAG_NONE;
      if (renderable == GrRenderable::kYes) {
          usageFlags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
      }
  
      D3D12_RESOURCE_DESC resourceDesc = {};
      resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
      resourceDesc.Alignment = 0;  // use default alignment
      resourceDesc.Width = dimensions.fWidth;
      resourceDesc.Height = dimensions.fHeight;
      resourceDesc.DepthOrArraySize = 1;
      resourceDesc.MipLevels = numMipLevels;
      resourceDesc.Format = dxgiFormat;
      resourceDesc.SampleDesc.Count = sampleCnt;
      resourceDesc.SampleDesc.Quality = DXGI_STANDARD_MULTISAMPLE_QUALITY_PATTERN;
      resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;  // use driver-selected swizzle
      resourceDesc.Flags = usageFlags;
  
      D3D12_CLEAR_VALUE* clearValuePtr = nullptr;
      D3D12_CLEAR_VALUE clearValue = {};
      if (renderable == GrRenderable::kYes) {
          clearValue.Format = dxgiFormat;
          // Assume transparent black
          clearValue.Color[0] = 0;
          clearValue.Color[1] = 0;
          clearValue.Color[2] = 0;
          clearValue.Color[3] = 0;
          clearValuePtr = &clearValue;
      }
  
      D3D12_RESOURCE_STATES initialState = (renderable == GrRenderable::kYes)
                                                   ? D3D12_RESOURCE_STATE_RENDER_TARGET
                                                   : D3D12_RESOURCE_STATE_COPY_DEST;
      if (!GrD3DTextureResource::InitTextureResourceInfo(this, resourceDesc, initialState,
                                                         isProtected, clearValuePtr, info)) {
          SkDebugf("Failed to init texture resource info\n");
          return false;
      }
  
      return true;
  }
  
  GrBackendTexture GrD3DGpu::onCreateBackendTexture(SkISize dimensions,
                                                    const GrBackendFormat& format,
                                                    GrRenderable renderable,
                                                    GrMipmapped mipMapped,
                                                    GrProtected isProtected) {
      const GrD3DCaps& caps = this->d3dCaps();
  
      if (this->protectedContext() != (isProtected == GrProtected::kYes)) {
          return {};
      }
  
      DXGI_FORMAT dxgiFormat;
      if (!format.asDxgiFormat(&dxgiFormat)) {
          return {};
      }
  
      // TODO: move the texturability check up to GrGpu::createBackendTexture and just assert here
      if (!caps.isFormatTexturable(dxgiFormat)) {
          return {};
      }
  
      GrD3DTextureResourceInfo info;
      if (!this->createTextureResourceForBackendSurface(dxgiFormat, dimensions, GrTexturable::kYes,
                                                        renderable, mipMapped, 1, &info,
                                                        isProtected)) {
          return {};
      }
  
      return GrBackendTexture(dimensions.width(), dimensions.height(), info);
  }
  
  static bool copy_color_data(const GrD3DCaps& caps,
                              char* mapPtr,
                              DXGI_FORMAT dxgiFormat,
                              SkISize dimensions,
                              D3D12_PLACED_SUBRESOURCE_FOOTPRINT* placedFootprints,
                              std::array<float, 4> color) {
      auto colorType = caps.getFormatColorType(dxgiFormat);
      if (colorType == GrColorType::kUnknown) {
          return false;
      }
      GrImageInfo ii(colorType, kUnpremul_SkAlphaType, nullptr, dimensions);
      if (!GrClearImage(ii, mapPtr, placedFootprints[0].Footprint.RowPitch, color)) {
          return false;
      }
  
      return true;
  }
  
  bool GrD3DGpu::onClearBackendTexture(const GrBackendTexture& backendTexture,
                                       sk_sp<GrRefCntedCallback> finishedCallback,
                                       std::array<float, 4> color) {
      GrD3DTextureResourceInfo info;
      SkAssertResult(backendTexture.getD3DTextureResourceInfo(&info));
      SkASSERT(!GrDxgiFormatIsCompressed(info.fFormat));
  
      sk_sp<GrD3DResourceState> state = backendTexture.getGrD3DResourceState();
      SkASSERT(state);
      sk_sp<GrD3DTexture> texture =
              GrD3DTexture::MakeWrappedTexture(this, backendTexture.dimensions(),
                                               GrWrapCacheable::kNo,
                                               kRW_GrIOType, info, std::move(state));
      if (!texture) {
          return false;
      }
  
      GrD3DDirectCommandList* cmdList = this->currentCommandList();
      if (!cmdList) {
          return false;
      }
  
      texture->setResourceState(this, D3D12_RESOURCE_STATE_COPY_DEST);
  
      ID3D12Resource* d3dResource = texture->d3dResource();
      SkASSERT(d3dResource);
      D3D12_RESOURCE_DESC desc = d3dResource->GetDesc();
      unsigned int mipLevelCount = 1;
      if (backendTexture.fMipmapped == GrMipmapped::kYes) {
          mipLevelCount = SkMipmap::ComputeLevelCount(backendTexture.dimensions()) + 1;
      }
      SkASSERT(mipLevelCount == info.fLevelCount);
      SkAutoSTMalloc<15, D3D12_PLACED_SUBRESOURCE_FOOTPRINT> placedFootprints(mipLevelCount);
      UINT numRows;
      UINT64 rowSizeInBytes;
      UINT64 combinedBufferSize;
      // We reuse the same top-level buffer area for all levels, hence passing 1 for level count.
      fDevice->GetCopyableFootprints(&desc,
                                     /* first resource  */ 0,
                                     /* mip level count */ 1,
                                     /* base offset     */ 0,
                                     placedFootprints.get(),
                                     &numRows,
                                     &rowSizeInBytes,
                                     &combinedBufferSize);
      SkASSERT(combinedBufferSize);
  
      GrStagingBufferManager::Slice slice = fStagingBufferManager.allocateStagingBufferSlice(
              combinedBufferSize, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT);
      if (!slice.fBuffer) {
          return false;
      }
  
      char* bufferData = (char*)slice.fOffsetMapPtr;
      SkASSERT(bufferData);
      if (!copy_color_data(this->d3dCaps(),
                           bufferData,
                           info.fFormat,
                           backendTexture.dimensions(),
                           placedFootprints,
                           color)) {
          return false;
      }
      // Update the offsets in the footprint to be relative to the slice's offset
      placedFootprints[0].Offset += slice.fOffset;
      // Since we're sharing data for all the levels, set all the upper level footprints to the base.
      UINT w = placedFootprints[0].Footprint.Width;
      UINT h = placedFootprints[0].Footprint.Height;
      for (unsigned int i = 1; i < mipLevelCount; ++i) {
          w = std::max(1U, w/2);
          h = std::max(1U, h/2);
          placedFootprints[i].Offset = placedFootprints[0].Offset;
          placedFootprints[i].Footprint.Format   = placedFootprints[0].Footprint.Format;
          placedFootprints[i].Footprint.Width    = w;
          placedFootprints[i].Footprint.Height   = h;
          placedFootprints[i].Footprint.Depth    = 1;
          placedFootprints[i].Footprint.RowPitch = placedFootprints[0].Footprint.RowPitch;
      }
  
      ID3D12Resource* d3dBuffer = static_cast<GrD3DBuffer*>(slice.fBuffer)->d3dResource();
      cmdList->copyBufferToTexture(d3dBuffer,
                                   texture.get(),
                                   mipLevelCount,
                                   placedFootprints.get(),
                                   /*left*/ 0,
                                   /*top */ 0);
  
      if (finishedCallback) {
          this->addFinishedCallback(std::move(finishedCallback));
      }
  
      return true;
  }
  
  GrBackendTexture GrD3DGpu::onCreateCompressedBackendTexture(
      SkISize dimensions, const GrBackendFormat& format, GrMipmapped mipMapped,
      GrProtected isProtected) {
      return this->onCreateBackendTexture(dimensions, format, GrRenderable::kNo, mipMapped,
                                          isProtected);
  }
  
  bool GrD3DGpu::onUpdateCompressedBackendTexture(const GrBackendTexture& backendTexture,
                                                  sk_sp<GrRefCntedCallback> finishedCallback,
                                                  const void* data,
                                                  size_t size) {
      GrD3DTextureResourceInfo info;
      SkAssertResult(backendTexture.getD3DTextureResourceInfo(&info));
  
      sk_sp<GrD3DResourceState> state = backendTexture.getGrD3DResourceState();
      SkASSERT(state);
      sk_sp<GrD3DTexture> texture = GrD3DTexture::MakeWrappedTexture(this,
                                                                     backendTexture.dimensions(),
                                                                     GrWrapCacheable::kNo,
                                                                     kRW_GrIOType,
                                                                     info,
                                                                     std::move(state));
      if (!texture) {
          return false;
      }
  
      GrD3DDirectCommandList* cmdList = this->currentCommandList();
      if (!cmdList) {
          return false;
      }
  
      texture->setResourceState(this, D3D12_RESOURCE_STATE_COPY_DEST);
  
      ID3D12Resource* d3dResource = texture->d3dResource();
      SkASSERT(d3dResource);
      D3D12_RESOURCE_DESC desc = d3dResource->GetDesc();
      unsigned int mipLevelCount = 1;
      if (backendTexture.hasMipmaps()) {
          mipLevelCount = SkMipmap::ComputeLevelCount(backendTexture.dimensions().width(),
                                                      backendTexture.dimensions().height()) + 1;
      }
      SkASSERT(mipLevelCount == info.fLevelCount);
      SkAutoTMalloc<D3D12_PLACED_SUBRESOURCE_FOOTPRINT> placedFootprints(mipLevelCount);
      UINT64 combinedBufferSize;
      SkAutoTMalloc<UINT> numRows(mipLevelCount);
      SkAutoTMalloc<UINT64> rowSizeInBytes(mipLevelCount);
      fDevice->GetCopyableFootprints(&desc,
                                     0,
                                     mipLevelCount,
                                     0,
                                     placedFootprints.get(),
                                     numRows.get(),
                                     rowSizeInBytes.get(),
                                     &combinedBufferSize);
      SkASSERT(combinedBufferSize);
      SkASSERT(GrDxgiFormatIsCompressed(info.fFormat));
  
      GrStagingBufferManager::Slice slice = fStagingBufferManager.allocateStagingBufferSlice(
              combinedBufferSize, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT);
      if (!slice.fBuffer) {
          return false;
      }
  
      char* bufferData = (char*)slice.fOffsetMapPtr;
      SkASSERT(bufferData);
      copy_compressed_data(bufferData,
                           info.fFormat,
                           placedFootprints.get(),
                           numRows.get(),
                           rowSizeInBytes.get(),
                           data,
                           info.fLevelCount);
  
      // Update the offsets in the footprints to be relative to the slice's offset
      for (unsigned int i = 0; i < mipLevelCount; ++i) {
          placedFootprints[i].Offset += slice.fOffset;
      }
  
      ID3D12Resource* d3dBuffer = static_cast<GrD3DBuffer*>(slice.fBuffer)->d3dResource();
      cmdList->copyBufferToTexture(d3dBuffer,
                                   texture.get(),
                                   mipLevelCount,
                                   placedFootprints.get(),
                                   0,
                                   0);
  
      if (finishedCallback) {
          this->addFinishedCallback(std::move(finishedCallback));
      }
  
      return true;
  }
  
  void GrD3DGpu::deleteBackendTexture(const GrBackendTexture& tex) {
      SkASSERT(GrBackendApi::kDirect3D == tex.fBackend);
      // Nothing to do here, will get cleaned up when the GrBackendTexture object goes away
  }
  
  bool GrD3DGpu::compile(const GrProgramDesc&, const GrProgramInfo&) {
      return false;
  }
  
  #if GR_TEST_UTILS
  bool GrD3DGpu::isTestingOnlyBackendTexture(const GrBackendTexture& tex) const {
      SkASSERT(GrBackendApi::kDirect3D == tex.backend());
  
      GrD3DTextureResourceInfo info;
      if (!tex.getD3DTextureResourceInfo(&info)) {
          return false;
      }
      ID3D12Resource* textureResource = info.fResource.get();
      if (!textureResource) {
          return false;
      }
      return !(textureResource->GetDesc().Flags & D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE);
  }
  
  GrBackendRenderTarget GrD3DGpu::createTestingOnlyBackendRenderTarget(SkISize dimensions,
                                                                       GrColorType colorType,
                                                                       int sampleCnt,
                                                                       GrProtected isProtected) {
      if (dimensions.width()  > this->caps()->maxRenderTargetSize() ||
          dimensions.height() > this->caps()->maxRenderTargetSize()) {
          return {};
      }
  
      DXGI_FORMAT dxgiFormat = this->d3dCaps().getFormatFromColorType(colorType);
  
      GrD3DTextureResourceInfo info;
      if (!this->createTextureResourceForBackendSurface(dxgiFormat, dimensions, GrTexturable::kNo,
                                                        GrRenderable::kYes, GrMipmapped::kNo,
                                                        sampleCnt, &info, isProtected)) {
          return {};
      }
  
      return GrBackendRenderTarget(dimensions.width(), dimensions.height(), info);
  }
  
  void GrD3DGpu::deleteTestingOnlyBackendRenderTarget(const GrBackendRenderTarget& rt) {
      SkASSERT(GrBackendApi::kDirect3D == rt.backend());
  
      GrD3DTextureResourceInfo info;
      if (rt.getD3DTextureResourceInfo(&info)) {
          this->submitToGpu(true);
          // Nothing else to do here, will get cleaned up when the GrBackendRenderTarget
          // is deleted.
      }
  }
  
  void GrD3DGpu::testingOnly_startCapture() {
      if (fGraphicsAnalysis) {
          fGraphicsAnalysis->BeginCapture();
      }
  }
  
  void GrD3DGpu::testingOnly_endCapture() {
      if (fGraphicsAnalysis) {
          fGraphicsAnalysis->EndCapture();
      }
  }
  #endif
  
  ///////////////////////////////////////////////////////////////////////////////
  
  void GrD3DGpu::addResourceBarriers(sk_sp<GrManagedResource> resource,
                                     int numBarriers,
                                     D3D12_RESOURCE_TRANSITION_BARRIER* barriers) const {
      SkASSERT(fCurrentDirectCommandList);
      SkASSERT(resource);
  
      fCurrentDirectCommandList->resourceBarrier(std::move(resource), numBarriers, barriers);
  }
  
  void GrD3DGpu::addBufferResourceBarriers(GrD3DBuffer* buffer,
                                           int numBarriers,
                                           D3D12_RESOURCE_TRANSITION_BARRIER* barriers) const {
      SkASSERT(fCurrentDirectCommandList);
      SkASSERT(buffer);
  
      fCurrentDirectCommandList->resourceBarrier(nullptr, numBarriers, barriers);
      fCurrentDirectCommandList->addGrBuffer(sk_ref_sp<const GrBuffer>(buffer));
  }
  
  
  void GrD3DGpu::prepareSurfacesForBackendAccessAndStateUpdates(
          SkSpan<GrSurfaceProxy*> proxies,
          SkSurface::BackendSurfaceAccess access,
          const GrBackendSurfaceMutableState* newState) {
      // prepare proxies by transitioning to PRESENT renderState
      if (!proxies.empty() && access == SkSurface::BackendSurfaceAccess::kPresent) {
          GrD3DTextureResource* resource;
          for (GrSurfaceProxy* proxy : proxies) {
              SkASSERT(proxy->isInstantiated());
              if (GrTexture* tex = proxy->peekTexture()) {
                  resource = static_cast<GrD3DTexture*>(tex);
              } else {
                  GrRenderTarget* rt = proxy->peekRenderTarget();
                  SkASSERT(rt);
                  resource = static_cast<GrD3DRenderTarget*>(rt);
              }
              resource->prepareForPresent(this);
          }
      }
  }
  
  void GrD3DGpu::takeOwnershipOfBuffer(sk_sp<GrGpuBuffer> buffer) {
      fCurrentDirectCommandList->addGrBuffer(std::move(buffer));
  }
  
  bool GrD3DGpu::onSubmitToGpu(bool syncCpu) {
      if (syncCpu) {
          return this->submitDirectCommandList(SyncQueue::kForce);
      } else {
          return this->submitDirectCommandList(SyncQueue::kSkip);
      }
  }
  
  std::unique_ptr<GrSemaphore> SK_WARN_UNUSED_RESULT GrD3DGpu::makeSemaphore(bool) {
      return GrD3DSemaphore::Make(this);
  }
  std::unique_ptr<GrSemaphore> GrD3DGpu::wrapBackendSemaphore(
          const GrBackendSemaphore& semaphore,
          GrResourceProvider::SemaphoreWrapType,
          GrWrapOwnership) {
      SkASSERT(this->caps()->semaphoreSupport());
      GrD3DFenceInfo fenceInfo;
      if (!semaphore.getD3DFenceInfo(&fenceInfo)) {
          return nullptr;
      }
      return GrD3DSemaphore::MakeWrapped(fenceInfo);
  }
  
  void GrD3DGpu::insertSemaphore(GrSemaphore* semaphore) {
      SkASSERT(semaphore);
      GrD3DSemaphore* d3dSem = static_cast<GrD3DSemaphore*>(semaphore);
      // TODO: Do we need to track the lifetime of this? How do we know it's done?
      fQueue->Signal(d3dSem->fence(), d3dSem->value());
  }
  
  void GrD3DGpu::waitSemaphore(GrSemaphore* semaphore) {
      SkASSERT(semaphore);
      GrD3DSemaphore* d3dSem = static_cast<GrD3DSemaphore*>(semaphore);
      // TODO: Do we need to track the lifetime of this?
      fQueue->Wait(d3dSem->fence(), d3dSem->value());
  }
  
  GrFence SK_WARN_UNUSED_RESULT GrD3DGpu::insertFence() {
      GR_D3D_CALL_ERRCHECK(fQueue->Signal(fFence.get(), ++fCurrentFenceValue));
      return fCurrentFenceValue;
  }
  
  bool GrD3DGpu::waitFence(GrFence fence) {
      return (fFence->GetCompletedValue() >= fence);
  }
  
  void GrD3DGpu::finishOutstandingGpuWork() {
      this->waitForQueueCompletion();
  }
  