Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
SkSLSwizzle.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2021 Google LLC
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
9
10#include "include/core/SkSpan.h"
17#include "src/sksl/SkSLString.h"
23
24#include <algorithm>
25#include <cstddef>
26#include <cstdint>
27#include <optional>
28
29using namespace skia_private;
30
31namespace SkSL {
32
33static bool validate_swizzle_domain(const ComponentArray& fields) {
34 enum SwizzleDomain {
35 kCoordinate,
36 kColor,
37 kUV,
39 };
40
41 std::optional<SwizzleDomain> domain;
42
43 for (int8_t field : fields) {
44 SwizzleDomain fieldDomain;
45 switch (field) {
50 fieldDomain = kCoordinate;
51 break;
56 fieldDomain = kColor;
57 break;
62 fieldDomain = kUV;
63 break;
68 fieldDomain = kRectangle;
69 break;
72 continue;
73 default:
74 return false;
75 }
76
77 if (!domain.has_value()) {
78 domain = fieldDomain;
79 } else if (domain != fieldDomain) {
80 return false;
81 }
82 }
83
84 return true;
85}
86
87static char mask_char(int8_t component) {
88 switch (component) {
89 case SwizzleComponent::X: return 'x';
90 case SwizzleComponent::Y: return 'y';
91 case SwizzleComponent::Z: return 'z';
92 case SwizzleComponent::W: return 'w';
93 case SwizzleComponent::R: return 'r';
94 case SwizzleComponent::G: return 'g';
95 case SwizzleComponent::B: return 'b';
96 case SwizzleComponent::A: return 'a';
97 case SwizzleComponent::S: return 's';
98 case SwizzleComponent::T: return 't';
99 case SwizzleComponent::P: return 'p';
100 case SwizzleComponent::Q: return 'q';
101 case SwizzleComponent::UL: return 'L';
102 case SwizzleComponent::UT: return 'T';
103 case SwizzleComponent::UR: return 'R';
104 case SwizzleComponent::UB: return 'B';
105 case SwizzleComponent::ZERO: return '0';
106 case SwizzleComponent::ONE: return '1';
107 default: SkUNREACHABLE;
108 }
109}
110
111std::string Swizzle::MaskString(const ComponentArray& components) {
112 std::string result;
113 for (int8_t component : components) {
114 result += mask_char(component);
115 }
116 return result;
117}
118
119static std::unique_ptr<Expression> optimize_constructor_swizzle(const Context& context,
122 ComponentArray components) {
123 auto baseArguments = base.argumentSpan();
124 std::unique_ptr<Expression> replacement;
125 const Type& exprType = base.type();
126 const Type& componentType = exprType.componentType();
127 int swizzleSize = components.size();
128
129 // Swizzles can duplicate some elements and discard others, e.g.
130 // `half4(1, 2, 3, 4).xxz` --> `half3(1, 1, 3)`. However, there are constraints:
131 // - Expressions with side effects need to occur exactly once, even if they would otherwise be
132 // swizzle-eliminated
133 // - Non-trivial expressions should not be repeated, but elimination is OK.
134 //
135 // Look up the argument for the constructor at each index. This is typically simple but for
136 // weird cases like `half4(bar.yz, half2(foo))`, it can be harder than it seems. This example
137 // would result in:
138 // argMap[0] = {.fArgIndex = 0, .fComponent = 0} (bar.yz .x)
139 // argMap[1] = {.fArgIndex = 0, .fComponent = 1} (bar.yz .y)
140 // argMap[2] = {.fArgIndex = 1, .fComponent = 0} (half2(foo) .x)
141 // argMap[3] = {.fArgIndex = 1, .fComponent = 1} (half2(foo) .y)
142 struct ConstructorArgMap {
143 int8_t fArgIndex;
144 int8_t fComponent;
145 };
146
147 int numConstructorArgs = base.type().columns();
148 ConstructorArgMap argMap[4] = {};
149 int writeIdx = 0;
150 for (int argIdx = 0; argIdx < (int)baseArguments.size(); ++argIdx) {
151 const Expression& arg = *baseArguments[argIdx];
152 const Type& argType = arg.type();
153
154 if (!argType.isScalar() && !argType.isVector()) {
155 return nullptr;
156 }
157
158 int argSlots = argType.slotCount();
159 for (int componentIdx = 0; componentIdx < argSlots; ++componentIdx) {
160 argMap[writeIdx].fArgIndex = argIdx;
161 argMap[writeIdx].fComponent = componentIdx;
162 ++writeIdx;
163 }
164 }
165 SkASSERT(writeIdx == numConstructorArgs);
166
167 // Count up the number of times each constructor argument is used by the swizzle.
168 // `half4(bar.yz, half2(foo)).xwxy` -> { 3, 1 }
169 // - bar.yz is referenced 3 times, by `.x_xy`
170 // - half(foo) is referenced 1 time, by `._w__`
171 int8_t exprUsed[4] = {};
172 for (int8_t c : components) {
173 exprUsed[argMap[c].fArgIndex]++;
174 }
175
176 for (int index = 0; index < numConstructorArgs; ++index) {
177 int8_t constructorArgIndex = argMap[index].fArgIndex;
178 const Expression& baseArg = *baseArguments[constructorArgIndex];
179
180 // Check that non-trivial expressions are not swizzled in more than once.
181 if (exprUsed[constructorArgIndex] > 1 && !Analysis::IsTrivialExpression(baseArg)) {
182 return nullptr;
183 }
184 // Check that side-effect-bearing expressions are swizzled in exactly once.
185 if (exprUsed[constructorArgIndex] != 1 && Analysis::HasSideEffects(baseArg)) {
186 return nullptr;
187 }
188 }
189
190 struct ReorderedArgument {
191 int8_t fArgIndex;
192 ComponentArray fComponents;
193 };
194 STArray<4, ReorderedArgument> reorderedArgs;
195 for (int8_t c : components) {
196 const ConstructorArgMap& argument = argMap[c];
197 const Expression& baseArg = *baseArguments[argument.fArgIndex];
198
199 if (baseArg.type().isScalar()) {
200 // This argument is a scalar; add it to the list as-is.
201 SkASSERT(argument.fComponent == 0);
202 reorderedArgs.push_back({argument.fArgIndex,
203 ComponentArray{}});
204 } else {
205 // This argument is a component from a vector.
206 SkASSERT(baseArg.type().isVector());
207 SkASSERT(argument.fComponent < baseArg.type().columns());
208 if (reorderedArgs.empty() ||
209 reorderedArgs.back().fArgIndex != argument.fArgIndex) {
210 // This can't be combined with the previous argument. Add a new one.
211 reorderedArgs.push_back({argument.fArgIndex,
212 ComponentArray{argument.fComponent}});
213 } else {
214 // Since we know this argument uses components, it should already have at least one
215 // component set.
216 SkASSERT(!reorderedArgs.back().fComponents.empty());
217 // Build up the current argument with one more component.
218 reorderedArgs.back().fComponents.push_back(argument.fComponent);
219 }
220 }
221 }
222
223 // Convert our reordered argument list to an actual array of expressions, with the new order and
224 // any new inner swizzles that need to be applied.
225 ExpressionArray newArgs;
226 newArgs.reserve_exact(swizzleSize);
227 for (const ReorderedArgument& reorderedArg : reorderedArgs) {
228 std::unique_ptr<Expression> newArg = baseArguments[reorderedArg.fArgIndex]->clone();
229
230 if (reorderedArg.fComponents.empty()) {
231 newArgs.push_back(std::move(newArg));
232 } else {
233 newArgs.push_back(Swizzle::Make(context, pos, std::move(newArg),
234 reorderedArg.fComponents));
235 }
236 }
237
238 // Wrap the new argument list in a compound constructor.
239 return ConstructorCompound::Make(context,
240 pos,
241 componentType.toCompound(context, swizzleSize, /*rows=*/1),
242 std::move(newArgs));
243}
244
245std::unique_ptr<Expression> Swizzle::Convert(const Context& context,
247 Position maskPos,
248 std::unique_ptr<Expression> base,
249 std::string_view maskString) {
251 for (size_t i = 0; i < maskString.length(); ++i) {
252 char field = maskString[i];
253 switch (field) {
256 case 'x': components.push_back(SwizzleComponent::X); break;
257 case 'r': components.push_back(SwizzleComponent::R); break;
258 case 's': components.push_back(SwizzleComponent::S); break;
259 case 'L': components.push_back(SwizzleComponent::UL); break;
260 case 'y': components.push_back(SwizzleComponent::Y); break;
261 case 'g': components.push_back(SwizzleComponent::G); break;
262 case 't': components.push_back(SwizzleComponent::T); break;
263 case 'T': components.push_back(SwizzleComponent::UT); break;
264 case 'z': components.push_back(SwizzleComponent::Z); break;
265 case 'b': components.push_back(SwizzleComponent::B); break;
266 case 'p': components.push_back(SwizzleComponent::P); break;
267 case 'R': components.push_back(SwizzleComponent::UR); break;
268 case 'w': components.push_back(SwizzleComponent::W); break;
269 case 'a': components.push_back(SwizzleComponent::A); break;
270 case 'q': components.push_back(SwizzleComponent::Q); break;
271 case 'B': components.push_back(SwizzleComponent::UB); break;
272 default:
273 context.fErrors->error(Position::Range(maskPos.startOffset() + i,
274 maskPos.startOffset() + i + 1),
275 String::printf("invalid swizzle component '%c'", field));
276 return nullptr;
277 }
278 }
279 return Convert(context, pos, maskPos, std::move(base), std::move(components));
280}
281
282// Swizzles are complicated due to constant components. The most difficult case is a mask like
283// '.x1w0'. A naive approach might turn that into 'float4(base.x, 1, base.w, 0)', but that evaluates
284// 'base' twice. We instead group the swizzle mask ('xw') and constants ('1, 0') together and use a
285// secondary swizzle to put them back into the right order, so in this case we end up with
286// 'float4(base.xw, 1, 0).xzyw'.
287std::unique_ptr<Expression> Swizzle::Convert(const Context& context,
289 Position maskPos,
290 std::unique_ptr<Expression> base,
291 ComponentArray inComponents) {
292 if (inComponents.size() > 4) {
293 context.fErrors->error(Position::Range(maskPos.startOffset() + 4,
294 maskPos.endOffset()),
295 "too many components in swizzle mask");
296 return nullptr;
297 }
298
299 if (!validate_swizzle_domain(inComponents)) {
300 context.fErrors->error(maskPos, "invalid swizzle mask '" + MaskString(inComponents) + "'");
301 return nullptr;
302 }
303
304 const Type& baseType = base->type().scalarTypeForLiteral();
305
306 if (!baseType.isVector() && !baseType.isScalar()) {
307 context.fErrors->error(pos, "cannot swizzle value of type '" +
308 baseType.displayName() + "'");
309 return nullptr;
310 }
311
312 ComponentArray maskComponents;
313 bool foundXYZW = false;
314 for (int i = 0; i < inComponents.size(); ++i) {
315 switch (inComponents[i]) {
318 // Skip over constant fields for now.
319 break;
324 foundXYZW = true;
325 maskComponents.push_back(SwizzleComponent::X);
326 break;
331 foundXYZW = true;
332 if (baseType.columns() >= 2) {
333 maskComponents.push_back(SwizzleComponent::Y);
334 break;
335 }
336 [[fallthrough]];
341 foundXYZW = true;
342 if (baseType.columns() >= 3) {
343 maskComponents.push_back(SwizzleComponent::Z);
344 break;
345 }
346 [[fallthrough]];
351 foundXYZW = true;
352 if (baseType.columns() >= 4) {
353 maskComponents.push_back(SwizzleComponent::W);
354 break;
355 }
356 [[fallthrough]];
357 default:
358 // The swizzle component references a field that doesn't exist in the base type.
359 context.fErrors->error(Position::Range(maskPos.startOffset() + i,
360 maskPos.startOffset() + i + 1),
361 String::printf("invalid swizzle component '%c'",
362 mask_char(inComponents[i])));
363 return nullptr;
364 }
365 }
366
367 if (!foundXYZW) {
368 context.fErrors->error(maskPos, "swizzle must refer to base expression");
369 return nullptr;
370 }
371
372 // Coerce literals in expressions such as `(12345).xxx` to their actual type.
373 base = baseType.coerceExpression(std::move(base), context);
374 if (!base) {
375 return nullptr;
376 }
377
378 // First, we need a vector expression that is the non-constant portion of the swizzle, packed:
379 // scalar.xxx -> type3(scalar)
380 // scalar.x0x0 -> type2(scalar)
381 // vector.zyx -> vector.zyx
382 // vector.x0y0 -> vector.xy
383 std::unique_ptr<Expression> expr = Swizzle::Make(context, pos, std::move(base), maskComponents);
384
385 // If we have processed the entire swizzle, we're done.
386 if (maskComponents.size() == inComponents.size()) {
387 return expr;
388 }
389
390 // Now we create a constructor that has the correct number of elements for the final swizzle,
391 // with all fields at the start. It's not finished yet; constants we need will be added below.
392 // scalar.x0x0 -> type4(type2(x), ...)
393 // vector.y111 -> type4(vector.y, ...)
394 // vector.z10x -> type4(vector.zx, ...)
395 //
396 // The constructor will have at most three arguments: { base expr, constant 0, constant 1 }
397 ExpressionArray constructorArgs;
398 constructorArgs.reserve_exact(3);
399 constructorArgs.push_back(std::move(expr));
400
401 // Apply another swizzle to shuffle the constants into the correct place. Any constant values we
402 // need are also tacked on to the end of the constructor.
403 // scalar.x0x0 -> type4(type2(x), 0).xyxy
404 // vector.y111 -> type2(vector.y, 1).xyyy
405 // vector.z10x -> type4(vector.zx, 1, 0).xzwy
406 const Type* scalarType = &baseType.componentType();
407 ComponentArray swizzleComponents;
408 int maskFieldIdx = 0;
409 int constantFieldIdx = maskComponents.size();
410 int constantZeroIdx = -1, constantOneIdx = -1;
411
412 for (int i = 0; i < inComponents.size(); i++) {
413 switch (inComponents[i]) {
415 if (constantZeroIdx == -1) {
416 // Synthesize a '0' argument at the end of the constructor.
417 constructorArgs.push_back(Literal::Make(pos, /*value=*/0, scalarType));
418 constantZeroIdx = constantFieldIdx++;
419 }
420 swizzleComponents.push_back(constantZeroIdx);
421 break;
423 if (constantOneIdx == -1) {
424 // Synthesize a '1' argument at the end of the constructor.
425 constructorArgs.push_back(Literal::Make(pos, /*value=*/1, scalarType));
426 constantOneIdx = constantFieldIdx++;
427 }
428 swizzleComponents.push_back(constantOneIdx);
429 break;
430 default:
431 // The non-constant fields are already in the expected order.
432 swizzleComponents.push_back(maskFieldIdx++);
433 break;
434 }
435 }
436
437 expr = ConstructorCompound::Make(context, pos,
438 scalarType->toCompound(context, constantFieldIdx, /*rows=*/1),
439 std::move(constructorArgs));
440
441 // Create (and potentially optimize-away) the resulting swizzle-expression.
442 return Swizzle::Make(context, pos, std::move(expr), swizzleComponents);
443}
444
445std::unique_ptr<Expression> Swizzle::Make(const Context& context,
447 std::unique_ptr<Expression> expr,
448 ComponentArray components) {
449 const Type& exprType = expr->type();
450 SkASSERTF(exprType.isVector() || exprType.isScalar(),
451 "cannot swizzle type '%s'", exprType.description().c_str());
452 SkASSERT(components.size() >= 1 && components.size() <= 4);
453
454 // Confirm that the component array only contains X/Y/Z/W. (Call MakeWith01 if you want support
455 // for ZERO and ONE. Once initial IR generation is complete, no swizzles should have zeros or
456 // ones in them.)
457 SkASSERT(std::all_of(components.begin(), components.end(), [](int8_t component) {
458 return component >= SwizzleComponent::X &&
459 component <= SwizzleComponent::W;
460 }));
461
462 // SkSL supports splatting a scalar via `scalar.xxxx`, but not all versions of GLSL allow this.
463 // Replace swizzles with equivalent splat constructors (`scalar.xxx` --> `half3(value)`).
464 if (exprType.isScalar()) {
465 return ConstructorSplat::Make(context, pos,
466 exprType.toCompound(context, components.size(), /*rows=*/1),
467 std::move(expr));
468 }
469
470 // Detect identity swizzles like `color.rgba` and optimize it away.
471 if (components.size() == exprType.columns()) {
472 bool identity = true;
473 for (int i = 0; i < components.size(); ++i) {
474 if (components[i] != i) {
475 identity = false;
476 break;
477 }
478 }
479 if (identity) {
480 expr->fPosition = pos;
481 return expr;
482 }
483 }
484
485 // Optimize swizzles of swizzles, e.g. replace `foo.argb.rggg` with `foo.arrr`.
486 if (expr->is<Swizzle>()) {
487 Swizzle& base = expr->as<Swizzle>();
488 ComponentArray combined;
489 for (int8_t c : components) {
490 combined.push_back(base.components()[c]);
491 }
492
493 // It may actually be possible to further simplify this swizzle. Go again.
494 // (e.g. `color.abgr.abgr` --> `color.rgba` --> `color`.)
495 return Swizzle::Make(context, pos, std::move(base.base()), combined);
496 }
497
498 // If we are swizzling a constant expression, we can use its value instead here (so that
499 // swizzles like `colorWhite.x` can be simplified to `1`).
501
502 // `half4(scalar).zyy` can be optimized to `half3(scalar)`, and `half3(scalar).y` can be
503 // optimized to just `scalar`. The swizzle components don't actually matter, as every field
504 // in a splat constructor holds the same value.
505 if (value->is<ConstructorSplat>()) {
506 const ConstructorSplat& splat = value->as<ConstructorSplat>();
508 context, pos,
509 splat.type().componentType().toCompound(context, components.size(), /*rows=*/1),
510 splat.argument()->clone());
511 }
512
513 // Swizzles on casts, like `half4(myFloat4).zyy`, can optimize to `half3(myFloat4.zyy)`.
514 if (value->is<ConstructorCompoundCast>()) {
516 const Type& castType = cast.type().componentType().toCompound(context, components.size(),
517 /*rows=*/1);
518 std::unique_ptr<Expression> swizzled = Swizzle::Make(context, pos, cast.argument()->clone(),
519 std::move(components));
520 return (castType.columns() > 1)
521 ? ConstructorCompoundCast::Make(context, pos, castType, std::move(swizzled))
522 : ConstructorScalarCast::Make(context, pos, castType, std::move(swizzled));
523 }
524
525 // Swizzles on compound constructors, like `half4(1, 2, 3, 4).yw`, can become `half2(2, 4)`.
526 if (value->is<ConstructorCompound>()) {
527 const ConstructorCompound& ctor = value->as<ConstructorCompound>();
528 if (auto replacement = optimize_constructor_swizzle(context, pos, ctor, components)) {
529 return replacement;
530 }
531 }
532
533 // The swizzle could not be simplified, so apply the requested swizzle to the base expression.
534 return std::make_unique<Swizzle>(context, pos, std::move(expr), components);
535}
536
538 return this->base()->description(OperatorPrecedence::kPostfix) + "." +
539 MaskString(this->components());
540}
541
542} // namespace SkSL
SkPoint pos
#define SkUNREACHABLE
Definition SkAssert.h:135
#define SkASSERT(cond)
Definition SkAssert.h:116
#define SkASSERTF(cond, fmt,...)
Definition SkAssert.h:117
SI D cast(const S &v)
Type::kYUV Type::kRGBA() int(0.7 *637)
static const Expression * GetConstantValueForVariable(const Expression &value)
static std::unique_ptr< Expression > Make(const Context &context, Position pos, const Type &type, std::unique_ptr< Expression > arg)
static std::unique_ptr< Expression > Make(const Context &context, Position pos, const Type &type, ExpressionArray args)
static std::unique_ptr< Expression > Make(const Context &context, Position pos, const Type &type, std::unique_ptr< Expression > arg)
static std::unique_ptr< Expression > Make(const Context &context, Position pos, const Type &type, std::unique_ptr< Expression > arg)
ErrorReporter * fErrors
Definition SkSLContext.h:36
void error(Position position, std::string_view msg)
virtual const Type & type() const
std::string description() const final
static std::unique_ptr< Literal > Make(Position pos, double value, const Type *type)
Definition SkSLLiteral.h:81
int startOffset() const
static Position Range(int startOffset, int endOffset)
int endOffset() const
std::unique_ptr< Expression > & argument()
std::unique_ptr< Expression > & base()
Definition SkSLSwizzle.h:82
static std::unique_ptr< Expression > Convert(const Context &context, Position pos, Position maskPos, std::unique_ptr< Expression > base, ComponentArray inComponents)
const ComponentArray & components() const
Definition SkSLSwizzle.h:90
static std::unique_ptr< Expression > Make(const Context &context, Position pos, std::unique_ptr< Expression > expr, ComponentArray inComponents)
static std::string MaskString(const ComponentArray &inComponents)
virtual bool isVector() const
Definition SkSLType.h:524
std::unique_ptr< Expression > coerceExpression(std::unique_ptr< Expression > expr, const Context &context) const
virtual const Type & componentType() const
Definition SkSLType.h:404
virtual int columns() const
Definition SkSLType.h:429
virtual size_t slotCount() const
Definition SkSLType.h:457
virtual bool isScalar() const
Definition SkSLType.h:512
std::string description() const override
Definition SkSLType.h:238
std::string displayName() const
Definition SkSLType.h:234
const Type & toCompound(const Context &context, int columns, int rows) const
bool empty() const
Definition SkTArray.h:194
int size() const
Definition SkTArray.h:416
void reserve_exact(int n)
Definition SkTArray.h:176
uint8_t value
GAsyncResult * result
bool IsTrivialExpression(const Expression &expr)
bool HasSideEffects(const Expression &expr)
std::string printf(const char *fmt,...) SK_PRINTF_LIKE(1
static bool validate_swizzle_domain(const ComponentArray &fields)
static char mask_char(int8_t component)
static std::unique_ptr< Expression > optimize_constructor_swizzle(const Context &context, Position pos, const ConstructorCompound &base, ComponentArray components)
OperatorPrecedence