text: /*
   * Copyright 2018 Google Inc.
   *
   * Use of this source code is governed by a BSD-style license that can be
   * found in the LICENSE file.
   */
  
  // This is a GPU-backend specific test. It relies on static intializers to work
  
  #include "include/core/SkTypes.h"
  
  #if SK_SUPPORT_GPU && defined(SK_VULKAN)
  
  #  include "include/gpu/vk/GrVkVulkan.h"
  
  #  include "include/core/SkDrawable.h"
  #  include "include/core/SkSurface.h"
  #  include "include/gpu/GrBackendDrawableInfo.h"
  #  include "include/gpu/GrDirectContext.h"
  #  include "src/gpu/GrDirectContextPriv.h"
  #  include "src/gpu/vk/GrVkGpu.h"
  #  include "src/gpu/vk/GrVkInterface.h"
  #  include "src/gpu/vk/GrVkMemory.h"
  #  include "src/gpu/vk/GrVkSecondaryCBDrawContext.h"
  #  include "src/gpu/vk/GrVkUtil.h"
  #  include "tests/Test.h"
  #  include "tools/gpu/GrContextFactory.h"
  
  using sk_gpu_test::GrContextFactory;
  
  static const int DEV_W = 16, DEV_H = 16;
  
  class TestDrawable : public SkDrawable {
   public:
    TestDrawable(
        const GrVkInterface* interface, GrDirectContext* dContext, int32_t width, int32_t height)
        : INHERITED(), fInterface(interface), fDContext(dContext), fWidth(width), fHeight(height) {}
  
    ~TestDrawable() override {}
  
    class DrawHandlerBasic : public GpuDrawHandler {
     public:
      DrawHandlerBasic(const GrVkInterface* interface, int32_t width, int32_t height)
          : INHERITED(), fInterface(interface), fWidth(width), fHeight(height) {}
      ~DrawHandlerBasic() override {}
  
      void draw(const GrBackendDrawableInfo& info) override {
        GrVkDrawableInfo vkInfo;
        SkAssertResult(info.getVkDrawableInfo(&vkInfo));
  
        // Clear to Red
        VkClearColorValue vkColor;
        vkColor.float32[0] = 1.0f;  // r
        vkColor.float32[1] = 0.0f;  // g
        vkColor.float32[2] = 0.0f;  // b
        vkColor.float32[3] = 1.0f;  // a
  
        // Clear right half of render target
        VkClearRect clearRect;
        clearRect.rect.offset = {fWidth / 2, 0};
        clearRect.rect.extent = {(uint32_t)fWidth / 2, (uint32_t)fHeight};
        clearRect.baseArrayLayer = 0;
        clearRect.layerCount = 1;
  
        VkClearAttachment attachment;
        attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        attachment.colorAttachment = vkInfo.fColorAttachmentIndex;
        attachment.clearValue.color = vkColor;
  
        GR_VK_CALL(
            fInterface,
            CmdClearAttachments(vkInfo.fSecondaryCommandBuffer, 1, &attachment, 1, &clearRect));
        vkInfo.fDrawBounds->offset = {fWidth / 2, 0};
        vkInfo.fDrawBounds->extent = {(uint32_t)fWidth / 2, (uint32_t)fHeight};
      }
  
     private:
      const GrVkInterface* fInterface;
      int32_t fWidth;
      int32_t fHeight;
  
      using INHERITED = GpuDrawHandler;
    };
  
    typedef void (*DrawProc)(
        TestDrawable*, const SkMatrix&, const SkIRect&, const SkImageInfo&, const GrVkDrawableInfo&);
    typedef void (*SubmitProc)(TestDrawable*);
  
    // Exercises the exporting of a secondary command buffer from one context and then importing
    // it into a second context. We then draw to the secondary command buffer from the second
    // context.
    class DrawHandlerImport : public GpuDrawHandler {
     public:
      DrawHandlerImport(
          TestDrawable* td, DrawProc drawProc, SubmitProc submitProc, const SkMatrix& matrix,
          const SkIRect& clipBounds, const SkImageInfo& bufferInfo)
          : INHERITED(),
            fTestDrawable(td),
            fDrawProc(drawProc),
            fSubmitProc(submitProc),
            fMatrix(matrix),
            fClipBounds(clipBounds),
            fBufferInfo(bufferInfo) {}
      ~DrawHandlerImport() override { fSubmitProc(fTestDrawable); }
  
      void draw(const GrBackendDrawableInfo& info) override {
        GrVkDrawableInfo vkInfo;
        SkAssertResult(info.getVkDrawableInfo(&vkInfo));
  
        fDrawProc(fTestDrawable, fMatrix, fClipBounds, fBufferInfo, vkInfo);
      }
  
     private:
      TestDrawable* fTestDrawable;
      DrawProc fDrawProc;
      SubmitProc fSubmitProc;
      const SkMatrix fMatrix;
      const SkIRect fClipBounds;
      const SkImageInfo fBufferInfo;
  
      using INHERITED = GpuDrawHandler;
    };
  
    // Helper function to test drawing to a secondary command buffer that we imported into the
    // context using a GrVkSecondaryCBDrawContext.
    static void ImportDraw(
        TestDrawable* td, const SkMatrix& matrix, const SkIRect& clipBounds,
        const SkImageInfo& bufferInfo, const GrVkDrawableInfo& info) {
      td->fDrawContext = GrVkSecondaryCBDrawContext::Make(td->fDContext, bufferInfo, info, nullptr);
      if (!td->fDrawContext) {
        return;
      }
  
      SkCanvas* canvas = td->fDrawContext->getCanvas();
      canvas->clipRect(SkRect::Make(clipBounds));
      canvas->setMatrix(matrix);
  
      SkIRect rect = SkIRect::MakeXYWH(td->fWidth / 2, 0, td->fWidth / 4, td->fHeight);
      SkPaint paint;
      paint.setColor(SK_ColorRED);
      canvas->drawIRect(rect, paint);
  
      // Draw to an offscreen target so that we end up with a mix of "real" secondary command
      // buffers and the imported secondary command buffer.
      sk_sp<SkSurface> surf =
          SkSurface::MakeRenderTarget(td->fDContext, SkBudgeted::kYes, bufferInfo);
      surf->getCanvas()->clear(SK_ColorRED);
  
      SkRect dstRect = SkRect::MakeXYWH(3 * td->fWidth / 4, 0, td->fWidth / 4, td->fHeight);
      SkIRect srcRect = SkIRect::MakeWH(td->fWidth / 4, td->fHeight);
      canvas->drawImageRect(surf->makeImageSnapshot(), srcRect, dstRect, &paint);
  
      td->fDrawContext->flush();
    }
  
    // Helper function to test waiting for the imported secondary command buffer to be submitted on
    // its original context and then cleaning up the GrVkSecondaryCBDrawContext from this context.
    static void ImportSubmitted(TestDrawable* td) {
      // Typical use case here would be to create a fence that we submit to the gpu and then wait
      // on before releasing the GrVkSecondaryCBDrawContext resources. To simulate that for this
      // test (and since we are running single threaded anyways), we will just force a sync of
      // the gpu and cpu here.
      td->fDContext->priv().getGpu()->testingOnly_flushGpuAndSync();
  
      td->fDrawContext->releaseResources();
      // We release the context here manually to test that we waited long enough before
      // releasing the GrVkSecondaryCBDrawContext. This simulates when a client is able to delete
      // the context it used to imported the secondary command buffer. If we had released the
      // context's resources earlier (before waiting on the gpu above), we would get vulkan
      // validation layer errors saying we freed some vulkan objects while they were still in use
      // on the GPU.
      td->fDContext->releaseResourcesAndAbandonContext();
    }
  
    std::unique_ptr<GpuDrawHandler> onSnapGpuDrawHandler(
        GrBackendApi backendApi, const SkMatrix& matrix, const SkIRect& clipBounds,
        const SkImageInfo& bufferInfo) override {
      if (backendApi != GrBackendApi::kVulkan) {
        return nullptr;
      }
      std::unique_ptr<GpuDrawHandler> draw;
      if (fDContext) {
        draw = std::make_unique<DrawHandlerImport>(
            this, ImportDraw, ImportSubmitted, matrix, clipBounds, bufferInfo);
      } else {
        draw = std::make_unique<DrawHandlerBasic>(fInterface, fWidth, fHeight);
      }
      return draw;
    }
  
    SkRect onGetBounds() override { return SkRect::MakeLTRB(fWidth / 2, 0, fWidth, fHeight); }
  
    void onDraw(SkCanvas*) override { SkASSERT(false); }
  
   private:
    const GrVkInterface* fInterface;
    GrDirectContext* fDContext;
    sk_sp<GrVkSecondaryCBDrawContext> fDrawContext;
    int32_t fWidth;
    int32_t fHeight;
  
    using INHERITED = SkDrawable;
  };
  
  void draw_drawable_test(
      skiatest::Reporter* reporter, GrDirectContext* dContext, GrDirectContext* childDContext) {
    GrVkGpu* gpu = static_cast<GrVkGpu*>(dContext->priv().getGpu());
  
    const SkImageInfo ii =
        SkImageInfo::Make(DEV_W, DEV_H, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
    sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(
        dContext, SkBudgeted::kNo, ii, 0, kTopLeft_GrSurfaceOrigin, nullptr));
    SkCanvas* canvas = surface->getCanvas();
    canvas->clear(SK_ColorBLUE);
  
    sk_sp<TestDrawable> drawable(new TestDrawable(gpu->vkInterface(), childDContext, DEV_W, DEV_H));
    canvas->drawDrawable(drawable.get());
  
    SkPaint paint;
    paint.setColor(SK_ColorGREEN);
    SkIRect rect = SkIRect::MakeLTRB(0, DEV_H / 2, DEV_W, DEV_H);
    canvas->drawIRect(rect, paint);
  
    // read pixels
    SkBitmap bitmap;
    bitmap.allocPixels(ii);
    canvas->readPixels(bitmap, 0, 0);
  
    const uint32_t* canvasPixels = static_cast<const uint32_t*>(bitmap.getPixels());
    bool failureFound = false;
    SkPMColor expectedPixel;
    for (int cy = 0; cy < DEV_H && !failureFound; ++cy) {
      for (int cx = 0; cx < DEV_W && !failureFound; ++cx) {
        SkPMColor canvasPixel = canvasPixels[cy * DEV_W + cx];
        if (cy < DEV_H / 2) {
          if (cx < DEV_W / 2) {
            expectedPixel = 0xFFFF0000;  // Blue
          } else {
            expectedPixel = 0xFF0000FF;  // Red
          }
        } else {
          expectedPixel = 0xFF00FF00;  // Green
        }
        if (expectedPixel != canvasPixel) {
          failureFound = true;
          ERRORF(
              reporter, "Wrong color at %d, %d. Got 0x%08x when we expected 0x%08x", cx, cy,
              canvasPixel, expectedPixel);
        }
      }
    }
  }
  
  DEF_GPUTEST_FOR_VULKAN_CONTEXT(VkDrawableTest, reporter, ctxInfo) {
    draw_drawable_test(reporter, ctxInfo.directContext(), nullptr);
  }
  
  DEF_GPUTEST(VkDrawableImportTest, reporter, options) {
    for (int typeInt = 0; typeInt < sk_gpu_test::GrContextFactory::kContextTypeCnt; ++typeInt) {
      sk_gpu_test::GrContextFactory::ContextType contextType =
          (sk_gpu_test::GrContextFactory::ContextType)typeInt;
      if (contextType != sk_gpu_test::GrContextFactory::kVulkan_ContextType) {
        continue;
      }
      sk_gpu_test::GrContextFactory factory(options);
      sk_gpu_test::ContextInfo ctxInfo = factory.getContextInfo(contextType);
      skiatest::ReporterContext ctx(
          reporter, SkString(sk_gpu_test::GrContextFactory::ContextTypeName(contextType)));
      if (ctxInfo.directContext()) {
        sk_gpu_test::ContextInfo child = factory.getSharedContextInfo(ctxInfo.directContext(), 0);
        if (!child.directContext()) {
          continue;
        }
  
        draw_drawable_test(reporter, ctxInfo.directContext(), child.directContext());
      }
    }
  }
  
  #endif
  