1 /*
2 * Copyright 2019 Google LLC
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 #include "gm/gm.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkColor.h"
11 #include "include/core/SkPaint.h"
12 #include "include/core/SkRect.h"
13 #include "include/core/SkSurface.h"
14 #include "include/core/SkYUVAInfo.h"
15 #include "include/core/SkYUVAPixmaps.h"
16 #include "include/gpu/GrDirectContext.h"
17 #include "include/gpu/GrRecordingContext.h"
18 #include "src/core/SkAutoPixmapStorage.h"
19 #include "src/core/SkScopeExit.h"
20 #include "tools/Resources.h"
21 #include "tools/ToolUtils.h"
22 #include "tools/gpu/YUVUtils.h"
23
24 namespace {
25 struct AsyncContext {
26 bool fCalled = false;
27 std::unique_ptr<const SkImage::AsyncReadResult> fResult;
28 };
29 } // anonymous namespace
30
31 // Making this a lambda in the test functions caused:
32 // "error: cannot compile this forwarded non-trivially copyable parameter yet"
33 // on x86/Win/Clang bot, referring to 'result'.
async_callback(void * c,std::unique_ptr<const SkImage::AsyncReadResult> result)34 static void async_callback(void* c, std::unique_ptr<const SkImage::AsyncReadResult> result) {
35 auto context = static_cast<AsyncContext*>(c);
36 context->fResult = std::move(result);
37 context->fCalled = true;
38 };
39
40 // Draws the image to a surface, does a asyncRescaleAndReadPixels of the image, and then sticks
41 // the result in a raster image.
42 template <typename Src>
do_read_and_scale(Src * src,GrDirectContext * direct,const SkIRect & srcRect,const SkImageInfo & ii,SkImage::RescaleGamma rescaleGamma,SkFilterQuality quality)43 static sk_sp<SkImage> do_read_and_scale(Src* src,
44 GrDirectContext* direct,
45 const SkIRect& srcRect,
46 const SkImageInfo& ii,
47 SkImage::RescaleGamma rescaleGamma,
48 SkFilterQuality quality) {
49 auto* asyncContext = new AsyncContext();
50 src->asyncRescaleAndReadPixels(ii, srcRect, rescaleGamma, quality, async_callback,
51 asyncContext);
52 if (direct) {
53 direct->submit();
54 }
55 while (!asyncContext->fCalled) {
56 // Only GPU should actually be asynchronous.
57 SkASSERT(direct);
58 direct->checkAsyncWorkCompletion();
59 }
60 if (!asyncContext->fResult) {
61 return nullptr;
62 }
63 SkPixmap pixmap(ii, asyncContext->fResult->data(0), asyncContext->fResult->rowBytes(0));
64 auto releasePixels = [](const void*, void* c) { delete static_cast<AsyncContext*>(c); };
65 return SkImage::MakeFromRaster(pixmap, releasePixels, asyncContext);
66 }
67
68 template <typename Src>
do_read_and_scale_yuv(Src * src,GrDirectContext * direct,SkYUVColorSpace yuvCS,const SkIRect & srcRect,SkISize size,SkImage::RescaleGamma rescaleGamma,SkFilterQuality quality,SkScopeExit * cleanup)69 static sk_sp<SkImage> do_read_and_scale_yuv(Src* src,
70 GrDirectContext* direct,
71 SkYUVColorSpace yuvCS,
72 const SkIRect& srcRect,
73 SkISize size,
74 SkImage::RescaleGamma rescaleGamma,
75 SkFilterQuality quality,
76 SkScopeExit* cleanup) {
77 SkASSERT(!(size.width() & 0b1) && !(size.height() & 0b1));
78
79 SkISize uvSize = {size.width()/2, size.height()/2};
80 SkImageInfo yII = SkImageInfo::Make(size, kGray_8_SkColorType, kPremul_SkAlphaType);
81 SkImageInfo uvII = SkImageInfo::Make(uvSize, kGray_8_SkColorType, kPremul_SkAlphaType);
82
83 AsyncContext asyncContext;
84 src->asyncRescaleAndReadPixelsYUV420(yuvCS, SkColorSpace::MakeSRGB(), srcRect, size,
85 rescaleGamma, quality, async_callback, &asyncContext);
86 if (direct) {
87 direct->submit();
88 }
89 while (!asyncContext.fCalled) {
90 // Only GPU should actually be asynchronous.
91 SkASSERT(direct);
92 direct->checkAsyncWorkCompletion();
93 }
94 if (!asyncContext.fResult) {
95 return nullptr;
96 }
97 SkYUVAInfo yuvaInfo(size,
98 SkYUVAInfo::PlaneConfig::kY_U_V,
99 SkYUVAInfo::Subsampling::k420,
100 yuvCS);
101 SkPixmap yuvPMs[] = {
102 {yII, asyncContext.fResult->data(0), asyncContext.fResult->rowBytes(0)},
103 {uvII, asyncContext.fResult->data(1), asyncContext.fResult->rowBytes(1)},
104 {uvII, asyncContext.fResult->data(2), asyncContext.fResult->rowBytes(2)}
105 };
106 auto pixmaps = SkYUVAPixmaps::FromExternalPixmaps(yuvaInfo, yuvPMs);
107 SkASSERT(pixmaps.isValid());
108 auto lazyYUVImage = sk_gpu_test::LazyYUVImage::Make(pixmaps);
109 SkASSERT(lazyYUVImage);
110 return lazyYUVImage->refImage(direct, sk_gpu_test::LazyYUVImage::Type::kFromTextures);
111 }
112
113 // Draws a grid of rescales. The columns are none, low, and high filter quality. The rows are
114 // rescale in src gamma and rescale in linear gamma.
115 template <typename Src>
do_rescale_grid(SkCanvas * canvas,Src * src,GrDirectContext * direct,const SkIRect & srcRect,SkISize newSize,bool doYUV420,SkString * errorMsg,int pad=0)116 static skiagm::DrawResult do_rescale_grid(SkCanvas* canvas,
117 Src* src,
118 GrDirectContext* direct,
119 const SkIRect& srcRect,
120 SkISize newSize,
121 bool doYUV420,
122 SkString* errorMsg,
123 int pad = 0) {
124 if (doYUV420 && !direct) {
125 errorMsg->printf("YUV420 only supported on direct GPU for now.");
126 return skiagm::DrawResult::kSkip;
127 }
128 if (canvas->imageInfo().colorType() == kUnknown_SkColorType) {
129 *errorMsg = "Not supported on recording/vector backends.";
130 return skiagm::DrawResult::kSkip;
131 }
132 const auto ii = canvas->imageInfo().makeDimensions(newSize);
133
134 SkYUVColorSpace yuvColorSpace = kRec601_SkYUVColorSpace;
135 canvas->save();
136 for (auto gamma : {SkImage::RescaleGamma::kSrc, SkImage::RescaleGamma::kLinear}) {
137 canvas->save();
138 for (auto quality : {kNone_SkFilterQuality, kLow_SkFilterQuality, kHigh_SkFilterQuality}) {
139 SkScopeExit cleanup;
140 sk_sp<SkImage> result;
141 if (doYUV420) {
142 result = do_read_and_scale_yuv(src, direct, yuvColorSpace, srcRect, newSize, gamma,
143 quality, &cleanup);
144 if (!result) {
145 errorMsg->printf("YUV420 async call failed. Allowed for now.");
146 return skiagm::DrawResult::kSkip;
147 }
148 int nextCS = static_cast<int>(yuvColorSpace + 1) % (kLastEnum_SkYUVColorSpace + 1);
149 yuvColorSpace = static_cast<SkYUVColorSpace>(nextCS);
150 } else {
151 result = do_read_and_scale(src, direct, srcRect, ii, gamma, quality);
152 if (!result) {
153 errorMsg->printf("async read call failed.");
154 return skiagm::DrawResult::kFail;
155 }
156 }
157 canvas->drawImage(result, 0, 0);
158 canvas->translate(newSize.width() + pad, 0);
159 }
160 canvas->restore();
161 canvas->translate(0, newSize.height() + pad);
162 }
163 canvas->restore();
164 return skiagm::DrawResult::kOk;
165 }
166
do_rescale_image_grid(SkCanvas * canvas,const char * imageFile,const SkIRect & srcRect,SkISize newSize,bool doSurface,bool doYUV420,SkString * errorMsg)167 static skiagm::DrawResult do_rescale_image_grid(SkCanvas* canvas,
168 const char* imageFile,
169 const SkIRect& srcRect,
170 SkISize newSize,
171 bool doSurface,
172 bool doYUV420,
173 SkString* errorMsg) {
174 auto image = GetResourceAsImage(imageFile);
175 if (!image) {
176 errorMsg->printf("Could not load image file %s.", imageFile);
177 return skiagm::DrawResult::kFail;
178 }
179 if (canvas->imageInfo().colorType() == kUnknown_SkColorType) {
180 *errorMsg = "Not supported on recording/vector backends.";
181 return skiagm::DrawResult::kSkip;
182 }
183
184 auto dContext = GrAsDirectContext(canvas->recordingContext());
185 if (!dContext && canvas->recordingContext()) {
186 *errorMsg = "Not supported in DDL mode";
187 return skiagm::DrawResult::kSkip;
188 }
189
190 if (doSurface) {
191 // Turn the image into a surface in order to call the read and rescale API
192 auto surfInfo = image->imageInfo().makeDimensions(image->dimensions());
193 auto surface = canvas->makeSurface(surfInfo);
194 if (!surface && surfInfo.colorType() == kBGRA_8888_SkColorType) {
195 surfInfo = surfInfo.makeColorType(kRGBA_8888_SkColorType);
196 surface = canvas->makeSurface(surfInfo);
197 }
198 if (!surface) {
199 *errorMsg = "Could not create surface for image.";
200 // When testing abandoned GrContext we expect surface creation to fail.
201 if (canvas->recordingContext() && canvas->recordingContext()->abandoned()) {
202 return skiagm::DrawResult::kSkip;
203 }
204 return skiagm::DrawResult::kFail;
205 }
206 SkPaint paint;
207 paint.setBlendMode(SkBlendMode::kSrc);
208 surface->getCanvas()->drawImage(image, 0, 0, &paint);
209 return do_rescale_grid(canvas, surface.get(), dContext, srcRect, newSize,
210 doYUV420, errorMsg);
211 } else if (dContext) {
212 image = image->makeTextureImage(dContext);
213 if (!image) {
214 *errorMsg = "Could not create image.";
215 // When testing abandoned GrContext we expect surface creation to fail.
216 if (canvas->recordingContext() && canvas->recordingContext()->abandoned()) {
217 return skiagm::DrawResult::kSkip;
218 }
219 return skiagm::DrawResult::kFail;
220 }
221 }
222 return do_rescale_grid(canvas, image.get(), dContext, srcRect, newSize, doYUV420,
223 errorMsg);
224 }
225
226 #define DEF_RESCALE_AND_READ_SURF_GM(IMAGE_FILE, TAG, SRC_RECT, W, H) \
227 DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_##TAG, canvas, errorMsg, 3 * W, 2 * H) { \
228 ToolUtils::draw_checkerboard(canvas, SK_ColorDKGRAY, SK_ColorLTGRAY, 25); \
229 return do_rescale_image_grid(canvas, #IMAGE_FILE, SRC_RECT, {W, H}, true, false, \
230 errorMsg); \
231 }
232
233 #define DEF_RESCALE_AND_READ_YUV_SURF_GM(IMAGE_FILE, TAG, SRC_RECT, W, H) \
234 DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_yuv420_##TAG, canvas, errorMsg, 3 * W, 2 * H) { \
235 ToolUtils::draw_checkerboard(canvas, SK_ColorDKGRAY, SK_ColorLTGRAY, 25); \
236 return do_rescale_image_grid(canvas, #IMAGE_FILE, SRC_RECT, {W, H}, true, true, errorMsg); \
237 }
238
239 #define DEF_RESCALE_AND_READ_IMG_GM(IMAGE_FILE, TAG, SRC_RECT, W, H) \
240 DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_##TAG, canvas, errorMsg, 3 * W, 2 * H) { \
241 ToolUtils::draw_checkerboard(canvas, SK_ColorDKGRAY, SK_ColorLTGRAY, 25); \
242 return do_rescale_image_grid(canvas, #IMAGE_FILE, SRC_RECT, {W, H}, false, false, \
243 errorMsg); \
244 }
245
246 #define DEF_RESCALE_AND_READ_YUV_IMG_GM(IMAGE_FILE, TAG, SRC_RECT, W, H) \
247 DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_yuv420_##TAG, canvas, errorMsg, 3 * W, 2 * H) { \
248 ToolUtils::draw_checkerboard(canvas, SK_ColorDKGRAY, SK_ColorLTGRAY, 25); \
249 return do_rescale_image_grid(canvas, #IMAGE_FILE, SRC_RECT, {W, H}, true, true, errorMsg); \
250 }
251
252 DEF_RESCALE_AND_READ_YUV_SURF_GM(
253 images/yellow_rose.webp, rose, SkIRect::MakeXYWH(50, 5, 200, 150), 410, 376)
254
255 DEF_RESCALE_AND_READ_YUV_IMG_GM(
256 images/yellow_rose.webp, rose_down, SkIRect::MakeXYWH(50, 5, 200, 150), 106, 60)
257
258 DEF_RESCALE_AND_READ_SURF_GM(
259 images/yellow_rose.webp, rose, SkIRect::MakeXYWH(100, 20, 100, 100), 410, 410)
260
261 DEF_RESCALE_AND_READ_SURF_GM(images/dog.jpg, dog_down, SkIRect::MakeXYWH(0, 10, 180, 150), 45, 45)
262 DEF_RESCALE_AND_READ_IMG_GM(images/dog.jpg, dog_up, SkIRect::MakeWH(180, 180), 800, 400)
263
264 DEF_RESCALE_AND_READ_IMG_GM(
265 images/text.png, text_down, SkIRect::MakeWH(637, 105), (int)(0.7 * 637), (int)(0.7 * 105))
266 DEF_RESCALE_AND_READ_SURF_GM(
267 images/text.png, text_up, SkIRect::MakeWH(637, 105), (int)(1.2 * 637), (int)(1.2 * 105))
268 DEF_RESCALE_AND_READ_IMG_GM(images/text.png,
269 text_up_large,
270 SkIRect::MakeXYWH(300, 0, 300, 105),
271 (int)(2.4 * 300),
272 (int)(2.4 * 105))
273
274 // Exercises non-scaling YUV420. Reads from the original canvas's surface in order to
275 // exercise case where source surface is not a texture (in glbert config).
276 DEF_SIMPLE_GM_CAN_FAIL(async_yuv_no_scale, canvas, errorMsg, 400, 300) {
277 auto surface = canvas->getSurface();
278 if (!surface) {
279 *errorMsg = "Not supported on recording/vector backends.";
280 return skiagm::DrawResult::kSkip;
281 }
282
283 auto dContext = GrAsDirectContext(surface->recordingContext());
284 if (!dContext && surface->recordingContext()) {
285 *errorMsg = "Not supported in DDL mode";
286 return skiagm::DrawResult::kSkip;
287 }
288
289 auto image = GetResourceAsImage("images/yellow_rose.webp");
290 if (!image) {
291 return skiagm::DrawResult::kFail;
292 }
293 SkPaint paint;
294 canvas->drawImage(image.get(), 0, 0);
295
296 SkScopeExit scopeExit;
297 auto yuvImage = do_read_and_scale_yuv(
298 surface, dContext, kRec601_SkYUVColorSpace, SkIRect::MakeWH(400, 300),
299 {400, 300}, SkImage::RescaleGamma::kSrc, kNone_SkFilterQuality, &scopeExit);
300
301 canvas->clear(SK_ColorWHITE);
302 canvas->drawImage(yuvImage.get(), 0, 0);
303
304 return skiagm::DrawResult::kOk;
305 }
306
307 DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_no_bleed, canvas, errorMsg, 60, 60) {
308 if (canvas->imageInfo().colorType() == kUnknown_SkColorType) {
309 *errorMsg = "Not supported on recording/vector backends.";
310 return skiagm::DrawResult::kSkip;
311 }
312
313 auto dContext = GrAsDirectContext(canvas->recordingContext());
314 if (!dContext && canvas->recordingContext()) {
315 *errorMsg = "Not supported in DDL mode";
316 return skiagm::DrawResult::kSkip;
317 }
318
319 static constexpr int kBorder = 5;
320 static constexpr int kInner = 5;
321 const auto srcRect = SkIRect::MakeXYWH(kBorder, kBorder, kInner, kInner);
322 auto surfaceII =
323 SkImageInfo::Make(kInner + 2 * kBorder, kInner + 2 * kBorder, kRGBA_8888_SkColorType,
324 kPremul_SkAlphaType, SkColorSpace::MakeSRGB());
325 auto surface = canvas->makeSurface(surfaceII);
326 if (!surface) {
327 *errorMsg = "Could not create surface for image.";
328 // When testing abandoned GrContext we expect surface creation to fail.
329 if (canvas->recordingContext() && canvas->recordingContext()->abandoned()) {
330 return skiagm::DrawResult::kSkip;
331 }
332 return skiagm::DrawResult::kFail;
333 }
334 surface->getCanvas()->clear(SK_ColorRED);
335 surface->getCanvas()->save();
336 surface->getCanvas()->clipRect(SkRect::Make(srcRect), SkClipOp::kIntersect, false);
337 surface->getCanvas()->clear(SK_ColorBLUE);
338 surface->getCanvas()->restore();
339 static constexpr int kPad = 2;
340 canvas->translate(kPad, kPad);
341 skiagm::DrawResult result;
342 SkISize downSize = {static_cast<int>(kInner/2), static_cast<int>(kInner / 2)};
343 result = do_rescale_grid(canvas, surface.get(), dContext, srcRect, downSize, false, errorMsg,
344 kPad);
345
346 if (result != skiagm::DrawResult::kOk) {
347 return result;
348 }
349 canvas->translate(0, 4 * downSize.height());
350 SkISize upSize = {static_cast<int>(kInner * 3.5), static_cast<int>(kInner * 4.6)};
351 result = do_rescale_grid(canvas, surface.get(), dContext, srcRect, upSize, false, errorMsg,
352 kPad);
353 if (result != skiagm::DrawResult::kOk) {
354 return result;
355 }
356 return skiagm::DrawResult::kOk;
357 }
358