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:
parent
fd3f23f90f
commit
2f2977e19d
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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),
|
||||
|
@ -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>();
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user