/* * Copyright 2018 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "src/gpu/ganesh/effects/GrSkSLFP.h" #include "include/core/SkAlphaType.h" #include "include/core/SkColor.h" #include "include/core/SkData.h" #include "include/core/SkString.h" #include "include/core/SkSurfaceProps.h" #include "include/effects/SkOverdrawColorFilter.h" #include "include/effects/SkRuntimeEffect.h" #include "include/private/SkSLSampleUsage.h" #include "include/private/base/SkMalloc.h" #include "include/private/base/SkTo.h" #include "include/private/gpu/ganesh/GrTypesPriv.h" #include "src/base/SkArenaAlloc.h" #include "src/base/SkRandom.h" #include "src/core/SkColorSpacePriv.h" #include "src/core/SkRasterPipeline.h" #include "src/core/SkRasterPipelineOpContexts.h" #include "src/core/SkRasterPipelineOpList.h" #include "src/core/SkRuntimeEffectPriv.h" #include "src/core/SkSLTypeShared.h" #include "src/gpu/KeyBuilder.h" #include "src/gpu/ganesh/GrColorInfo.h" #include "src/gpu/ganesh/GrColorSpaceXform.h" #include "src/gpu/ganesh/GrFragmentProcessors.h" #include "src/gpu/ganesh/GrShaderVar.h" #include "src/gpu/ganesh/glsl/GrGLSLFragmentShaderBuilder.h" #include "src/gpu/ganesh/glsl/GrGLSLUniformHandler.h" #include "src/sksl/SkSLString.h" #include "src/sksl/SkSLUtil.h" #include "src/sksl/codegen/SkSLPipelineStageCodeGenerator.h" #include "src/sksl/codegen/SkSLRasterPipelineBuilder.h" #include "src/sksl/ir/SkSLProgram.h" #include "src/sksl/ir/SkSLType.h" #include "src/sksl/ir/SkSLVarDeclarations.h" #include "src/sksl/ir/SkSLVariable.h" #include namespace SkSL { class Context; } struct GrShaderCaps; class GrSkSLFP::Impl : public ProgramImpl { public: void emitCode(EmitArgs& args) override { const GrSkSLFP& fp = args.fFp.cast(); const SkSL::Program& program = *fp.fEffect->fBaseProgram; class FPCallbacks : public SkSL::PipelineStage::Callbacks { public: FPCallbacks(Impl* self, EmitArgs& args, const char* inputColor, const SkSL::Context& context, const uint8_t* uniformData, const Specialized* specialized) : fSelf(self) , fArgs(args) , fInputColor(inputColor) , fContext(context) , fUniformData(uniformData) , fSpecialized(specialized) {} std::string declareUniform(const SkSL::VarDeclaration* decl) override { const SkSL::Variable* var = decl->var(); if (var->type().isOpaque()) { // Nothing to do. The only opaque types we should see are children, and those // are handled specially. SkASSERT(var->type().isEffectChild()); return std::string(var->name()); } const SkSL::Type* type = &var->type(); size_t sizeInBytes = type->slotCount() * sizeof(float); const float* floatData = reinterpret_cast(fUniformData); const int* intData = reinterpret_cast(fUniformData); fUniformData += sizeInBytes; bool isArray = false; if (type->isArray()) { type = &type->componentType(); isArray = true; } SkSLType gpuType; SkAssertResult(SkSL::type_to_sksltype(fContext, *type, &gpuType)); if (*fSpecialized++ == Specialized::kYes) { SkASSERTF(!isArray, "specializing array uniforms is not allowed"); std::string value = SkSLTypeString(gpuType); value.append("("); bool isFloat = SkSLTypeIsFloatType(gpuType); size_t slots = type->slotCount(); for (size_t i = 0; i < slots; ++i) { value.append(isFloat ? skstd::to_string(floatData[i]) : std::to_string(intData[i])); value.append(","); } value.back() = ')'; return value; } const char* uniformName = nullptr; auto handle = fArgs.fUniformHandler->addUniformArray(&fArgs.fFp.cast(), kFragment_GrShaderFlag, gpuType, SkString(var->name()).c_str(), isArray ? var->type().columns() : 0, &uniformName); fSelf->fUniformHandles.push_back(handle); return std::string(uniformName); } std::string getMangledName(const char* name) override { return std::string(fArgs.fFragBuilder->getMangledFunctionName(name).c_str()); } void defineFunction(const char* decl, const char* body, bool isMain) override { if (isMain) { fArgs.fFragBuilder->codeAppend(body); } else { fArgs.fFragBuilder->emitFunction(decl, body); } } void declareFunction(const char* decl) override { fArgs.fFragBuilder->emitFunctionPrototype(decl); } void defineStruct(const char* definition) override { fArgs.fFragBuilder->definitionAppend(definition); } void declareGlobal(const char* declaration) override { fArgs.fFragBuilder->definitionAppend(declaration); } std::string sampleShader(int index, std::string coords) override { // If the child was sampled using the coords passed to main (and they are never // modified), then we will have marked the child as PassThrough. The code generator // doesn't know that, and still supplies coords. Inside invokeChild, we assert that // any coords passed for a PassThrough child match args.fSampleCoords exactly. // // Normally, this is valid. Here, we *copied* the sample coords to a local variable // (so that they're mutable in the runtime effect SkSL). Thus, the coords string we // get here is the name of the local copy, and fSampleCoords still points to the // unmodified original (which might be a varying, for example). // To prevent the assert, we pass the empty string in this case. Note that for // children sampled like this, invokeChild doesn't even use the coords parameter, // except for that assert. const GrFragmentProcessor* child = fArgs.fFp.childProcessor(index); if (child && child->sampleUsage().isPassThrough()) { coords.clear(); } return child ? std::string(fSelf->invokeChild(index, fInputColor, fArgs, coords) .c_str()) : std::string("half4(0)"); } std::string sampleColorFilter(int index, std::string color) override { return std::string(fSelf->invokeChild(index, color.empty() ? fInputColor : color.c_str(), fArgs) .c_str()); } std::string sampleBlender(int index, std::string src, std::string dst) override { if (!fSelf->childProcessor(index)) { return SkSL::String::printf("blend_src_over(%s, %s)", src.c_str(), dst.c_str()); } return std::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. std::string toLinearSrgb(std::string color) override { const GrSkSLFP& fp = fArgs.fFp.cast(); if (fp.fToLinearSrgbChildIndex < 0) { return color; } color = SkSL::String::printf("(%s).rgb1", color.c_str()); SkString xformedColor = fSelf->invokeChild( fp.fToLinearSrgbChildIndex, color.c_str(), fArgs); return SkSL::String::printf("(%s).rgb", xformedColor.c_str()); } std::string fromLinearSrgb(std::string color) override { const GrSkSLFP& fp = fArgs.fFp.cast(); if (fp.fFromLinearSrgbChildIndex < 0) { return color; } color = SkSL::String::printf("(%s).rgb1", color.c_str()); SkString xformedColor = fSelf->invokeChild( fp.fFromLinearSrgbChildIndex, color.c_str(), fArgs); return SkSL::String::printf("(%s).rgb", xformedColor.c_str()); } Impl* fSelf; EmitArgs& fArgs; const char* fInputColor; const SkSL::Context& fContext; const uint8_t* fUniformData; const Specialized* fSpecialized; int fUniformIndex = 0; }; // If we have an input child, we invoke it now, and make the result of that be the "input // color" for all other purposes later (eg, the default passed via sample calls, etc.) if (fp.fInputChildIndex >= 0) { args.fFragBuilder->codeAppendf("%s = %s;\n", args.fInputColor, this->invokeChild(fp.fInputChildIndex, args).c_str()); } if (fp.fEffect->allowBlender()) { // If we have an dest-color child, we invoke it now, and make the result of that be the // "dest color" for all other purposes later. if (fp.fDestColorChildIndex >= 0) { args.fFragBuilder->codeAppendf( "%s = %s;\n", args.fDestColor, this->invokeChild(fp.fDestColorChildIndex, args.fDestColor, args).c_str()); } } else { // We're not making a blender, so we don't expect a dest-color child FP to exist. SkASSERT(fp.fDestColorChildIndex < 0); } // Snap off a global copy of the input color at the start of main. We need this when // we call child processors (particularly from helper functions, which can't "see" the // parameter to main). Even from within main, if the code mutates the parameter, calls to // sample should still be passing the original color (by default). SkString inputColorName; if (fp.fEffect->samplesOutsideMain()) { GrShaderVar inputColorCopy(args.fFragBuilder->getMangledFunctionName("inColor"), SkSLType::kHalf4); args.fFragBuilder->declareGlobal(inputColorCopy); inputColorName = inputColorCopy.getName(); args.fFragBuilder->codeAppendf("%s = %s;\n", inputColorName.c_str(), args.fInputColor); } else { inputColorName = args.fFragBuilder->newTmpVarName("inColor"); args.fFragBuilder->codeAppendf( "half4 %s = %s;\n", inputColorName.c_str(), args.fInputColor); } // Copy the incoming coords to a local variable. Code in main might modify the coords // parameter. fSampleCoord could be a varying, so writes to it would be illegal. const char* coords = "float2(0)"; SkString coordsVarName; if (fp.usesSampleCoordsDirectly()) { coordsVarName = args.fFragBuilder->newTmpVarName("coords"); coords = coordsVarName.c_str(); args.fFragBuilder->codeAppendf("float2 %s = %s;\n", coords, args.fSampleCoord); } FPCallbacks callbacks(this, args, inputColorName.c_str(), *program.fContext, fp.uniformData(), fp.specialized()); SkSL::PipelineStage::ConvertProgram( program, coords, args.fInputColor, args.fDestColor, &callbacks); } private: void onSetData(const GrGLSLProgramDataManager& pdman, const GrFragmentProcessor& _proc) override { const GrSkSLFP& outer = _proc.cast(); pdman.setRuntimeEffectUniforms(outer.fEffect->uniforms(), SkSpan(fUniformHandles), SkSpan(outer.specialized(), outer.uniformCount()), outer.uniformData()); } std::vector fUniformHandles; }; std::unique_ptr GrSkSLFP::MakeWithData( sk_sp effect, const char* name, sk_sp dstColorSpace, std::unique_ptr inputFP, std::unique_ptr destColorFP, const sk_sp& uniforms, SkSpan> childFPs) { if (uniforms->size() != effect->uniformSize()) { return nullptr; } size_t uniformSize = uniforms->size(); size_t specializedSize = effect->uniforms().size() * sizeof(Specialized); std::unique_ptr fp(new (uniformSize + specializedSize) GrSkSLFP(std::move(effect), name, OptFlags::kNone)); sk_careful_memcpy(fp->uniformData(), uniforms->data(), uniformSize); for (auto& childFP : childFPs) { fp->addChild(std::move(childFP), /*mergeOptFlags=*/true); } if (inputFP) { fp->setInput(std::move(inputFP)); } if (destColorFP) { fp->setDestColorFP(std::move(destColorFP)); } if (fp->fEffect->usesColorTransform() && dstColorSpace) { fp->addColorTransformChildren(dstColorSpace.get()); } return fp; } GrFragmentProcessor::OptimizationFlags GrSkSLFP::DetermineOptimizationFlags( OptFlags of, SkRuntimeEffect* effect) { OptimizationFlags optFlags = static_cast(of); if (SkRuntimeEffectPriv::SupportsConstantOutputForConstantInput(effect)) { optFlags |= kConstantOutputForConstantInput_OptimizationFlag; } return optFlags; } GrSkSLFP::GrSkSLFP(sk_sp effect, const char* name, OptFlags optFlags) : INHERITED(kGrSkSLFP_ClassID, DetermineOptimizationFlags(optFlags, effect.get())) , fEffect(std::move(effect)) , fName(name) , fUniformSize(SkToU32(fEffect->uniformSize())) { std::fill_n(this->specialized(), this->uniformCount(), Specialized::kNo); if (fEffect->usesSampleCoords()) { this->setUsesSampleCoordsDirectly(); } if (fEffect->allowBlender()) { this->setIsBlendFunction(); } } GrSkSLFP::GrSkSLFP(const GrSkSLFP& other) : INHERITED(other) , fEffect(other.fEffect) , fName(other.fName) , fUniformSize(other.fUniformSize) , fInputChildIndex(other.fInputChildIndex) , fDestColorChildIndex(other.fDestColorChildIndex) , fToLinearSrgbChildIndex(other.fToLinearSrgbChildIndex) , fFromLinearSrgbChildIndex(other.fFromLinearSrgbChildIndex) { std::copy_n(other.specialized(), this->uniformCount(), this->specialized()); sk_careful_memcpy(this->uniformData(), other.uniformData(), fUniformSize); } void GrSkSLFP::addChild(std::unique_ptr child, bool mergeOptFlags) { SkASSERTF(fInputChildIndex == -1, "all addChild calls must happen before setInput"); SkASSERTF(fDestColorChildIndex == -1, "all addChild calls must happen before setDestColorFP"); int childIndex = this->numChildProcessors(); SkASSERT((size_t)childIndex < fEffect->fSampleUsages.size()); if (mergeOptFlags) { this->mergeOptimizationFlags(ProcessorOptimizationFlags(child.get())); } this->clearConstantOutputForConstantInputFlag(); this->registerChild(std::move(child), fEffect->fSampleUsages[childIndex]); } void GrSkSLFP::setInput(std::unique_ptr input) { SkASSERTF(fInputChildIndex == -1, "setInput should not be called more than once"); fInputChildIndex = this->numChildProcessors(); SkASSERT((size_t)fInputChildIndex >= fEffect->fSampleUsages.size()); this->mergeOptimizationFlags(ProcessorOptimizationFlags(input.get())); this->registerChild(std::move(input), SkSL::SampleUsage::PassThrough()); } void GrSkSLFP::setDestColorFP(std::unique_ptr destColorFP) { SkASSERTF(fEffect->allowBlender(), "dest colors are only used by blend effects"); SkASSERTF(fDestColorChildIndex == -1, "setDestColorFP should not be called more than once"); fDestColorChildIndex = this->numChildProcessors(); SkASSERT((size_t)fDestColorChildIndex >= fEffect->fSampleUsages.size()); this->mergeOptimizationFlags(ProcessorOptimizationFlags(destColorFP.get())); this->registerChild(std::move(destColorFP), SkSL::SampleUsage::PassThrough()); } void GrSkSLFP::addColorTransformChildren(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, kUnpremul_SkAlphaType, sk_srgb_linear_singleton(), kUnpremul_SkAlphaType); auto linearToWorking = GrColorSpaceXformEffect::Make(nullptr, sk_srgb_linear_singleton(), kUnpremul_SkAlphaType, dstColorSpace, 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 GrSkSLFP::onMakeProgramImpl() const { return std::make_unique(); } void GrSkSLFP::onAddToKey(const GrShaderCaps& caps, skgpu::KeyBuilder* b) const { // In the unlikely event of a hash collision, we also include the uniform size in the key. // That ensures that we will (at worst) use the wrong program, but one that expects the same // amount of uniform data. b->add32(fEffect->hash()); b->add32(fUniformSize); const Specialized* specialized = this->specialized(); const uint8_t* uniformData = this->uniformData(); size_t uniformCount = this->uniformCount(); auto iter = fEffect->uniforms().begin(); for (size_t i = 0; i < uniformCount; ++i, ++iter) { bool specialize = specialized[i] == Specialized::kYes; b->addBool(specialize, "specialize"); if (specialize) { b->addBytes(iter->sizeInBytes(), uniformData + iter->offset, iter->name); } } } bool GrSkSLFP::onIsEqual(const GrFragmentProcessor& other) const { const GrSkSLFP& sk = other.cast(); const size_t specializedSize = this->uniformCount() * sizeof(Specialized); return fEffect->hash() == sk.fEffect->hash() && this->uniformCount() == sk.uniformCount() && fUniformSize == sk.fUniformSize && !sk_careful_memcmp(this->uniformData(), sk.uniformData(), fUniformSize + specializedSize); } std::unique_ptr GrSkSLFP::clone() const { return std::unique_ptr(new (UniformPayloadSize(fEffect.get())) GrSkSLFP(*this)); } SkPMColor4f GrSkSLFP::constantOutputForConstantInput(const SkPMColor4f& inputColor) const { SkPMColor4f color = (fInputChildIndex >= 0) ? ConstantOutputForConstantInput(this->childProcessor(fInputChildIndex), inputColor) : inputColor; class ConstantOutputForConstantInput_SkRPCallbacks : public SkSL::RP::Callbacks { public: bool appendShader(int index) override { SkDEBUGFAIL("constant-output-for-constant-input unsupported when child shaders present"); return false; } bool appendColorFilter(int index) override { SkDEBUGFAIL("constant-output-for-constant-input unsupported when child shaders present"); return false; } bool appendBlender(int index) override { SkDEBUGFAIL("constant-output-for-constant-input unsupported when child shaders present"); return false; } void toLinearSrgb(const void* color) override { /* identity color conversion */ } void fromLinearSrgb(const void* color) override { /* identity color conversion */ } }; if (const SkSL::RP::Program* program = fEffect->getRPProgram(/*debugTrace=*/nullptr)) { // No color conversion is happening here, so we can use untransformed uniforms. SkSpan uniforms{reinterpret_cast(this->uniformData()), fUniformSize / sizeof(float)}; SkSTArenaAlloc<2048> alloc; // sufficient for a tiny SkSL program SkRasterPipeline pipeline(&alloc); pipeline.appendConstantColor(&alloc, color.vec()); ConstantOutputForConstantInput_SkRPCallbacks callbacks; if (program->appendStages(&pipeline, &alloc, &callbacks, uniforms)) { SkPMColor4f outputColor; SkRasterPipeline_MemoryCtx outputCtx = {&outputColor, 0}; pipeline.append(SkRasterPipelineOp::store_f32, &outputCtx); pipeline.run(0, 0, 1, 1); return outputColor; } } // We weren't able to run the Raster Pipeline program. return color; } /**************************************************************************************************/ GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrSkSLFP) #if defined(GPU_TEST_UTILS) std::unique_ptr GrSkSLFP::TestCreate(GrProcessorTestData* d) { SkColor colors[SkOverdrawColorFilter::kNumColors]; for (SkColor& c : colors) { c = d->fRandom->nextU(); } auto filter = SkOverdrawColorFilter::MakeWithSkColors(colors); SkSurfaceProps props; // default props for testing auto [success, fp] = GrFragmentProcessors::Make( d->context(), filter.get(), /*inputFP=*/nullptr, GrColorInfo{}, props); SkASSERT(success); return std::move(fp); } #endif