xref: /aosp_15_r20/external/skia/src/sksl/SkSLMemoryLayout.h (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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_Base,       // treats f16 as a full 32-bit float
32         kWGSLUniform_EnableF16,  // treats f16 as a 16-bit half float
33 
34         // WebGPU Shading Language buffer layout constraints for the storage address space.
35         kWGSLStorage_Base,
36         kWGSLStorage_EnableF16,
37     };
38 
MemoryLayout(Standard std)39     MemoryLayout(Standard std) : fStd(std) {}
40 
isWGSL_Base()41     bool isWGSL_Base() const {
42         return fStd == Standard::kWGSLUniform_Base ||
43                fStd == Standard::kWGSLStorage_Base;
44     }
45 
isWGSL_F16()46     bool isWGSL_F16() const {
47         return fStd == Standard::kWGSLUniform_EnableF16 ||
48                fStd == Standard::kWGSLStorage_EnableF16;
49     }
50 
isWGSL_Uniform()51     bool isWGSL_Uniform() const {
52         return fStd == Standard::kWGSLUniform_Base ||
53                fStd == Standard::kWGSLUniform_EnableF16;
54     }
55 
isWGSL()56     bool isWGSL() const {
57         return fStd == Standard::kWGSLUniform_Base ||
58                fStd == Standard::kWGSLStorage_Base ||
59                fStd == Standard::kWGSLUniform_EnableF16 ||
60                fStd == Standard::kWGSLStorage_EnableF16;
61     }
62 
isMetal()63     bool isMetal() const {
64         return fStd == Standard::kMetal;
65     }
66 
67     /**
68      * WGSL and std140 require various types of variables (structs, arrays, and matrices) in the
69      * uniform address space to be rounded up to the nearest multiple of 16. This function performs
70      * the rounding depending on the given `type` and the current memory layout standard.
71      *
72      * (For WGSL, see https://www.w3.org/TR/WGSL/#address-space-layout-constraints).
73      */
roundUpIfNeeded(size_t raw,Type::TypeKind type)74     size_t roundUpIfNeeded(size_t raw, Type::TypeKind type) const {
75         if (fStd == Standard::k140) {
76             return roundUp16(raw);
77         }
78         // WGSL uniform matrix layout is simply the alignment of the matrix columns and
79         // doesn't have a 16-byte multiple alignment constraint.
80         if (this->isWGSL_Uniform() && type != Type::TypeKind::kMatrix) {
81             return roundUp16(raw);
82         }
83         return raw;
84     }
85 
86     /**
87      * Rounds up the integer `n` to the smallest multiple of 16 greater than `n`.
88      */
roundUp16(size_t n)89     size_t roundUp16(size_t n) const { return (n + 15) & ~15; }
90 
91     /**
92      * Returns a type's required alignment when used as a standalone variable.
93      */
alignment(const Type & type)94     size_t alignment(const Type& type) const {
95         // See OpenGL Spec 7.6.2.2 Standard Uniform Block Layout
96         switch (type.typeKind()) {
97             case Type::TypeKind::kScalar:
98             case Type::TypeKind::kAtomic:
99                 return this->size(type);
100 
101             case Type::TypeKind::kVector:
102                 return GetVectorAlignment(this->size(type.componentType()), type.columns());
103 
104             case Type::TypeKind::kMatrix:
105                 return this->roundUpIfNeeded(
106                         GetVectorAlignment(this->size(type.componentType()), type.rows()),
107                         type.typeKind());
108 
109             case Type::TypeKind::kArray:
110                 return this->roundUpIfNeeded(this->alignment(type.componentType()),
111                                              type.typeKind());
112 
113             case Type::TypeKind::kStruct: {
114                 size_t result = 0;
115                 for (const auto& f : type.fields()) {
116                     size_t alignment = this->alignment(*f.fType);
117                     if (alignment > result) {
118                         result = alignment;
119                     }
120                 }
121                 return this->roundUpIfNeeded(result, type.typeKind());
122             }
123             default:
124                 SK_ABORT("cannot determine alignment of type '%s'", type.displayName().c_str());
125         }
126     }
127 
128     /**
129      * For matrices and arrays, returns the number of bytes from the start of one entry (row, in
130      * the case of matrices) to the start of the next.
131      */
stride(const Type & type)132     size_t stride(const Type& type) const {
133         switch (type.typeKind()) {
134             case Type::TypeKind::kMatrix:
135                 return this->alignment(type);
136 
137             case Type::TypeKind::kArray: {
138                 int stride = this->size(type.componentType());
139                 if (stride > 0) {
140                     int align = this->alignment(type.componentType());
141                     stride += align - 1;
142                     stride -= stride % align;
143                     stride = this->roundUpIfNeeded(stride, type.typeKind());
144                 }
145                 return stride;
146             }
147             default:
148                 SK_ABORT("type '%s' does not have a stride", type.displayName().c_str());
149         }
150     }
151 
152     /**
153      * Returns the size of a type in bytes. Returns 0 if the given type is not supported.
154      */
size(const Type & type)155     size_t size(const Type& type) const {
156         switch (type.typeKind()) {
157             case Type::TypeKind::kScalar:
158                 if (type.isBoolean()) {
159                     return this->isWGSL() ? 0 : 1;
160                 }
161                 if (this->isMetal() && !type.highPrecision() && type.isNumber()) {
162                     return 2;
163                 }
164                 if (this->isWGSL_F16() && !type.highPrecision() && type.isFloat()) {
165                     return 2;
166                 }
167                 return 4;
168 
169             case Type::TypeKind::kAtomic:
170                 // Our atomic types (currently atomicUint) always occupy 4 bytes.
171                 return 4;
172 
173             case Type::TypeKind::kVector:
174                 if (this->isMetal() && type.columns() == 3) {
175                     return 4 * this->size(type.componentType());
176                 }
177                 return type.columns() * this->size(type.componentType());
178 
179             case Type::TypeKind::kMatrix: // fall through
180             case Type::TypeKind::kArray:
181                 return type.isUnsizedArray() ? 0 : (type.columns() * this->stride(type));
182 
183             case Type::TypeKind::kStruct: {
184                 size_t total = 0;
185                 for (const auto& f : type.fields()) {
186                     size_t alignment = this->alignment(*f.fType);
187                     if (total % alignment != 0) {
188                         total += alignment - total % alignment;
189                     }
190                     SkASSERT(total % alignment == 0);
191                     total += this->size(*f.fType);
192                 }
193                 size_t alignment = this->alignment(type);
194                 SkASSERT(!type.fields().size() ||
195                        (0 == alignment % this->alignment(*type.fields()[0].fType)));
196                 return (total + alignment - 1) & ~(alignment - 1);
197             }
198             default:
199                 SK_ABORT("cannot determine size of type '%s'", type.displayName().c_str());
200         }
201     }
202 
203     /**
204      * Not all types are compatible with memory layout.
205      */
isSupported(const Type & type)206     size_t isSupported(const Type& type) const {
207         switch (type.typeKind()) {
208             case Type::TypeKind::kAtomic:
209                 return true;
210 
211             case Type::TypeKind::kScalar:
212                 // bool is not host-shareable in WGSL.
213                 return this->isWGSL() ? !type.isBoolean() : true;
214 
215             case Type::TypeKind::kVector:
216             case Type::TypeKind::kMatrix:
217             case Type::TypeKind::kArray:
218                 return this->isSupported(type.componentType());
219 
220             case Type::TypeKind::kStruct:
221                 return std::all_of(
222                         type.fields().begin(), type.fields().end(), [this](const Field& f) {
223                             return this->isSupported(*f.fType);
224                         });
225 
226             default:
227                 return false;
228         }
229     }
230 
getStandard()231     Standard getStandard() const { return fStd; }
232 
233 private:
GetVectorAlignment(size_t componentSize,int columns)234     static size_t GetVectorAlignment(size_t componentSize, int columns) {
235         return componentSize * (columns + columns % 2);
236     }
237 
238     const Standard fStd;
239 };
240 
241 }  // namespace SkSL
242 
243 #endif
244