blob: eba33c6112752518847d8d7a72d701e4fd4eead2 [file] [log] [blame]
/*
* Copyright 2018 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrSkSLFP_DEFINED
#define GrSkSLFP_DEFINED
#include "include/core/SkRefCnt.h"
#include "src/gpu/GrCaps.h"
#include "src/gpu/GrCoordTransform.h"
#include "src/gpu/GrFragmentProcessor.h"
#include "src/gpu/GrShaderCaps.h"
#include "src/gpu/GrSkSLFPFactoryCache.h"
#include "src/sksl/SkSLCompiler.h"
#include "src/sksl/SkSLPipelineStageCodeGenerator.h"
#include <atomic>
#if GR_TEST_UTILS
#define GR_FP_SRC_STRING const char*
#else
#define GR_FP_SRC_STRING static const char*
#endif
class GrContext_Base;
class GrSkSLFPFactory;
class GrSkSLFP : public GrFragmentProcessor {
public:
/**
* Returns a new unique identifier. Each different SkSL fragment processor should call
* NewIndex once, statically, and use this index for all calls to Make.
*/
static int NewIndex() {
static std::atomic<int> nextIndex{0};
return nextIndex++;
}
/**
* Creates a new fragment processor from an SkSL source string and a struct of inputs to the
* program. The input struct's type is derived from the 'in' and 'uniform' variables in the SkSL
* source, so e.g. the shader:
*
* in bool dither;
* uniform float x;
* uniform float y;
* ....
*
* would expect a pointer to a struct set up like:
*
* struct {
* bool dither;
* float x;
* float y;
* };
*
* While both 'in' and 'uniform' variables go into this struct, the difference between them is
* that 'in' variables are statically "baked in" to the generated code, becoming literals,
* whereas uniform variables may be changed from invocation to invocation without having to
* recompile the shader.
*
* As the decision of whether to create a new shader or just upload new uniforms all happens
* behind the scenes, the difference between the two from an end-user perspective is primarily
* in performance: on the one hand, changing the value of an 'in' variable is very expensive
* (requiring the compiler to regenerate the code, upload a new shader to the GPU, and so
* forth), but on the other hand the compiler can optimize around its value because it is known
* at compile time. 'in' variables are therefore suitable for things like flags, where there are
* only a few possible values and a known-in-advance value can cause entire chunks of code to
* become dead (think static @ifs), while 'uniform's are used for continuous values like colors
* and coordinates, where it would be silly to create a separate shader for each possible set of
* values. Other than the (significant) performance implications, the only difference between
* the two is that 'in' variables can be used in static @if / @switch tests. When in doubt, use
* 'uniform'.
*
* As turning SkSL into GLSL / SPIR-V / etc. is fairly expensive, and the output may differ
* based on the inputs, internally the process is divided into two steps: we first parse and
* semantically analyze the SkSL into an internal representation, and then "specialize" this
* internal representation based on the inputs. The unspecialized internal representation of
* the program is cached, so further specializations of the same code are much faster than the
* first call.
*
* This caching is based on the 'index' parameter, which should be derived by statically calling
* 'NewIndex()'. Each given SkSL string should have a single, statically defined index
* associated with it.
*/
static std::unique_ptr<GrSkSLFP> Make(
GrContext_Base* context,
int index,
const char* name,
const char* sksl,
const void* inputs,
size_t inputSize,
SkSL::Program::Kind kind = SkSL::Program::kPipelineStage_Kind,
const SkMatrix* matrix = nullptr);
static std::unique_ptr<GrSkSLFP> Make(
GrContext_Base* context,
int index,
const char* name,
SkString sksl,
const void* inputs,
size_t inputSize,
SkSL::Program::Kind kind = SkSL::Program::kPipelineStage_Kind,
const SkMatrix* matrix = nullptr);
const char* name() const override;
void addChild(std::unique_ptr<GrFragmentProcessor> child);
std::unique_ptr<GrFragmentProcessor> clone() const override;
private:
GrSkSLFP(sk_sp<GrSkSLFPFactoryCache> factoryCache, const GrShaderCaps* shaderCaps,
SkSL::Program::Kind kind, int fIndex, const char* name, const char* sksl,
SkString skslString, const void* inputs, size_t inputSize, const SkMatrix* matrix);
GrSkSLFP(const GrSkSLFP& other);
GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
bool onIsEqual(const GrFragmentProcessor&) const override;
void createFactory() const;
sk_sp<GrSkSLFPFactoryCache> fFactoryCache;
const sk_sp<GrShaderCaps> fShaderCaps;
mutable sk_sp<GrSkSLFPFactory> fFactory;
SkSL::Program::Kind fKind;
int fIndex;
const char* fName;
// For object lifetime purposes, we have fields for the SkSL as both a const char* and a
// SkString. The const char* is the one we actually use, but it may point to the SkString's
// bytes. Since GrSkSLFPs are frequently created from constant strings, this allows us to
// generally avoid the overhead of copying the bytes into an SkString (in which case fSkSLString
// is the empty string), while still allowing the GrSkSLFP to manage the string's lifetime when
// needed.
SkString fSkSLString;
const char* fSkSL;
const std::unique_ptr<int8_t[]> fInputs;
size_t fInputSize;
GrCoordTransform fCoordTransform;
mutable SkSL::String fKey;
GR_DECLARE_FRAGMENT_PROCESSOR_TEST
typedef GrFragmentProcessor INHERITED;
friend class GrGLSLSkSLFP;
friend class GrSkSLFPFactory;
};
/**
* Produces GrFragmentProcessors from SkSL code. As the shader code produced from the SkSL depends
* upon the inputs to the SkSL (static if's, etc.) we first create a factory for a given SkSL
* string, then use that to create the actual GrFragmentProcessor.
*/
class GrSkSLFPFactory : public SkNVRefCnt<GrSkSLFPFactory> {
public:
/**
* Constructs a GrSkSLFPFactory for a given SkSL source string. Creating a factory will
* preprocess the SkSL and determine which of its inputs are declared "key" (meaning they cause
* the produced shaders to differ), so it is important to reuse the same factory instance for
* the same shader in order to avoid repeatedly re-parsing the SkSL.
*/
GrSkSLFPFactory(const char* name, const GrShaderCaps* shaderCaps, const char* sksl,
SkSL::Program::Kind kind = SkSL::Program::kPipelineStage_Kind);
const SkSL::Program* getSpecialization(const SkSL::String& key, const void* inputs,
size_t inputSize);
SkSL::Program::Kind fKind;
const char* fName;
SkSL::Compiler fCompiler;
std::shared_ptr<SkSL::Program> fBaseProgram;
std::vector<const SkSL::Variable*> fInAndUniformVars;
std::unordered_map<SkSL::String, std::unique_ptr<const SkSL::Program>> fSpecializations;
friend class GrSkSLFP;
};
#endif