DEF_TEST(SkColorSpace_New_TransferFnStages, r) { // We'll create a little SkRasterPipelineBlitter-like scenario, // blending the same src color over the same dst color, but with // three different transfer functions, for simplicity the same for src and dst. SkColor src = 0x7f7f0000; SkColor dsts[3]; for (SkColor& dst : dsts) { dst = 0xff007f00; } auto gamut = SkMatrix44::I(); auto blending = SkColorSpace_New::Blending::Linear; SkColorSpace_New linear{SkColorSpace_New::TransferFn::MakeLinear(), gamut, blending}, srgb{SkColorSpace_New::TransferFn::MakeSRGB(), gamut, blending}, gamma{SkColorSpace_New::TransferFn::MakeGamma(3), gamut, blending}; SkColor* dst = dsts; for (const SkColorSpace_New* cs : {&linear, &srgb, &gamma}) { SkJumper_MemoryCtx src_ctx = { &src, 0 }, dst_ctx = { dst++, 0 }; SkRasterPipeline_<256> p; p.append(SkRasterPipeline::load_8888, &src_ctx); cs->transferFn().linearizeSrc(&p); p.append(SkRasterPipeline::premul); p.append(SkRasterPipeline::load_8888_dst, &dst_ctx); cs->transferFn().linearizeDst(&p); p.append(SkRasterPipeline::premul_dst); p.append(SkRasterPipeline::srcover); p.append(SkRasterPipeline::unpremul); cs->transferFn().encodeSrc(&p); p.append(SkRasterPipeline::store_8888, &dst_ctx); p.run(0,0,1,1); } // Double check the uninteresting channels: alpha's opaque, no blue. REPORTER_ASSERT(r, SkColorGetA(dsts[0]) == 0xff && SkColorGetB(dsts[0]) == 0x00); REPORTER_ASSERT(r, SkColorGetA(dsts[1]) == 0xff && SkColorGetB(dsts[1]) == 0x00); REPORTER_ASSERT(r, SkColorGetA(dsts[2]) == 0xff && SkColorGetB(dsts[2]) == 0x00); // Because we're doing linear blending, a more-exponential transfer function will // brighten the encoded values more when linearizing. So we expect to see that // linear is darker than sRGB, and sRGB in turn is darker than gamma 3. REPORTER_ASSERT(r, SkColorGetR(dsts[0]) < SkColorGetR(dsts[1])); REPORTER_ASSERT(r, SkColorGetR(dsts[1]) < SkColorGetR(dsts[2])); REPORTER_ASSERT(r, SkColorGetG(dsts[0]) < SkColorGetG(dsts[1])); REPORTER_ASSERT(r, SkColorGetG(dsts[1]) < SkColorGetG(dsts[2])); }
DEF_TEST(sk_pipeline_srgb_edge_cases, r) { // We need to run at least 4 pixels to make sure we hit all specializations. SkPM4f colors[4] = { {{0,1,1,1}}, {{0,0,0,0}}, {{0,0,0,0}}, {{0,0,0,0}} }; auto& color = colors[0]; void* dst = &color; SkRasterPipeline_<256> p; p.append(SkRasterPipeline::uniform_color, &color); p.append(SkRasterPipeline::to_srgb); p.append(SkRasterPipeline::store_f32, &dst); p.run(0,0,4); if (color.r() != 0.0f) { ERRORF(r, "expected to_srgb() to map 0.0f to 0.0f, got %f", color.r()); } if (color.g() != 1.0f) { float f = color.g(); uint32_t x; memcpy(&x, &f, 4); ERRORF(r, "expected to_srgb() to map 1.0f to 1.0f, got %f (%08x)", color.g(), x); } }
DEF_TEST(sk_pipeline_srgb_roundtrip, r) { uint32_t reds[256]; for (int i = 0; i < 256; i++) { reds[i] = i; } SkJumper_MemoryCtx ptr = { reds, 0 }; SkRasterPipeline_<256> p; p.append(SkRasterPipeline::load_8888, &ptr); p.append(SkRasterPipeline::from_srgb); p.append(SkRasterPipeline::to_srgb); p.append(SkRasterPipeline::store_8888, &ptr); p.run(0,0,256,1); for (int i = 0; i < 256; i++) { if (reds[i] != (uint32_t)i) { ERRORF(r, "%d doesn't round trip, %d", i, reds[i]); } } }
DEF_TEST(sk_pipeline_srgb_roundtrip, r) { uint32_t reds[256]; for (int i = 0; i < 256; i++) { reds[i] = i; } auto ptr = (void*)reds; SkRasterPipeline_<256> p; p.append(SkRasterPipeline::load_8888, &ptr); p.append_from_srgb(kUnpremul_SkAlphaType); p.append(SkRasterPipeline::to_srgb); p.append(SkRasterPipeline::store_8888, &ptr); p.run(0,0,256); for (int i = 0; i < 256; i++) { if (reds[i] != (uint32_t)i) { ERRORF(r, "%d doesn't round trip, %d", i, reds[i]); } } }
SkBlitter* SkRasterPipelineBlitter::Create(const SkPixmap& dst, const SkPaint& paint, SkArenaAlloc* alloc, const SkRasterPipeline& shaderPipeline, SkShaderBase::Context* burstCtx, bool is_opaque, bool is_constant) { auto blitter = alloc->make<SkRasterPipelineBlitter>(dst, paint.getBlendMode(), alloc, burstCtx); // Our job in this factory is to fill out the blitter's color pipeline. // This is the common front of the full blit pipelines, each constructed lazily on first use. // The full blit pipelines handle reading and writing the dst, blending, coverage, dithering. auto colorPipeline = &blitter->fColorPipeline; // Let's get the shader in first. if (burstCtx) { colorPipeline->append(SkRasterPipeline::load_f32, &blitter->fShaderOutput); } else { colorPipeline->extend(shaderPipeline); } // If there's a color filter it comes next. if (auto colorFilter = paint.getColorFilter()) { colorFilter->appendStages(colorPipeline, dst.colorSpace(), alloc, is_opaque); is_opaque = is_opaque && (colorFilter->getFlags() & SkColorFilter::kAlphaUnchanged_Flag); } // Not all formats make sense to dither (think, F16). We set their dither rate to zero. // We need to decide if we're going to dither now to keep is_constant accurate. if (paint.isDither()) { switch (dst.info().colorType()) { default: blitter->fDitherRate = 0.0f; break; case kARGB_4444_SkColorType: blitter->fDitherRate = 1/15.0f; break; case kRGB_565_SkColorType: blitter->fDitherRate = 1/63.0f; break; case kGray_8_SkColorType: case kRGBA_8888_SkColorType: case kBGRA_8888_SkColorType: blitter->fDitherRate = 1/255.0f; break; } // TODO: for constant colors, we could try to measure the effect of dithering, and if // it has no value (i.e. all variations result in the same 32bit color, then we // could disable it (for speed, by not adding the stage). } is_constant = is_constant && (blitter->fDitherRate == 0.0f); // We're logically done here. The code between here and return blitter is all optimization. // A pipeline that's still constant here can collapse back into a constant color. if (is_constant) { SkPM4f storage; SkPM4f* constantColor = &storage; colorPipeline->append(SkRasterPipeline::store_f32, &constantColor); colorPipeline->run(0,0,1); colorPipeline->reset(); colorPipeline->append_uniform_color(alloc, *constantColor); is_opaque = constantColor->a() == 1.0f; } // We can strength-reduce SrcOver into Src when opaque. if (is_opaque && blitter->fBlend == SkBlendMode::kSrcOver) { blitter->fBlend = SkBlendMode::kSrc; } // When we're drawing a constant color in Src mode, we can sometimes just memset. // (The previous two optimizations help find more opportunities for this one.) if (is_constant && blitter->fBlend == SkBlendMode::kSrc) { // Run our color pipeline all the way through to produce what we'd memset when we can. // Not all blits can memset, so we need to keep colorPipeline too. SkRasterPipeline_<256> p; p.extend(*colorPipeline); blitter->fDstPtr = &blitter->fMemsetColor; blitter->append_store(&p); p.run(0,0,1); blitter->fCanMemsetInBlitH = true; } return blitter; }