blob: 691e0b0b465ed2ebe4255491b7729102ef76700f [file] [log] [blame]
/*
* Copyright 2021 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/effects/SkImageFilters.h"
#ifdef SK_ENABLE_SKSL
#include "include/core/SkData.h"
#include "include/core/SkFlattenable.h"
#include "include/core/SkImageFilter.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/core/SkShader.h"
#include "include/core/SkSize.h"
#include "include/core/SkSpan.h"
#include "include/core/SkString.h"
#include "include/effects/SkRuntimeEffect.h"
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkTArray.h"
#include "src/base/SkSpinlock.h"
#include "src/core/SkImageFilterTypes.h"
#include "src/core/SkImageFilter_Base.h"
#include "src/core/SkPicturePriv.h"
#include "src/core/SkReadBuffer.h"
#include "src/core/SkRectPriv.h"
#include "src/core/SkRuntimeEffectPriv.h"
#include "src/core/SkWriteBuffer.h"
#include <cstddef>
#include <string>
#include <string_view>
#include <utility>
using namespace skia_private;
// NOTE: Not in an anonymous namespace so that SkRuntimeShaderBuilder can friend it.
class SkRuntimeImageFilter final : public SkImageFilter_Base {
public:
SkRuntimeImageFilter(const SkRuntimeShaderBuilder& builder,
float maxSampleRadius,
std::string_view childShaderNames[],
const sk_sp<SkImageFilter> inputs[],
int inputCount)
: SkImageFilter_Base(inputs, inputCount, /*cropRect=*/nullptr)
, fRuntimeEffectBuilder(builder)
, fMaxSampleRadius(maxSampleRadius) {
SkASSERT(maxSampleRadius >= 0.f);
fChildShaderNames.reserve_back(inputCount);
for (int i = 0; i < inputCount; i++) {
fChildShaderNames.push_back(SkString(childShaderNames[i]));
}
}
SkRect computeFastBounds(const SkRect& src) const override;
protected:
void flatten(SkWriteBuffer&) const override;
private:
friend void ::SkRegisterRuntimeImageFilterFlattenable();
SK_FLATTENABLE_HOOKS(SkRuntimeImageFilter)
bool onAffectsTransparentBlack() const override { return true; }
// Currently there is no way for a client to specify the semantics of geometric uniforms that
// should respond to the canvas matrix. Forcing translate-only is a hammer that lets the output
// be correct at the expense of resolution when there's a lot of scaling. See skbug.com/13416.
MatrixCapability onGetCTMCapability() const override { return MatrixCapability::kTranslate; }
skif::FilterResult onFilterImage(const skif::Context&) const override;
skif::LayerSpace<SkIRect> onGetInputLayerBounds(
const skif::Mapping&,
const skif::LayerSpace<SkIRect>& desiredOutput,
const skif::LayerSpace<SkIRect>& contentBounds) const override;
skif::LayerSpace<SkIRect> onGetOutputLayerBounds(
const skif::Mapping&,
const skif::LayerSpace<SkIRect>& contentBounds) const override;
skif::LayerSpace<SkIRect> applyMaxSampleRadius(
const skif::Mapping& mapping,
skif::LayerSpace<SkIRect> bounds) const {
skif::LayerSpace<SkISize> maxSampleRadius = mapping.paramToLayer(
skif::ParameterSpace<SkSize>({fMaxSampleRadius, fMaxSampleRadius})).ceil();
bounds.outset(maxSampleRadius);
return bounds;
}
mutable SkSpinlock fRuntimeEffectLock;
mutable SkRuntimeShaderBuilder fRuntimeEffectBuilder;
STArray<1, SkString> fChildShaderNames;
float fMaxSampleRadius;
};
sk_sp<SkImageFilter> SkImageFilters::RuntimeShader(const SkRuntimeShaderBuilder& builder,
SkScalar sampleRadius,
std::string_view 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.empty()) {
auto children = builder.effect()->children();
if (children.size() != 1) {
return nullptr;
}
childShaderName = children.front().name;
}
return SkImageFilters::RuntimeShader(builder, sampleRadius, &childShaderName, &input, 1);
}
sk_sp<SkImageFilter> SkImageFilters::RuntimeShader(const SkRuntimeShaderBuilder& builder,
SkScalar maxSampleRadius,
std::string_view childShaderNames[],
const sk_sp<SkImageFilter> inputs[],
int inputCount) {
if (maxSampleRadius < 0.f) {
return nullptr; // invalid sample radius
}
auto child_is_shader = [](const SkRuntimeEffect::Child* child) {
return child && child->type == SkRuntimeEffect::ChildType::kShader;
};
for (int i = 0; i < inputCount; i++) {
std::string_view name = childShaderNames[i];
// All names must be non-empty, and present as a child shader in the effect:
if (name.empty() || !child_is_shader(builder.effect()->findChild(name))) {
return nullptr;
}
// We don't allow duplicates, either:
for (int j = 0; j < i; j++) {
if (name == childShaderNames[j]) {
return nullptr;
}
}
}
return sk_sp<SkImageFilter>(new SkRuntimeImageFilter(builder, maxSampleRadius, childShaderNames,
inputs, inputCount));
}
void SkRegisterRuntimeImageFilterFlattenable() {
SK_REGISTER_FLATTENABLE(SkRuntimeImageFilter);
}
sk_sp<SkFlattenable> SkRuntimeImageFilter::CreateProc(SkReadBuffer& buffer) {
// We don't know how many inputs to expect yet. Passing -1 allows any number of children.
SK_IMAGEFILTER_UNFLATTEN_COMMON(common, -1);
if (common.cropRect()) {
return nullptr;
}
// 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 names
STArray<4, std::string_view> childShaderNames;
STArray<4, SkString> childShaderNameStrings;
childShaderNames.resize(common.inputCount());
childShaderNameStrings.resize(common.inputCount());
for (int i = 0; i < common.inputCount(); i++) {
buffer.readString(&childShaderNameStrings[i]);
childShaderNames[i] = childShaderNameStrings[i].c_str();
}
SkRuntimeShaderBuilder builder(std::move(effect), std::move(uniforms));
// Populate the builder with the corresponding children
for (const SkRuntimeEffect::Child& child : builder.effect()->children()) {
std::string_view name = child.name;
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;
}
}
}
float maxSampleRadius = 0.f; // default before sampleRadius was exposed in the factory
if (!buffer.isVersionLT(SkPicturePriv::kRuntimeImageFilterSampleRadius)) {
maxSampleRadius = buffer.readScalar();
}
if (!buffer.isValid()) {
return nullptr;
}
return SkImageFilters::RuntimeShader(builder, maxSampleRadius, childShaderNames.data(),
common.inputs(), common.inputCount());
}
void SkRuntimeImageFilter::flatten(SkWriteBuffer& buffer) const {
this->SkImageFilter_Base::flatten(buffer);
fRuntimeEffectLock.acquire();
buffer.writeString(fRuntimeEffectBuilder.effect()->source().c_str());
buffer.writeDataAsByteArray(fRuntimeEffectBuilder.uniforms().get());
for (const SkString& name : fChildShaderNames) {
buffer.writeString(name.c_str());
}
for (size_t x = 0; x < fRuntimeEffectBuilder.children().size(); x++) {
buffer.writeFlattenable(fRuntimeEffectBuilder.children()[x].flattenable());
}
fRuntimeEffectLock.release();
buffer.writeScalar(fMaxSampleRadius);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
skif::FilterResult SkRuntimeImageFilter::onFilterImage(const skif::Context& ctx) const {
using ShaderFlags = skif::FilterResult::ShaderFlags;
const int inputCount = this->countInputs();
SkASSERT(inputCount == fChildShaderNames.size());
skif::Context inputCtx = ctx.withNewDesiredOutput(
this->applyMaxSampleRadius(ctx.mapping(), ctx.desiredOutput()));
skif::FilterResult::Builder builder{ctx};
for (int i = 0; i < inputCount; ++i) {
builder.add(this->getChildOutput(i, inputCtx));
}
return builder.eval([&](SkSpan<sk_sp<SkShader>> inputs) {
// lock the mutation of the builder and creation of the shader so that the builder's state
// is const and is safe for multi-threaded access.
fRuntimeEffectLock.acquire();
for (int i = 0; i < inputCount; i++) {
fRuntimeEffectBuilder.child(fChildShaderNames[i].c_str()) = inputs[i];
}
sk_sp<SkShader> shader = fRuntimeEffectBuilder.makeShader();
// Remove the inputs from the builder to avoid unnecessarily prolonging the input shaders'
// lifetimes.
for (int i = 0; i < inputCount; i++) {
fRuntimeEffectBuilder.child(fChildShaderNames[i].c_str()) = nullptr;
}
fRuntimeEffectLock.release();
return shader;
}, ShaderFlags::kSampleInParameterSpace | ShaderFlags::kNonLinearSampling);
}
skif::LayerSpace<SkIRect> SkRuntimeImageFilter::onGetInputLayerBounds(
const skif::Mapping& mapping,
const skif::LayerSpace<SkIRect>& desiredOutput,
const skif::LayerSpace<SkIRect>& contentBounds) const {
const int inputCount = this->countInputs();
if (inputCount <= 0) {
return skif::LayerSpace<SkIRect>::Empty();
} else {
// Provide 'maxSampleRadius' pixels (in layer space) to the child shaders.
skif::LayerSpace<SkIRect> requiredInput =
this->applyMaxSampleRadius(mapping, desiredOutput);
// Union of all child input bounds so that one source image can provide for all of them.
skif::LayerSpace<SkIRect> merged =
this->getChildInputLayerBounds(0, mapping, requiredInput, contentBounds);
for (int i = 1; i < inputCount; ++i) {
merged.join(this->getChildInputLayerBounds(i, mapping, requiredInput, contentBounds));
}
return merged;
}
}
skif::LayerSpace<SkIRect> SkRuntimeImageFilter::onGetOutputLayerBounds(
const skif::Mapping& /*mapping*/,
const skif::LayerSpace<SkIRect>& /*contentBounds*/) const {
// Pessimistically assume it can cover anything
return skif::LayerSpace<SkIRect>(SkRectPriv::MakeILarge());
}
SkRect SkRuntimeImageFilter::computeFastBounds(const SkRect& src) const {
// Can't predict what the RT Shader will generate (see onGetOutputLayerBounds)
return SkRectPriv::MakeLargeS32();
}
#endif // SK_ENABLE_SKSL