| /* |
| * Copyright 2012 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/effects/GrGaussianConvolutionFragmentProcessor.h" |
| |
| #include "src/core/SkGpuBlurUtils.h" |
| #include "src/gpu/GrTexture.h" |
| #include "src/gpu/GrTextureProxy.h" |
| #include "src/gpu/effects/GrTextureEffect.h" |
| #include "src/gpu/glsl/GrGLSLFragmentProcessor.h" |
| #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" |
| #include "src/gpu/glsl/GrGLSLProgramDataManager.h" |
| #include "src/gpu/glsl/GrGLSLUniformHandler.h" |
| #include "src/sksl/dsl/priv/DSLFPs.h" |
| |
| // For brevity |
| using UniformHandle = GrGLSLProgramDataManager::UniformHandle; |
| using Direction = GrGaussianConvolutionFragmentProcessor::Direction; |
| |
| class GrGaussianConvolutionFragmentProcessor::Impl : public GrFragmentProcessor::ProgramImpl { |
| public: |
| void emitCode(EmitArgs&) override; |
| |
| static inline void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*); |
| |
| protected: |
| void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; |
| |
| private: |
| UniformHandle fKernelUni; |
| UniformHandle fOffsetsUni; |
| UniformHandle fKernelWidthUni; |
| UniformHandle fIncrementUni; |
| |
| using INHERITED = ProgramImpl; |
| }; |
| |
| enum class LoopType { |
| kUnrolled, |
| kFixedLength, |
| kVariableLength, |
| }; |
| |
| static LoopType loop_type(const GrShaderCaps& caps) { |
| // This checks that bitwise integer operations and array indexing by non-consts are allowed. |
| if (caps.generation() < k130_GrGLSLGeneration) { |
| return LoopType::kUnrolled; |
| } |
| // If we're in reduced shader mode and we can have a loop then use a uniform to limit the |
| // number of iterations so we don't need a code variation for each width. |
| return caps.reducedShaderMode() ? LoopType::kVariableLength : LoopType::kFixedLength; |
| } |
| |
| void GrGaussianConvolutionFragmentProcessor::Impl::emitCode(EmitArgs& args) { |
| const GrGaussianConvolutionFragmentProcessor& ce = |
| args.fFp.cast<GrGaussianConvolutionFragmentProcessor>(); |
| |
| using namespace SkSL::dsl; |
| StartFragmentProcessor(this, &args); |
| GlobalVar increment(kUniform_Modifier, kHalf2_Type, "Increment"); |
| Declare(increment); |
| fIncrementUni = VarUniformHandle(increment); |
| |
| int width = SkGpuBlurUtils::LinearKernelWidth(ce.fRadius); |
| |
| LoopType loopType = loop_type(*args.fShaderCaps); |
| |
| int arrayCount; |
| if (loopType == LoopType::kVariableLength) { |
| // Size the kernel uniform for the maximum width. |
| arrayCount = (SkGpuBlurUtils::LinearKernelWidth(kMaxKernelRadius) + 3) / 4; |
| } else { |
| arrayCount = (width + 3) / 4; |
| SkASSERT(4 * arrayCount >= width); |
| } |
| |
| GlobalVar kernel(kUniform_Modifier, Array(kHalf4_Type, arrayCount), "Kernel"); |
| Declare(kernel); |
| fKernelUni = VarUniformHandle(kernel); |
| |
| |
| GlobalVar offsets(kUniform_Modifier, Array(kHalf4_Type, arrayCount), "Offsets"); |
| Declare(offsets); |
| fOffsetsUni = VarUniformHandle(offsets); |
| |
| Var color(kHalf4_Type, "color", Half4(0)); |
| Declare(color); |
| |
| Var coord(kFloat2_Type, "coord", sk_SampleCoord()); |
| Declare(coord); |
| |
| switch (loopType) { |
| case LoopType::kUnrolled: |
| for (int i = 0; i < width; i++) { |
| color += SampleChild(/*index=*/0, coord + offsets[i / 4][i & 3] * increment) * |
| kernel[i / 4][i & 0x3]; |
| } |
| break; |
| case LoopType::kFixedLength: { |
| Var i(kInt_Type, "i", 0); |
| For(Declare(i), i < width, i++, |
| color += SampleChild(/*index=*/0, coord + offsets[i / 4][i & 3] * increment) * |
| kernel[i / 4][i & 0x3]); |
| break; |
| } |
| case LoopType::kVariableLength: { |
| GlobalVar kernelWidth(kUniform_Modifier, kInt_Type, "kernelWidth"); |
| Declare(kernelWidth); |
| fKernelWidthUni = VarUniformHandle(kernelWidth); |
| Var i(kInt_Type, "i", 0); |
| For(Declare(i), i < kernelWidth, i++, |
| color += SampleChild(/*index=*/0, coord + offsets[i / 4][i & 3] * increment) * |
| kernel[i / 4][i & 0x3]); |
| break; |
| } |
| } |
| |
| Return(color); |
| EndFragmentProcessor(); |
| } |
| |
| void GrGaussianConvolutionFragmentProcessor::Impl::onSetData(const GrGLSLProgramDataManager& pdman, |
| const GrFragmentProcessor& processor) { |
| const auto& conv = processor.cast<GrGaussianConvolutionFragmentProcessor>(); |
| |
| float increment[2] = {}; |
| increment[static_cast<int>(conv.fDirection)] = 1; |
| pdman.set2fv(fIncrementUni, 1, increment); |
| |
| int width = SkGpuBlurUtils::LinearKernelWidth(conv.fRadius); |
| int arrayCount = (width + 3)/4; |
| SkDEBUGCODE(size_t arraySize = 4*arrayCount;) |
| SkASSERT(arraySize >= static_cast<size_t>(width)); |
| SkASSERT(arraySize <= SK_ARRAY_COUNT(GrGaussianConvolutionFragmentProcessor::fKernel)); |
| pdman.set4fv(fKernelUni, arrayCount, conv.fKernel); |
| pdman.set4fv(fOffsetsUni, arrayCount, conv.fOffsets); |
| if (fKernelWidthUni.isValid()) { |
| pdman.set1i(fKernelWidthUni, width); |
| } |
| } |
| |
| void GrGaussianConvolutionFragmentProcessor::Impl::GenKey(const GrProcessor& processor, |
| const GrShaderCaps& shaderCaps, |
| GrProcessorKeyBuilder* b) { |
| const auto& conv = processor.cast<GrGaussianConvolutionFragmentProcessor>(); |
| if (loop_type(shaderCaps) != LoopType::kVariableLength) { |
| b->add32(conv.fRadius); |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| std::unique_ptr<GrFragmentProcessor> GrGaussianConvolutionFragmentProcessor::Make( |
| GrSurfaceProxyView view, |
| SkAlphaType alphaType, |
| Direction dir, |
| int halfWidth, |
| float gaussianSigma, |
| GrSamplerState::WrapMode wm, |
| const SkIRect& subset, |
| const SkIRect* pixelDomain, |
| const GrCaps& caps) { |
| std::unique_ptr<GrFragmentProcessor> child; |
| bool is_zero_sigma = SkGpuBlurUtils::IsEffectivelyZeroSigma(gaussianSigma); |
| // We should sample as nearest if there will be no shader to preserve existing behaviour, but |
| // the linear blur requires a linear sample. |
| GrSamplerState::Filter filter = is_zero_sigma ? |
| GrSamplerState::Filter::kNearest : GrSamplerState::Filter::kLinear; |
| GrSamplerState sampler(wm, filter); |
| if (is_zero_sigma) { |
| halfWidth = 0; |
| } |
| // It's pretty common to blur a subset of an input texture. In reduced shader mode we always |
| // apply the wrap mode in the shader. |
| bool alwaysUseShaderTileMode = caps.reducedShaderMode(); |
| if (pixelDomain && !alwaysUseShaderTileMode) { |
| // Inset because we expect to be invoked at pixel centers. |
| SkRect domain = SkRect::Make(*pixelDomain).makeInset(0.5, 0.5f); |
| switch (dir) { |
| case Direction::kX: domain.outset(halfWidth, 0); break; |
| case Direction::kY: domain.outset(0, halfWidth); break; |
| } |
| child = GrTextureEffect::MakeSubset(std::move(view), |
| alphaType, |
| SkMatrix::I(), |
| sampler, |
| SkRect::Make(subset), |
| domain, |
| caps, |
| GrTextureEffect::kDefaultBorder); |
| } else { |
| child = GrTextureEffect::MakeSubset(std::move(view), |
| alphaType, |
| SkMatrix::I(), |
| sampler, |
| SkRect::Make(subset), |
| caps, |
| GrTextureEffect::kDefaultBorder, |
| alwaysUseShaderTileMode); |
| } |
| |
| if (is_zero_sigma) { |
| return child; |
| } |
| return std::unique_ptr<GrFragmentProcessor>(new GrGaussianConvolutionFragmentProcessor( |
| std::move(child), dir, halfWidth, gaussianSigma)); |
| } |
| |
| GrGaussianConvolutionFragmentProcessor::GrGaussianConvolutionFragmentProcessor( |
| std::unique_ptr<GrFragmentProcessor> child, |
| Direction direction, |
| int radius, |
| float gaussianSigma) |
| : INHERITED(kGrGaussianConvolutionFragmentProcessor_ClassID, |
| ProcessorOptimizationFlags(child.get())) |
| , fRadius(radius) |
| , fDirection(direction) { |
| this->registerChild(std::move(child), SkSL::SampleUsage::Explicit()); |
| SkASSERT(radius <= kMaxKernelRadius); |
| SkGpuBlurUtils::Compute1DLinearGaussianKernel(fKernel, fOffsets, gaussianSigma, fRadius); |
| this->setUsesSampleCoordsDirectly(); |
| } |
| |
| GrGaussianConvolutionFragmentProcessor::GrGaussianConvolutionFragmentProcessor( |
| const GrGaussianConvolutionFragmentProcessor& that) |
| : INHERITED(kGrGaussianConvolutionFragmentProcessor_ClassID, that.optimizationFlags()) |
| , fRadius(that.fRadius) |
| , fDirection(that.fDirection) { |
| this->cloneAndRegisterAllChildProcessors(that); |
| memcpy(fKernel, that.fKernel, SkGpuBlurUtils::LinearKernelWidth(fRadius) * sizeof(float)); |
| memcpy(fOffsets, that.fOffsets, SkGpuBlurUtils::LinearKernelWidth(fRadius) * sizeof(float)); |
| this->setUsesSampleCoordsDirectly(); |
| } |
| |
| void GrGaussianConvolutionFragmentProcessor::onAddToKey(const GrShaderCaps& caps, |
| GrProcessorKeyBuilder* b) const { |
| Impl::GenKey(*this, caps, b); |
| } |
| |
| std::unique_ptr<GrFragmentProcessor::ProgramImpl> |
| GrGaussianConvolutionFragmentProcessor::onMakeProgramImpl() const { |
| return std::make_unique<Impl>(); |
| } |
| |
| bool GrGaussianConvolutionFragmentProcessor::onIsEqual(const GrFragmentProcessor& sBase) const { |
| const auto& that = sBase.cast<GrGaussianConvolutionFragmentProcessor>(); |
| return fRadius == that.fRadius && fDirection == that.fDirection && |
| std::equal(fKernel, fKernel + SkGpuBlurUtils::LinearKernelWidth(fRadius), that.fKernel) && |
| std::equal(fOffsets, fOffsets + SkGpuBlurUtils::LinearKernelWidth(fRadius), that.fOffsets); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrGaussianConvolutionFragmentProcessor); |
| |
| #if GR_TEST_UTILS |
| std::unique_ptr<GrFragmentProcessor> GrGaussianConvolutionFragmentProcessor::TestCreate( |
| GrProcessorTestData* d) { |
| auto [view, ct, at] = d->randomView(); |
| |
| Direction dir = d->fRandom->nextBool() ? Direction::kY : Direction::kX; |
| SkIRect subset{ |
| static_cast<int>(d->fRandom->nextRangeU(0, view.width() - 1)), |
| static_cast<int>(d->fRandom->nextRangeU(0, view.height() - 1)), |
| static_cast<int>(d->fRandom->nextRangeU(0, view.width() - 1)), |
| static_cast<int>(d->fRandom->nextRangeU(0, view.height() - 1)), |
| }; |
| subset.sort(); |
| |
| auto wm = static_cast<GrSamplerState::WrapMode>( |
| d->fRandom->nextULessThan(GrSamplerState::kWrapModeCount)); |
| int radius = d->fRandom->nextRangeU(1, kMaxKernelRadius); |
| float sigma = radius / 3.f; |
| SkIRect temp; |
| SkIRect* domain = nullptr; |
| if (d->fRandom->nextBool()) { |
| temp = { |
| static_cast<int>(d->fRandom->nextRangeU(0, view.width() - 1)), |
| static_cast<int>(d->fRandom->nextRangeU(0, view.height() - 1)), |
| static_cast<int>(d->fRandom->nextRangeU(0, view.width() - 1)), |
| static_cast<int>(d->fRandom->nextRangeU(0, view.height() - 1)), |
| }; |
| temp.sort(); |
| domain = &temp; |
| } |
| |
| return GrGaussianConvolutionFragmentProcessor::Make(std::move(view), at, dir, radius, sigma, wm, |
| subset, domain, *d->caps()); |
| } |
| #endif |