xref: /aosp_15_r20/external/skia/tests/VkDrawableTest.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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 initializers to work
9 
10 #include "include/core/SkTypes.h"
11 
12 #if defined(SK_GANESH) && defined(SK_VULKAN)
13 #include "include/core/SkAlphaType.h"
14 #include "include/core/SkBitmap.h"
15 #include "include/core/SkCanvas.h"
16 #include "include/core/SkColor.h"
17 #include "include/core/SkColorType.h"
18 #include "include/core/SkDrawable.h"
19 #include "include/core/SkImageInfo.h"
20 #include "include/core/SkMatrix.h"
21 #include "include/core/SkPaint.h"
22 #include "include/core/SkRect.h"
23 #include "include/core/SkRefCnt.h"
24 #include "include/core/SkSamplingOptions.h"
25 #include "include/core/SkString.h"
26 #include "include/core/SkSurface.h"
27 #include "include/gpu/GpuTypes.h"
28 #include "include/gpu/ganesh/GrContextOptions.h"
29 #include "include/gpu/ganesh/GrDirectContext.h"
30 #include "include/gpu/ganesh/GrTypes.h"
31 #include "include/gpu/ganesh/SkSurfaceGanesh.h"
32 #include "include/gpu/ganesh/vk/GrBackendDrawableInfo.h"
33 #include "include/gpu/ganesh/vk/GrVkTypes.h"
34 #include "include/private/chromium/GrVkSecondaryCBDrawContext.h"
35 #include "src/gpu/ganesh/GrDirectContextPriv.h"
36 #include "src/gpu/ganesh/vk/GrVkGpu.h"
37 #include "src/gpu/ganesh/vk/GrVkUtil.h"
38 #include "tests/CtsEnforcement.h"
39 #include "tests/Test.h"
40 #include "tools/gpu/ContextType.h"
41 
42 #include <vulkan/vulkan_core.h>
43 #include <cstdint>
44 #include <memory>
45 
46 namespace skgpu { struct VulkanInterface; }
47 
48 using sk_gpu_test::GrContextFactory;
49 
50 static const int DEV_W = 16, DEV_H = 16;
51 
52 class TestDrawable : public SkDrawable {
53 public:
TestDrawable(const skgpu::VulkanInterface * interface,GrDirectContext * dContext,int32_t width,int32_t height)54     TestDrawable(const skgpu::VulkanInterface* interface, GrDirectContext* dContext,
55                  int32_t width, int32_t height)
56             : INHERITED()
57             , fInterface(interface)
58             , fDContext(dContext)
59             , fWidth(width)
60             , fHeight(height) {}
61 
~TestDrawable()62     ~TestDrawable() override {}
63 
64     class DrawHandlerBasic : public GpuDrawHandler {
65     public:
DrawHandlerBasic(const skgpu::VulkanInterface * interface,int32_t width,int32_t height)66         DrawHandlerBasic(const skgpu::VulkanInterface* interface, int32_t width, int32_t height)
67             : INHERITED()
68             , fInterface(interface)
69             , fWidth(width)
70             , fHeight(height) {}
~DrawHandlerBasic()71         ~DrawHandlerBasic() override {}
72 
draw(const GrBackendDrawableInfo & info)73         void draw(const GrBackendDrawableInfo& info) override {
74             GrVkDrawableInfo vkInfo;
75             SkAssertResult(info.getVkDrawableInfo(&vkInfo));
76 
77             // Clear to Red
78             VkClearColorValue vkColor;
79             vkColor.float32[0] = 1.0f; // r
80             vkColor.float32[1] = 0.0f; // g
81             vkColor.float32[2] = 0.0f; // b
82             vkColor.float32[3] = 1.0f; // a
83 
84             // Clear right half of render target
85             VkClearRect clearRect;
86             clearRect.rect.offset = { fWidth / 2, 0 };
87             clearRect.rect.extent = { (uint32_t)fWidth / 2, (uint32_t)fHeight };
88             clearRect.baseArrayLayer = 0;
89             clearRect.layerCount = 1;
90 
91             VkClearAttachment attachment;
92             attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
93             attachment.colorAttachment = vkInfo.fColorAttachmentIndex;
94             attachment.clearValue.color = vkColor;
95 
96             GR_VK_CALL(fInterface, CmdClearAttachments(vkInfo.fSecondaryCommandBuffer,
97                                                        1,
98                                                        &attachment,
99                                                        1,
100                                                        &clearRect));
101             vkInfo.fDrawBounds->offset = { fWidth / 2, 0 };
102             vkInfo.fDrawBounds->extent = { (uint32_t)fWidth / 2, (uint32_t)fHeight };
103         }
104     private:
105         const skgpu::VulkanInterface* fInterface;
106         int32_t                       fWidth;
107         int32_t                       fHeight;
108 
109         using INHERITED = GpuDrawHandler;
110     };
111 
112     typedef void (*DrawProc)(TestDrawable*, const SkMatrix&, const SkIRect&,
113                              const SkImageInfo&, const GrVkDrawableInfo&);
114     typedef void (*SubmitProc)(TestDrawable*);
115 
116     // Exercises the exporting of a secondary command buffer from one context and then importing
117     // it into a second context. We then draw to the secondary command buffer from the second
118     // context.
119     class DrawHandlerImport : public GpuDrawHandler {
120     public:
DrawHandlerImport(TestDrawable * td,DrawProc drawProc,SubmitProc submitProc,const SkMatrix & matrix,const SkIRect & clipBounds,const SkImageInfo & bufferInfo)121         DrawHandlerImport(TestDrawable* td, DrawProc drawProc, SubmitProc submitProc,
122                           const SkMatrix& matrix,
123                           const SkIRect& clipBounds,
124                           const SkImageInfo& bufferInfo)
125             : INHERITED()
126             , fTestDrawable(td)
127             , fDrawProc(drawProc)
128             , fSubmitProc(submitProc)
129             , fMatrix(matrix)
130             , fClipBounds(clipBounds)
131             , fBufferInfo(bufferInfo) {}
~DrawHandlerImport()132         ~DrawHandlerImport() override {
133             fSubmitProc(fTestDrawable);
134         }
135 
draw(const GrBackendDrawableInfo & info)136         void draw(const GrBackendDrawableInfo& info) override {
137             GrVkDrawableInfo vkInfo;
138             SkAssertResult(info.getVkDrawableInfo(&vkInfo));
139 
140             fDrawProc(fTestDrawable, fMatrix, fClipBounds, fBufferInfo, vkInfo);
141         }
142     private:
143         TestDrawable*     fTestDrawable;
144         DrawProc          fDrawProc;
145         SubmitProc        fSubmitProc;
146         const SkMatrix    fMatrix;
147         const SkIRect     fClipBounds;
148         const SkImageInfo fBufferInfo;
149 
150         using INHERITED = GpuDrawHandler;
151     };
152 
153     // Helper function to test drawing to a secondary command buffer that we imported into the
154     // context using a GrVkSecondaryCBDrawContext.
ImportDraw(TestDrawable * td,const SkMatrix & matrix,const SkIRect & clipBounds,const SkImageInfo & bufferInfo,const GrVkDrawableInfo & info)155     static void ImportDraw(TestDrawable* td, const SkMatrix& matrix, const SkIRect& clipBounds,
156                            const SkImageInfo& bufferInfo, const GrVkDrawableInfo& info) {
157         td->fDrawContext = GrVkSecondaryCBDrawContext::Make(td->fDContext, bufferInfo,
158                                                             info, nullptr);
159         if (!td->fDrawContext) {
160             return;
161         }
162 
163         SkCanvas* canvas = td->fDrawContext->getCanvas();
164         canvas->clipRect(SkRect::Make(clipBounds));
165         canvas->setMatrix(matrix);
166 
167         SkIRect rect = SkIRect::MakeXYWH(td->fWidth/2, 0, td->fWidth/4, td->fHeight);
168         SkPaint paint;
169         paint.setColor(SK_ColorRED);
170         canvas->drawIRect(rect, paint);
171 
172         // Draw to an offscreen target so that we end up with a mix of "real" secondary command
173         // buffers and the imported secondary command buffer. Also we do two separate offscreen
174         // draws to test that we don't share scratch textures. The GrResourceAllocator would think
175         // that the two offscreens can share a texture here since we draw them to the SCB before
176         // drawing the 2nd offscreen. However, since the SCB ends up not being submitted the GPU
177         // immediately we need to make sure to not share offscreen textures or else the second will
178         // overwrite the first. This test makes sure this non sharing logic is workng correctly.
179         sk_sp<SkSurface> surf =
180                 SkSurfaces::RenderTarget(td->fDContext, skgpu::Budgeted::kYes, bufferInfo);
181         surf->getCanvas()->clear(SK_ColorBLUE);
182 
183         SkRect dstRect = SkRect::MakeXYWH(td->fWidth/4, 0, td->fWidth/4, td->fHeight);
184         SkRect srcRect = SkRect::MakeIWH(td->fWidth/4, td->fHeight);
185         canvas->drawImageRect(surf->makeImageSnapshot(), srcRect, dstRect, SkSamplingOptions(),
186                               &paint, SkCanvas::kStrict_SrcRectConstraint);
187 
188         surf = SkSurfaces::RenderTarget(td->fDContext, skgpu::Budgeted::kYes, bufferInfo);
189         surf->getCanvas()->clear(SK_ColorRED);
190 
191         dstRect = SkRect::MakeXYWH(3*td->fWidth/4, 0, td->fWidth/4, td->fHeight);
192         canvas->drawImageRect(surf->makeImageSnapshot(), srcRect, dstRect, SkSamplingOptions(),
193                               &paint, SkCanvas::kStrict_SrcRectConstraint);
194 
195         surf.reset();
196 
197         td->fDrawContext->flush();
198     }
199 
200     // Helper function to test waiting for the imported secondary command buffer to be submitted on
201     // its original context and then cleaning up the GrVkSecondaryCBDrawContext from this context.
ImportSubmitted(TestDrawable * td)202     static void ImportSubmitted(TestDrawable* td) {
203         // Typical use case here would be to create a fence that we submit to the gpu and then wait
204         // on before releasing the GrVkSecondaryCBDrawContext resources. To simulate that for this
205         // test (and since we are running single threaded anyways), we will just force a sync of
206         // the gpu and cpu here.
207         td->fDContext->submit(GrSyncCpu::kYes);
208 
209         td->fDrawContext->releaseResources();
210         // We release the context here manually to test that we waited long enough before
211         // releasing the GrVkSecondaryCBDrawContext. This simulates when a client is able to delete
212         // the context it used to imported the secondary command buffer. If we had released the
213         // context's resources earlier (before waiting on the gpu above), we would get vulkan
214         // validation layer errors saying we freed some vulkan objects while they were still in use
215         // on the GPU.
216         td->fDContext->releaseResourcesAndAbandonContext();
217     }
218 
219 
onSnapGpuDrawHandler(GrBackendApi backendApi,const SkMatrix & matrix,const SkIRect & clipBounds,const SkImageInfo & bufferInfo)220     std::unique_ptr<GpuDrawHandler> onSnapGpuDrawHandler(GrBackendApi backendApi,
221                                                          const SkMatrix& matrix,
222                                                          const SkIRect& clipBounds,
223                                                          const SkImageInfo& bufferInfo) override {
224         if (backendApi != GrBackendApi::kVulkan) {
225             return nullptr;
226         }
227         std::unique_ptr<GpuDrawHandler> draw;
228         if (fDContext) {
229             draw = std::make_unique<DrawHandlerImport>(this, ImportDraw, ImportSubmitted, matrix,
230                                                        clipBounds, bufferInfo);
231         } else {
232             draw = std::make_unique<DrawHandlerBasic>(fInterface, fWidth, fHeight);
233         }
234         return draw;
235     }
236 
onGetBounds()237     SkRect onGetBounds() override {
238         return SkRect::MakeLTRB(fWidth / 2, 0, fWidth, fHeight);
239     }
240 
onDraw(SkCanvas *)241     void onDraw(SkCanvas*) override {
242         SkASSERT(false);
243     }
244 
245 private:
246     const skgpu::VulkanInterface*     fInterface;
247     GrDirectContext*                  fDContext;
248     sk_sp<GrVkSecondaryCBDrawContext> fDrawContext;
249     int32_t                           fWidth;
250     int32_t                           fHeight;
251 
252     using INHERITED = SkDrawable;
253 };
254 
draw_drawable_test(skiatest::Reporter * reporter,GrDirectContext * dContext,GrDirectContext * childDContext)255 void draw_drawable_test(skiatest::Reporter* reporter,
256                         GrDirectContext* dContext,
257                         GrDirectContext* childDContext) {
258     GrVkGpu* gpu = static_cast<GrVkGpu*>(dContext->priv().getGpu());
259 
260     const SkImageInfo ii = SkImageInfo::Make(DEV_W, DEV_H, kRGBA_8888_SkColorType,
261                                              kPremul_SkAlphaType);
262     sk_sp<SkSurface> surface(SkSurfaces::RenderTarget(
263             dContext, skgpu::Budgeted::kNo, ii, 0, kTopLeft_GrSurfaceOrigin, nullptr));
264     SkCanvas* canvas = surface->getCanvas();
265     canvas->clear(SK_ColorBLUE);
266 
267     sk_sp<TestDrawable> drawable(new TestDrawable(gpu->vkInterface(), childDContext, DEV_W, DEV_H));
268     canvas->drawDrawable(drawable.get());
269 
270     SkPaint paint;
271     paint.setColor(SK_ColorGREEN);
272     SkIRect rect = SkIRect::MakeLTRB(0, DEV_H/2, DEV_W, DEV_H);
273     canvas->drawIRect(rect, paint);
274 
275     // read pixels
276     SkBitmap bitmap;
277     bitmap.allocPixels(ii);
278     canvas->readPixels(bitmap, 0, 0);
279 
280     const uint32_t* canvasPixels = static_cast<const uint32_t*>(bitmap.getPixels());
281     bool failureFound = false;
282     SkPMColor expectedPixel;
283     for (int cy = 0; cy < DEV_H && !failureFound; ++cy) {
284         for (int cx = 0; cx < DEV_W && !failureFound; ++cx) {
285             SkPMColor canvasPixel = canvasPixels[cy * DEV_W + cx];
286             if (cy < DEV_H / 2) {
287                 if (cx < DEV_W / 2) {
288                     expectedPixel = 0xFFFF0000; // Blue
289                 } else {
290                     expectedPixel = 0xFF0000FF; // Red
291                 }
292             } else {
293                 expectedPixel = 0xFF00FF00; // Green
294             }
295             if (expectedPixel != canvasPixel) {
296                 failureFound = true;
297                 ERRORF(reporter, "Wrong color at %d, %d. Got 0x%08x when we expected 0x%08x",
298                        cx, cy, canvasPixel, expectedPixel);
299             }
300         }
301     }
302 }
303 
DEF_GANESH_TEST_FOR_VULKAN_CONTEXT(VkDrawableTest,reporter,ctxInfo,CtsEnforcement::kApiLevel_T)304 DEF_GANESH_TEST_FOR_VULKAN_CONTEXT(VkDrawableTest, reporter, ctxInfo, CtsEnforcement::kApiLevel_T) {
305     draw_drawable_test(reporter, ctxInfo.directContext(), nullptr);
306 }
307 
DEF_GANESH_TEST(VkDrawableImportTest,reporter,options,CtsEnforcement::kApiLevel_T)308 DEF_GANESH_TEST(VkDrawableImportTest, reporter, options, CtsEnforcement::kApiLevel_T) {
309     for (int typeInt = 0; typeInt < skgpu::kContextTypeCount; ++typeInt) {
310         skgpu::ContextType contextType = static_cast<skgpu::ContextType>(typeInt);
311         if (contextType != skgpu::ContextType::kVulkan) {
312             continue;
313         }
314         GrContextOptions childOptions = options;
315         // Part of our testing of secondary command buffers here is that we don't recycle scratch
316         // textures that are drawing into the SCB. The reason being that the SCB gets played back
317         // later and thus logically gets reordered to the end. This can mess up our resource
318         // allocator which thinks it is safe to reuse things. To test this behavior we need to make
319         // sure we aren't pre-emptively reordering draws.
320         childOptions.fReduceOpsTaskSplitting = GrContextOptions::Enable::kNo;
321         sk_gpu_test::GrContextFactory factory(childOptions);
322         sk_gpu_test::ContextInfo ctxInfo = factory.getContextInfo(contextType);
323         skiatest::ReporterContext ctx(reporter, SkString(skgpu::ContextTypeName(contextType)));
324         if (ctxInfo.directContext()) {
325             sk_gpu_test::ContextInfo child =
326                     factory.getSharedContextInfo(ctxInfo.directContext(), 0);
327             if (!child.directContext()) {
328                 continue;
329             }
330 
331             draw_drawable_test(reporter, ctxInfo.directContext(), child.directContext());
332         }
333     }
334 }
335 
336 #endif
337