1 /*
2  * Copyright 2018 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 // This is a GPU-backend specific test. It relies on static intializers to work
9 
10 #include "SkTypes.h"
11 
12 #if SK_SUPPORT_GPU && defined(SK_VULKAN)
13 
14 #include "vk/GrVkVulkan.h"
15 
16 #include "GrBackendDrawableInfo.h"
17 #include "GrContextFactory.h"
18 #include "GrContextPriv.h"
19 #include "SkDrawable.h"
20 #include "SkSurface.h"
21 #include "Test.h"
22 #include "vk/GrVkGpu.h"
23 #include "vk/GrVkInterface.h"
24 #include "vk/GrVkMemory.h"
25 #include "vk/GrVkSecondaryCBDrawContext.h"
26 #include "vk/GrVkUtil.h"
27 
28 using sk_gpu_test::GrContextFactory;
29 
30 static const int DEV_W = 16, DEV_H = 16;
31 
32 class TestDrawable : public SkDrawable {
33 public:
TestDrawable(const GrVkInterface * interface,GrContext * context,int32_t width,int32_t height)34     TestDrawable(const GrVkInterface* interface, GrContext* context, int32_t width, int32_t height)
35             : INHERITED()
36             , fInterface(interface)
37             , fContext(context)
38             , fWidth(width)
39             , fHeight(height) {}
40 
~TestDrawable()41     ~TestDrawable() override {}
42 
43     class DrawHandlerBasic : public GpuDrawHandler {
44     public:
DrawHandlerBasic(const GrVkInterface * interface,int32_t width,int32_t height)45         DrawHandlerBasic(const GrVkInterface* interface, int32_t width, int32_t height)
46             : INHERITED()
47             , fInterface(interface)
48             , fWidth(width)
49             , fHeight(height) {}
~DrawHandlerBasic()50         ~DrawHandlerBasic() override {}
51 
draw(const GrBackendDrawableInfo & info)52         void draw(const GrBackendDrawableInfo& info) override {
53             GrVkDrawableInfo vkInfo;
54             SkAssertResult(info.getVkDrawableInfo(&vkInfo));
55 
56             // Clear to Red
57             VkClearColorValue vkColor;
58             vkColor.float32[0] = 1.0f; // r
59             vkColor.float32[1] = 0.0f; // g
60             vkColor.float32[2] = 0.0f; // b
61             vkColor.float32[3] = 1.0f; // a
62 
63             // Clear right half of render target
64             VkClearRect clearRect;
65             clearRect.rect.offset = { fWidth / 2, 0 };
66             clearRect.rect.extent = { (uint32_t)fWidth / 2, (uint32_t)fHeight };
67             clearRect.baseArrayLayer = 0;
68             clearRect.layerCount = 1;
69 
70             VkClearAttachment attachment;
71             attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
72             attachment.colorAttachment = vkInfo.fColorAttachmentIndex;
73             attachment.clearValue.color = vkColor;
74 
75             GR_VK_CALL(fInterface, CmdClearAttachments(vkInfo.fSecondaryCommandBuffer,
76                                                        1,
77                                                        &attachment,
78                                                        1,
79                                                        &clearRect));
80             vkInfo.fDrawBounds->offset = { fWidth / 2, 0 };
81             vkInfo.fDrawBounds->extent = { (uint32_t)fWidth / 2, (uint32_t)fHeight };
82         }
83     private:
84         const GrVkInterface* fInterface;
85         int32_t              fWidth;
86         int32_t              fHeight;
87 
88         typedef GpuDrawHandler INHERITED;
89     };
90 
91     typedef void (*DrawProc)(TestDrawable*, const SkMatrix&, const SkIRect&,
92                              const SkImageInfo&, const GrVkDrawableInfo&);
93     typedef void (*SubmitProc)(TestDrawable*);
94 
95     // Exercises the exporting of a secondary command buffer from one GrContext and then importing
96     // it into a second GrContext. We then draw to the secondary command buffer from the second
97     // GrContext.
98     class DrawHandlerImport : public GpuDrawHandler {
99     public:
DrawHandlerImport(TestDrawable * td,DrawProc drawProc,SubmitProc submitProc,const SkMatrix & matrix,const SkIRect & clipBounds,const SkImageInfo & bufferInfo)100         DrawHandlerImport(TestDrawable* td, DrawProc drawProc, SubmitProc submitProc,
101                           const SkMatrix& matrix,
102                           const SkIRect& clipBounds,
103                           const SkImageInfo& bufferInfo)
104             : INHERITED()
105             , fTestDrawable(td)
106             , fDrawProc(drawProc)
107             , fSubmitProc(submitProc)
108             , fMatrix(matrix)
109             , fClipBounds(clipBounds)
110             , fBufferInfo(bufferInfo) {}
~DrawHandlerImport()111         ~DrawHandlerImport() override {
112             fSubmitProc(fTestDrawable);
113         }
114 
draw(const GrBackendDrawableInfo & info)115         void draw(const GrBackendDrawableInfo& info) override {
116             GrVkDrawableInfo vkInfo;
117             SkAssertResult(info.getVkDrawableInfo(&vkInfo));
118 
119             fDrawProc(fTestDrawable, fMatrix, fClipBounds, fBufferInfo, vkInfo);
120         }
121     private:
122         TestDrawable*     fTestDrawable;
123         DrawProc          fDrawProc;
124         SubmitProc        fSubmitProc;
125         const SkMatrix    fMatrix;
126         const SkIRect     fClipBounds;
127         const SkImageInfo fBufferInfo;
128 
129         typedef GpuDrawHandler INHERITED;
130     };
131 
132     // Helper function to test drawing to a secondary command buffer that we imported into the
133     // GrContext using a GrVkSecondaryCBDrawContext.
ImportDraw(TestDrawable * td,const SkMatrix & matrix,const SkIRect & clipBounds,const SkImageInfo & bufferInfo,const GrVkDrawableInfo & info)134     static void ImportDraw(TestDrawable* td, const SkMatrix& matrix, const SkIRect& clipBounds,
135                            const SkImageInfo& bufferInfo, const GrVkDrawableInfo& info) {
136         td->fDrawContext = GrVkSecondaryCBDrawContext::Make(td->fContext, bufferInfo, info, nullptr);
137         if (!td->fDrawContext) {
138             return;
139         }
140 
141         SkCanvas* canvas = td->fDrawContext->getCanvas();
142         canvas->clipRect(SkRect::Make(clipBounds));
143         canvas->setMatrix(matrix);
144 
145         SkIRect rect = SkIRect::MakeXYWH(td->fWidth/2, 0, td->fWidth/4, td->fHeight);
146         SkPaint paint;
147         paint.setColor(SK_ColorRED);
148         canvas->drawIRect(rect, paint);
149 
150         // Draw to an offscreen target so that we end up with a mix of "real" secondary command
151         // buffers and the imported secondary command buffer.
152         sk_sp<SkSurface> surf = SkSurface::MakeRenderTarget(td->fContext, SkBudgeted::kYes,
153                                                             bufferInfo);
154         surf->getCanvas()->clear(SK_ColorRED);
155 
156         SkRect dstRect = SkRect::MakeXYWH(3*td->fWidth/4, 0, td->fWidth/4, td->fHeight);
157         SkIRect srcRect = SkIRect::MakeWH(td->fWidth/4, td->fHeight);
158         canvas->drawImageRect(surf->makeImageSnapshot(), srcRect, dstRect, &paint);
159 
160         td->fDrawContext->flush();
161     }
162 
163     // Helper function to test waiting for the imported secondary command buffer to be submitted on
164     // its original context and then cleaning up the GrVkSecondaryCBDrawContext from this GrContext.
ImportSubmitted(TestDrawable * td)165     static void ImportSubmitted(TestDrawable* td) {
166         // Typical use case here would be to create a fence that we submit to the gpu and then wait
167         // on before releasing the GrVkSecondaryCBDrawContext resources. To simulate that for this
168         // test (and since we are running single threaded anyways), we will just force a sync of
169         // the gpu and cpu here.
170         td->fContext->contextPriv().getGpu()->testingOnly_flushGpuAndSync();
171 
172         td->fDrawContext->releaseResources();
173         // We release the GrContext here manually to test that we waited long enough before
174         // releasing the GrVkSecondaryCBDrawContext. This simulates when a client is able to delete
175         // the GrContext it used to imported the secondary command buffer. If we had released the
176         // GrContext's resources earlier (before waiting on the gpu above), we would get vulkan
177         // validation layer errors saying we freed some vulkan objects while they were still in use
178         // on the GPU.
179         td->fContext->releaseResourcesAndAbandonContext();
180     }
181 
182 
onSnapGpuDrawHandler(GrBackendApi backendApi,const SkMatrix & matrix,const SkIRect & clipBounds,const SkImageInfo & bufferInfo)183     std::unique_ptr<GpuDrawHandler> onSnapGpuDrawHandler(GrBackendApi backendApi,
184                                                          const SkMatrix& matrix,
185                                                          const SkIRect& clipBounds,
186                                                          const SkImageInfo& bufferInfo) override {
187         if (backendApi != GrBackendApi::kVulkan) {
188             return nullptr;
189         }
190         std::unique_ptr<GpuDrawHandler> draw;
191         if (fContext) {
192             draw.reset(new DrawHandlerImport(this, ImportDraw, ImportSubmitted, matrix,
193                                              clipBounds, bufferInfo));
194         } else {
195             draw.reset(new DrawHandlerBasic(fInterface, fWidth, fHeight));
196         }
197         return draw;
198     }
199 
onGetBounds()200     SkRect onGetBounds() override {
201         return SkRect::MakeLTRB(fWidth / 2, 0, fWidth, fHeight);
202     }
203 
onDraw(SkCanvas *)204     void onDraw(SkCanvas*) override {
205         SkASSERT(false);
206     }
207 
208 private:
209     const GrVkInterface* fInterface;
210     GrContext*           fContext;
211     sk_sp<GrVkSecondaryCBDrawContext> fDrawContext;
212     int32_t              fWidth;
213     int32_t              fHeight;
214 
215     typedef SkDrawable INHERITED;
216 };
217 
draw_drawable_test(skiatest::Reporter * reporter,GrContext * context,GrContext * childContext)218 void draw_drawable_test(skiatest::Reporter* reporter, GrContext* context, GrContext* childContext) {
219     GrVkGpu* gpu = static_cast<GrVkGpu*>(context->contextPriv().getGpu());
220 
221     const SkImageInfo ii = SkImageInfo::Make(DEV_W, DEV_H, kRGBA_8888_SkColorType,
222                                              kPremul_SkAlphaType);
223     sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(context, SkBudgeted::kNo,
224                                                          ii, 0, kTopLeft_GrSurfaceOrigin, nullptr));
225     SkCanvas* canvas = surface->getCanvas();
226     canvas->clear(SK_ColorBLUE);
227 
228     sk_sp<TestDrawable> drawable(new TestDrawable(gpu->vkInterface(), childContext, DEV_W, DEV_H));
229     canvas->drawDrawable(drawable.get());
230 
231     SkPaint paint;
232     paint.setColor(SK_ColorGREEN);
233     SkIRect rect = SkIRect::MakeLTRB(0, DEV_H/2, DEV_W, DEV_H);
234     canvas->drawIRect(rect, paint);
235 
236     // read pixels
237     SkBitmap bitmap;
238     bitmap.allocPixels(ii);
239     canvas->readPixels(bitmap, 0, 0);
240 
241     const uint32_t* canvasPixels = static_cast<const uint32_t*>(bitmap.getPixels());
242     bool failureFound = false;
243     SkPMColor expectedPixel;
244     for (int cy = 0; cy < DEV_H && !failureFound; ++cy) {
245         for (int cx = 0; cx < DEV_W && !failureFound; ++cx) {
246             SkPMColor canvasPixel = canvasPixels[cy * DEV_W + cx];
247             if (cy < DEV_H / 2) {
248                 if (cx < DEV_W / 2) {
249                     expectedPixel = 0xFFFF0000; // Blue
250                 } else {
251                     expectedPixel = 0xFF0000FF; // Red
252                 }
253             } else {
254                 expectedPixel = 0xFF00FF00; // Green
255             }
256             if (expectedPixel != canvasPixel) {
257                 failureFound = true;
258                 ERRORF(reporter, "Wrong color at %d, %d. Got 0x%08x when we expected 0x%08x",
259                        cx, cy, canvasPixel, expectedPixel);
260             }
261         }
262     }
263 }
264 
DEF_GPUTEST_FOR_VULKAN_CONTEXT(VkDrawableTest,reporter,ctxInfo)265 DEF_GPUTEST_FOR_VULKAN_CONTEXT(VkDrawableTest, reporter, ctxInfo) {
266     draw_drawable_test(reporter, ctxInfo.grContext(), nullptr);
267 }
268 
DEF_GPUTEST(VkDrawableImportTest,reporter,options)269 DEF_GPUTEST(VkDrawableImportTest, reporter, options) {
270     for (int typeInt = 0; typeInt < sk_gpu_test::GrContextFactory::kContextTypeCnt; ++typeInt) {
271         sk_gpu_test::GrContextFactory::ContextType contextType =
272                 (sk_gpu_test::GrContextFactory::ContextType) typeInt;
273         if (contextType != sk_gpu_test::GrContextFactory::kVulkan_ContextType) {
274             continue;
275         }
276         sk_gpu_test::GrContextFactory factory(options);
277         sk_gpu_test::ContextInfo ctxInfo = factory.getContextInfo(
278                 contextType, sk_gpu_test::GrContextFactory::ContextOverrides::kDisableNVPR);
279         skiatest::ReporterContext ctx(
280                    reporter, SkString(sk_gpu_test::GrContextFactory::ContextTypeName(contextType)));
281         if (ctxInfo.grContext()) {
282             sk_gpu_test::ContextInfo child =
283                     factory.getSharedContextInfo(ctxInfo.grContext(), 0);
284             if (!child.grContext()) {
285                 continue;
286             }
287 
288             draw_drawable_test(reporter, ctxInfo.grContext(), child.grContext());
289         }
290     }
291 }
292 
293 #endif
294