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