1 /*
2 * Copyright 2023 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "include/core/SkFlattenable.h"
9 #include "src/base/SkArenaAlloc.h"
10 #include "src/core/SkRasterPipeline.h"
11 #include "src/core/SkReadBuffer.h"
12 #include "src/core/SkRuntimeEffectPriv.h"
13 #include "src/core/SkVM.h"
14 #include "src/core/SkWriteBuffer.h"
15 #include "src/shaders/SkShaderBase.h"
16
17 #if defined(SK_GANESH)
18 #include "include/effects/SkRuntimeEffect.h"
19 #include "include/gpu/GrRecordingContext.h"
20 #include "src/gpu/ganesh/GrFPArgs.h"
21 #include "src/gpu/ganesh/GrFragmentProcessor.h"
22 #include "src/gpu/ganesh/effects/GrMatrixEffect.h"
23 #include "src/gpu/ganesh/effects/GrSkSLFP.h"
24 #endif
25
26 class SkShader_CoordClamp final : public SkShaderBase {
27 public:
SkShader_CoordClamp(sk_sp<SkShader> shader,const SkRect & subset)28 SkShader_CoordClamp(sk_sp<SkShader> shader, const SkRect& subset)
29 : fShader(std::move(shader)), fSubset(subset) {}
30
31 #if defined(SK_GANESH)
32 std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&,
33 const MatrixRec&) const override;
34 #endif
35
36 protected:
37 SkShader_CoordClamp(SkReadBuffer&);
38 void flatten(SkWriteBuffer&) const override;
39 bool appendStages(const SkStageRec&, const MatrixRec&) const override;
40 skvm::Color program(skvm::Builder*,
41 skvm::Coord device,
42 skvm::Coord local,
43 skvm::Color paint,
44 const MatrixRec&,
45 const SkColorInfo& dst,
46 skvm::Uniforms*,
47 SkArenaAlloc*) const override;
48
49 private:
50 friend void ::SkRegisterCoordClampShaderFlattenable();
51 SK_FLATTENABLE_HOOKS(SkShader_CoordClamp)
52
53 sk_sp<SkShader> fShader;
54 SkRect fSubset;
55 };
56
CreateProc(SkReadBuffer & buffer)57 sk_sp<SkFlattenable> SkShader_CoordClamp::CreateProc(SkReadBuffer& buffer) {
58 sk_sp<SkShader> shader(buffer.readShader());
59 SkRect subset = buffer.readRect();
60 if (!buffer.validate(SkToBool(shader))) {
61 return nullptr;
62 }
63 return SkShaders::CoordClamp(std::move(shader), subset);
64 }
65
flatten(SkWriteBuffer & buffer) const66 void SkShader_CoordClamp::flatten(SkWriteBuffer& buffer) const {
67 buffer.writeFlattenable(fShader.get());
68 buffer.writeRect(fSubset);
69 }
70
appendStages(const SkStageRec & rec,const MatrixRec & mRec) const71 bool SkShader_CoordClamp::appendStages(const SkStageRec& rec, const MatrixRec& mRec) const {
72 std::optional<MatrixRec> childMRec = mRec.apply(rec);
73 if (!childMRec.has_value()) {
74 return false;
75 }
76 // Strictly speaking, childMRec's total matrix is not valid. It is only valid inside the subset
77 // rectangle. However, we don't mark it as such because we want the "total matrix is valid"
78 // behavior in SkImageShader for filtering.
79 auto clampCtx = rec.fAlloc->make<SkRasterPipeline_CoordClampCtx>();
80 *clampCtx = {fSubset.fLeft, fSubset.fTop, fSubset.fRight, fSubset.fBottom};
81 rec.fPipeline->append(SkRasterPipelineOp::clamp_x_and_y, clampCtx);
82 return as_SB(fShader)->appendStages(rec, *childMRec);
83 }
84
program(skvm::Builder * p,skvm::Coord device,skvm::Coord local,skvm::Color paint,const MatrixRec & mRec,const SkColorInfo & cinfo,skvm::Uniforms * uniforms,SkArenaAlloc * alloc) const85 skvm::Color SkShader_CoordClamp::program(skvm::Builder* p,
86 skvm::Coord device,
87 skvm::Coord local,
88 skvm::Color paint,
89 const MatrixRec& mRec,
90 const SkColorInfo& cinfo,
91 skvm::Uniforms* uniforms,
92 SkArenaAlloc* alloc) const {
93 std::optional<MatrixRec> childMRec = mRec.apply(p, &local, uniforms);
94 if (!childMRec.has_value()) {
95 return {};
96 }
97 // See comment in appendStages about not marking childMRec with an invalid total matrix.
98
99 auto l = uniforms->pushF(fSubset.left());
100 auto t = uniforms->pushF(fSubset.top());
101 auto r = uniforms->pushF(fSubset.right());
102 auto b = uniforms->pushF(fSubset.bottom());
103
104 local.x = p->clamp(local.x, p->uniformF(l), p->uniformF(r));
105 local.y = p->clamp(local.y, p->uniformF(t), p->uniformF(b));
106
107 return as_SB(fShader)->program(p, device, local, paint, *childMRec, cinfo, uniforms, alloc);
108 }
109
110 #if defined(SK_GANESH)
asFragmentProcessor(const GrFPArgs & args,const MatrixRec & mRec) const111 std::unique_ptr<GrFragmentProcessor> SkShader_CoordClamp::asFragmentProcessor(
112 const GrFPArgs& args, const MatrixRec& mRec) const {
113 static const SkRuntimeEffect* effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
114 "uniform shader c;"
115 "uniform float4 s;"
116 "half4 main(float2 p) {"
117 "return c.eval(clamp(p, s.LT, s.RB));"
118 "}");
119
120 auto fp = as_SB(fShader)->asFragmentProcessor(args, mRec.applied());
121 if (!fp) {
122 return nullptr;
123 }
124
125 GrSkSLFP::OptFlags flags = GrSkSLFP::OptFlags::kNone;
126 if (fp->compatibleWithCoverageAsAlpha()) {
127 flags |= GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha;
128 }
129 if (fp->preservesOpaqueInput()) {
130 flags |= GrSkSLFP::OptFlags::kPreservesOpaqueInput;
131 }
132 fp = GrSkSLFP::Make(effect,
133 "clamp_fp",
134 /*inputFP=*/nullptr,
135 flags,
136 "c", std::move(fp),
137 "s", fSubset);
138 bool success;
139 std::tie(success, fp) = mRec.apply(std::move(fp));
140 return success ? std::move(fp) : nullptr;
141 }
142 #endif // defined(SK_GANESH)
143
SkRegisterCoordClampShaderFlattenable()144 void SkRegisterCoordClampShaderFlattenable() { SK_REGISTER_FLATTENABLE(SkShader_CoordClamp); }
145
CoordClamp(sk_sp<SkShader> shader,const SkRect & subset)146 sk_sp<SkShader> SkShaders::CoordClamp(sk_sp<SkShader> shader, const SkRect& subset) {
147 if (!shader) {
148 return nullptr;
149 }
150 if (!subset.isSorted()) {
151 return nullptr;
152 }
153 return sk_make_sp<SkShader_CoordClamp>(std::move(shader), subset);
154 }
155