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