skia2/include/effects/SkRuntimeEffect.h

269 lines
9.2 KiB
C
Raw Normal View History

/*
* Copyright 2019 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkRuntimeEffect_DEFINED
#define SkRuntimeEffect_DEFINED
#include "include/core/SkData.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkString.h"
#include "include/private/SkSLSampleMatrix.h"
#include <string>
#include <vector>
#if SK_SUPPORT_GPU
#include "include/gpu/GrContextOptions.h"
#include "include/private/GrTypesPriv.h"
#endif
class GrShaderCaps;
class SkColorFilter;
class SkShader;
namespace SkSL {
class ByteCode;
struct PipelineStageArgs;
struct Program;
class SharedCompiler;
}
/*
* SkRuntimeEffect supports creating custom SkShader and SkColorFilter objects using Skia's SkSL
* shading language.
*
* NOTE: This API is experimental and subject to change.
*/
class SK_API SkRuntimeEffect : public SkRefCnt {
public:
struct Variable {
enum class Qualifier {
kUniform,
kIn,
};
enum class Type {
kBool,
kInt,
kFloat,
kFloat2,
kFloat3,
kFloat4,
kFloat2x2,
kFloat3x3,
kFloat4x4,
};
enum Flags {
kArray_Flag = 0x1,
kMarker_Flag = 0x2,
kMarkerNormals_Flag = 0x4,
kSRGBUnpremul_Flag = 0x8,
};
SkString fName;
size_t fOffset;
Qualifier fQualifier;
Type fType;
int fCount;
uint32_t fFlags;
uint32_t fMarker;
#if SK_SUPPORT_GPU
GrSLType fGPUType;
#endif
bool isArray() const { return SkToBool(fFlags & kArray_Flag); }
size_t sizeInBytes() const;
};
struct Varying {
SkString fName;
int fWidth; // 1 - 4 (floats)
};
// [Effect, ErrorText]
// If successful, Effect != nullptr, otherwise, ErrorText contains the reason for failure.
using EffectResult = std::tuple<sk_sp<SkRuntimeEffect>, SkString>;
static EffectResult Make(SkString sksl);
sk_sp<SkShader> makeShader(sk_sp<SkData> inputs,
sk_sp<SkShader> children[],
size_t childCount,
const SkMatrix* localMatrix,
bool isOpaque);
sk_sp<SkColorFilter> makeColorFilter(sk_sp<SkData> inputs);
const SkString& source() const { return fSkSL; }
uint32_t hash() const { return fHash; }
template <typename T>
class ConstIterable {
public:
ConstIterable(const std::vector<T>& vec) : fVec(vec) {}
using const_iterator = typename std::vector<T>::const_iterator;
const_iterator begin() const { return fVec.begin(); }
const_iterator end() const { return fVec.end(); }
size_t count() const { return fVec.size(); }
private:
const std::vector<T>& fVec;
};
// Combined size of all 'in' and 'uniform' variables. When calling makeColorFilter or
// makeShader, provide an SkData of this size, containing values for all of those variables.
size_t inputSize() const;
ConstIterable<Variable> inputs() const { return ConstIterable<Variable>(fInAndUniformVars); }
ConstIterable<SkString> children() const { return ConstIterable<SkString>(fChildren); }
ConstIterable<Varying> varyings() const { return ConstIterable<Varying>(fVaryings); }
// Returns pointer to the named in/uniform variable's description, or nullptr if not found
const Variable* findInput(const char* name) const;
// Returns index of the named child, or -1 if not found
int findChild(const char* name) const;
Update how sample(matrix) calls are invoked in SkSL This removes the kMixed type of SkSL::SampleMatrix. All analysis of FP sampling due to parent-child relationships is tracked in flags on GrFragmentProcessor now. The sample strategy is tracked as follows: - An FP marks itself as using the local coordinate builtin directly (automatically done for .fp code based on reference to sk_TransformedCoords2D[0]). - This state propagates up the parent towards the root, marking FPs as using coordinates indirectly. We stop the propagation when we hit a parent FP that explicitly samples the child because it becomes the source of the child's coordinates. - If that parent references its local coordinates directly, that kicks off its own upwards propagation. - Being sampled explicitly propagates down to all children, and effectively disables vertex-shader evaluation of transforms. - A variable matrix automatically marks this flag as well, since it's essentially a shortcut to (matrix expression) * coords. - The matrix type also propagates down, but right now that's only for whether or not there's perspective. - This doesn't affect FS coord evaluation since each FP applies its action independently. - But for VS-promoted transforms, the child's varying may inherit perspective (or other more general matrix types) from the parent and switch from a float2 to a float3. - A SampleMatrix no longer tracks a base or owner, GrFragmentProcessor exposes its parent FP. An FP's sample matrix is always owned by its immediate parent. - This means that you can have a hierarchy from root to leaf like: [uniform, none, none, uses local coords], and that leaf will have a SampleMatrix of kNone type. However, because of parent tracking, the coordinate generation can walk up to the root and detect the proper transform expression it needs to produce, and automatically de-duplicate across children. Currently, all FP's that are explicitly sampled have a signature of (color, float2 coord). FP's that don't use local coords, or whose coords are promoted to a varying have a signature of (color). - In this case, the shader builder either updates args.fLocalCoords to point to the varying directly, or adds a float2 local to the function body that includes the perspective divide. GrFragmentProcessor automatically pretends it has an identity coord transform if the FP is marked as referencing the local coord builtin. This allows these FPs to still be processed as part of GrGLSLGeometryProcessor::collectTransforms, but removes the need for FP implementations to declare an identity GrCoordTransform. - To test this theory, GrTextureEffect and GrSkSLFP no longer have coord transforms explicitly. - Later CLs can trivially remove them from a lot of the other effects. - The coord generation should not change because it detects in both cases that the coord transform matrices were identity. GrGLSLGeometryProcessor's collectTransforms and emitTransformCode has been completely overhauled to recurse up an FP's parent pointers and collect the expressions that affect the result. It de-duplicates expressions between siblings, and is able to produce a single varying for the base local coord (either when there are no intervening transforms, or the root FP needs an explicit coordinate to start off with). This also adds the fp_sample_chaining GM from Brian, with a few more configurations to fill out the cells. Bug: skia:10396 Change-Id: I86acc0c34c9f29d6371b34370bee9a18c2acf1c1 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/297868 Commit-Queue: Michael Ludwig <michaelludwig@google.com> Reviewed-by: Brian Salomon <bsalomon@google.com> Reviewed-by: Brian Osman <brianosman@google.com>
2020-06-24 13:04:56 +00:00
bool usesSampleCoords() const { return fMainFunctionHasSampleCoords; }
static void RegisterFlattenables();
~SkRuntimeEffect();
private:
SkRuntimeEffect(SkString sksl,
std::unique_ptr<SkSL::Program> baseProgram,
std::vector<Variable>&& inAndUniformVars,
std::vector<SkString>&& children,
std::vector<SkSL::SampleMatrix>&& sampleMatrices,
std::vector<Varying>&& varyings,
size_t uniformSize,
bool mainHasSampleCoords);
using SpecializeResult = std::tuple<std::unique_ptr<SkSL::Program>, SkString>;
SpecializeResult specialize(SkSL::Program& baseProgram, const void* inputs,
const SkSL::SharedCompiler&) const;
#if SK_SUPPORT_GPU
friend class GrSkSLFP; // toPipelineStage
friend class GrGLSLSkSLFP; // fSampleMatrices
// This re-compiles the program from scratch, using the supplied shader caps.
// This is necessary to get the correct values of settings.
bool toPipelineStage(const void* inputs, const GrShaderCaps* shaderCaps,
GrContextOptions::ShaderErrorHandler* errorHandler,
SkSL::PipelineStageArgs* outArgs);
#endif
friend class SkRTShader; // toByteCode & uniformSize
friend class SkRuntimeColorFilter; //
// [ByteCode, ErrorText]
// If successful, ByteCode != nullptr, otherwise, ErrorText contains the reason for failure.
using ByteCodeResult = std::tuple<std::unique_ptr<SkSL::ByteCode>, SkString>;
first sksl on skvm Exactly enough implemented to run fm --skvm -b cpu -s runtime_shader -w foo This shader 0: 0077 load2 0 10: 00a2 pushimmediate 998277249(0.0039215688593685627) 22: 0070 dup 31: 0094 multiplyf2 40: 0080 loaduniform 2 50: 00a2 pushimmediate 1065353216(1.0) 62: 00c1 store4 2 71: 00b4 return 0 becomes this blitter, including matrix, blending, asserts, etc: 17 registers, 57 instructions: 0 r0 = uniform32 arg(0) 4 1 r0 = to_f32 r0 2 r1 = splat 3F000000 (0.5) 3 r0 = add_f32 r0 r1 4 r2 = uniform32 arg(0) 2C 5 r3 = uniform32 arg(0) 28 6 r2 = fma_f32 r0 r3 r2 7 r3 = uniform32 arg(0) 0 8 r4 = uniform32 arg(0) 24 9 r5 = splat 3F800000 (1) 10 r6 = uniform32 arg(0) 38 11 r6 = min_f32 r6 r5 12 r7 = splat 0 (0) 13 r6 = max_f32 r7 r6 14 r8 = splat 437F0000 (255) 15 r9 = mul_f32 r6 r8 16 r9 = round r9 17 r10 = splat FF (3.5733111e-43) 18 r10 = pack r9 r10 8 19 r9 = splat 3B808081 (0.0039215689) 20 r11 = uniform32 arg(0) 20 21 r12 = uniform32 arg(0) 1C 22 r11 = fma_f32 r0 r12 r11 23 r12 = uniform32 arg(0) 18 24 r0 = splat 3F800001 (1.0000001) 25 r13 = min_f32 r6 r0 26 r14 = splat B4000000 (-1.1920929e-07) 27 r13 = max_f32 r14 r13 28 r13 = eq_f32 r6 r13 29 assert_true r13 r6 loop: 30 r6 = index 31 r6 = sub_i32 r3 r6 32 r6 = to_f32 r6 33 r6 = add_f32 r6 r1 34 r13 = fma_f32 r6 r4 r2 35 r13 = mul_f32 r9 r13 36 r6 = fma_f32 r6 r12 r11 37 r13 = min_f32 r13 r5 38 r13 = max_f32 r7 r13 39 r15 = mul_f32 r13 r8 40 r15 = round r15 41 r6 = mul_f32 r9 r6 42 r6 = min_f32 r6 r5 43 r6 = max_f32 r7 r6 44 r16 = mul_f32 r6 r8 45 r16 = round r16 46 r15 = pack r16 r15 8 47 r15 = pack r15 r10 16 48 store32 arg(1) r15 49 r15 = min_f32 r13 r0 50 r15 = max_f32 r14 r15 51 r15 = eq_f32 r13 r15 52 assert_true r15 r13 53 r13 = min_f32 r6 r0 54 r13 = max_f32 r14 r13 55 r13 = eq_f32 r6 r13 56 assert_true r13 r6 And that JITs using 11 ymm registers. Change-Id: Ib45b5fa6aee427f290b77d8900f10d433ad81133 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/281746 Reviewed-by: Brian Osman <brianosman@google.com> Commit-Queue: Mike Klein <mtklein@google.com>
2020-04-06 13:54:47 +00:00
ByteCodeResult toByteCode(const void* inputs) const;
// Combined size of just the 'uniform' variables.
size_t uniformSize() const { return fUniformSize; }
uint32_t fHash;
SkString fSkSL;
std::unique_ptr<SkSL::Program> fBaseProgram;
std::vector<Variable> fInAndUniformVars;
std::vector<SkString> fChildren;
std::vector<SkSL::SampleMatrix> fSampleMatrices;
std::vector<Varying> fVaryings;
size_t fUniformSize;
Update how sample(matrix) calls are invoked in SkSL This removes the kMixed type of SkSL::SampleMatrix. All analysis of FP sampling due to parent-child relationships is tracked in flags on GrFragmentProcessor now. The sample strategy is tracked as follows: - An FP marks itself as using the local coordinate builtin directly (automatically done for .fp code based on reference to sk_TransformedCoords2D[0]). - This state propagates up the parent towards the root, marking FPs as using coordinates indirectly. We stop the propagation when we hit a parent FP that explicitly samples the child because it becomes the source of the child's coordinates. - If that parent references its local coordinates directly, that kicks off its own upwards propagation. - Being sampled explicitly propagates down to all children, and effectively disables vertex-shader evaluation of transforms. - A variable matrix automatically marks this flag as well, since it's essentially a shortcut to (matrix expression) * coords. - The matrix type also propagates down, but right now that's only for whether or not there's perspective. - This doesn't affect FS coord evaluation since each FP applies its action independently. - But for VS-promoted transforms, the child's varying may inherit perspective (or other more general matrix types) from the parent and switch from a float2 to a float3. - A SampleMatrix no longer tracks a base or owner, GrFragmentProcessor exposes its parent FP. An FP's sample matrix is always owned by its immediate parent. - This means that you can have a hierarchy from root to leaf like: [uniform, none, none, uses local coords], and that leaf will have a SampleMatrix of kNone type. However, because of parent tracking, the coordinate generation can walk up to the root and detect the proper transform expression it needs to produce, and automatically de-duplicate across children. Currently, all FP's that are explicitly sampled have a signature of (color, float2 coord). FP's that don't use local coords, or whose coords are promoted to a varying have a signature of (color). - In this case, the shader builder either updates args.fLocalCoords to point to the varying directly, or adds a float2 local to the function body that includes the perspective divide. GrFragmentProcessor automatically pretends it has an identity coord transform if the FP is marked as referencing the local coord builtin. This allows these FPs to still be processed as part of GrGLSLGeometryProcessor::collectTransforms, but removes the need for FP implementations to declare an identity GrCoordTransform. - To test this theory, GrTextureEffect and GrSkSLFP no longer have coord transforms explicitly. - Later CLs can trivially remove them from a lot of the other effects. - The coord generation should not change because it detects in both cases that the coord transform matrices were identity. GrGLSLGeometryProcessor's collectTransforms and emitTransformCode has been completely overhauled to recurse up an FP's parent pointers and collect the expressions that affect the result. It de-duplicates expressions between siblings, and is able to produce a single varying for the base local coord (either when there are no intervening transforms, or the root FP needs an explicit coordinate to start off with). This also adds the fp_sample_chaining GM from Brian, with a few more configurations to fill out the cells. Bug: skia:10396 Change-Id: I86acc0c34c9f29d6371b34370bee9a18c2acf1c1 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/297868 Commit-Queue: Michael Ludwig <michaelludwig@google.com> Reviewed-by: Brian Salomon <bsalomon@google.com> Reviewed-by: Brian Osman <brianosman@google.com>
2020-06-24 13:04:56 +00:00
bool fMainFunctionHasSampleCoords;
};
/**
* SkRuntimeShaderBuilder is a utility to simplify creating SkShader objects from SkRuntimeEffects.
*
* NOTE: Like SkRuntimeEffect, this API is experimental and subject to change!
*
* Given an SkRuntimeEffect, the SkRuntimeShaderBuilder manages creating an input data block and
* provides named access to the 'in' and 'uniform' variables in that block, as well as named access
* to a list of child shader slots. Usage:
*
* sk_sp<SkRuntimeEffect> effect = ...;
* SkRuntimeShaderBuilder builder(effect);
* builder.input("some_uniform_float") = 3.14f;
* builder.input("some_uniform_matrix") = SkM44::Rotate(...);
* builder.child("some_child_effect") = mySkImage->makeShader(...);
* ...
* sk_sp<SkShader> shader = builder.makeShader(nullptr, false);
*
* Note that SkRuntimeShaderBuilder is built entirely on the public API of SkRuntimeEffect,
* so can be used as-is or serve as inspiration for other interfaces or binding techniques.
*/
struct SkRuntimeShaderBuilder {
SkRuntimeShaderBuilder(sk_sp<SkRuntimeEffect>);
~SkRuntimeShaderBuilder();
struct BuilderInput {
// Copy 'val' to this variable. No type conversion is performed - 'val' must be same
// size as expected by the effect. Information about the variable can be queried by
// looking at fVar. If the size is incorrect, no copy will be performed, and debug
// builds will abort. If this is the result of querying a missing variable, fVar will
// be nullptr, and assigning will also do nothing (and abort in debug builds).
template <typename T>
std::enable_if_t<std::is_trivially_copyable<T>::value, BuilderInput&> operator=(
const T& val) {
if (!fVar) {
SkDEBUGFAIL("Assigning to missing variable");
} else if (sizeof(val) != fVar->sizeInBytes()) {
SkDEBUGFAIL("Incorrect value size");
} else {
memcpy(SkTAddOffset<void>(fOwner->fInputs->writable_data(), fVar->fOffset),
&val, sizeof(val));
}
return *this;
}
BuilderInput& operator=(const SkMatrix& val) {
if (!fVar) {
SkDEBUGFAIL("Assigning to missing variable");
} else if (fVar->sizeInBytes() != 9 * sizeof(float)) {
SkDEBUGFAIL("Incorrect value size");
} else {
float* data = SkTAddOffset<float>(fOwner->fInputs->writable_data(), fVar->fOffset);
data[0] = val.get(0); data[1] = val.get(3); data[2] = val.get(6);
data[3] = val.get(1); data[4] = val.get(4); data[5] = val.get(7);
data[6] = val.get(2); data[7] = val.get(5); data[8] = val.get(8);
}
return *this;
}
SkRuntimeShaderBuilder* fOwner;
const SkRuntimeEffect::Variable* fVar; // nullptr if the variable was not found
};
struct BuilderChild {
BuilderChild& operator=(const sk_sp<SkShader>& val);
SkRuntimeShaderBuilder* fOwner;
int fIndex; // -1 if the child was not found
};
BuilderInput input(const char* name) { return { this, fEffect->findInput(name) }; }
BuilderChild child(const char* name) { return { this, fEffect->findChild(name) }; }
sk_sp<SkShader> makeShader(const SkMatrix* localMatrix, bool isOpaque);
sk_sp<SkRuntimeEffect> fEffect;
sk_sp<SkData> fInputs;
std::vector<sk_sp<SkShader>> fChildren;
};
#endif