Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
ProcessorTest.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2016 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
14#include "include/core/SkRect.h"
17#include "include/core/SkSize.h"
24#include "include/gpu/GrTypes.h"
30#include "src/base/SkRandom.h"
31#include "src/gpu/KeyBuilder.h"
33#include "src/gpu/Swizzle.h"
48#include "src/gpu/ganesh/SkGr.h"
56#include "tests/Test.h"
57#include "tests/TestHarness.h"
58#include "tests/TestUtils.h"
59#include "tools/EncodeUtils.h"
61
62#include <algorithm>
63#include <atomic>
64#include <cmath>
65#include <cstdint>
66#include <initializer_list>
67#include <memory>
68#include <random>
69#include <string>
70#include <tuple>
71#include <utility>
72#include <vector>
73
74using namespace skia_private;
75
76class GrDstProxyView;
78class GrOpFlushState;
79class GrProgramInfo;
82class SkArenaAlloc;
83enum class GrXferBarrierFlags;
84struct GrContextOptions;
85struct GrShaderCaps;
86
87namespace {
88class TestOp : public GrMeshDrawOp {
89public:
91 static GrOp::Owner Make(GrRecordingContext* rContext,
92 std::unique_ptr<GrFragmentProcessor> fp) {
93 return GrOp::Make<TestOp>(rContext, std::move(fp));
94 }
95
96 const char* name() const override { return "TestOp"; }
97
98 void visitProxies(const GrVisitProxyFunc& func) const override {
99 fProcessors.visitProxies(func);
100 }
101
102 FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
103
104 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
105 GrClampType clampType) override {
106 static constexpr GrProcessorAnalysisColor kUnknownColor;
107 SkPMColor4f overrideColor;
108 return fProcessors.finalize(
110 &GrUserStencilSettings::kUnused, caps, clampType, &overrideColor);
111 }
112
113private:
114 friend class ::GrOp; // for ctor
115
116 TestOp(std::unique_ptr<GrFragmentProcessor> fp)
117 : INHERITED(ClassID()), fProcessors(std::move(fp)) {
118 this->setBounds(SkRect::MakeWH(100, 100), HasAABloat::kNo, IsHairline::kNo);
119 }
120
121 GrProgramInfo* programInfo() override { return nullptr; }
122 void onCreateProgramInfo(const GrCaps*,
124 const GrSurfaceProxyView& writeView,
125 bool usesMSAASurface,
127 const GrDstProxyView&,
128 GrXferBarrierFlags renderPassXferBarriers,
129 GrLoadOp colorLoadOp) override {}
130 void onPrePrepareDraws(GrRecordingContext*,
131 const GrSurfaceProxyView& writeView,
133 const GrDstProxyView&,
134 GrXferBarrierFlags renderPassXferBarriers,
135 GrLoadOp colorLoadOp) override {}
136 void onPrepareDraws(GrMeshDrawTarget*) override { return; }
137 void onExecute(GrOpFlushState*, const SkRect&) override { return; }
138
139 GrProcessorSet fProcessors;
140
141 using INHERITED = GrMeshDrawOp;
142};
143
144/**
145 * FP used to test ref counts on owned GrGpuResources. Can also be a parent FP to test counts
146 * of resources owned by child FPs.
147 */
148class TestFP : public GrFragmentProcessor {
149public:
150 static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> child) {
151 return std::unique_ptr<GrFragmentProcessor>(new TestFP(std::move(child)));
152 }
153 static std::unique_ptr<GrFragmentProcessor> Make(const TArray<GrSurfaceProxyView>& views) {
154 return std::unique_ptr<GrFragmentProcessor>(new TestFP(views));
155 }
156
157 const char* name() const override { return "test"; }
158
159 void onAddToKey(const GrShaderCaps&, skgpu::KeyBuilder* b) const override {
160 static std::atomic<int32_t> nextKey{0};
161 b->add32(nextKey++);
162 }
163
164 std::unique_ptr<GrFragmentProcessor> clone() const override {
165 return std::unique_ptr<GrFragmentProcessor>(new TestFP(*this));
166 }
167
168private:
169 TestFP(const TArray<GrSurfaceProxyView>& views)
170 : INHERITED(kTestFP_ClassID, kNone_OptimizationFlags) {
171 for (const GrSurfaceProxyView& view : views) {
172 this->registerChild(GrTextureEffect::Make(view, kUnknown_SkAlphaType));
173 }
174 }
175
176 TestFP(std::unique_ptr<GrFragmentProcessor> child)
177 : INHERITED(kTestFP_ClassID, kNone_OptimizationFlags) {
178 this->registerChild(std::move(child));
179 }
180
181 explicit TestFP(const TestFP& that) : INHERITED(that) {}
182
183 std::unique_ptr<ProgramImpl> onMakeProgramImpl() const override {
184 class Impl : public ProgramImpl {
185 public:
186 void emitCode(EmitArgs& args) override {
187 args.fFragBuilder->codeAppendf("return half4(1);");
188 }
189
190 private:
191 };
192 return std::make_unique<Impl>();
193 }
194
195 bool onIsEqual(const GrFragmentProcessor&) const override { return false; }
196
198};
199} // namespace
200
202 auto dContext = ctxInfo.directContext();
203 GrProxyProvider* proxyProvider = dContext->priv().proxyProvider();
204
205 static constexpr SkISize kDims = {10, 10};
206
207 const GrBackendFormat format =
208 dContext->priv().caps()->getDefaultBackendFormat(GrColorType::kRGBA_8888,
209 GrRenderable::kNo);
210 skgpu::Swizzle swizzle = dContext->priv().caps()->getReadSwizzle(format,
212
213 for (bool makeClone : {false, true}) {
214 for (int parentCnt = 0; parentCnt < 2; parentCnt++) {
215 auto sdc = skgpu::ganesh::SurfaceDrawContext::Make(dContext,
217 nullptr,
219 {1, 1},
221 /*label=*/{});
222 {
224 proxyProvider->createProxy(format,
225 kDims,
226 GrRenderable::kNo,
227 1,
228 skgpu::Mipmapped::kNo,
231 GrProtected::kNo,
232 /*label=*/"ProcessorRefTest");
233
234 {
236 views.push_back({proxy, kTopLeft_GrSurfaceOrigin, swizzle});
237 auto fp = TestFP::Make(std::move(views));
238 for (int i = 0; i < parentCnt; ++i) {
239 fp = TestFP::Make(std::move(fp));
240 }
241 std::unique_ptr<GrFragmentProcessor> clone;
242 if (makeClone) {
243 clone = fp->clone();
244 }
245 GrOp::Owner op = TestOp::Make(dContext, std::move(fp));
246 sdc->addDrawOp(std::move(op));
247 if (clone) {
248 op = TestOp::Make(dContext, std::move(clone));
249 sdc->addDrawOp(std::move(op));
250 }
251 }
252
253 // If the fp is cloned the number of refs should increase by one (for the clone)
254 int expectedProxyRefs = makeClone ? 3 : 2;
255
256 CheckSingleThreadedProxyRefs(reporter, proxy.get(), expectedProxyRefs, -1);
257
258 dContext->flushAndSubmit();
259
260 // just one from the 'proxy' sk_sp
262 }
263 }
264 }
265}
266
267static DEFINE_bool(randomProcessorTest, false,
268 "Use non-deterministic seed for random processor tests?");
269static DEFINE_int(processorSeed, 0,
270 "Use specific seed for processor tests. Overridden by --randomProcessorTest.");
271
272#if defined(GR_TEST_UTILS)
273
274static GrColor input_texel_color(int x, int y, SkScalar delta) {
275 // Delta must be less than 0.5 to prevent over/underflow issues with the input color
276 SkASSERT(delta <= 0.5);
277
278 SkColor color = SkColorSetARGB((uint8_t)(x & 0xFF),
279 (uint8_t)(y & 0xFF),
280 (uint8_t)((x + y) & 0xFF),
281 (uint8_t)((2 * y - x) & 0xFF));
283 // We only apply delta to the r,g, and b channels. This is because we're using this
284 // to test the canTweakAlphaForCoverage() optimization. A processor is allowed
285 // to use the input color's alpha in its calculation and report this optimization.
286 for (int i = 0; i < 3; i++) {
287 if (color4f[i] > 0.5) {
288 color4f[i] -= delta;
289 } else {
290 color4f[i] += delta;
291 }
292 }
293 return color4f.premul().toBytes_RGBA();
294}
295
296// The output buffer must be the same size as the render-target context.
297static void render_fp(GrDirectContext* dContext,
299 std::unique_ptr<GrFragmentProcessor> fp,
300 GrColor* outBuffer) {
301 sdc->fillWithFP(std::move(fp));
302 std::fill_n(outBuffer, sdc->width() * sdc->height(), 0);
304 GrPixmap resultPM(ii, outBuffer, sdc->width()*sizeof(uint32_t));
305 sdc->readPixels(dContext, resultPM, {0, 0});
306}
307
308// This class is responsible for reproducibly generating a random fragment processor.
309// An identical randomly-designed FP can be generated as many times as needed.
310class TestFPGenerator {
311 public:
312 TestFPGenerator() = delete;
313 TestFPGenerator(GrDirectContext* context, GrResourceProvider* resourceProvider)
314 : fContext(context)
315 , fResourceProvider(resourceProvider)
316 , fInitialSeed(synthesizeInitialSeed())
317 , fRandomSeed(fInitialSeed) {}
318
319 uint32_t initialSeed() { return fInitialSeed; }
320
321 bool init() {
322 // Initializes the two test texture proxies that are available to the FP test factories.
323 SkRandom random{fRandomSeed};
324 static constexpr int kTestTextureSize = 256;
325
326 {
327 // Put premul data into the RGBA texture that the test FPs can optionally use.
328 GrColor* rgbaData = new GrColor[kTestTextureSize * kTestTextureSize];
329 for (int y = 0; y < kTestTextureSize; ++y) {
330 for (int x = 0; x < kTestTextureSize; ++x) {
331 rgbaData[kTestTextureSize * y + x] = input_texel_color(
332 random.nextULessThan(256), random.nextULessThan(256), 0.0f);
333 }
334 }
335
336 SkImageInfo ii = SkImageInfo::Make(kTestTextureSize, kTestTextureSize,
339 bitmap.installPixels(
340 ii, rgbaData, ii.minRowBytes(),
341 [](void* addr, void* context) { delete[](GrColor*) addr; }, nullptr);
342 bitmap.setImmutable();
343 auto view = std::get<0>(GrMakeUncachedBitmapProxyView(fContext, bitmap));
344 if (!view || !view.proxy()->instantiate(fResourceProvider)) {
345 SkDebugf("Unable to instantiate RGBA8888 test texture.");
346 return false;
347 }
348 fTestViews[0] = GrProcessorTestData::ViewInfo{view, GrColorType::kRGBA_8888,
350 }
351
352 {
353 // Put random values into the alpha texture that the test FPs can optionally use.
354 uint8_t* alphaData = new uint8_t[kTestTextureSize * kTestTextureSize];
355 for (int y = 0; y < kTestTextureSize; ++y) {
356 for (int x = 0; x < kTestTextureSize; ++x) {
357 alphaData[kTestTextureSize * y + x] = random.nextULessThan(256);
358 }
359 }
360
361 SkImageInfo ii = SkImageInfo::Make(kTestTextureSize, kTestTextureSize,
364 bitmap.installPixels(
365 ii, alphaData, ii.minRowBytes(),
366 [](void* addr, void* context) { delete[](uint8_t*) addr; }, nullptr);
367 bitmap.setImmutable();
368 auto view = std::get<0>(GrMakeUncachedBitmapProxyView(fContext, bitmap));
369 if (!view || !view.proxy()->instantiate(fResourceProvider)) {
370 SkDebugf("Unable to instantiate A8 test texture.");
371 return false;
372 }
373 fTestViews[1] = GrProcessorTestData::ViewInfo{view, GrColorType::kAlpha_8,
375 }
376
377 return true;
378 }
379
380 void reroll() {
381 // Feed our current random seed into SkRandom to generate a new seed.
382 SkRandom random{fRandomSeed};
383 fRandomSeed = random.nextU();
384 }
385
386 std::unique_ptr<GrFragmentProcessor> make(int type, int randomTreeDepth,
387 std::unique_ptr<GrFragmentProcessor> inputFP) {
388 // This will generate the exact same randomized FP (of each requested type) each time
389 // it's called. Call `reroll` to get a different FP.
390 SkRandom random{fRandomSeed};
391 GrProcessorTestData testData{&random, fContext, randomTreeDepth,
392 static_cast<int>(std::size(fTestViews)), fTestViews,
393 std::move(inputFP)};
394 return GrFragmentProcessorTestFactory::MakeIdx(type, &testData);
395 }
396
397 std::unique_ptr<GrFragmentProcessor> make(int type, int randomTreeDepth,
400 return make(type, randomTreeDepth, GrTextureEffect::Make(std::move(view), alpha));
401 }
402
403 private:
404 static uint32_t synthesizeInitialSeed() {
405 if (FLAGS_randomProcessorTest) {
406 std::random_device rd;
407 return rd();
408 } else {
409 return FLAGS_processorSeed;
410 }
411 }
412
413 GrDirectContext* fContext; // owned by caller
414 GrResourceProvider* fResourceProvider; // owned by caller
415 const uint32_t fInitialSeed;
416 uint32_t fRandomSeed;
417 GrProcessorTestData::ViewInfo fTestViews[2];
418};
419
420// Creates an array of color values from input_texel_color(), to be used as an input texture.
421static std::vector<GrColor> make_input_pixels(int width, int height, SkScalar delta) {
422 std::vector<GrColor> pixel(width * height);
423 for (int y = 0; y < width; ++y) {
424 for (int x = 0; x < height; ++x) {
425 pixel[width * y + x] = input_texel_color(x, y, delta);
426 }
427 }
428
429 return pixel;
430}
431
432// Creates a texture of premul colors used as the output of the fragment processor that precedes
433// the fragment processor under test. An array of W*H colors are passed in as the texture data.
434static GrSurfaceProxyView make_input_texture(GrRecordingContext* context,
435 int width, int height, GrColor* pixel) {
438 bitmap.installPixels(ii, pixel, ii.minRowBytes());
439 bitmap.setImmutable();
440 return std::get<0>(GrMakeUncachedBitmapProxyView(context, bitmap));
441}
442
443// We tag logged data as unpremul to avoid conversion when encoding as PNG. The input texture
444// actually contains unpremul data. Also, even though we made the result data by rendering into
445// a "unpremul" SurfaceDrawContext, our input texture is unpremul and outside of the random
446// effect configuration, we didn't do anything to ensure the output is actually premul. We just
447// don't currently allow kUnpremul GrSurfaceDrawContexts.
448static constexpr auto kLogAlphaType = kUnpremul_SkAlphaType;
449
450static bool log_pixels(GrColor* pixels, int widthHeight, SkString* dst) {
452 SkImageInfo::Make(widthHeight, widthHeight, kRGBA_8888_SkColorType, kLogAlphaType);
453 SkBitmap bmp;
454 bmp.installPixels(info, pixels, widthHeight * sizeof(GrColor));
455 return ToolUtils::BitmapToBase64DataURI(bmp, dst);
456}
457
458static bool log_texture_view(GrDirectContext* dContext, GrSurfaceProxyView src, SkString* dst) {
459 SkImageInfo ii = SkImageInfo::Make(src.proxy()->dimensions(), kRGBA_8888_SkColorType,
460 kLogAlphaType);
461
462 auto sContext = dContext->priv().makeSC(std::move(src), ii.colorInfo());
463 SkBitmap bm;
465 SkAssertResult(sContext->readPixels(dContext, bm.pixmap(), {0, 0}));
466 return ToolUtils::BitmapToBase64DataURI(bm, dst);
467}
468
469static bool fuzzy_color_equals(const SkPMColor4f& c1, const SkPMColor4f& c2) {
470 // With the loss of precision of rendering into 32-bit color, then estimating the FP's output
471 // from that, it is not uncommon for a valid output to differ from estimate by up to 0.01
472 // (really 1/128 ~ .0078, but frequently floating point issues make that tolerance a little
473 // too unforgiving).
474 static constexpr SkScalar kTolerance = 0.01f;
475 for (int i = 0; i < 4; i++) {
476 if (!SkScalarNearlyEqual(c1[i], c2[i], kTolerance)) {
477 return false;
478 }
479 }
480 return true;
481}
482
483// Given three input colors (color preceding the FP being tested) provided to the FP at the same
484// local coord and the three corresponding FP outputs, this ensures that either:
485// out[0] = fp * in[0].a, out[1] = fp * in[1].a, and out[2] = fp * in[2].a
486// where fp is the pre-modulated color that should not be changing across frames (FP's state doesn't
487// change), OR:
488// out[0] = fp * in[0], out[1] = fp * in[1], and out[2] = fp * in[2]
489// (per-channel modulation instead of modulation by just the alpha channel)
490// It does this by estimating the pre-modulated fp color from one of the input/output pairs and
491// confirms the conditions hold for the other two pairs.
492// It is required that the three input colors have the same alpha as fp is allowed to be a function
493// of the input alpha (but not r, g, or b).
494static bool legal_modulation(const GrColor inGr[3], const GrColor outGr[3]) {
495 // Convert to floating point, which is the number space the FP operates in (more or less)
496 SkPMColor4f inf[3], outf[3];
497 for (int i = 0; i < 3; ++i) {
498 inf[i] = SkPMColor4f::FromBytes_RGBA(inGr[i]);
499 outf[i] = SkPMColor4f::FromBytes_RGBA(outGr[i]);
500 }
501 // This test is only valid if all the input alphas are the same.
502 SkASSERT(inf[0].fA == inf[1].fA && inf[1].fA == inf[2].fA);
503
504 // Reconstruct the output of the FP before the shader modulated its color with the input value.
505 // When the original input is very small, it may cause the final output color to round
506 // to 0, in which case we estimate the pre-modulated color using one of the stepped frames that
507 // will then have a guaranteed larger channel value (since the offset will be added to it).
508 SkPMColor4f fpPreColorModulation = {0,0,0,0};
509 SkPMColor4f fpPreAlphaModulation = {0,0,0,0};
510 for (int i = 0; i < 4; i++) {
511 // Use the most stepped up frame
512 int maxInIdx = inf[0][i] > inf[1][i] ? 0 : 1;
513 maxInIdx = inf[maxInIdx][i] > inf[2][i] ? maxInIdx : 2;
514 const SkPMColor4f& in = inf[maxInIdx];
515 const SkPMColor4f& out = outf[maxInIdx];
516 if (in[i] > 0) {
517 fpPreColorModulation[i] = out[i] / in[i];
518 }
519 if (in[3] > 0) {
520 fpPreAlphaModulation[i] = out[i] / in[3];
521 }
522 }
523
524 // With reconstructed pre-modulated FP output, derive the expected value of fp * input for each
525 // of the transformed input colors.
526 SkPMColor4f expectedForAlphaModulation[3];
527 SkPMColor4f expectedForColorModulation[3];
528 for (int i = 0; i < 3; ++i) {
529 expectedForAlphaModulation[i] = fpPreAlphaModulation * inf[i].fA;
530 expectedForColorModulation[i] = fpPreColorModulation * inf[i];
531 // If the input alpha is 0 then the other channels should also be zero
532 // since the color is assumed to be premul. Modulating zeros by anything
533 // should produce zeros.
534 if (inf[i].fA == 0) {
535 SkASSERT(inf[i].fR == 0 && inf[i].fG == 0 && inf[i].fB == 0);
536 expectedForColorModulation[i] = expectedForAlphaModulation[i] = {0, 0, 0, 0};
537 }
538 }
539
540 bool isLegalColorModulation = fuzzy_color_equals(outf[0], expectedForColorModulation[0]) &&
541 fuzzy_color_equals(outf[1], expectedForColorModulation[1]) &&
542 fuzzy_color_equals(outf[2], expectedForColorModulation[2]);
543
544 bool isLegalAlphaModulation = fuzzy_color_equals(outf[0], expectedForAlphaModulation[0]) &&
545 fuzzy_color_equals(outf[1], expectedForAlphaModulation[1]) &&
546 fuzzy_color_equals(outf[2], expectedForAlphaModulation[2]);
547
548 // This can be enabled to print the values that caused this check to fail.
549 if ((false)) {
550 if (!isLegalColorModulation && !isLegalAlphaModulation) {
551 SkDebugf("Color modulation test\n\timplied mod color: (%.03f, %.03f, %.03f, %.03f)\n",
552 fpPreColorModulation[0],
553 fpPreColorModulation[1],
554 fpPreColorModulation[2],
555 fpPreColorModulation[3]);
556 for (int i = 0; i < 3; ++i) {
557 SkDebugf("\t(%.03f, %.03f, %.03f, %.03f) -> "
558 "(%.03f, %.03f, %.03f, %.03f) | "
559 "(%.03f, %.03f, %.03f, %.03f), ok: %d\n",
560 inf[i].fR, inf[i].fG, inf[i].fB, inf[i].fA,
561 outf[i].fR, outf[i].fG, outf[i].fB, outf[i].fA,
562 expectedForColorModulation[i].fR, expectedForColorModulation[i].fG,
563 expectedForColorModulation[i].fB, expectedForColorModulation[i].fA,
564 fuzzy_color_equals(outf[i], expectedForColorModulation[i]));
565 }
566 SkDebugf("Alpha modulation test\n\timplied mod color: (%.03f, %.03f, %.03f, %.03f)\n",
567 fpPreAlphaModulation[0],
568 fpPreAlphaModulation[1],
569 fpPreAlphaModulation[2],
570 fpPreAlphaModulation[3]);
571 for (int i = 0; i < 3; ++i) {
572 SkDebugf("\t(%.03f, %.03f, %.03f, %.03f) -> "
573 "(%.03f, %.03f, %.03f, %.03f) | "
574 "(%.03f, %.03f, %.03f, %.03f), ok: %d\n",
575 inf[i].fR, inf[i].fG, inf[i].fB, inf[i].fA,
576 outf[i].fR, outf[i].fG, outf[i].fB, outf[i].fA,
577 expectedForAlphaModulation[i].fR, expectedForAlphaModulation[i].fG,
578 expectedForAlphaModulation[i].fB, expectedForAlphaModulation[i].fA,
579 fuzzy_color_equals(outf[i], expectedForAlphaModulation[i]));
580 }
581 }
582 }
583 return isLegalColorModulation || isLegalAlphaModulation;
584}
585
586DEF_GANESH_TEST_FOR_GL_CONTEXT(ProcessorOptimizationValidationTest,
587 reporter,
588 ctxInfo,
590 GrDirectContext* context = ctxInfo.directContext();
591 GrResourceProvider* resourceProvider = context->priv().resourceProvider();
592 using FPFactory = GrFragmentProcessorTestFactory;
593
594 TestFPGenerator fpGenerator{context, resourceProvider};
595 if (!fpGenerator.init()) {
596 ERRORF(reporter, "Could not initialize TestFPGenerator");
597 return;
598 }
599
600 // Make the destination context for the test.
601 static constexpr int kRenderSize = 256;
604 nullptr,
606 {kRenderSize, kRenderSize},
608 /*label=*/{});
609
610 // Coverage optimization uses three frames with a linearly transformed input texture. The first
611 // frame has no offset, second frames add .2 and .4, which should then be present as a fixed
612 // difference between the frame outputs if the FP is properly following the modulation
613 // requirements of the coverage optimization.
614 static constexpr SkScalar kInputDelta = 0.2f;
615 std::vector<GrColor> inputPixels1 = make_input_pixels(kRenderSize, kRenderSize, 0.0f);
616 std::vector<GrColor> inputPixels2 =
617 make_input_pixels(kRenderSize, kRenderSize, 1 * kInputDelta);
618 std::vector<GrColor> inputPixels3 =
619 make_input_pixels(kRenderSize, kRenderSize, 2 * kInputDelta);
620 GrSurfaceProxyView inputTexture1 =
621 make_input_texture(context, kRenderSize, kRenderSize, inputPixels1.data());
622 GrSurfaceProxyView inputTexture2 =
623 make_input_texture(context, kRenderSize, kRenderSize, inputPixels2.data());
624 GrSurfaceProxyView inputTexture3 =
625 make_input_texture(context, kRenderSize, kRenderSize, inputPixels3.data());
626
627 // Encoded images are very verbose and this tests many potential images, so only export the
628 // first failure (subsequent failures have a reasonable chance of being related).
629 bool loggedFirstFailure = false;
630 bool loggedFirstWarning = false;
631
632 // Storage for the three frames required for coverage compatibility optimization testing.
633 // Each frame uses the correspondingly numbered inputTextureX.
634 std::vector<GrColor> readData1(kRenderSize * kRenderSize);
635 std::vector<GrColor> readData2(kRenderSize * kRenderSize);
636 std::vector<GrColor> readData3(kRenderSize * kRenderSize);
637
638 // Because processor factories configure themselves in random ways, this is not exhaustive.
639 for (int i = 0; i < FPFactory::Count(); ++i) {
640 int optimizedForOpaqueInput = 0;
641 int optimizedForCoverageAsAlpha = 0;
642 int optimizedForConstantOutputForInput = 0;
643
644#ifdef __MSVC_RUNTIME_CHECKS
645 // This test is infuriatingly slow with MSVC runtime checks enabled
646 static constexpr int kMinimumTrials = 1;
647 static constexpr int kMaximumTrials = 1;
648 static constexpr int kExpectedSuccesses = 1;
649#else
650 // We start by testing each fragment-processor 100 times, watching the optimization bits
651 // that appear. If we see an optimization bit appear in those first 100 trials, we keep
652 // running tests until we see at least five successful trials that have this optimization
653 // bit enabled. If we never see a particular optimization bit after 100 trials, we assume
654 // that this FP doesn't support that optimization at all.
655 static constexpr int kMinimumTrials = 100;
656 static constexpr int kMaximumTrials = 2000;
657 static constexpr int kExpectedSuccesses = 5;
658#endif
659
660 for (int trial = 0;; ++trial) {
661 // Create a randomly-configured FP.
662 fpGenerator.reroll();
663 std::unique_ptr<GrFragmentProcessor> fp =
664 fpGenerator.make(i, /*randomTreeDepth=*/1, inputTexture1);
665
666 // If we have iterated enough times and seen a sufficient number of successes on each
667 // optimization bit that can be returned, stop running trials.
668 if (trial >= kMinimumTrials) {
669 bool moreTrialsNeeded = (optimizedForOpaqueInput > 0 &&
670 optimizedForOpaqueInput < kExpectedSuccesses) ||
671 (optimizedForCoverageAsAlpha > 0 &&
672 optimizedForCoverageAsAlpha < kExpectedSuccesses) ||
673 (optimizedForConstantOutputForInput > 0 &&
674 optimizedForConstantOutputForInput < kExpectedSuccesses);
675 if (!moreTrialsNeeded) break;
676
677 if (trial >= kMaximumTrials) {
678 SkDebugf("Abandoning ProcessorOptimizationValidationTest after %d trials. "
679 "Seed: 0x%08x, processor:\n%s",
680 kMaximumTrials, fpGenerator.initialSeed(), fp->dumpTreeInfo().c_str());
681 break;
682 }
683 }
684
685 // Skip further testing if this trial has no optimization bits enabled.
686 if (!fp->hasConstantOutputForConstantInput() && !fp->preservesOpaqueInput() &&
687 !fp->compatibleWithCoverageAsAlpha()) {
688 continue;
689 }
690
691 // We can make identical copies of the test FP in order to test coverage-as-alpha.
692 if (fp->compatibleWithCoverageAsAlpha()) {
693 // Create and render two identical versions of this FP, but using different input
694 // textures, to check coverage optimization. We don't need to do this step for
695 // constant-output or preserving-opacity tests.
696 render_fp(context, sdc.get(),
697 fpGenerator.make(i, /*randomTreeDepth=*/1, inputTexture2),
698 readData2.data());
699 render_fp(context, sdc.get(),
700 fpGenerator.make(i, /*randomTreeDepth=*/1, inputTexture3),
701 readData3.data());
702 ++optimizedForCoverageAsAlpha;
703 }
704
705 if (fp->hasConstantOutputForConstantInput()) {
706 ++optimizedForConstantOutputForInput;
707 }
708
709 if (fp->preservesOpaqueInput()) {
710 ++optimizedForOpaqueInput;
711 }
712
713 // Draw base frame last so that rtc holds the original FP behavior if we need to dump
714 // the image to the log.
715 render_fp(context, sdc.get(), fpGenerator.make(i, /*randomTreeDepth=*/1, inputTexture1),
716 readData1.data());
717
718 // This test has a history of being flaky on a number of devices. If an FP is logically
719 // violating the optimizations, it's reasonable to expect it to violate requirements on
720 // a large number of pixels in the image. Sporadic pixel violations are more indicative
721 // of device errors and represents a separate problem.
722 static const int kMaxAcceptableFailedPixels =
723 CurrentTestHarnessIsSkQP() ? 0 : // Strict when running as SKQP
724 2 * kRenderSize; // ~0.7% of the image
725
726 // Collect first optimization failure message, to be output later as a warning or an
727 // error depending on whether the rendering "passed" or failed.
728 int failedPixelCount = 0;
729 SkString coverageMessage;
730 SkString opaqueMessage;
731 SkString constMessage;
732 for (int y = 0; y < kRenderSize; ++y) {
733 for (int x = 0; x < kRenderSize; ++x) {
734 bool passing = true;
735 GrColor input = inputPixels1[y * kRenderSize + x];
736 GrColor output = readData1[y * kRenderSize + x];
737
738 if (fp->compatibleWithCoverageAsAlpha()) {
739 GrColor ins[3];
740 ins[0] = input;
741 ins[1] = inputPixels2[y * kRenderSize + x];
742 ins[2] = inputPixels3[y * kRenderSize + x];
743
744 GrColor outs[3];
745 outs[0] = output;
746 outs[1] = readData2[y * kRenderSize + x];
747 outs[2] = readData3[y * kRenderSize + x];
748
749 if (!legal_modulation(ins, outs)) {
750 passing = false;
751 if (coverageMessage.isEmpty()) {
752 coverageMessage.printf(
753 "\"Modulating\" processor did not match alpha-modulation "
754 "nor color-modulation rules.\n"
755 "Input: 0x%08x, Output: 0x%08x, pixel (%d, %d).",
756 input, output, x, y);
757 }
758 }
759 }
760
762 SkPMColor4f output4f = SkPMColor4f::FromBytes_RGBA(output);
763 SkPMColor4f expected4f;
764 if (fp->hasConstantOutputForConstantInput(input4f, &expected4f)) {
765 float rDiff = fabsf(output4f.fR - expected4f.fR);
766 float gDiff = fabsf(output4f.fG - expected4f.fG);
767 float bDiff = fabsf(output4f.fB - expected4f.fB);
768 float aDiff = fabsf(output4f.fA - expected4f.fA);
769 static constexpr float kTol = 4 / 255.f;
770 if (rDiff > kTol || gDiff > kTol || bDiff > kTol || aDiff > kTol) {
771 if (constMessage.isEmpty()) {
772 passing = false;
773
774 constMessage.printf(
775 "Processor claimed output for const input doesn't match "
776 "actual output.\n"
777 "Error: %f, Tolerance: %f, input: (%f, %f, %f, %f), "
778 "actual: (%f, %f, %f, %f), expected(%f, %f, %f, %f).",
779 std::max(rDiff, std::max(gDiff, std::max(bDiff, aDiff))),
780 kTol, input4f.fR, input4f.fG, input4f.fB, input4f.fA,
781 output4f.fR, output4f.fG, output4f.fB, output4f.fA,
782 expected4f.fR, expected4f.fG, expected4f.fB, expected4f.fA);
783 }
784 }
785 }
786 if (input4f.isOpaque() && fp->preservesOpaqueInput() && !output4f.isOpaque()) {
787 passing = false;
788
789 if (opaqueMessage.isEmpty()) {
790 opaqueMessage.printf(
791 "Processor claimed opaqueness is preserved but "
792 "it is not. Input: 0x%08x, Output: 0x%08x.",
793 input, output);
794 }
795 }
796
797 if (!passing) {
798 // Regardless of how many optimizations the pixel violates, count it as a
799 // single bad pixel.
800 failedPixelCount++;
801 }
802 }
803 }
804
805 // Finished analyzing the entire image, see if the number of pixel failures meets the
806 // threshold for an FP violating the optimization requirements.
807 if (failedPixelCount > kMaxAcceptableFailedPixels) {
809 "Processor violated %d of %d pixels, seed: 0x%08x.\n"
810 "Processor:\n%s\nFirst failing pixel details are below:",
811 failedPixelCount, kRenderSize * kRenderSize, fpGenerator.initialSeed(),
812 fp->dumpTreeInfo().c_str());
813
814 // Print first failing pixel's details.
815 if (!coverageMessage.isEmpty()) {
816 ERRORF(reporter, "%s", coverageMessage.c_str());
817 }
818 if (!constMessage.isEmpty()) {
819 ERRORF(reporter, "%s", constMessage.c_str());
820 }
821 if (!opaqueMessage.isEmpty()) {
822 ERRORF(reporter, "%s", opaqueMessage.c_str());
823 }
824
825 if (!loggedFirstFailure) {
826 // Print with ERRORF to make sure the encoded image is output
827 SkString input;
828 log_texture_view(context, inputTexture1, &input);
830 log_pixels(readData1.data(), kRenderSize, &output);
831 ERRORF(reporter, "Input image: %s\n\n"
832 "===========================================================\n\n"
833 "Output image: %s\n", input.c_str(), output.c_str());
834 loggedFirstFailure = true;
835 }
836 } else if (failedPixelCount > 0) {
837 // Don't trigger an error, but don't just hide the failures either.
838 INFOF(reporter, "Processor violated %d of %d pixels (below error threshold), seed: "
839 "0x%08x, processor: %s", failedPixelCount, kRenderSize * kRenderSize,
840 fpGenerator.initialSeed(), fp->dumpInfo().c_str());
841 if (!coverageMessage.isEmpty()) {
842 INFOF(reporter, "%s", coverageMessage.c_str());
843 }
844 if (!constMessage.isEmpty()) {
845 INFOF(reporter, "%s", constMessage.c_str());
846 }
847 if (!opaqueMessage.isEmpty()) {
848 INFOF(reporter, "%s", opaqueMessage.c_str());
849 }
850 if (!loggedFirstWarning) {
851 SkString input;
852 log_texture_view(context, inputTexture1, &input);
854 log_pixels(readData1.data(), kRenderSize, &output);
855 INFOF(reporter, "Input image: %s\n\n"
856 "===========================================================\n\n"
857 "Output image: %s\n", input.c_str(), output.c_str());
858 loggedFirstWarning = true;
859 }
860 }
861 }
862 }
863}
864
865static void assert_processor_equality(skiatest::Reporter* reporter,
866 const GrFragmentProcessor& fp,
867 const GrFragmentProcessor& clone) {
868 REPORTER_ASSERT(reporter, !strcmp(fp.name(), clone.name()),
869 "\n%s", fp.dumpTreeInfo().c_str());
870 REPORTER_ASSERT(reporter, fp.compatibleWithCoverageAsAlpha() ==
872 "\n%s", fp.dumpTreeInfo().c_str());
873 REPORTER_ASSERT(reporter, fp.isEqual(clone),
874 "\n%s", fp.dumpTreeInfo().c_str());
875 REPORTER_ASSERT(reporter, fp.preservesOpaqueInput() == clone.preservesOpaqueInput(),
876 "\n%s", fp.dumpTreeInfo().c_str());
877 REPORTER_ASSERT(reporter, fp.hasConstantOutputForConstantInput() ==
879 "\n%s", fp.dumpTreeInfo().c_str());
880 REPORTER_ASSERT(reporter, fp.numChildProcessors() == clone.numChildProcessors(),
881 "\n%s", fp.dumpTreeInfo().c_str());
882 REPORTER_ASSERT(reporter, fp.sampleUsage() == clone.sampleUsage(),
883 "\n%s", fp.dumpTreeInfo().c_str());
884 REPORTER_ASSERT(reporter, fp.usesSampleCoords() == clone.usesSampleCoords(),
885 "\n%s", fp.dumpTreeInfo().c_str());
886}
887
888static bool verify_identical_render(skiatest::Reporter* reporter, int renderSize,
889 const char* processorType,
890 const GrColor readData1[], const GrColor readData2[]) {
891 // The ProcessorClone test has a history of being flaky on a number of devices. If an FP clone
892 // is logically wrong, it's reasonable to expect it produce a large number of pixel differences
893 // in the image. Sporadic pixel violations are more indicative device errors and represents a
894 // separate problem.
895 static const int maxAcceptableFailedPixels =
896 CurrentTestHarnessIsSkQP() ? 0 : // Strict when running as SKQP
897 2 * renderSize; // ~0.002% of the pixels (size 1024*1024)
898
899 int failedPixelCount = 0;
900 int firstWrongX = 0;
901 int firstWrongY = 0;
902 int idx = 0;
903 for (int y = 0; y < renderSize; ++y) {
904 for (int x = 0; x < renderSize; ++x, ++idx) {
905 if (readData1[idx] != readData2[idx]) {
906 if (!failedPixelCount) {
907 firstWrongX = x;
908 firstWrongY = y;
909 }
910 ++failedPixelCount;
911 }
912 if (failedPixelCount > maxAcceptableFailedPixels) {
913 idx = firstWrongY * renderSize + firstWrongX;
915 "%s produced different output at (%d, %d). "
916 "Input color: 0x%08x, Original Output Color: 0x%08x, "
917 "Clone Output Color: 0x%08x.",
918 processorType, firstWrongX, firstWrongY, input_texel_color(x, y, 0.0f),
919 readData1[idx], readData2[idx]);
920
921 return false;
922 }
923 }
924 }
925
926 return true;
927}
928
929static void log_clone_failure(skiatest::Reporter* reporter, int renderSize,
930 GrDirectContext* context, const GrSurfaceProxyView& inputTexture,
931 GrColor pixelsFP[], GrColor pixelsClone[], GrColor pixelsRegen[]) {
932 // Write the images out as data URLs for inspection.
933 SkString inputURL, origURL, cloneURL, regenURL;
934 if (log_texture_view(context, inputTexture, &inputURL) &&
935 log_pixels(pixelsFP, renderSize, &origURL) &&
936 log_pixels(pixelsClone, renderSize, &cloneURL) &&
937 log_pixels(pixelsRegen, renderSize, &regenURL)) {
939 "\nInput image:\n%s\n\n"
940 "==========================================================="
941 "\n\n"
942 "Orig output image:\n%s\n"
943 "==========================================================="
944 "\n\n"
945 "Clone output image:\n%s\n"
946 "==========================================================="
947 "\n\n"
948 "Regen output image:\n%s\n",
949 inputURL.c_str(), origURL.c_str(), cloneURL.c_str(), regenURL.c_str());
950 }
951}
952
953// Tests that a fragment processor returned by GrFragmentProcessor::clone() is equivalent to its
954// progenitor.
956 GrDirectContext* context = ctxInfo.directContext();
957 GrResourceProvider* resourceProvider = context->priv().resourceProvider();
958
959 TestFPGenerator fpGenerator{context, resourceProvider};
960 if (!fpGenerator.init()) {
961 ERRORF(reporter, "Could not initialize TestFPGenerator");
962 return;
963 }
964
965 // Make the destination context for the test.
966 static constexpr int kRenderSize = 1024;
969 nullptr,
971 {kRenderSize, kRenderSize},
973 /*label=*/{});
974
975 std::vector<GrColor> inputPixels = make_input_pixels(kRenderSize, kRenderSize, 0.0f);
976 GrSurfaceProxyView inputTexture =
977 make_input_texture(context, kRenderSize, kRenderSize, inputPixels.data());
978
979 // On failure we write out images, but just write the first failing set as the print is very
980 // large.
981 bool loggedFirstFailure = false;
982
983 // Storage for the original frame's readback and the readback of its clone.
984 std::vector<GrColor> readDataFP(kRenderSize * kRenderSize);
985 std::vector<GrColor> readDataClone(kRenderSize * kRenderSize);
986 std::vector<GrColor> readDataRegen(kRenderSize * kRenderSize);
987
988 // Because processor factories configure themselves in random ways, this is not exhaustive.
989 for (int i = 0; i < GrFragmentProcessorTestFactory::Count(); ++i) {
990 static constexpr int kTimesToInvokeFactory = 10;
991 for (int j = 0; j < kTimesToInvokeFactory; ++j) {
992 fpGenerator.reroll();
993 std::unique_ptr<GrFragmentProcessor> fp =
994 fpGenerator.make(i, /*randomTreeDepth=*/1, /*inputFP=*/nullptr);
995 std::unique_ptr<GrFragmentProcessor> regen =
996 fpGenerator.make(i, /*randomTreeDepth=*/1, /*inputFP=*/nullptr);
997 std::unique_ptr<GrFragmentProcessor> clone = fp->clone();
998 if (!clone) {
999 ERRORF(reporter, "Clone of processor %s failed.", fp->dumpTreeInfo().c_str());
1000 continue;
1001 }
1002 assert_processor_equality(reporter, *fp, *clone);
1003
1004 // Draw with original and read back the results.
1005 render_fp(context, sdc.get(), std::move(fp), readDataFP.data());
1006
1007 // Draw with clone and read back the results.
1008 render_fp(context, sdc.get(), std::move(clone), readDataClone.data());
1009
1010 // Check that the results are the same.
1011 if (!verify_identical_render(reporter, kRenderSize, "Processor clone",
1012 readDataFP.data(), readDataClone.data())) {
1013 // Dump a description from the regenerated processor (since the original FP has
1014 // already been consumed).
1015 ERRORF(reporter, "FP hierarchy:\n%s", regen->dumpTreeInfo().c_str());
1016
1017 // Render and readback output from the regenerated FP. If this also mismatches, the
1018 // FP itself doesn't generate consistent output. This could happen if:
1019 // - the FP's TestCreate() does not always generate the same FP from a given seed
1020 // - the FP's Make() does not always generate the same FP when given the same inputs
1021 // - the FP itself generates inconsistent pixels (shader UB?)
1022 // - the driver has a bug
1023 render_fp(context, sdc.get(), std::move(regen), readDataRegen.data());
1024
1025 if (!verify_identical_render(reporter, kRenderSize, "Regenerated processor",
1026 readDataFP.data(), readDataRegen.data())) {
1027 ERRORF(reporter, "Output from regen did not match original!\n");
1028 } else {
1029 ERRORF(reporter, "Regenerated processor output matches original results.\n");
1030 }
1031
1032 // If this is the first time we've encountered a cloning failure, log the generated
1033 // images to the reporter as data URLs.
1034 if (!loggedFirstFailure) {
1035 log_clone_failure(reporter, kRenderSize, context, inputTexture,
1036 readDataFP.data(), readDataClone.data(),
1037 readDataRegen.data());
1038 loggedFirstFailure = true;
1039 }
1040 }
1041 }
1042 }
1043}
1044
1045#endif // defined(GR_TEST_UTILS)
#define DEFINE_bool(name, defaultValue, helpString)
#define DEFINE_int(name, defaultValue, helpString)
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition DM.cpp:213
reporter
uint32_t GrColor
Definition GrColor.h:25
#define DEFINE_OP_CLASS_ID
Definition GrOp.h:64
static constexpr float kTolerance
GrClampType
std::function< void(GrSurfaceProxy *, skgpu::Mipmapped)> GrVisitProxyFunc
GrLoadOp
@ kTopLeft_GrSurfaceOrigin
Definition GrTypes.h:148
GrXferBarrierFlags
SkColor4f color
kUnpremul_SkAlphaType
SkAlphaType
Definition SkAlphaType.h:26
@ kUnknown_SkAlphaType
uninitialized
Definition SkAlphaType.h:27
@ kPremul_SkAlphaType
pixel components are premultiplied by alpha
Definition SkAlphaType.h:29
#define SkAssertResult(cond)
Definition SkAssert.h:123
#define SkASSERT(cond)
Definition SkAssert.h:116
@ kAlpha_8_SkColorType
pixel with alpha in 8-bit byte
Definition SkColorType.h:21
@ kRGBA_8888_SkColorType
pixel with 8 bits for red, green, blue, alpha; in 32-bit word
Definition SkColorType.h:24
uint32_t SkColor
Definition SkColor.h:37
static constexpr SkColor SkColorSetARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b)
Definition SkColor.h:49
void SK_SPI SkDebugf(const char format[],...) SK_PRINTF_LIKE(1
std::tuple< GrSurfaceProxyView, GrColorType > GrMakeUncachedBitmapProxyView(GrRecordingContext *rContext, const SkBitmap &bitmap, skgpu::Mipmapped mipmapped, SkBackingFit fit, skgpu::Budgeted budgeted)
Definition SkGr.cpp:253
static std::unique_ptr< SkEncoder > Make(SkWStream *dst, const SkPixmap *src, const SkYUVAPixmaps *srcYUVA, const SkColorSpace *srcYUVAColorSpace, const SkJpegEncoder::Options &options)
static SkPath clip(const SkPath &path, const SkHalfPlane &plane)
Definition SkPath.cpp:3824
#define INHERITED(method,...)
const Context & fContext
static bool SkScalarNearlyEqual(SkScalar x, SkScalar y, SkScalar tolerance=SK_ScalarNearlyZero)
Definition SkScalar.h:107
bool CurrentTestHarnessIsSkQP()
void CheckSingleThreadedProxyRefs(skiatest::Reporter *reporter, GrSurfaceProxy *proxy, int32_t expectedProxyRefs, int32_t expectedBackingRefs)
#define REPORTER_ASSERT(r, cond,...)
Definition Test.h:286
#define DEF_GANESH_TEST_FOR_GL_CONTEXT(name, reporter, context_info, ctsEnforcement)
Definition Test.h:442
#define INFOF(REPORTER,...)
Definition Test.h:298
#define ERRORF(r,...)
Definition Test.h:293
#define DEF_GANESH_TEST_FOR_ALL_CONTEXTS(name, reporter, context_info, ctsEnforcement)
Definition Test.h:431
GrResourceProvider * resourceProvider()
GrDirectContextPriv priv()
bool preservesOpaqueInput() const
bool hasConstantOutputForConstantInput(SkPMColor4f inputColor, SkPMColor4f *outputColor) const
const SkSL::SampleUsage & sampleUsage() const
bool compatibleWithCoverageAsAlpha() const
std::unique_ptr< GrOp > Owner
Definition GrOp.h:72
virtual const char * name() const =0
sk_sp< GrTextureProxy > createProxy(const GrBackendFormat &, SkISize dimensions, GrRenderable, int renderTargetSampleCnt, skgpu::Mipmapped, SkBackingFit, skgpu::Budgeted, GrProtected, std::string_view label, GrInternalSurfaceFlags=GrInternalSurfaceFlags::kNone, UseAllocator useAllocator=UseAllocator::kYes)
std::unique_ptr< skgpu::ganesh::SurfaceContext > makeSC(GrSurfaceProxyView readView, const GrColorInfo &)
static std::unique_ptr< GrFragmentProcessor > Make(GrSurfaceProxyView, SkAlphaType, const SkMatrix &=SkMatrix::I(), GrSamplerState::Filter=GrSamplerState::Filter::kNearest, GrSamplerState::MipmapMode mipmapMode=GrSamplerState::MipmapMode::kNone)
bool installPixels(const SkImageInfo &info, void *pixels, size_t rowBytes, void(*releaseProc)(void *addr, void *context), void *context)
Definition SkBitmap.cpp:323
const SkPixmap & pixmap() const
Definition SkBitmap.h:133
bool tryAllocPixels(const SkImageInfo &info, size_t rowBytes)
Definition SkBitmap.cpp:271
void printf(const char format[],...) SK_PRINTF_LIKE(2
Definition SkString.cpp:534
bool isEmpty() const
Definition SkString.h:130
const char * c_str() const
Definition SkString.h:133
T * get() const
Definition SkRefCnt.h:303
bool readPixels(GrDirectContext *dContext, GrPixmap dst, SkIPoint srcPt)
static std::unique_ptr< SurfaceDrawContext > Make(GrRecordingContext *, GrColorType, sk_sp< GrSurfaceProxy >, sk_sp< SkColorSpace >, GrSurfaceOrigin, const SkSurfaceProps &)
void fillWithFP(std::unique_ptr< GrFragmentProcessor > fp)
float SkScalar
Definition extension.cpp:12
static bool b
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
uint32_t uint32_t * format
const char * name
Definition fuchsia.cc:50
static sk_sp< SkImage > make(sk_sp< SkColorSpace > cs)
Definition mipmap.cpp:65
double y
double x
bool BitmapToBase64DataURI(const SkBitmap &bitmap, SkString *dst)
const uint32_t fp
Definition ref_ptr.h:256
init(device_serial, adb_binary)
Definition _adb_path.py:12
int32_t height
int32_t width
static const GrUserStencilSettings & kUnused
const SkColorInfo & colorInfo() const
size_t minRowBytes() const
static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at)
float fB
blue component
Definition SkColor.h:265
static SkRGBA4f FromBytes_RGBA(uint32_t color)
float fR
red component
Definition SkColor.h:263
static SkRGBA4f FromColor(SkColor color)
float fG
green component
Definition SkColor.h:264
float fA
alpha component
Definition SkColor.h:266
bool isOpaque() const
Definition SkColor.h:344
static constexpr SkRect MakeWH(float w, float h)
Definition SkRect.h:609