1/*
2 * Copyright 2018 Google Inc.
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 "src/sksl/SkSLCPPUniformCTypes.h"
9#include "src/sksl/SkSLHCodeGenerator.h"
10#include "src/sksl/SkSLStringStream.h"
11
12#include <vector>
13
14namespace SkSL {
15
16/////////////////////////
17// Template evaluation //
18/////////////////////////
19
20static String eval_template(const String& format, const std::vector<String>& tokens,
21 const std::vector<const String*>& values) {
22 StringStream stream;
23
24 int tokenNameStart = -1;
25 for (size_t i = 0; i < format.size(); i++) {
26 if (tokenNameStart >= 0) {
27 // Within a token name so check if it is the end
28 if (format[i] == '}') {
29 // Skip 2 extra characters at the beginning for the $ and {, which must exist since
30 // otherwise tokenNameStart < 0
31 String token(format.c_str() + tokenNameStart + 2, i - tokenNameStart - 2);
32 // Search for the token in supported list
33 bool found = false;
34 for (size_t j = 0; j < tokens.size(); j++) {
35 if (token == tokens[j]) {
36 // Found a match so append the value corresponding to j to the output
37 stream.writeText(values[j]->c_str());
38 found = true;
39 break;
40 }
41 }
42
43 if (!found) {
44 // Write out original characters as if we didn't consider it to be a token name
45 stream.writeText("${");
46 stream.writeText(token.c_str());
47 stream.writeText("}");
48 }
49
50 // And end the token name state
51 tokenNameStart = -1;
52 }
53 } else {
54 // Outside of a token name, so check if this character starts a name:
55 // i == $ and i+1 == {
56 if (i < format.size() - 1 && format[i] == '$' && format[i + 1] == '{') {
57 // Begin parsing the token
58 tokenNameStart = i;
59 } else {
60 // Just a character so append it
61 stream.write8(format[i]);
62 }
63 }
64 }
65
66 return stream.str();
67}
68
69static bool determine_inline_from_template(const String& uniformTemplate) {
70 // True if there is at most one instance of the ${var} template matcher in fUniformTemplate.
71 int firstMatch = uniformTemplate.find("${var}");
72
73 if (firstMatch < 0) {
74 // Template doesn't use the value variable at all, so it can "inlined"
75 return true;
76 }
77
78 // Check for another occurrence of ${var}, after firstMatch + 6
79 int secondMatch = uniformTemplate.find("${var}", firstMatch + strlen("${var}"));
80 // If there's no second match, then the value can be inlined in the c++ code
81 return secondMatch < 0;
82}
83
84///////////////////////////////////////
85// UniformCTypeMapper implementation //
86///////////////////////////////////////
87
88String UniformCTypeMapper::dirtyExpression(const String& newVar, const String& oldVar) const {
89 if (fSupportsTracking) {
90 std::vector<String> tokens = { "newVar", "oldVar" };
91 std::vector<const String*> values = { &newVar, &oldVar };
92 return eval_template(fDirtyExpressionTemplate, tokens, values);
93 } else {
94 return "";
95 }
96}
97
98String UniformCTypeMapper::saveState(const String& newVar, const String& oldVar) const {
99 if (fSupportsTracking) {
100 std::vector<String> tokens = { "newVar", "oldVar" };
101 std::vector<const String*> values = { &newVar, &oldVar };
102 return eval_template(fSaveStateTemplate, tokens, values);
103 } else {
104 return "";
105 }
106}
107
108String UniformCTypeMapper::setUniform(const String& pdman, const String& uniform,
109 const String& var) const {
110 std::vector<String> tokens = { "pdman", "uniform", "var" };
111 std::vector<const String*> values = { &pdman, &uniform, &var };
112 return eval_template(fUniformTemplate, tokens, values);
113}
114
115UniformCTypeMapper::UniformCTypeMapper(
116 Layout::CType ctype, const std::vector<String>& skslTypes, const String& setUniformFormat,
117 bool enableTracking, const String& defaultValue, const String& dirtyExpressionFormat,
118 const String& saveStateFormat)
119 : fCType(ctype)
120 , fSKSLTypes(skslTypes)
121 , fUniformTemplate(setUniformFormat)
122 , fInlineValue(determine_inline_from_template(setUniformFormat))
123 , fSupportsTracking(enableTracking)
124 , fDefaultValue(defaultValue)
125 , fDirtyExpressionTemplate(dirtyExpressionFormat)
126 , fSaveStateTemplate(saveStateFormat) { }
127
128// NOTE: These would be macros, but C++ initialization lists for the sksl type names do not play
129// well with macro parsing.
130
131static UniformCTypeMapper REGISTER(Layout::CType ctype, const std::vector<String>& skslTypes,
132 const char* uniformFormat, const char* defaultValue,
133 const char* dirtyExpression) {
134 return UniformCTypeMapper(ctype, skslTypes, uniformFormat, defaultValue, dirtyExpression,
135 "${oldVar} = ${newVar}");
136}
137
138static UniformCTypeMapper REGISTER(Layout::CType ctype, const std::vector<String>& skslTypes,
139 const char* uniformFormat, const char* defaultValue) {
140 return REGISTER(ctype, skslTypes, uniformFormat, defaultValue,
141 "${oldVar} != ${newVar}");
142}
143
144//////////////////////////////
145// Currently defined ctypes //
146//////////////////////////////
147
148static const std::vector<UniformCTypeMapper>& get_mappers() {
149 static const std::vector<UniformCTypeMapper> registeredMappers = {
150 REGISTER(Layout::CType::kSkRect, { "half4", "float4", "double4" },
151 "${pdman}.set4fv(${uniform}, 1, reinterpret_cast<const float*>(&${var}))", // to gpu
152 "SkRect::MakeEmpty()", // default value
153 "${oldVar}.isEmpty() || ${oldVar} != ${newVar}"), // dirty check
154
155 REGISTER(Layout::CType::kSkIRect, { "int4", "short4", "byte4" },
156 "${pdman}.set4iv(${uniform}, 1, reinterpret_cast<const int*>(&${var}))", // to gpu
157 "SkIRect::MakeEmpty()", // default value
158 "${oldVar}.isEmpty() || ${oldVar} != ${newVar}"), // dirty check
159
160 REGISTER(Layout::CType::kSkPMColor4f, { "half4", "float4", "double4" },
161 "${pdman}.set4fv(${uniform}, 1, ${var}.vec())", // to gpu
162 "{SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}"), // default value
163
164 REGISTER(Layout::CType::kSkV4, { "half4", "float4", "double4" },
165 "${pdman}.set4fv(${uniform}, 1, ${var}.ptr())", // to gpu
166 "SkV4{SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN}", // default value
167 "${oldVar} != (${newVar})"), // dirty check
168
169 REGISTER(Layout::CType::kSkPoint, { "half2", "float2", "double2" } ,
170 "${pdman}.set2f(${uniform}, ${var}.fX, ${var}.fY)", // to gpu
171 "SkPoint::Make(SK_FloatNaN, SK_FloatNaN)"), // default value
172
173 REGISTER(Layout::CType::kSkIPoint, { "int2", "short2", "byte2" },
174 "${pdman}.set2i(${uniform}, ${var}.fX, ${var}.fY)", // to gpu
175 "SkIPoint::Make(SK_NaN32, SK_NaN32)"), // default value
176
177 REGISTER(Layout::CType::kSkMatrix, { "half3x3", "float3x3", "double3x3" },
178 "${pdman}.setSkMatrix(${uniform}, ${var})", // to gpu
179 "SkMatrix::MakeScale(SK_FloatNaN)", // default value
180 "!${oldVar}.cheapEqualTo(${newVar})"), // dirty check
181
182 REGISTER(Layout::CType::kSkM44, { "half4x4", "float4x4", "double4x4" },
183 "${pdman}.setSkM44(${uniform}, ${var})", // to gpu
184 "SkM44(SkM44::kNaN_Constructor)", // default value
185 "${oldVar} != (${newVar})"), // dirty check
186
187 REGISTER(Layout::CType::kFloat, { "half", "float", "double" },
188 "${pdman}.set1f(${uniform}, ${var})", // to gpu
189 "SK_FloatNaN"), // default value
190
191 REGISTER(Layout::CType::kInt32, { "int", "short", "byte" },
192 "${pdman}.set1i(${uniform}, ${var})", // to gpu
193 "SK_NaN32"), // default value
194 };
195
196 return registeredMappers;
197}
198
199/////
200
201// Greedy search through registered handlers for one that has a matching
202// ctype and supports the sksl type of the variable.
203const UniformCTypeMapper* UniformCTypeMapper::Get(const Context& context, const Type& type,
204 const Layout& layout) {
205 const std::vector<UniformCTypeMapper>& registeredMappers = get_mappers();
206
207 Layout::CType ctype = layout.fCType;
208 // If there's no custom ctype declared in the layout, use the default type mapping
209 if (ctype == Layout::CType::kDefault) {
210 ctype = HCodeGenerator::ParameterCType(context, type, layout);
211 }
212
213 const String& skslType = type.name();
214
215 for (size_t i = 0; i < registeredMappers.size(); i++) {
216 if (registeredMappers[i].ctype() == ctype) {
217 // Check for sksl support, since some c types (e.g. SkMatrix) can be used in multiple
218 // uniform types and send data to the gpu differently in those conditions
219 const std::vector<String> supportedSKSL = registeredMappers[i].supportedTypeNames();
220 for (size_t j = 0; j < supportedSKSL.size(); j++) {
221 if (supportedSKSL[j] == skslType) {
222 // Found a match, so return it or an explicitly untracked version if tracking is
223 // disabled in the layout
224 return &registeredMappers[i];
225 }
226 }
227 }
228 }
229
230 // Didn't find a match
231 return nullptr;
232}
233
234} // namespace
235