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:
parent
15ecccf0dd
commit
ad9d774c1f
@ -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
|
||||
------------
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user