Add support to gpu command buffers to wrap an external command buffer.

Bug: skia:
Change-Id: Ic679d292f42c61f9f1c36315ae605504a0283306
Reviewed-on: https://skia-review.googlesource.com/c/179521
Commit-Queue: Greg Daniel <egdaniel@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/tests/VkDrawableTest.cpp b/tests/VkDrawableTest.cpp
index 0ff94aa..3ff2607 100644
--- a/tests/VkDrawableTest.cpp
+++ b/tests/VkDrawableTest.cpp
@@ -22,6 +22,7 @@
 #include "vk/GrVkGpu.h"
 #include "vk/GrVkInterface.h"
 #include "vk/GrVkMemory.h"
+#include "vk/GrVkSecondaryCBDrawContext.h"
 #include "vk/GrVkUtil.h"
 
 using sk_gpu_test::GrContextFactory;
@@ -30,22 +31,23 @@
 
 class TestDrawable : public SkDrawable {
 public:
-    TestDrawable(const GrVkInterface* interface, int32_t width, int32_t height)
+    TestDrawable(const GrVkInterface* interface, GrContext* context, int32_t width, int32_t height)
             : INHERITED()
             , fInterface(interface)
+            , fContext(context)
             , fWidth(width)
             , fHeight(height) {}
 
     ~TestDrawable() override {}
 
-    class DrawHandler : public GpuDrawHandler {
+    class DrawHandlerBasic : public GpuDrawHandler {
     public:
-        DrawHandler(const GrVkInterface* interface, int32_t width, int32_t height)
+        DrawHandlerBasic(const GrVkInterface* interface, int32_t width, int32_t height)
             : INHERITED()
             , fInterface(interface)
             , fWidth(width)
             , fHeight(height) {}
-        ~DrawHandler() override {}
+        ~DrawHandlerBasic() override {}
 
         void draw(const GrBackendDrawableInfo& info) override {
             GrVkDrawableInfo vkInfo;
@@ -86,14 +88,100 @@
         typedef GpuDrawHandler INHERITED;
     };
 
+    typedef void (*DrawProc)(TestDrawable*, const GrVkDrawableInfo&);
+    typedef void (*SubmitProc)(TestDrawable*);
+
+    // Exercises the exporting of a secondary command buffer from one GrContext and then importing
+    // it into a second GrContext. We then draw to the secondary command buffer from the second
+    // GrContext.
+    class DrawHandlerImport : public GpuDrawHandler {
+    public:
+        DrawHandlerImport(TestDrawable* td, DrawProc drawProc, SubmitProc submitProc)
+            : INHERITED()
+            , fTestDrawable(td)
+            , fDrawProc(drawProc)
+            , fSubmitProc(submitProc) {}
+        ~DrawHandlerImport() override {
+            fSubmitProc(fTestDrawable);
+        }
+
+        void draw(const GrBackendDrawableInfo& info) override {
+            GrVkDrawableInfo vkInfo;
+            SkAssertResult(info.getVkDrawableInfo(&vkInfo));
+
+            fDrawProc(fTestDrawable, vkInfo);
+        }
+    private:
+        TestDrawable* fTestDrawable;
+        DrawProc      fDrawProc;
+        SubmitProc    fSubmitProc;
+
+        typedef GpuDrawHandler INHERITED;
+    };
+
+    // Helper function to test drawing to a secondary command buffer that we imported into the
+    // GrContext using a GrVkSecondaryCBDrawContext.
+    static void ImportDraw(TestDrawable* td, const GrVkDrawableInfo& info) {
+        SkImageInfo imageInfo = SkImageInfo::Make(td->fWidth, td->fHeight, kRGBA_8888_SkColorType,
+                                                  kPremul_SkAlphaType);
+
+        td->fDrawContext = GrVkSecondaryCBDrawContext::Make(td->fContext, imageInfo, info, nullptr);
+        if (!td->fDrawContext) {
+            return;
+        }
+
+        SkCanvas* canvas = td->fDrawContext->getCanvas();
+        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->fContext, SkBudgeted::kYes,
+                                                            imageInfo);
+        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 GrContext.
+    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->fContext->contextPriv().getGpu()->testingOnly_flushGpuAndSync();
+
+        td->fDrawContext->releaseResources();
+        // We release the GrContext here manually to test that we waited long enough before
+        // releasing the GrVkSecondaryCBDrawContext. This simulates when a client is able to delete
+        // the GrContext it used to imported the secondary command buffer. If we had released the
+        // GrContext'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->fContext->releaseResourcesAndAbandonContext();
+    }
+
+
     std::unique_ptr<GpuDrawHandler> onSnapGpuDrawHandler(GrBackendApi backendApi,
                                                          const SkMatrix& matrix,
                                                          const SkIRect& clipBounds) override {
         if (backendApi != GrBackendApi::kVulkan) {
             return nullptr;
         }
-        std::unique_ptr<DrawHandler> draw(new DrawHandler(fInterface, fWidth, fHeight));
-        return std::move(draw);
+        std::unique_ptr<GpuDrawHandler> draw;
+        if (fContext) {
+            draw.reset(new DrawHandlerImport(this, ImportDraw, ImportSubmitted));
+        } else {
+            draw.reset(new DrawHandlerBasic(fInterface, fWidth, fHeight));
+        }
+        return draw;
     }
 
     SkRect onGetBounds() override {
@@ -106,13 +194,15 @@
 
 private:
     const GrVkInterface* fInterface;
-    int32_t fWidth;
-    int32_t fHeight;
+    GrContext*           fContext;
+    sk_sp<GrVkSecondaryCBDrawContext> fDrawContext;
+    int32_t              fWidth;
+    int32_t              fHeight;
 
     typedef SkDrawable INHERITED;
 };
 
-void draw_drawable_test(skiatest::Reporter* reporter, GrContext* context) {
+void draw_drawable_test(skiatest::Reporter* reporter, GrContext* context, GrContext* childContext) {
     GrVkGpu* gpu = static_cast<GrVkGpu*>(context->contextPriv().getGpu());
 
     const SkImageInfo ii = SkImageInfo::Make(DEV_W, DEV_H, kRGBA_8888_SkColorType,
@@ -122,7 +212,7 @@
     SkCanvas* canvas = surface->getCanvas();
     canvas->clear(SK_ColorBLUE);
 
-    sk_sp<TestDrawable> drawable(new TestDrawable(gpu->vkInterface(), DEV_W, DEV_H));
+    sk_sp<TestDrawable> drawable(new TestDrawable(gpu->vkInterface(), childContext, DEV_W, DEV_H));
     canvas->drawDrawable(drawable.get());
 
     SkPaint paint;
@@ -138,8 +228,8 @@
     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) {
+    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) {
@@ -159,9 +249,32 @@
     }
 }
 
-
 DEF_GPUTEST_FOR_VULKAN_CONTEXT(VkDrawableTest, reporter, ctxInfo) {
-    draw_drawable_test(reporter, ctxInfo.grContext());
+    draw_drawable_test(reporter, ctxInfo.grContext(), 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, sk_gpu_test::GrContextFactory::ContextOverrides::kDisableNVPR);
+        skiatest::ReporterContext ctx(
+                   reporter, SkString(sk_gpu_test::GrContextFactory::ContextTypeName(contextType)));
+        if (ctxInfo.grContext()) {
+            sk_gpu_test::ContextInfo child =
+                    factory.getSharedContextInfo(ctxInfo.grContext(), 0);
+            if (!child.grContext()) {
+                continue;
+            }
+
+            draw_drawable_test(reporter, ctxInfo.grContext(), child.grContext());
+        }
+    }
 }
 
 #endif