• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016 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 #ifndef SKIASL_MEMORYLAYOUT
9 #define SKIASL_MEMORYLAYOUT
10 
11 #include <algorithm>
12 
13 #include "src/sksl/ir/SkSLType.h"
14 
15 namespace SkSL {
16 
17 class MemoryLayout {
18 public:
19     enum class Standard {
20         // GLSL std140 layout as described in OpenGL Spec v4.5, 7.6.2.2.
21         k140,
22 
23         // GLSL std430 layout. This layout is like std140 but with optimizations. This layout can
24         // ONLY be used with shader storage blocks.
25         k430,
26 
27         // MSL memory layout.
28         kMetal,
29 
30         // WebGPU Shading Language buffer layout constraints for the uniform address space.
31         kWGSLUniform,
32 
33         // WebGPU Shading Language buffer layout constraints for the storage address space.
34         kWGSLStorage,
35     };
36 
MemoryLayout(Standard std)37     MemoryLayout(Standard std)
38     : fStd(std) {}
39 
isWGSL()40     bool isWGSL() const { return fStd == Standard::kWGSLUniform || fStd == Standard::kWGSLStorage; }
41 
isMetal()42     bool isMetal() const { return fStd == Standard::kMetal; }
43 
44     /**
45      * WGSL and std140 require various types of variables (structs, arrays, and matrices) in the
46      * uniform address space to be rounded up to the nearest multiple of 16. This function performs
47      * the rounding depending on the given `type` and the current memory layout standard.
48      *
49      * (For WGSL, see https://www.w3.org/TR/WGSL/#address-space-layout-constraints).
50      */
roundUpIfNeeded(size_t raw,Type::TypeKind type)51     size_t roundUpIfNeeded(size_t raw, Type::TypeKind type) const {
52         if (fStd == Standard::k140) {
53             return roundUp16(raw);
54         }
55         // WGSL uniform matrix layout is simply the alignment of the matrix columns and
56         // doesn't have a 16-byte multiple alignment constraint.
57         if (fStd == Standard::kWGSLUniform && type != Type::TypeKind::kMatrix) {
58             return roundUp16(raw);
59         }
60         return raw;
61     }
62 
63     /**
64      * Rounds up the integer `n` to the smallest multiple of 16 greater than `n`.
65      */
roundUp16(size_t n)66     size_t roundUp16(size_t n) const { return (n + 15) & ~15; }
67 
68     /**
69      * Returns a type's required alignment when used as a standalone variable.
70      */
alignment(const Type & type)71     size_t alignment(const Type& type) const {
72         // See OpenGL Spec 7.6.2.2 Standard Uniform Block Layout
73         switch (type.typeKind()) {
74             case Type::TypeKind::kScalar:
75             case Type::TypeKind::kAtomic:
76                 return this->size(type);
77             case Type::TypeKind::kVector:
78                 return GetVectorAlignment(this->size(type.componentType()), type.columns());
79             case Type::TypeKind::kMatrix:
80                 return this->roundUpIfNeeded(
81                         GetVectorAlignment(this->size(type.componentType()), type.rows()),
82                         type.typeKind());
83             case Type::TypeKind::kArray:
84                 return this->roundUpIfNeeded(this->alignment(type.componentType()),
85                                              type.typeKind());
86             case Type::TypeKind::kStruct: {
87                 size_t result = 0;
88                 for (const auto& f : type.fields()) {
89                     size_t alignment = this->alignment(*f.fType);
90                     if (alignment > result) {
91                         result = alignment;
92                     }
93                 }
94                 return this->roundUpIfNeeded(result, type.typeKind());
95             }
96             default:
97                 SK_ABORT("cannot determine alignment of type %s", type.displayName().c_str());
98         }
99     }
100 
101     /**
102      * For matrices and arrays, returns the number of bytes from the start of one entry (row, in
103      * the case of matrices) to the start of the next.
104      */
stride(const Type & type)105     size_t stride(const Type& type) const {
106         switch (type.typeKind()) {
107             case Type::TypeKind::kMatrix:
108                 return this->alignment(type);
109             case Type::TypeKind::kArray: {
110                 int stride = this->size(type.componentType());
111                 if (stride > 0) {
112                     int align = this->alignment(type.componentType());
113                     stride += align - 1;
114                     stride -= stride % align;
115                     stride = this->roundUpIfNeeded(stride, type.typeKind());
116                 }
117                 return stride;
118             }
119             default:
120                 SK_ABORT("type does not have a stride");
121         }
122     }
123 
124     /**
125      * Returns the size of a type in bytes. Returns 0 if the given type is not supported.
126      */
size(const Type & type)127     size_t size(const Type& type) const {
128         switch (type.typeKind()) {
129             case Type::TypeKind::kScalar:
130                 if (type.isBoolean()) {
131                     if (this->isWGSL()) {
132                         return 0;
133                     }
134                     return 1;
135                 }
136                 if ((this->isMetal() || this->isWGSL()) && !type.highPrecision() &&
137                     type.isNumber()) {
138                     return 2;
139                 }
140                 return 4;
141             case Type::TypeKind::kAtomic:
142                 // Our atomic types (currently atomicUint) always occupy 4 bytes.
143                 return 4;
144             case Type::TypeKind::kVector:
145                 if (this->isMetal() && type.columns() == 3) {
146                     return 4 * this->size(type.componentType());
147                 }
148                 return type.columns() * this->size(type.componentType());
149             case Type::TypeKind::kMatrix: // fall through
150             case Type::TypeKind::kArray:
151                 return type.isUnsizedArray() ? 0 : (type.columns() * this->stride(type));
152             case Type::TypeKind::kStruct: {
153                 size_t total = 0;
154                 for (const auto& f : type.fields()) {
155                     size_t alignment = this->alignment(*f.fType);
156                     if (total % alignment != 0) {
157                         total += alignment - total % alignment;
158                     }
159                     SkASSERT(total % alignment == 0);
160                     total += this->size(*f.fType);
161                 }
162                 size_t alignment = this->alignment(type);
163                 SkASSERT(!type.fields().size() ||
164                        (0 == alignment % this->alignment(*type.fields()[0].fType)));
165                 return (total + alignment - 1) & ~(alignment - 1);
166             }
167             default:
168                 SK_ABORT("cannot determine size of type %s", type.displayName().c_str());
169         }
170     }
171 
172     /**
173      * Not all types are compatible with memory layout.
174      */
isSupported(const Type & type)175     size_t isSupported(const Type& type) const {
176         switch (type.typeKind()) {
177             case Type::TypeKind::kAtomic:
178                 return true;
179 
180             case Type::TypeKind::kScalar:
181                 // bool and short are not host-shareable in WGSL.
182                 return !this->isWGSL() ||
183                        (!type.isBoolean() && (type.isFloat() || type.highPrecision()));
184 
185             case Type::TypeKind::kVector:
186             case Type::TypeKind::kMatrix:
187             case Type::TypeKind::kArray:
188                 return this->isSupported(type.componentType());
189 
190             case Type::TypeKind::kStruct:
191                 return std::all_of(
192                         type.fields().begin(), type.fields().end(), [this](const Type::Field& f) {
193                             return this->isSupported(*f.fType);
194                         });
195 
196             default:
197                 return false;
198         }
199     }
200 
201 private:
GetVectorAlignment(size_t componentSize,int columns)202     static size_t GetVectorAlignment(size_t componentSize, int columns) {
203         return componentSize * (columns + columns % 2);
204     }
205 
206     const Standard fStd;
207 };
208 
209 }  // namespace SkSL
210 
211 #endif
212