blob: 63cc2c633f6074078d403a5a9c6db5c2f40ab13e [file] [log] [blame]
/*
* Copyright 2022 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/core/SkShaderCodeDictionary.h"
#include "include/private/SkSLString.h"
#include "src/core/SkOpts.h"
namespace {
void add_indent(std::string* result, int indent) {
result->append(4*indent, ' ');
}
} // anonymous namespace
// TODO: SkShaderInfo::toSkSL needs to work outside of both just graphite and metal. To do
// so we'll need to switch over to using SkSL's uniform capabilities.
#if SK_SUPPORT_GPU && defined(SK_GRAPHITE_ENABLED) && defined(SK_METAL)
#include <set>
// TODO: switch this over to using SkSL's uniform system
namespace skgpu::mtl {
std::string GetMtlUniforms(int bufferID,
const char* name,
const std::vector<SkShaderInfo::SnippetEntry>&);
} // namespace skgpu::mtl
// Emit the glue code needed to invoke a single static helper isolated w/in its own scope.
// The structure of this will be:
//
// half4 outColor%d;
// {
// half4 child-outColor%d; // for each child
// {
// /* emitted snippet sksl assigns to child-outColor%d */
// }
//
// /* emitted snippet sksl assigns to outColor%d - taking a vector of child var names */
// }
// Where the %d is filled in with 'entryIndex'.
std::string SkShaderInfo::emitGlueCodeForEntry(int* entryIndex,
std::string* result,
int indent) const {
const SkShaderInfo::SnippetEntry& entry = fEntries[*entryIndex];
int curEntryIndex = *entryIndex;
std::string scopeOutputVar(std::string("outColor") + std::to_string(curEntryIndex));
add_indent(result, indent);
SkSL::String::appendf(result, "half4 %s;\n", scopeOutputVar.c_str());
add_indent(result, indent);
*result += "{\n";
// Although the children appear after the parent in the shader info they are emitted
// before the parent
std::vector<std::string> childNames;
for (int j = 0; j < entry.fNumChildren; ++j) {
*entryIndex += 1;
std::string childOutputVar = this->emitGlueCodeForEntry(entryIndex, result, indent+1);
childNames.push_back(childOutputVar);
}
*result += (entry.fGlueCodeGenerator)(scopeOutputVar, curEntryIndex,
entry, childNames, indent+1);
add_indent(result, indent);
*result += "}\n";
return scopeOutputVar;
}
// The current, incomplete, model for shader construction is:
// each static code snippet (which can have an arbitrary signature) gets emitted once as a
// preamble
// glue code is then generated w/in the "main" method. The glue code is responsible for:
// 1) gathering the correct (mangled) uniforms
// 2) passing the uniforms and any other parameters to the helper method
// The result of the last code snippet is then copied into "sk_FragColor".
// Note: that each entry's 'fStaticFunctionName' field must match the name of the method defined
// in the 'fStaticSkSL' field.
std::string SkShaderInfo::toSkSL() const {
// The uniforms are mangled by having their index in 'fEntries' as a suffix (i.e., "_%d")
std::string result = skgpu::mtl::GetMtlUniforms(2, "FS", fEntries);
std::set<const char*> emittedStaticSnippets;
for (auto c : fEntries) {
if (emittedStaticSnippets.find(c.fStaticFunctionName) == emittedStaticSnippets.end()) {
result += c.fStaticSkSL;
emittedStaticSnippets.insert(c.fStaticFunctionName);
}
}
result += "layout(location = 0, index = 0) out half4 sk_FragColor;\n";
result += "void main() {\n";
// TODO: for some effects (e.g., SW blending) we will need to feed the output variable
// name from the prior step into the current step's glue code (and deal with the
// initial color issue).
std::string lastOutputVar;
for (int entryIndex = 0; entryIndex < (int) fEntries.size(); ++entryIndex) {
lastOutputVar = this->emitGlueCodeForEntry(&entryIndex, &result, 1);
}
SkSL::String::appendf(&result, " sk_FragColor = %s;\n", lastOutputVar.c_str());
result += "}\n";
return result;
}
#endif
SkShaderCodeDictionary::Entry* SkShaderCodeDictionary::makeEntry(
std::unique_ptr<SkPaintParamsKey> key) {
return fArena.make([&](void *ptr) { return new(ptr) Entry(std::move(key)); });
}
size_t SkShaderCodeDictionary::Hash::operator()(const SkPaintParamsKey* key) const {
return SkOpts::hash_fn(key->data(), key->sizeInBytes(), 0);
}
const SkShaderCodeDictionary::Entry* SkShaderCodeDictionary::findOrCreate(
std::unique_ptr<SkPaintParamsKey> key) {
SkAutoSpinlock lock{fSpinLock};
auto iter = fHash.find(key.get());
if (iter != fHash.end()) {
SkASSERT(fEntryVector[iter->second->uniqueID().asUInt()] == iter->second);
return iter->second;
}
Entry* newEntry = this->makeEntry(std::move(key));
newEntry->setUniqueID(fEntryVector.size());
fHash.insert(std::make_pair(newEntry->paintParamsKey(), newEntry));
fEntryVector.push_back(newEntry);
return newEntry;
}
const SkShaderCodeDictionary::Entry* SkShaderCodeDictionary::lookup(
SkUniquePaintParamsID codeID) const {
if (!codeID.isValid()) {
return nullptr;
}
SkAutoSpinlock lock{fSpinLock};
SkASSERT(codeID.asUInt() < fEntryVector.size());
return fEntryVector[codeID.asUInt()];
}
SkSpan<const SkUniform> SkShaderCodeDictionary::getUniforms(SkBuiltInCodeSnippetID id) const {
return fBuiltInCodeSnippets[(int) id].fUniforms;
}
const SkShaderInfo::SnippetEntry* SkShaderCodeDictionary::getEntry(SkBuiltInCodeSnippetID id) const {
return &fBuiltInCodeSnippets[(int) id];
}
void SkShaderCodeDictionary::getShaderInfo(SkUniquePaintParamsID uniqueID, SkShaderInfo* info) {
auto entry = this->lookup(uniqueID);
entry->paintParamsKey()->toShaderInfo(this, info);
}
int SkShaderCodeDictionary::addUserDefinedSnippet() {
fUserDefinedCodeSnippets.push_back({});
return kBuiltInCodeSnippetIDCount + fUserDefinedCodeSnippets.size() - 1;
}
//--------------------------------------------------------------------------------------------------
namespace {
// The default glue code just calls a helper function with the signature:
// half4 fStaticFunctionName(/* all uniforms as parameters */);
// and stores the result in a variable named "resultName".
std::string GenerateDefaultGlueCode(const std::string& resultName,
int entryIndex,
const SkShaderInfo::SnippetEntry& entry,
const std::vector<std::string>& childNames,
int indent) {
SkASSERT(childNames.empty());
std::string result;
add_indent(&result, indent);
SkSL::String::appendf(&result, "%s = %s(", resultName.c_str(), entry.fStaticFunctionName);
for (size_t i = 0; i < entry.fUniforms.size(); ++i) {
// The uniform names are mangled w/ the entry's index as a suffix
result += entry.fUniforms[i].name() + std::string("_") + std::to_string(entryIndex);
if (i+1 < entry.fUniforms.size()) {
result += ", ";
}
}
result += ");\n";
return result;
}
//--------------------------------------------------------------------------------------------------
static constexpr int kFourStopGradient = 4;
// TODO: For the sprint we unify all the gradient uniforms into a standard set of 6:
// kMaxStops colors
// kMaxStops offsets
// 2 points
// 2 radii
static constexpr int kNumGradientUniforms = 7;
static constexpr SkUniform kGradientUniforms[kNumGradientUniforms] = {
{ "colors", SkSLType::kFloat4, kFourStopGradient },
{ "offsets", SkSLType::kFloat, kFourStopGradient },
{ "point0", SkSLType::kFloat2 },
{ "point1", SkSLType::kFloat2 },
{ "radius0", SkSLType::kFloat },
{ "radius1", SkSLType::kFloat },
{ "padding", SkSLType::kFloat2 } // TODO: add automatic uniform padding
};
static const char *kLinearGradient4Name = "linear_grad_4_shader";
static const char *kLinearGradient4SkSL =
// TODO: This should use local coords
"half4 linear_grad_4_shader(float4 colorsParam[4],\n"
" float offsetsParam[4],\n"
" float2 point0Param,\n"
" float2 point1Param,\n"
" float radius0Param,\n"
" float radius1Param,\n"
" float2 padding) {\n"
" float2 pos = sk_FragCoord.xy;\n"
" float2 delta = point1Param - point0Param;\n"
" float2 pt = pos - point0Param;\n"
" float t = dot(pt, delta) / dot(delta, delta);\n"
" float4 result = colorsParam[0];\n"
" result = mix(result, colorsParam[1],\n"
" clamp((t-offsetsParam[0])/(offsetsParam[1]-offsetsParam[0]),\n"
" 0, 1));\n"
" result = mix(result, colorsParam[2],\n"
" clamp((t-offsetsParam[1])/(offsetsParam[2]-offsetsParam[1]),\n"
" 0, 1));\n"
" result = mix(result, colorsParam[3],\n"
" clamp((t-offsetsParam[2])/(offsetsParam[3]-offsetsParam[2]),\n"
" 0, 1));\n"
" return half4(result);\n"
"}\n";
//--------------------------------------------------------------------------------------------------
static constexpr int kNumSolidShaderUniforms = 1;
static constexpr SkUniform kSolidShaderUniforms[kNumSolidShaderUniforms] = {
{ "color", SkSLType::kFloat4 }
};
static const char* kSolidShaderName = "solid_shader";
static const char* kSolidShaderSkSL =
"half4 solid_shader(float4 colorParam) {\n"
" return half4(colorParam);\n"
"}\n";
//--------------------------------------------------------------------------------------------------
static constexpr int kNumImageShaderUniforms = 0;
static const char* kImageShaderName = "image_shader";
static const char* kImageShaderSkSL =
"half4 image_shader() {\n"
" float c = fract(abs(sk_FragCoord.x/10.0));\n"
" return half4(c, c, c, 1.0);\n"
"}\n";
//--------------------------------------------------------------------------------------------------
static constexpr int kNumBlendShaderUniforms = 0;
static constexpr int kNumBlendShaderChildren = 2;
// Note: we're counting on the compiler to inline this code and trim it down to just the used
// branch(es).
static const char* kBlendShaderName = "blend_shader";
static const char* kBlendShaderSkSL =
"const int kClear = 0;\n"
"const int kSrc = 1;\n"
"const int kDst = 2;\n"
"const int kSrcOver = 3;\n"
"const int kDstOver = 4;\n"
"const int kSrcIn = 5;\n"
"const int kDstIn = 6;\n"
"const int kSrcOut = 7;\n"
"const int kDstOut = 8;\n"
"const int kSrcATop = 9;\n"
"const int kDstATop = 10;\n"
"const int kXor = 11;\n"
"const int kPlus = 12;\n"
"const int kModulate = 13;\n"
"const int kScreen = 14;\n"
"const int kOverlay = 15;\n"
"const int kDarken = 16;\n"
"const int kLighten = 17;\n"
"const int kColorDodge = 18;\n"
"const int kColorBurn = 19;\n"
"const int kHardLight = 20;\n"
"const int kSoftLight = 21;\n"
"const int kDifference = 22;\n"
"const int kExclusion = 23;\n"
"const int kMultiply = 24;\n"
"const int kHue = 25;\n"
"const int kSaturation = 26;\n"
"const int kColor = 27;\n"
"const int kLuminosity = 28;\n"
"\n"
"half4 blend(int mode, half4 src, half4 dst) {\n"
" switch (mode) {\n"
" case kClear: { return blend_clear(src, dst); }\n"
" case kSrc: { return blend_src(src, dst); }\n"
" case kDst: { return blend_dst(src, dst); }\n"
" case kSrcOver: { return blend_src_over(src, dst); }\n"
" case kDstOver: { return blend_dst_over(src, dst); }\n"
" case kSrcIn: { return blend_src_in(src, dst); }\n"
" case kDstIn: { return blend_dst_in(src, dst); }\n"
" case kSrcOut: { return blend_src_out(src, dst); }\n"
" case kDstOut: { return blend_dst_out(src, dst); }\n"
" case kSrcATop: { return blend_src_atop(src, dst); }\n"
" case kDstATop: { return blend_dst_atop(src, dst); }\n"
" case kXor: { return blend_xor(src, dst); }\n"
" case kPlus: { return blend_plus(src, dst); }\n"
" case kModulate: { return blend_modulate(src, dst); }\n"
" case kScreen: { return blend_screen(src, dst); }\n"
" case kOverlay: { return blend_overlay(src, dst); }\n"
" case kDarken: { return blend_darken(src, dst); }\n"
" case kLighten: { return blend_lighten(src, dst); }\n"
" case kColorDodge: { return blend_color_dodge(src, dst); }\n"
" case kColorBurn: { return blend_color_burn(src, dst); }\n"
" case kHardLight: { return blend_hard_light(src, dst); }\n"
" case kSoftLight: { return blend_soft_light(src, dst); }\n"
" case kDifference: { return blend_difference(src, dst); }\n"
" case kExclusion: { return blend_exclusion(src, dst); }\n"
" case kMultiply: { return blend_multiply(src, dst); }\n"
" case kHue: { return blend_hue(src, dst); }\n"
" case kSaturation: { return blend_saturation(src, dst); }\n"
" case kColor: { return blend_color(src, dst); }\n"
" case kLuminosity: { return blend_luminosity(src, dst); }\n"
" default: return half4(0); // Avoids 'blend can exit without returning a value' error\n"
" }\n"
"}\n";
std::string GenerateBlendShaderGlueCode(const std::string& resultName,
int entryIndex,
const SkShaderInfo::SnippetEntry& entry,
const std::vector<std::string>& childNames,
int indent) {
SkASSERT(childNames.size() == kNumBlendShaderChildren);
std::string result;
add_indent(&result, indent);
// TODO: actually feed in the blend mode either through a uniform or, somehow, from the
// SkPaintParamsKey
SkSL::String::appendf(&result, "%s = blend(kModulate, %s, %s);\n",
resultName.c_str(),
childNames[1].c_str(),
childNames[0].c_str());
return result;
}
//--------------------------------------------------------------------------------------------------
static constexpr int kNumErrorUniforms = 0;
static const char* kErrorName = "error";
static const char* kErrorSkSL =
"half4 error() {\n"
" return half4(1.0, 0.0, 1.0, 1.0);\n"
"}\n";
} // anonymous namespace
static constexpr int kNoChildren = 0;
SkShaderCodeDictionary::SkShaderCodeDictionary() {
// The 0th index is reserved as invalid
fEntryVector.push_back(nullptr);
fBuiltInCodeSnippets[(int) SkBuiltInCodeSnippetID::kDepthStencilOnlyDraw] = {
{ nullptr, kNumErrorUniforms },
kErrorName, kErrorSkSL,
GenerateDefaultGlueCode,
kNoChildren
};
fBuiltInCodeSnippets[(int) SkBuiltInCodeSnippetID::kError] = {
{ nullptr, kNumErrorUniforms },
kErrorName, kErrorSkSL,
GenerateDefaultGlueCode,
kNoChildren
};
fBuiltInCodeSnippets[(int) SkBuiltInCodeSnippetID::kSolidColorShader] = {
SkMakeSpan(kSolidShaderUniforms, kNumSolidShaderUniforms),
kSolidShaderName, kSolidShaderSkSL,
GenerateDefaultGlueCode,
kNoChildren
};
fBuiltInCodeSnippets[(int) SkBuiltInCodeSnippetID::kLinearGradientShader] = {
SkMakeSpan(kGradientUniforms, kNumGradientUniforms),
kLinearGradient4Name, kLinearGradient4SkSL,
GenerateDefaultGlueCode,
kNoChildren
};
fBuiltInCodeSnippets[(int) SkBuiltInCodeSnippetID::kRadialGradientShader] = {
SkMakeSpan(kGradientUniforms, kNumGradientUniforms),
kLinearGradient4Name, kLinearGradient4SkSL,
GenerateDefaultGlueCode,
kNoChildren
};
fBuiltInCodeSnippets[(int) SkBuiltInCodeSnippetID::kSweepGradientShader] = {
SkMakeSpan(kGradientUniforms, kNumGradientUniforms),
kLinearGradient4Name, kLinearGradient4SkSL,
GenerateDefaultGlueCode,
kNoChildren
};
fBuiltInCodeSnippets[(int) SkBuiltInCodeSnippetID::kConicalGradientShader] = {
SkMakeSpan(kGradientUniforms, kNumGradientUniforms),
kLinearGradient4Name, kLinearGradient4SkSL,
GenerateDefaultGlueCode,
kNoChildren
};
fBuiltInCodeSnippets[(int) SkBuiltInCodeSnippetID::kImageShader] = {
{ nullptr, kNumImageShaderUniforms },
kImageShaderName, kImageShaderSkSL,
GenerateDefaultGlueCode,
kNoChildren
};
fBuiltInCodeSnippets[(int) SkBuiltInCodeSnippetID::kBlendShader] = {
{ nullptr, kNumBlendShaderUniforms },
kBlendShaderName, kBlendShaderSkSL,
GenerateBlendShaderGlueCode,
kNumBlendShaderChildren
};
fBuiltInCodeSnippets[(int) SkBuiltInCodeSnippetID::kSimpleBlendMode] = {
{ nullptr, kNumErrorUniforms },
kErrorName, kErrorSkSL,
GenerateDefaultGlueCode,
kNoChildren
};
}