SkSL: Add color transform intrinsics

These convert a color between the working color space and a known,
useful space (linear TF, sRGB gamut).

Bug: skia:10479
Change-Id: I3308e691beeaca5120ed0c2e30cf08661caa3684
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/481416
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: John Stiles <johnstiles@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
This commit is contained in:
Brian Osman 2021-12-07 16:05:39 -05:00 committed by SkCQ
parent fd3f23f90f
commit 2f2977e19d
19 changed files with 401 additions and 44 deletions

View File

@ -2,6 +2,22 @@ Skia Graphics Release Notes
This file includes a list of high level updates for each milestone release.
Milestone 99
------------
* Added two new intrinsic functions to SkSL for use in runtime effects:
vec3 toLinearSrgb(vec3 color)
vec3 fromLinearSrgb(vec3 color)
These convert RGB color values between the working color space (the color space of the
destination surface) and a known, fixed color space. `toLinearSrgb` converts a color to the
sRGB color gamut, with a linear transfer function. `fromLinearSrgb` converts a color from that
same color space. These are helpful for effects that need to work in a specific color space, or
want to apply effects (like lighting) that work best in a linear color space.
Note that if the destination surface has no color space (color space is `nullptr`), these
intrinsics will do no conversion, and return the input color unchanged.
https://review.skia.org/481416
* * *
Milestone 98
------------
* The following functions and methods are not defined in SkSurface when SK_SUPPORT_GPU is 0:

View File

@ -49,6 +49,9 @@ bool FuzzSKSL2Pipeline(sk_sp<SkData> bytes) {
String sampleBlender(int index, String src, String dst) override {
return "child_" + SkSL::to_string(index) + ".eval(" + src + ", " + dst + ")";
}
String toLinearSrgb(String color) override { return color; }
String fromLinearSrgb(String color) override { return color; }
};
Callbacks callbacks;

View File

@ -17,6 +17,7 @@
#include "include/effects/SkImageFilters.h"
#include "include/effects/SkRuntimeEffect.h"
#include "include/utils/SkRandom.h"
#include "src/core/SkColorSpacePriv.h"
#include "tools/Resources.h"
enum RT_Flags {
@ -620,6 +621,60 @@ DEF_GM(return new ClipSuperRRect("clip_super_rrect_pow3.5", 3.5);)
// DEF_GM(return new ClipSuperRRect("clip_super_rrect_pow4.5", 4.5);)
// DEF_GM(return new ClipSuperRRect("clip_super_rrect_pow5", 5);)
class LinearGradientRT : public RuntimeShaderGM {
public:
LinearGradientRT() : RuntimeShaderGM("linear_gradient_rt", {256 + 10, 128 + 15}, R"(
layout(color) uniform vec4 in_colors0;
layout(color) uniform vec4 in_colors1;
vec4 main(vec2 p) {
float t = p.x / 256;
if (p.y < 32) {
return mix(in_colors0, in_colors1, t);
} else {
vec3 linColor0 = toLinearSrgb(in_colors0.rgb);
vec3 linColor1 = toLinearSrgb(in_colors1.rgb);
vec3 linColor = mix(linColor0, linColor1, t);
return fromLinearSrgb(linColor).rgb1;
}
}
)") {}
void onDraw(SkCanvas* canvas) override {
// Colors chosen to use values other than 0 and 1 - so that it's obvious if the conversion
// intrinsics are doing anything. (Most transfer functions map 0 -> 0 and 1 -> 1).
SkRuntimeShaderBuilder builder(fEffect);
builder.uniform("in_colors0") = SkColor4f{0.75f, 0.25f, 0.0f, 1.0f};
builder.uniform("in_colors1") = SkColor4f{0.0f, 0.75f, 0.25f, 1.0f};
SkPaint paint;
paint.setShader(builder.makeShader(nullptr, true));
canvas->save();
canvas->clear(SK_ColorWHITE);
canvas->translate(5, 5);
// We draw everything twice. First to a surface with no color management, where the
// intrinsics should do nothing (eg, the top bar should look the same in the top and bottom
// halves). Then to an sRGB surface, where they should produce linearly interpolated
// gradients (the bottom half of the second bar should be brighter than the top half).
for (auto cs : {static_cast<SkColorSpace*>(nullptr), sk_srgb_singleton()}) {
SkImageInfo info = SkImageInfo::Make(
256, 64, kN32_SkColorType, kPremul_SkAlphaType, sk_ref_sp(cs));
auto surface = canvas->makeSurface(info);
if (!surface) {
surface = SkSurface::MakeRaster(info);
}
surface->getCanvas()->drawRect({0, 0, 256, 64}, paint);
canvas->drawImage(surface->makeImageSnapshot(), 0, 0);
canvas->translate(0, 64 + 5);
}
canvas->restore();
}
};
DEF_GM(return new LinearGradientRT;)
DEF_SIMPLE_GM(child_sampling_rt, canvas, 256,256) {
static constexpr char scale[] =
"uniform shader child;"
@ -700,7 +755,7 @@ static sk_sp<SkShader> normal_map_raw_unpremul_image_shader() {
}
static sk_sp<SkShader> lit_shader(sk_sp<SkShader> normals) {
// Simple N.L against a fixed, directional light:
// Simple N-dot-L against a fixed, directional light:
static const char* kSrc = R"(
uniform shader normals;
half4 main(vec2 p) {
@ -713,6 +768,20 @@ static sk_sp<SkShader> lit_shader(sk_sp<SkShader> normals) {
return effect->makeShader(nullptr, &normals, 1, nullptr, true);
}
static sk_sp<SkShader> lit_shader_linear(sk_sp<SkShader> normals) {
// Simple N-dot-L against a fixed, directional light, done in linear space:
static const char* kSrc = R"(
uniform shader normals;
half4 main(vec2 p) {
vec3 n = normalize(normals.eval(p).xyz * 2 - 1);
vec3 l = normalize(vec3(1, -1, 1));
return fromLinearSrgb(saturate(dot(n, l)).xxx).xxx1;
}
)";
auto effect = SkRuntimeEffect::MakeForShader(SkString(kSrc)).effect;
return effect->makeShader(nullptr, &normals, 1, nullptr, true);
}
DEF_SIMPLE_GM(paint_alpha_normals_rt, canvas, 512,512) {
// Various draws, with non-opaque paint alpha. This demonstrates several issues around how
// paint alpha is applied differently on CPU (globally, after all shaders) and GPU (per shader,
@ -789,3 +858,34 @@ DEF_SIMPLE_GM(raw_image_shader_normals_rt, canvas, 768, 512) {
draw_shader(512, 0, lit_shader(normal_map_unpremul_image_shader()), canvas);
draw_shader(512, 256, lit_shader(normal_map_raw_unpremul_image_shader()), canvas);
}
DEF_SIMPLE_GM(lit_shader_linear_rt, canvas, 512, 256) {
// First, make an offscreen surface, so we can control the destination color space:
auto surfInfo = SkImageInfo::Make(512, 256,
kN32_SkColorType,
kPremul_SkAlphaType,
SkColorSpace::MakeSRGB());
auto surface = canvas->makeSurface(surfInfo);
if (!surface) {
surface = SkSurface::MakeRaster(surfInfo);
}
auto draw_shader = [](int x, int y, sk_sp<SkShader> shader, SkCanvas* canvas) {
SkPaint p;
p.setShader(shader);
canvas->save();
canvas->translate(x, y);
canvas->clipRect({0, 0, 256, 256});
canvas->drawPaint(p);
canvas->restore();
};
// We draw two lit spheres - one does math in the working space (so gamma-encoded). The second
// works in linear space, then converts to sRGB. This produces (more accurate) sharp falloff:
draw_shader(0, 0, lit_shader(normal_map_shader()), surface->getCanvas());
draw_shader(256, 0, lit_shader_linear(normal_map_shader()), surface->getCanvas());
// Now draw the offscreen surface back to our original canvas:
canvas->drawImage(surface->makeImageSnapshot(), 0, 0);
}

View File

@ -255,11 +255,12 @@ public:
private:
enum Flags {
kUsesSampleCoords_Flag = 0x1,
kAllowColorFilter_Flag = 0x2,
kAllowShader_Flag = 0x4,
kAllowBlender_Flag = 0x8,
kUsesSampleCoords_Flag = 0x01,
kAllowColorFilter_Flag = 0x02,
kAllowShader_Flag = 0x04,
kAllowBlender_Flag = 0x08,
kSamplesOutsideMain_Flag = 0x10,
kUsesColorTransform_Flag = 0x20,
};
SkRuntimeEffect(std::unique_ptr<SkSL::Program> baseProgram,
@ -281,11 +282,12 @@ private:
SkSL::ProgramKind kind);
uint32_t hash() const { return fHash; }
bool usesSampleCoords() const { return (fFlags & kUsesSampleCoords_Flag); }
bool allowShader() const { return (fFlags & kAllowShader_Flag); }
bool allowColorFilter() const { return (fFlags & kAllowColorFilter_Flag); }
bool allowBlender() const { return (fFlags & kAllowBlender_Flag); }
bool usesSampleCoords() const { return (fFlags & kUsesSampleCoords_Flag); }
bool allowShader() const { return (fFlags & kAllowShader_Flag); }
bool allowColorFilter() const { return (fFlags & kAllowColorFilter_Flag); }
bool allowBlender() const { return (fFlags & kAllowBlender_Flag); }
bool samplesOutsideMain() const { return (fFlags & kSamplesOutsideMain_Flag); }
bool usesColorTransform() const { return (fFlags & kUsesColorTransform_Flag); }
const SkFilterColorProgram* getFilterColorProgram();

View File

@ -244,6 +244,9 @@ SkCustomMeshSpecification::Result SkCustomMeshSpecification::MakeFromSourceWithS
if (!has_main(*vsProgram)) {
RETURN_FAILURE("Vertex shader must have main function.");
}
if (SkSL::Analysis::CallsColorTransformIntrinsics(*vsProgram)) {
RETURN_FAILURE("Color transform intrinsics are not permitted in custom mesh shaders");
}
fsProgram = compiler->convertProgram(SkSL::ProgramKind::kCustomMeshFragment,
SkSL::String(fs.c_str()),
@ -254,6 +257,9 @@ SkCustomMeshSpecification::Result SkCustomMeshSpecification::MakeFromSourceWithS
if (!has_main(*fsProgram)) {
RETURN_FAILURE("Fragment shader must have main function.");
}
if (SkSL::Analysis::CallsColorTransformIntrinsics(*fsProgram)) {
RETURN_FAILURE("Color transform intrinsics are not permitted in custom mesh shaders");
}
}
ColorType ct = color_type(*fsProgram);

View File

@ -249,6 +249,12 @@ SkRuntimeEffect::Result SkRuntimeEffect::MakeInternal(std::unique_ptr<SkSL::Prog
flags |= kSamplesOutsideMain_Flag;
}
// Determine if this effect uses of the color transform intrinsics. Effects need to know this
// so they can allocate color transform objects, etc.
if (SkSL::Analysis::CallsColorTransformIntrinsics(*program)) {
flags |= kUsesColorTransform_Flag;
}
size_t offset = 0;
std::vector<Uniform> uniforms;
std::vector<Child> children;
@ -519,6 +525,14 @@ std::unique_ptr<SkFilterColorProgram> SkFilterColorProgram::Make(const SkRuntime
return nullptr;
}
// TODO(skia:10479): Can we support this? When the color filter is invoked like this, there
// may not be a real working space? If there is, we'd need to add it as a parameter to eval,
// and then coordinate where the relevant uniforms go. For now, just fall back to the slow
// path if we see these intrinsics being called.
if (effect->usesColorTransform()) {
return nullptr;
}
// We require that any children are color filters (not shaders or blenders). In theory, we could
// detect the coords being passed to shader children, and replicate those calls, but that's very
// complicated, and has diminishing returns. (eg, for table lookup color filters).
@ -625,6 +639,17 @@ std::unique_ptr<SkFilterColorProgram> SkFilterColorProgram::Make(const SkRuntime
return {};
}
// We did an early return from this function if we saw any call to these intrinsics, so it
// should be impossible for either of these callbacks to occur:
skvm::Color toLinearSrgb(skvm::Color color) override {
SkDEBUGFAIL("Unexpected color transform intrinsic");
return {};
}
skvm::Color fromLinearSrgb(skvm::Color color) override {
SkDEBUGFAIL("Unexpected color transform intrinsic");
return {};
}
skvm::Builder* fBuilder;
const skvm::Uniforms* fSkslUniforms;
skvm::Uniforms* fChildColorUniforms;
@ -800,6 +825,7 @@ static GrFPResult make_effect_fp(sk_sp<SkRuntimeEffect> effect,
}
auto fp = GrSkSLFP::MakeWithData(std::move(effect),
name,
childArgs.fDstColorInfo->refColorSpace(),
std::move(inputFP),
std::move(destColorFP),
std::move(uniforms),
@ -847,6 +873,26 @@ public:
return blend(SkBlendMode::kSrcOver, src, dst);
}
skvm::Color toLinearSrgb(skvm::Color color) override {
if (!fColorInfo.colorSpace()) {
// These intrinsics do nothing when color management is disabled
return color;
}
return SkColorSpaceXformSteps{fColorInfo.colorSpace(), kUnpremul_SkAlphaType,
sk_srgb_linear_singleton(), kUnpremul_SkAlphaType}
.program(fBuilder, fUniforms, color);
}
skvm::Color fromLinearSrgb(skvm::Color color) override {
if (!fColorInfo.colorSpace()) {
// These intrinsics do nothing when color management is disabled
return color;
}
return SkColorSpaceXformSteps{sk_srgb_linear_singleton(), kUnpremul_SkAlphaType,
fColorInfo.colorSpace(), kUnpremul_SkAlphaType}
.program(fBuilder, fUniforms, color);
}
skvm::Builder* fBuilder;
skvm::Uniforms* fUniforms;
SkArenaAlloc* fAlloc;
@ -1276,6 +1322,7 @@ sk_sp<SkImage> SkRuntimeEffect::makeImage(GrRecordingContext* rContext,
}
auto fp = GrSkSLFP::MakeWithData(sk_ref_sp(this),
"runtime_image",
colorInfo.refColorSpace(),
/*inputFP=*/nullptr,
/*destColorFP=*/nullptr,
std::move(uniforms),

View File

@ -155,6 +155,30 @@ public:
return String(fSelf->invokeChild(index, src.c_str(), dst.c_str(), fArgs).c_str());
}
// These intrinsics take and return 3-component vectors, but child FPs operate on
// 4-component vectors. We use swizzles here to paper over the difference.
String toLinearSrgb(String color) override {
const GrSkSLFP& fp = fArgs.fFp.cast<GrSkSLFP>();
if (fp.fToLinearSrgbChildIndex < 0) {
return color;
}
color = String::printf("(%s).rgb1", color.c_str());
SkString xformedColor = fSelf->invokeChild(
fp.fToLinearSrgbChildIndex, color.c_str(), fArgs);
return String::printf("(%s).rgb", xformedColor.c_str());
}
String fromLinearSrgb(String color) override {
const GrSkSLFP& fp = fArgs.fFp.cast<GrSkSLFP>();
if (fp.fFromLinearSrgbChildIndex < 0) {
return color;
}
color = String::printf("(%s).rgb1", color.c_str());
SkString xformedColor = fSelf->invokeChild(
fp.fFromLinearSrgbChildIndex, color.c_str(), fArgs);
return String::printf("(%s).rgb", xformedColor.c_str());
}
Impl* fSelf;
EmitArgs& fArgs;
const char* fInputColor;
@ -266,6 +290,7 @@ private:
std::unique_ptr<GrSkSLFP> GrSkSLFP::MakeWithData(
sk_sp<SkRuntimeEffect> effect,
const char* name,
sk_sp<SkColorSpace> dstColorSpace,
std::unique_ptr<GrFragmentProcessor> inputFP,
std::unique_ptr<GrFragmentProcessor> destColorFP,
sk_sp<SkData> uniforms,
@ -287,6 +312,9 @@ std::unique_ptr<GrSkSLFP> GrSkSLFP::MakeWithData(
if (destColorFP) {
fp->setDestColorFP(std::move(destColorFP));
}
if (fp->fEffect->usesColorTransform() && dstColorSpace) {
fp->addColorTransformChildren(std::move(dstColorSpace));
}
return fp;
}
@ -314,7 +342,9 @@ GrSkSLFP::GrSkSLFP(const GrSkSLFP& other)
, fName(other.fName)
, fUniformSize(other.fUniformSize)
, fInputChildIndex(other.fInputChildIndex)
, fDestColorChildIndex(other.fDestColorChildIndex) {
, fDestColorChildIndex(other.fDestColorChildIndex)
, fToLinearSrgbChildIndex(other.fToLinearSrgbChildIndex)
, fFromLinearSrgbChildIndex(other.fFromLinearSrgbChildIndex) {
sk_careful_memcpy(this->uniformFlags(),
other.uniformFlags(),
fEffect->uniforms().size() * sizeof(UniformFlags));
@ -349,6 +379,33 @@ void GrSkSLFP::setDestColorFP(std::unique_ptr<GrFragmentProcessor> destColorFP)
this->registerChild(std::move(destColorFP), SkSL::SampleUsage::PassThrough());
}
void GrSkSLFP::addColorTransformChildren(sk_sp<SkColorSpace> dstColorSpace) {
SkASSERTF(fToLinearSrgbChildIndex == -1 && fFromLinearSrgbChildIndex == -1,
"addColorTransformChildren should not be called more than once");
// We use child FPs for the color transforms. They're really just code snippets that get
// invoked, but each one injects a collection of uniforms and helper functions. Doing it
// this way leverages per-FP name mangling to avoid conflicts.
auto workingToLinear = GrColorSpaceXformEffect::Make(nullptr,
dstColorSpace.get(),
kUnpremul_SkAlphaType,
sk_srgb_linear_singleton(),
kUnpremul_SkAlphaType);
auto linearToWorking = GrColorSpaceXformEffect::Make(nullptr,
sk_srgb_linear_singleton(),
kUnpremul_SkAlphaType,
dstColorSpace.get(),
kUnpremul_SkAlphaType);
fToLinearSrgbChildIndex = this->numChildProcessors();
SkASSERT((size_t)fToLinearSrgbChildIndex >= fEffect->fSampleUsages.size());
this->registerChild(std::move(workingToLinear), SkSL::SampleUsage::PassThrough());
fFromLinearSrgbChildIndex = this->numChildProcessors();
SkASSERT((size_t)fFromLinearSrgbChildIndex >= fEffect->fSampleUsages.size());
this->registerChild(std::move(linearToWorking), SkSL::SampleUsage::PassThrough());
}
std::unique_ptr<GrFragmentProcessor::ProgramImpl> GrSkSLFP::onMakeProgramImpl() const {
return std::make_unique<Impl>();
}

View File

@ -103,6 +103,7 @@ public:
static std::unique_ptr<GrSkSLFP> MakeWithData(
sk_sp<SkRuntimeEffect> effect,
const char* name,
sk_sp<SkColorSpace> dstColorSpace,
std::unique_ptr<GrFragmentProcessor> inputFP,
std::unique_ptr<GrFragmentProcessor> destColorFP,
sk_sp<SkData> uniforms,
@ -149,6 +150,10 @@ public:
effect->fChildren.end(),
std::forward<Args>(args)...);
#endif
// This factory is used internally (for "runtime FPs"). We don't pass/know the destination
// color space, so these effects can't use the color transform intrinsics. Callers of this
// factory should instead construct an GrColorSpaceXformEffect as part of the FP tree.
SkASSERT(!effect->usesColorTransform());
size_t uniformPayloadSize = UniformPayloadSize(effect.get());
std::unique_ptr<GrSkSLFP> fp(new (uniformPayloadSize)
@ -172,6 +177,7 @@ private:
void addChild(std::unique_ptr<GrFragmentProcessor> child, bool mergeOptFlags);
void setInput(std::unique_ptr<GrFragmentProcessor> input);
void setDestColorFP(std::unique_ptr<GrFragmentProcessor> destColorFP);
void addColorTransformChildren(sk_sp<SkColorSpace> dstColorSpace);
std::unique_ptr<ProgramImpl> onMakeProgramImpl() const override;
@ -406,6 +412,8 @@ private:
uint32_t fUniformSize;
int fInputChildIndex = -1;
int fDestColorChildIndex = -1;
int fToLinearSrgbChildIndex = -1;
int fFromLinearSrgbChildIndex = -1;
GR_DECLARE_FRAGMENT_PROCESSOR_TEST

View File

@ -126,6 +126,14 @@ private:
SK_ABORT("No children allowed.");
}
String toLinearSrgb(String color) override {
SK_ABORT("Color transform intrinsics not allowed.");
}
String fromLinearSrgb(String Color) override {
SK_ABORT("Color transform intrinsics not allowed.");
}
Impl* fSelf;
GrGLSLShaderBuilder* fBuilder;
const char* fMainName;

View File

@ -336,6 +336,16 @@ bool Analysis::CallsSampleOutsideMain(const Program& program) {
return visitor.visit(program);
}
bool Analysis::CallsColorTransformIntrinsics(const Program& program) {
for (auto [fn, count] : program.usage()->fCallCounts) {
if (count != 0 && (fn->intrinsicKind() == k_toLinearSrgb_IntrinsicKind ||
fn->intrinsicKind() == k_fromLinearSrgb_IntrinsicKind)) {
return true;
}
}
return false;
}
bool Analysis::DetectVarDeclarationWithoutScope(const Statement& stmt, ErrorReporter* errors) {
// A variable declaration can create either a lone VarDeclaration or an unscoped Block
// containing multiple VarDeclaration statements. We need to detect either case.

View File

@ -56,6 +56,8 @@ bool ReferencesFragCoords(const Program& program);
bool CallsSampleOutsideMain(const Program& program);
bool CallsColorTransformIntrinsics(const Program& program);
/**
* Computes the size of the program in a completely flattened state--loops fully unrolled,
* function calls inlined--and rejects programs that exceed an arbitrary upper bound. This is

View File

@ -102,6 +102,8 @@
SKSL_INTRINSIC(unpackSnorm2x16) \
SKSL_INTRINSIC(unpackSnorm4x8) \
SKSL_INTRINSIC(unpackUnorm2x16) \
SKSL_INTRINSIC(unpackUnorm4x8)
SKSL_INTRINSIC(unpackUnorm4x8) \
SKSL_INTRINSIC(toLinearSrgb) \
SKSL_INTRINSIC(fromLinearSrgb)
#endif

View File

@ -468,6 +468,13 @@ ResultCode processCommand(std::vector<SkSL::String>& args) {
dst + ")";
}
String toLinearSrgb(String color) override {
return "toLinearSrgb(" + color + ")";
}
String fromLinearSrgb(String color) override {
return "fromLinearSrgb(" + color + ")";
}
String fOutput;
};
// The .stage output looks almost like valid SkSL, but not quite.

View File

@ -213,6 +213,30 @@ void PipelineStageCodeGenerator::writeChildCall(const ChildCall& c) {
void PipelineStageCodeGenerator::writeFunctionCall(const FunctionCall& c) {
const FunctionDeclaration& function = c.function();
if (function.intrinsicKind() == IntrinsicKind::k_toLinearSrgb_IntrinsicKind ||
function.intrinsicKind() == IntrinsicKind::k_fromLinearSrgb_IntrinsicKind) {
SkASSERT(c.arguments().size() == 1);
String colorArg;
{
AutoOutputBuffer exprBuffer(this);
this->writeExpression(*c.arguments()[0], Precedence::kSequence);
colorArg = exprBuffer.fBuffer.str();
}
switch (function.intrinsicKind()) {
case IntrinsicKind::k_toLinearSrgb_IntrinsicKind:
this->write(fCallbacks->toLinearSrgb(std::move(colorArg)));
break;
case IntrinsicKind::k_fromLinearSrgb_IntrinsicKind:
this->write(fCallbacks->fromLinearSrgb(std::move(colorArg)));
break;
default:
SkUNREACHABLE;
}
return;
}
if (function.isBuiltin()) {
this->write(function.name());
} else {

View File

@ -33,6 +33,9 @@ namespace PipelineStage {
virtual String sampleShader(int index, String coords) = 0;
virtual String sampleColorFilter(int index, String color) = 0;
virtual String sampleBlender(int index, String src, String dst) = 0;
virtual String toLinearSrgb(String color) = 0;
virtual String fromLinearSrgb(String color) = 0;
};
/*

View File

@ -1480,6 +1480,27 @@ Value SkVMGenerator::writeIntrinsicCall(const FunctionCall& c) {
}
case k_not_IntrinsicKind: return unary(args[0], [](skvm::I32 x) { return ~x; });
case k_toLinearSrgb_IntrinsicKind: {
skvm::Color color = {
f32(args[0][0]), f32(args[0][1]), f32(args[0][2]), fBuilder->splat(1.0f)};
color = fCallbacks->toLinearSrgb(color);
Value result(3);
result[0] = color.r;
result[1] = color.g;
result[2] = color.b;
return result;
}
case k_fromLinearSrgb_IntrinsicKind: {
skvm::Color color = {
f32(args[0][0]), f32(args[0][1]), f32(args[0][2]), fBuilder->splat(1.0f)};
color = fCallbacks->fromLinearSrgb(color);
Value result(3);
result[0] = color.r;
result[1] = color.g;
result[2] = color.b;
return result;
}
default:
SkDEBUGFAILF("unsupported intrinsic %s", c.function().description().c_str());
return {};
@ -2107,6 +2128,15 @@ bool ProgramToSkVM(const Program& program,
return fColor;
}
skvm::Color toLinearSrgb(skvm::Color) override {
fUsedUnsupportedFeatures = true;
return fColor;
}
skvm::Color fromLinearSrgb(skvm::Color) override {
fUsedUnsupportedFeatures = true;
return fColor;
}
bool fUsedUnsupportedFeatures = false;
const skvm::Color fColor;
};
@ -2260,6 +2290,15 @@ bool testingOnly_ProgramToSkVMShader(const Program& program,
return blend(SkBlendMode::kSrcOver, src, dst);
}
// TODO(skia:10479): Make these actually convert to/from something like sRGB, for use in
// test files.
skvm::Color toLinearSrgb(skvm::Color color) override {
return color;
}
skvm::Color fromLinearSrgb(skvm::Color color) override {
return color;
}
struct Child {
skvm::Uniform addr;
skvm::I32 rowBytesAsPixels;

View File

@ -28,6 +28,9 @@ public:
virtual skvm::Color sampleShader(int index, skvm::Coord coord) = 0;
virtual skvm::Color sampleColorFilter(int index, skvm::Color color) = 0;
virtual skvm::Color sampleBlender(int index, skvm::Color src, skvm::Color dst) = 0;
virtual skvm::Color toLinearSrgb(skvm::Color color) = 0;
virtual skvm::Color fromLinearSrgb(skvm::Color color) = 0;
};
// Convert 'function' to skvm instructions in 'builder', for use by blends, shaders, & color filters

View File

@ -1,4 +1,4 @@
static uint8_t SKSL_INCLUDE_sksl_public[] = {199,3,
static uint8_t SKSL_INCLUDE_sksl_public[] = {227,3,
7,100,101,103,114,101,101,115,
8,36,103,101,110,84,121,112,101,
7,114,97,100,105,97,110,115,
@ -131,6 +131,8 @@ static uint8_t SKSL_INCLUDE_sksl_public[] = {199,3,
6,102,119,105,100,116,104,
5,99,111,108,111,114,
8,117,110,112,114,101,109,117,108,
12,116,111,76,105,110,101,97,114,83,114,103,98,
14,102,114,111,109,76,105,110,101,97,114,83,114,103,98,
6,99,111,111,114,100,115,
1,115,
6,115,104,97,100,101,114,
@ -141,7 +143,7 @@ static uint8_t SKSL_INCLUDE_sksl_public[] = {199,3,
3,100,115,116,
1,98,
7,98,108,101,110,100,101,114,
48,129,2,
48,133,2,
52,1,0,
17,2,0,
49,2,0,10,0,3,
@ -2342,43 +2344,55 @@ static uint8_t SKSL_INCLUDE_sksl_public[] = {199,3,
46,18,2,
46,153,2,
52,154,2,
17,147,3,
46,103,1,3,
52,155,2,
17,154,3,
49,156,2,156,3,3,
17,132,3,
46,131,1,3,
29,155,2,
17,147,3,1,154,2,
46,131,1,
52,156,2,
17,132,3,
46,131,1,3,
29,157,2,
17,163,3,2,154,2,155,2,
46,23,2,
17,160,3,1,156,2,
46,131,1,
52,158,2,
17,175,3,
46,103,1,3,
52,159,2,
17,182,3,
49,160,2,184,3,3,
29,161,2,
17,191,3,2,158,2,159,2,
46,23,2,
52,162,2,
17,132,3,
46,23,2,3,
52,159,2,
17,169,3,
49,160,2,171,3,3,
51,161,2,2,
46,157,2,
29,162,2,
17,163,3,2,158,2,159,2,
46,23,2,
46,162,2,
52,163,2,
17,183,3,
46,23,2,3,
52,164,2,
17,187,3,
46,23,2,3,
52,165,2,
17,191,3,
49,166,2,193,3,3,
51,167,2,3,
46,157,2,
46,162,2,
29,168,2,
17,163,3,3,163,2,164,2,165,2,
17,197,3,
49,164,2,199,3,3,
51,165,2,2,
46,161,2,
29,166,2,
17,191,3,2,162,2,163,2,
46,23,2,
46,168,2,73,0,
127,2,
46,166,2,
52,167,2,
17,211,3,
46,23,2,3,
52,168,2,
17,215,3,
46,23,2,3,
52,169,2,
17,219,3,
49,170,2,221,3,3,
51,171,2,3,
46,161,2,
46,166,2,
29,172,2,
17,191,3,3,167,2,168,2,169,2,
46,23,2,
46,172,2,75,0,
131,2,
118,0,
33,0,
71,0,
@ -2407,6 +2421,7 @@ static uint8_t SKSL_INCLUDE_sksl_public[] = {199,3,
9,1,
128,0,
138,0,
120,2,
110,2,
48,2,
59,2,
@ -2446,6 +2461,7 @@ static uint8_t SKSL_INCLUDE_sksl_public[] = {199,3,
241,0,
23,0,
61,0,
118,2,
216,1,
17,1,
13,1,

View File

@ -232,6 +232,10 @@ $es3 $genHType fwidth($genHType p);
half4 unpremul(half4 color) { return half4 (color.rgb / max(color.a, 0.0001), color.a); }
float4 unpremul(float4 color) { return float4(color.rgb / max(color.a, 0.0001), color.a); }
// Color space transformation, between the working (destination) space and fixed (known) spaces:
half3 toLinearSrgb(half3 color);
half3 fromLinearSrgb(half3 color);
// SkSL intrinsics that reflect Skia's C++ object model:
half4 $eval(float2 coords, shader s);
half4 $eval(half4 color, colorFilter f);