Add public API support for SkImageFilters::RuntimeShader

This new image filter constructor enables SkRuntimeEffects to be
used as shaders within the ImageFilter DAG. The shader is created
lazily using the SkRuntimeShaderBuilder enabling the resulting
shader to consume the previous stage of the ImageFilter graph.

Change-Id: I5d6917e34a8e5fdd053399f15a1e2cc7409e686f
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/470459
Reviewed-by: Brian Osman <brianosman@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Derek Sollenberger <djsollen@google.com>
This commit is contained in:
Derek Sollenberger 2021-11-22 14:57:40 -05:00 committed by SkCQ
parent 15ecccf0dd
commit ad9d774c1f
6 changed files with 205 additions and 24 deletions

View File

@ -16,6 +16,10 @@ Milestone 98
could be created, but the CPU backend would fail to index them correctly.
* SkCanvas::drawVertices and SkCanvas::drawPatch variants that did not take SkBlendMode are
removed.
* SkImageFilters::RuntimeShader is a new public API that enables adding RuntimeShaderEffects into
image filter graph.
* * *
Milestone 97
------------

View File

@ -30,9 +30,8 @@ static sk_sp<SkImageFilter> make_filter() {
return child.eval(coord);
}
)")).effect;
return SkMakeRuntimeImageFilter(std::move(effect),
/*uniforms=*/nullptr,
/*input=*/nullptr);
SkRuntimeShaderBuilder builder(std::move(effect));
return SkImageFilters::RuntimeShader(builder, /*childShaderName=*/nullptr, /*input=*/nullptr);
}
DEF_SIMPLE_GM_BG(rtif_distort, canvas, 500, 750, SK_ColorBLACK) {

View File

@ -15,6 +15,8 @@
#include "include/core/SkPicture.h"
#include "include/core/SkRect.h"
#include "include/core/SkTileMode.h"
#include "include/core/SkTypes.h"
#include "include/effects/SkRuntimeEffect.h"
#include <cstddef>
@ -332,6 +334,26 @@ public:
return Picture(std::move(pic), target);
}
#ifdef SK_ENABLE_SKSL
/**
* Create a filter that fills the output with the per-pixel evaluation of the SkShader produced
* by the SkRuntimeShaderBuilder. The shader is defined in the image filter's local coordinate
* system, so it will automatically be affected by SkCanvas' transform.
*
* @param builder The builder used to produce the runtime shader, that will in turn
* fill the result image
* @param childShaderName The name of the child shader defined in the builder that will be
* bound to the input param (or the source image if the input param
* is null). If null the builder can have exactly one child shader,
* which automatically binds the input param.
* @param input The image filter that will be provided as input to the runtime
* shader. If null the implicit source image is used instead
*/
static sk_sp<SkImageFilter> RuntimeShader(const SkRuntimeShaderBuilder& builder,
const char* childShaderName,
sk_sp<SkImageFilter> input);
#endif // SK_ENABLE_SKSL
enum class Dither : bool {
kNo = false,
kYes = true

View File

@ -28,6 +28,7 @@
class GrRecordingContext;
class SkFilterColorProgram;
class SkImage;
class SkRuntimeImageFilter;
namespace SkSL {
class FunctionDefinition;
@ -382,6 +383,10 @@ protected:
: fEffect(std::move(effect))
, fUniforms(SkData::MakeZeroInitialized(fEffect->uniformSize()))
, fChildren(fEffect->children().size()) {}
explicit SkRuntimeEffectBuilder(sk_sp<SkRuntimeEffect> effect, sk_sp<SkData> uniforms)
: fEffect(std::move(effect))
, fUniforms(std::move(uniforms))
, fChildren(fEffect->children().size()) {}
SkRuntimeEffectBuilder(SkRuntimeEffectBuilder&&) = default;
SkRuntimeEffectBuilder(const SkRuntimeEffectBuilder&) = default;
@ -442,6 +447,11 @@ public:
private:
using INHERITED = SkRuntimeEffectBuilder;
explicit SkRuntimeShaderBuilder(sk_sp<SkRuntimeEffect> effect, sk_sp<SkData> uniforms)
: INHERITED(std::move(effect), std::move(uniforms)) {}
friend class SkRuntimeImageFilter;
};
/**

View File

@ -19,16 +19,20 @@
#ifdef SK_ENABLE_SKSL
namespace {
class SkRuntimeImageFilter final : public SkImageFilter_Base {
public:
SkRuntimeImageFilter(sk_sp<SkRuntimeEffect> effect,
sk_sp<SkData> uniforms,
sk_sp<SkImageFilter> input)
: INHERITED(&input, 1, /*cropRect=*/nullptr)
, fEffect(std::move(effect))
, fUniforms(std::move(uniforms)) {}
, fShaderBuilder(std::move(effect), std::move(uniforms))
, fChildShaderName(fShaderBuilder.effect()->children().front().name) {}
SkRuntimeImageFilter(const SkRuntimeShaderBuilder& builder,
const char* childShaderName,
sk_sp<SkImageFilter> input)
: INHERITED(&input, 1, /*cropRect=*/nullptr)
, fShaderBuilder(builder)
, fChildShaderName(childShaderName) {}
bool onAffectsTransparentBlack() const override { return true; }
MatrixCapability onGetCTMCapability() const override { return MatrixCapability::kTranslate; }
@ -41,14 +45,12 @@ private:
friend void ::SkRegisterRuntimeImageFilterFlattenable();
SK_FLATTENABLE_HOOKS(SkRuntimeImageFilter)
sk_sp<SkRuntimeEffect> fEffect;
sk_sp<SkData> fUniforms;
mutable SkRuntimeShaderBuilder fShaderBuilder;
SkString fChildShaderName;
using INHERITED = SkImageFilter_Base;
};
} // end namespace
sk_sp<SkImageFilter> SkMakeRuntimeImageFilter(sk_sp<SkRuntimeEffect> effect,
sk_sp<SkData> uniforms,
sk_sp<SkImageFilter> input) {
@ -71,25 +73,67 @@ void SkRegisterRuntimeImageFilterFlattenable() {
sk_sp<SkFlattenable> SkRuntimeImageFilter::CreateProc(SkReadBuffer& buffer) {
SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
SkString sksl;
buffer.readString(&sksl);
sk_sp<SkData> uniforms = buffer.readByteArrayAsData();
auto effect = SkMakeCachedRuntimeEffect(SkRuntimeEffect::MakeForShader, std::move(sksl));
if (!buffer.validate(effect != nullptr)) {
return nullptr;
}
if (common.cropRect()) {
return nullptr;
}
return SkMakeRuntimeImageFilter(std::move(effect), std::move(uniforms), common.getInput(0));
// Read the SkSL string and convert it into a runtime effect
SkString sksl;
buffer.readString(&sksl);
auto effect = SkMakeCachedRuntimeEffect(SkRuntimeEffect::MakeForShader, std::move(sksl));
if (!buffer.validate(effect != nullptr)) {
return nullptr;
}
// Read the uniform data and make sure it matches the size from the runtime effect
sk_sp<SkData> uniforms = buffer.readByteArrayAsData();
if (!buffer.validate(uniforms->size() == effect->uniformSize())) {
return nullptr;
}
// Read the child shader name and make sure it matches one declared in the effect
SkString childShaderName;
buffer.readString(&childShaderName);
if (!buffer.validate(effect->findChild(childShaderName.c_str()) != nullptr)) {
return nullptr;
}
SkRuntimeShaderBuilder builder(std::move(effect), std::move(uniforms));
// Populate the builder with the corresponding children
for (auto& child : builder.effect()->children()) {
const char* name = child.name.c_str();
switch (child.type) {
case SkRuntimeEffect::ChildType::kBlender: {
builder.child(name) = buffer.readBlender();
break;
}
case SkRuntimeEffect::ChildType::kColorFilter: {
builder.child(name) = buffer.readColorFilter();
break;
}
case SkRuntimeEffect::ChildType::kShader: {
builder.child(name) = buffer.readShader();
break;
}
}
}
if (!buffer.isValid()) {
return nullptr;
}
return SkImageFilters::RuntimeShader(builder, childShaderName.c_str(), common.getInput(0));
}
void SkRuntimeImageFilter::flatten(SkWriteBuffer& buffer) const {
this->INHERITED::flatten(buffer);
buffer.writeString(fEffect->source().c_str());
buffer.writeDataAsByteArray(fUniforms.get());
buffer.writeString(fShaderBuilder.effect()->source().c_str());
buffer.writeDataAsByteArray(fShaderBuilder.uniforms().get());
buffer.writeString(fChildShaderName.c_str());
for (size_t x = 0; x < fShaderBuilder.numChildren(); x++) {
buffer.writeFlattenable(fShaderBuilder.children()[x].flattenable());
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
@ -119,8 +163,9 @@ sk_sp<SkSpecialImage> SkRuntimeImageFilter::onFilterImage(const Context& ctx,
input->asImage()->makeShader(SkSamplingOptions(SkFilterMode::kLinear), &localM);
SkASSERT(inputShader);
auto shader = fEffect->makeShader(fUniforms, &inputShader, 1, nullptr, false);
SkASSERT(shader);
fShaderBuilder.child(fChildShaderName.c_str()) = inputShader;
sk_sp<SkShader> shader = fShaderBuilder.makeShader(nullptr, false);
SkASSERT(shader.get());
SkPaint paint;
paint.setShader(std::move(shader));
@ -136,8 +181,31 @@ sk_sp<SkSpecialImage> SkRuntimeImageFilter::onFilterImage(const Context& ctx,
canvas->drawPaint(paint);
// Remove the shader from the builder to avoid unnecessarily prolonging the shader's lifetime
fShaderBuilder.child(fChildShaderName.c_str()) = nullptr;
*offset = outputBounds.topLeft();
return surf->makeImageSnapshot();
}
sk_sp<SkImageFilter> SkImageFilters::RuntimeShader(const SkRuntimeShaderBuilder& builder,
const char* childShaderName,
sk_sp<SkImageFilter> input) {
// if no childShaderName is provided check to see if we can implicitly assign it to the only
// child in the effect
if (childShaderName == nullptr) {
auto children = builder.effect()->children();
if (children.size() != 1) {
return nullptr;
}
childShaderName = children.front().name.c_str();
} else if (builder.effect()->findChild(childShaderName) == nullptr) {
// there was no child declared in the runtime effect that matches the provided name
return nullptr;
}
return sk_sp<SkImageFilter>(
new SkRuntimeImageFilter(builder, childShaderName, std::move(input)));
}
#endif // SK_ENABLE_SKSL

View File

@ -8,8 +8,11 @@
#include "include/core/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkShader.h"
#include "include/core/SkSurface.h"
#include "include/effects/SkGradientShader.h"
#include "include/effects/SkImageFilters.h"
#include "include/effects/SkRuntimeEffect.h"
#include "src/effects/imagefilters/SkRuntimeImageFilter.h"
#include "tests/Test.h"
static void test_unscaled(skiatest::Reporter* reporter) {
@ -114,3 +117,78 @@ DEF_TEST(PaintImageFilter, reporter) {
test_unscaled(reporter);
test_scaled(reporter);
}
static void test_runtime_shader(skiatest::Reporter* r, SkSurface* surface) {
sk_sp<SkRuntimeEffect> effect = SkRuntimeEffect::MakeForShader(SkString(R"(
uniform shader child;
vec4 main(vec2 coord) {
return child.eval(coord) * 0.5;
}
)"))
.effect;
SkRuntimeShaderBuilder builder(effect);
// create a red image filter to feed as input into the SkImageFilters::RuntimeShader
SkPaint redPaint;
redPaint.setColor(SK_ColorRED);
sk_sp<SkImageFilter> input = SkImageFilters::Paint(redPaint);
// Create the different variations of SkImageFilters::RuntimeShader
// All 3 variations should produce the same pixel output
std::vector<sk_sp<SkImageFilter>> filters = {
SkMakeRuntimeImageFilter(effect, /*uniforms=*/nullptr, input),
SkImageFilters::RuntimeShader(builder, /*childShaderName=*/nullptr, input),
SkImageFilters::RuntimeShader(builder, /*childShaderName=*/"child", input)};
for (auto&& filter : filters) {
auto canvas = surface->getCanvas();
// clear to transparent
SkPaint paint;
paint.setColor(SK_ColorTRANSPARENT);
paint.setBlendMode(SkBlendMode::kSrc);
canvas->drawPaint(paint);
SkPaint filterPaint;
// the green color will be ignored by the filter within the runtime shader
filterPaint.setColor(SK_ColorGREEN);
filterPaint.setImageFilter(filter);
canvas->saveLayer(nullptr, &filterPaint);
// the blue color will be ignored by the filter because the input to the image filter is not
// null
canvas->drawColor(SK_ColorBLUE);
canvas->restore();
// This is expected to read back the half transparent red pixel produced by the image filter
SkBitmap bitmap;
REPORTER_ASSERT(r, bitmap.tryAllocPixels(surface->imageInfo()));
REPORTER_ASSERT(r,
surface->readPixels(bitmap.info(),
bitmap.getPixels(),
bitmap.rowBytes(),
/*srcX=*/0,
/*srcY=*/0));
SkColor color = bitmap.getColor(/*x=*/0, /*y=*/0);
// check alpha with a small tolerance
SkAlpha alpha = SkColorGetA(color);
REPORTER_ASSERT(r, alpha >= 127 && alpha <= 129, "Expected: %d Actual: %d", 128, alpha);
// check each color channel
color = SkColorSetA(color, 255);
REPORTER_ASSERT(r, SK_ColorRED == color, "Expected: %08x Actual: %08x", SK_ColorRED, color);
}
}
DEF_TEST(SkRuntimeShaderImageFilter_CPU, r) {
const SkImageInfo info = SkImageInfo::MakeN32Premul(/*width=*/1, /*height=*/1);
sk_sp<SkSurface> surface(SkSurface::MakeRaster(info));
test_runtime_shader(r, surface.get());
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SkRuntimeShaderImageFilter_GPU, r, ctxInfo) {
const SkImageInfo info = SkImageInfo::MakeN32Premul(/*width=*/1, /*height=*/1);
sk_sp<SkSurface> surface(
SkSurface::MakeRenderTarget(ctxInfo.directContext(), SkBudgeted::kNo, info));
test_runtime_shader(r, surface.get());
}