| // Copyright 2021 The Dawn Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include <algorithm> |
| #include <array> |
| #include <functional> |
| #include <string> |
| #include <vector> |
| |
| #include "dawn/common/Math.h" |
| #include "dawn/tests/DawnTest.h" |
| #include "dawn/utils/WGPUHelpers.h" |
| |
| namespace dawn { |
| namespace { |
| |
| // Helper for replacing all occurrences of substr in str with replacement |
| std::string ReplaceAll(std::string str, const std::string& substr, const std::string& replacement) { |
| size_t pos = 0; |
| while ((pos = str.find(substr, pos)) != std::string::npos) { |
| str.replace(pos, substr.length(), replacement); |
| pos += replacement.length(); |
| } |
| return str; |
| } |
| |
| // AddressSpace is an enumerator of address spaces used by ComputeLayoutMemoryBufferTests.Fields |
| enum class AddressSpace { |
| Uniform, |
| Storage, |
| }; |
| |
| std::ostream& operator<<(std::ostream& o, AddressSpace addressSpace) { |
| switch (addressSpace) { |
| case AddressSpace::Uniform: |
| o << "uniform"; |
| break; |
| case AddressSpace::Storage: |
| o << "storage"; |
| break; |
| } |
| return o; |
| } |
| |
| // Host-sharable scalar types |
| enum class ScalarType { |
| f32, |
| i32, |
| u32, |
| f16, |
| }; |
| |
| std::string ScalarTypeName(ScalarType scalarType) { |
| switch (scalarType) { |
| case ScalarType::f32: |
| return "f32"; |
| case ScalarType::i32: |
| return "i32"; |
| case ScalarType::u32: |
| return "u32"; |
| case ScalarType::f16: |
| return "f16"; |
| } |
| UNREACHABLE(); |
| return ""; |
| } |
| |
| size_t ScalarTypeSize(ScalarType scalarType) { |
| switch (scalarType) { |
| case ScalarType::f32: |
| case ScalarType::i32: |
| case ScalarType::u32: |
| return 4; |
| case ScalarType::f16: |
| return 2; |
| } |
| UNREACHABLE(); |
| return 0; |
| } |
| |
| // MemoryDataBuilder records and performs operations of following types on a memory buffer `buf`: |
| // 1. "Align": Align to a alignment `alignment`, which will ensure |
| // `buf.size() % alignment == 0` by adding padding bytes into the buffer |
| // if necessary; |
| // 2. "Data": Add `size` bytes of data bytes into buffer; |
| // 3. "Padding": Add `size` bytes of padding bytes into buffer; |
| // 4. "FillingFixed": Fill all `size` given (fixed) bytes into the memory buffer. |
| // Note that data bytes and padding bytes are generated seperatedly and designed to |
| // be distinguishable, i.e. data bytes have the second most significant bit set to 0 while padding |
| // bytes 1. |
| // We don't want testing data includes NaN or Inf, because according to WGSL spec an implementation |
| // may give indeterminate value if a expression evaluated to NaN or Inf, and in Tint generated |
| // HLSL reading a f16 NaN from buffer is not bit-pattern preserved (i.e. a NaN input may be changed |
| // to another NaN with different bit pattern). In bit representation of both f32 and f16, the first |
| // (most significant) bit is sign bit, and some biased exponent bits go after it (start from the |
| // second most significant bit). A float value is NaN or Inf if and only if all its exponent bits |
| // are 1. By setting the second most significant bit of every data byte to 0, we ensure that the |
| // second most significant bit of any float data in the buffer is 0, and therefore avoid generating |
| // NaN or Inf float datas. |
| class MemoryDataBuilder { |
| public: |
| // Record a "Align" operation |
| MemoryDataBuilder& AlignTo(uint32_t alignment) { |
| mOperations.push_back({OperationType::Align, alignment, {}}); |
| return *this; |
| } |
| |
| // Record a "Data" operation |
| MemoryDataBuilder& AddData(size_t size) { |
| mOperations.push_back({OperationType::Data, size, {}}); |
| return *this; |
| } |
| |
| // Record a "Padding" operation |
| MemoryDataBuilder& AddPadding(size_t size) { |
| mOperations.push_back({OperationType::Padding, size, {}}); |
| return *this; |
| } |
| |
| // Record a "FillingFixed" operation |
| MemoryDataBuilder& AddFixedBytes(std::vector<uint8_t>& bytes) { |
| mOperations.push_back({OperationType::FillingFixed, bytes.size(), bytes}); |
| return *this; |
| } |
| |
| // A helper function to record a "FillingFixed" operation with all four bytes of a given U32 |
| MemoryDataBuilder& AddFixedU32(uint32_t u32) { |
| std::vector<uint8_t> bytes; |
| bytes.emplace_back((u32 >> 0) & 0xff); |
| bytes.emplace_back((u32 >> 8) & 0xff); |
| bytes.emplace_back((u32 >> 16) & 0xff); |
| bytes.emplace_back((u32 >> 24) & 0xff); |
| return AddFixedBytes(bytes); |
| } |
| |
| // Record all operations that `builder` recorded |
| MemoryDataBuilder& AddSubBuilder(MemoryDataBuilder builder) { |
| mOperations.insert(mOperations.end(), builder.mOperations.begin(), |
| builder.mOperations.end()); |
| return *this; |
| } |
| |
| // Apply all recorded operations, one by one, on a given memory buffer. |
| // dataXorKey and paddingXorKey controls the generated data and padding bytes seperatedly, make |
| // it possible to, for example, generate two buffers that have different data bytes but |
| // identical padding bytes, thus can be used as initializer and expectation bytes of the copy |
| // destination buffer, expecting data bytes are changed while padding bytes are left unchanged. |
| void ApplyOperationsToBuffer(std::vector<uint8_t>& buffer, |
| uint8_t dataXorKey, |
| uint8_t paddingXorKey) { |
| uint8_t dataByte = 0x0u; |
| uint8_t paddingByte = 0x2u; |
| // Padding mask, setting the second most significant bit to 1 |
| constexpr uint8_t paddingMask = 0x40u; |
| // Data mask, masking the second most significant bit to 0, distinguished from padding |
| // bytes and avoid NaN or Inf. |
| constexpr uint8_t dataMask = ~paddingMask; |
| // Get a data byte |
| auto NextDataByte = [&]() { |
| dataByte += 0x11u; |
| return static_cast<uint8_t>((dataByte ^ dataXorKey) & dataMask); |
| }; |
| // Get a padding byte |
| auto NextPaddingByte = [&]() { |
| paddingByte += 0x13u; |
| return static_cast<uint8_t>((paddingByte ^ paddingXorKey) | paddingMask); |
| }; |
| for (auto& operation : mOperations) { |
| switch (operation.mType) { |
| case OperationType::FillingFixed: { |
| ASSERT(operation.mOperand == operation.mFixedFillingData.size()); |
| buffer.insert(buffer.end(), operation.mFixedFillingData.begin(), |
| operation.mFixedFillingData.end()); |
| break; |
| } |
| case OperationType::Align: { |
| size_t targetSize = Align(buffer.size(), operation.mOperand); |
| size_t paddingSize = targetSize - buffer.size(); |
| for (size_t i = 0; i < paddingSize; i++) { |
| buffer.push_back(NextPaddingByte()); |
| } |
| break; |
| } |
| case OperationType::Data: { |
| for (size_t i = 0; i < operation.mOperand; i++) { |
| buffer.push_back(NextDataByte()); |
| } |
| break; |
| } |
| case OperationType::Padding: { |
| for (size_t i = 0; i < operation.mOperand; i++) { |
| buffer.push_back(NextPaddingByte()); |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| // Create a empty memory buffer and apply all recorded operations one by one on it. |
| std::vector<uint8_t> CreateBufferAndApplyOperations(uint8_t dataXorKey = 0u, |
| uint8_t paddingXorKey = 0u) { |
| std::vector<uint8_t> buffer; |
| ApplyOperationsToBuffer(buffer, dataXorKey, paddingXorKey); |
| return buffer; |
| } |
| |
| protected: |
| enum class OperationType { |
| Align, |
| Data, |
| Padding, |
| FillingFixed, |
| }; |
| struct Operation { |
| OperationType mType; |
| // mOperand is `alignment` for Align operation, and `size` for Data, Padding, and |
| // FillingFixed. |
| size_t mOperand; |
| // The data that will be filled into buffer if the segment type is FillingFixed. Otherwise |
| // for Padding and Data segment, the filling bytes are byte-wise generated based on xor |
| // keys. |
| std::vector<uint8_t> mFixedFillingData; |
| }; |
| |
| std::vector<Operation> mOperations; |
| }; |
| |
| // DataMatcherCallback is the callback function by DataMatcher. |
| // It is called for each contiguous sequence of bytes that should be checked |
| // for equality. |
| // offset and size are in units of bytes. |
| using DataMatcherCallback = std::function<void(uint32_t offset, uint32_t size)>; |
| |
| // Field describe a type that has contiguous data bytes, e.g. `i32`, `vec2f`, `mat4x4<f32>` or |
| // `array<f32, 5>`, or have a fixed data stride, e.g. `mat3x3<f32>` or `array<vec3f, 4>`. |
| // `@size` and `@align` attributes, when used as a struct member, can also described by this struct. |
| class Field { |
| public: |
| // Constructor with WGSL type name, natural alignment and natural size. Set mStrideDataBytes to |
| // natural size and mStridePaddingBytes to 0 by default to indicate continious data part. |
| Field(std::string wgslType, size_t align, size_t size, bool requireF16Feature) |
| : mWGSLType(wgslType), |
| mAlign(align), |
| mSize(size), |
| mRequireF16Feature(requireF16Feature), |
| mStrideDataBytes(size), |
| mStridePaddingBytes(0) {} |
| |
| const std::string& GetWGSLType() const { return mWGSLType; } |
| size_t GetAlign() const { return mAlign; } |
| // The natural size of this field type, i.e. the size without @size attribute |
| size_t GetUnpaddedSize() const { return mSize; } |
| // The padded size determined by @size attribute if existed, otherwise the natural size |
| size_t GetPaddedSize() const { return mHasSizeAttribute ? mPaddedSize : mSize; } |
| bool IsRequireF16Feature() const { return mRequireF16Feature; } |
| |
| // Applies a @size attribute, sets the mPaddedSize to value. |
| // Returns this Field so calls can be chained. |
| Field& SizeAttribute(size_t value) { |
| ASSERT(value >= mSize); |
| mHasSizeAttribute = true; |
| mPaddedSize = value; |
| return *this; |
| } |
| |
| bool HasSizeAttribute() const { return mHasSizeAttribute; } |
| |
| // Applies a @align attribute, sets the align to value. |
| // Returns this Field so calls can be chained. |
| Field& AlignAttribute(size_t value) { |
| ASSERT(value >= mAlign); |
| ASSERT(IsPowerOfTwo(value)); |
| mAlign = value; |
| mHasAlignAttribute = true; |
| return *this; |
| } |
| |
| bool HasAlignAttribute() const { return mHasAlignAttribute; } |
| |
| // Mark that the data part of this field is strided, and record given mStrideDataBytes and |
| // mStridePaddingBytes. Returns this Field so calls can be chained. |
| Field& Strided(size_t bytesData, size_t bytesPadding) { |
| // Check that stride pattern cover the whole data part, i.e. the data part contains N x |
| // whole data bytes and N or (N-1) x whole padding bytes. |
| ASSERT((mSize % (bytesData + bytesPadding) == 0) || |
| ((mSize + bytesPadding) % (bytesData + bytesPadding) == 0)); |
| mStrideDataBytes = bytesData; |
| mStridePaddingBytes = bytesPadding; |
| return *this; |
| } |
| |
| // Marks that this should only be used for storage buffer tests. |
| // Returns this Field so calls can be chained. |
| Field& StorageBufferOnly() { |
| mStorageBufferOnly = true; |
| return *this; |
| } |
| |
| bool IsStorageBufferOnly() const { return mStorageBufferOnly; } |
| |
| // Call the DataMatcherCallback `callback` for continuous or strided data bytes, based on the |
| // strided information of this field. The callback may be called once or multiple times. Note |
| // that padding bytes are tested as well, as they must be preserved by the implementation. |
| void CheckData(DataMatcherCallback callback) const { |
| // Calls `callback` with the strided intervals of length mStrideDataBytes + |
| // mStridePaddingBytes. For example, for a field of mSize = 18, mStrideDataBytes = 2, and |
| // mStridePaddingBytes = 4, calls `callback` with the intervals: [0, 6), [6, 12), [12, 18). |
| // If the data is continuous, i.e. mStrideDataBytes = 18 and mStridePaddingBytes = 0, |
| // `callback` would be called only once with the whole interval [0, 18). |
| size_t offset = 0; |
| while (offset < mSize) { |
| callback(offset, mStrideDataBytes + mStridePaddingBytes); |
| offset += mStrideDataBytes + mStridePaddingBytes; |
| } |
| } |
| |
| // Get a MemoryDataBuilder that do alignment, place data bytes and padding bytes, according to |
| // field's alignment, size, padding, and stride information. This MemoryDataBuilder can be used |
| // by other MemoryDataBuilder as needed. |
| MemoryDataBuilder GetDataBuilder() const { |
| MemoryDataBuilder builder; |
| builder.AlignTo(mAlign); |
| // Check that stride pattern cover the whole data part, i.e. the data part contains N x |
| // whole data bytes and N or (N-1) x whole padding bytes. Note that this also handle |
| // continious data, i.e. mStrideDataBytes == mSize and mStridePaddingBytes == 0, correctly. |
| ASSERT((mSize % (mStrideDataBytes + mStridePaddingBytes) == 0) || |
| ((mSize + mStridePaddingBytes) % (mStrideDataBytes + mStridePaddingBytes) == 0)); |
| size_t offset = 0; |
| while (offset < mSize) { |
| builder.AddData(mStrideDataBytes); |
| offset += mStrideDataBytes; |
| if (offset < mSize) { |
| builder.AddPadding(mStridePaddingBytes); |
| offset += mStridePaddingBytes; |
| } |
| } |
| if (mHasSizeAttribute) { |
| builder.AddPadding(mPaddedSize - mSize); |
| } |
| return builder; |
| } |
| |
| // Helper function to build a Field describing a scalar type. |
| static Field Scalar(ScalarType type) { |
| return Field(ScalarTypeName(type), ScalarTypeSize(type), ScalarTypeSize(type), |
| type == ScalarType::f16); |
| } |
| |
| // Helper function to build a Field describing a vector type. |
| static Field Vector(uint32_t n, ScalarType type) { |
| ASSERT(2 <= n && n <= 4); |
| size_t elementSize = ScalarTypeSize(type); |
| size_t vectorSize = n * elementSize; |
| size_t vectorAlignment = (n == 3 ? 4 : n) * elementSize; |
| return Field{"vec" + std::to_string(n) + "<" + ScalarTypeName(type) + ">", vectorAlignment, |
| vectorSize, type == ScalarType::f16}; |
| } |
| |
| // Helper function to build a Field describing a matrix type. |
| static Field Matrix(uint32_t col, uint32_t row, ScalarType type) { |
| ASSERT(2 <= col && col <= 4); |
| ASSERT(2 <= row && row <= 4); |
| ASSERT(type == ScalarType::f32 || type == ScalarType::f16); |
| size_t elementSize = ScalarTypeSize(type); |
| size_t colVectorSize = row * elementSize; |
| size_t colVectorAlignment = (row == 3 ? 4 : row) * elementSize; |
| Field field = Field{"mat" + std::to_string(col) + "x" + std::to_string(row) + "<" + |
| ScalarTypeName(type) + ">", |
| colVectorAlignment, col * colVectorAlignment, type == ScalarType::f16}; |
| if (colVectorSize != colVectorAlignment) { |
| field.Strided(colVectorSize, colVectorAlignment - colVectorSize); |
| } |
| return field; |
| } |
| |
| private: |
| const std::string mWGSLType; // Friendly WGSL name of the type of the field |
| size_t mAlign; // Alignment of the type in bytes, can be change by @align attribute |
| const size_t mSize; // Natural size of the type in bytes |
| const bool mRequireF16Feature; |
| |
| bool mHasAlignAttribute = false; |
| bool mHasSizeAttribute = false; |
| // Decorated size of the type in bytes indicated by @size attribute, if existed |
| size_t mPaddedSize = 0; |
| // Whether this type doesn't meet the layout constraints for uniform buffer and thus should only |
| // be used for storage buffer tests |
| bool mStorageBufferOnly = false; |
| |
| // Describe the striding pattern of data part (i.e. the "natural size" part). Note that |
| // continious types are described as mStrideDataBytes == mSize and mStridePaddingBytes == 0. |
| size_t mStrideDataBytes; |
| size_t mStridePaddingBytes; |
| }; |
| |
| std::ostream& operator<<(std::ostream& o, Field field) { |
| o << "@align(" << field.GetAlign() << ") @size(" << field.GetPaddedSize() << ") " |
| << field.GetWGSLType(); |
| return o; |
| } |
| |
| std::ostream& operator<<(std::ostream& o, const std::vector<uint8_t>& byteBuffer) { |
| o << "\n"; |
| uint32_t i = 0; |
| for (auto byte : byteBuffer) { |
| o << std::hex << std::setw(2) << std::setfill('0') << uint32_t(byte); |
| if (i < 31) { |
| o << " "; |
| i++; |
| } else { |
| o << "\n"; |
| i = 0; |
| } |
| } |
| if (i != 0) { |
| o << "\n"; |
| } |
| return o; |
| } |
| |
| // Create a compute pipeline with all buffer in bufferList binded in order starting from slot 0, and |
| // run the given shader. |
| void RunComputeShaderWithBuffers(const wgpu::Device& device, |
| const wgpu::Queue& queue, |
| const std::string& shader, |
| std::initializer_list<wgpu::Buffer> bufferList) { |
| // Set up shader and pipeline |
| auto module = utils::CreateShaderModule(device, shader.c_str()); |
| |
| wgpu::ComputePipelineDescriptor csDesc; |
| csDesc.compute.module = module; |
| csDesc.compute.entryPoint = "main"; |
| |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); |
| |
| // Set up bind group and issue dispatch |
| std::vector<wgpu::BindGroupEntry> entries; |
| uint32_t bufferSlot = 0; |
| for (const wgpu::Buffer& buffer : bufferList) { |
| wgpu::BindGroupEntry entry; |
| entry.binding = bufferSlot++; |
| entry.buffer = buffer; |
| entry.offset = 0; |
| entry.size = wgpu::kWholeSize; |
| entries.push_back(entry); |
| } |
| |
| wgpu::BindGroupDescriptor descriptor; |
| descriptor.layout = pipeline.GetBindGroupLayout(0); |
| descriptor.entryCount = static_cast<uint32_t>(entries.size()); |
| descriptor.entries = entries.data(); |
| |
| wgpu::BindGroup bindGroup = device.CreateBindGroup(&descriptor); |
| |
| wgpu::CommandBuffer commands; |
| { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| pass.SetPipeline(pipeline); |
| pass.SetBindGroup(0, bindGroup); |
| pass.DispatchWorkgroups(1); |
| pass.End(); |
| |
| commands = encoder.Finish(); |
| } |
| |
| queue.Submit(1, &commands); |
| } |
| |
| DAWN_TEST_PARAM_STRUCT(ComputeLayoutMemoryBufferTestParams, AddressSpace, Field); |
| |
| class ComputeLayoutMemoryBufferTests |
| : public DawnTestWithParams<ComputeLayoutMemoryBufferTestParams> { |
| // void SetUp() override { DawnTestBase::SetUp(); } |
| |
| protected: |
| // Require f16 feature if possible |
| std::vector<wgpu::FeatureName> GetRequiredFeatures() override { |
| mIsShaderF16SupportedOnAdapter = SupportsFeatures({wgpu::FeatureName::ShaderF16}); |
| if (!mIsShaderF16SupportedOnAdapter) { |
| return {}; |
| } |
| |
| if (!IsD3D12()) { |
| mUseDxcEnabledOrNonD3D12 = true; |
| } else { |
| for (auto* enabledToggle : GetParam().forceEnabledWorkarounds) { |
| if (strncmp(enabledToggle, "use_dxc", 7) == 0) { |
| mUseDxcEnabledOrNonD3D12 = true; |
| break; |
| } |
| } |
| } |
| |
| if (mUseDxcEnabledOrNonD3D12) { |
| return {wgpu::FeatureName::ShaderF16}; |
| } |
| |
| return {}; |
| } |
| |
| bool IsShaderF16SupportedOnAdapter() const { return mIsShaderF16SupportedOnAdapter; } |
| bool UseDxcEnabledOrNonD3D12() const { return mUseDxcEnabledOrNonD3D12; } |
| |
| private: |
| bool mIsShaderF16SupportedOnAdapter = false; |
| bool mUseDxcEnabledOrNonD3D12 = false; |
| }; |
| |
| // Align returns the WGSL decoration for an explicit structure field alignment |
| std::string AlignDeco(uint32_t value) { |
| return "@align(" + std::to_string(value) + ") "; |
| } |
| |
| // Test different types used as a struct member |
| TEST_P(ComputeLayoutMemoryBufferTests, StructMember) { |
| // TODO(crbug.com/dawn/1606): find out why these tests fail on Windows for OpenGL. |
| DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES() && IsWindows()); |
| |
| const bool isUniform = GetParam().mAddressSpace == AddressSpace::Uniform; |
| |
| // Sentinel value markers codes used to check that the start and end of |
| // structures are correctly aligned. Each of these codes are distinct and |
| // are not likely to be confused with data. |
| constexpr uint32_t kDataHeaderCode = 0xa0b0c0a0u; |
| constexpr uint32_t kDataFooterCode = 0x40302010u; |
| constexpr uint32_t kInputHeaderCode = 0x91827364u; |
| constexpr uint32_t kInputFooterCode = 0x19283764u; |
| |
| // Status codes returned by the shader. |
| constexpr uint32_t kStatusBadInputHeader = 100u; |
| constexpr uint32_t kStatusBadInputFooter = 101u; |
| constexpr uint32_t kStatusBadDataHeader = 102u; |
| constexpr uint32_t kStatusBadDataFooter = 103u; |
| constexpr uint32_t kStatusOk = 200u; |
| |
| const Field& field = GetParam().mField; |
| |
| if (field.IsRequireF16Feature() && !device.HasFeature(wgpu::FeatureName::ShaderF16)) { |
| return; |
| } |
| |
| std::string shader = std::string(field.IsRequireF16Feature() ? "enable f16;" : "") + |
| R"( |
| struct Data { |
| header : u32, |
| @align({field_align}) @size({field_size}) field : {field_type}, |
| footer : u32, |
| } |
| |
| struct Input { |
| header : u32, |
| {data_align}data : Data, |
| {footer_align}footer : u32, |
| } |
| |
| struct Output { |
| data : {field_type} |
| } |
| |
| struct Status { |
| code : u32 |
| } |
| |
| @group(0) @binding(0) var<{input_qualifiers}> input : Input; |
| @group(0) @binding(1) var<storage, read_write> output : Output; |
| @group(0) @binding(2) var<storage, read_write> status : Status; |
| |
| @compute @workgroup_size(1,1,1) |
| fn main() { |
| if (input.header != {input_header_code}u) { |
| status.code = {status_bad_input_header}u; |
| } else if (input.footer != {input_footer_code}u) { |
| status.code = {status_bad_input_footer}u; |
| } else if (input.data.header != {data_header_code}u) { |
| status.code = {status_bad_data_header}u; |
| } else if (input.data.footer != {data_footer_code}u) { |
| status.code = {status_bad_data_footer}u; |
| } else { |
| status.code = {status_ok}u; |
| output.data = input.data.field; |
| } |
| })"; |
| |
| // https://www.w3.org/TR/WGSL/#alignment-and-size |
| // Structure size: RoundUp(AlignOf(S), OffsetOf(S, L) + SizeOf(S, L)) |
| // https://www.w3.org/TR/WGSL/#storage-class-constraints |
| // RequiredAlignOf(S, uniform): RoundUp(16, max(AlignOf(T0), ..., AlignOf(TN))) |
| uint32_t dataAlign = isUniform ? std::max(size_t(16u), field.GetAlign()) : field.GetAlign(); |
| |
| // https://www.w3.org/TR/WGSL/#structure-layout-rules |
| // Note: When underlying the target is a Vulkan device, we assume the device does not support |
| // the scalarBlockLayout feature. Therefore, a data value must not be placed in the padding at |
| // the end of a structure or matrix, nor in the padding at the last element of an array. |
| uint32_t footerAlign = isUniform ? 16 : 4; |
| |
| shader = ReplaceAll(shader, "{data_align}", isUniform ? AlignDeco(dataAlign) : ""); |
| shader = ReplaceAll(shader, "{field_align}", std::to_string(field.GetAlign())); |
| shader = ReplaceAll(shader, "{footer_align}", isUniform ? AlignDeco(footerAlign) : ""); |
| shader = ReplaceAll(shader, "{field_size}", std::to_string(field.GetPaddedSize())); |
| shader = ReplaceAll(shader, "{field_type}", field.GetWGSLType()); |
| shader = ReplaceAll(shader, "{input_header_code}", std::to_string(kInputHeaderCode)); |
| shader = ReplaceAll(shader, "{input_footer_code}", std::to_string(kInputFooterCode)); |
| shader = ReplaceAll(shader, "{data_header_code}", std::to_string(kDataHeaderCode)); |
| shader = ReplaceAll(shader, "{data_footer_code}", std::to_string(kDataFooterCode)); |
| shader = ReplaceAll(shader, "{status_bad_input_header}", std::to_string(kStatusBadInputHeader)); |
| shader = ReplaceAll(shader, "{status_bad_input_footer}", std::to_string(kStatusBadInputFooter)); |
| shader = ReplaceAll(shader, "{status_bad_data_header}", std::to_string(kStatusBadDataHeader)); |
| shader = ReplaceAll(shader, "{status_bad_data_footer}", std::to_string(kStatusBadDataFooter)); |
| shader = ReplaceAll(shader, "{status_ok}", std::to_string(kStatusOk)); |
| shader = ReplaceAll(shader, "{input_qualifiers}", |
| isUniform ? "uniform" // |
| : "storage, read_write"); |
| |
| // Build the input and expected data. |
| MemoryDataBuilder inputDataBuilder; // The whole SSBO data |
| { |
| inputDataBuilder.AddFixedU32(kInputHeaderCode); // Input.header |
| inputDataBuilder.AlignTo(dataAlign); // Input.data |
| { |
| inputDataBuilder.AddFixedU32(kDataHeaderCode); // Input.data.header |
| inputDataBuilder.AddSubBuilder(field.GetDataBuilder()); // Input.data.field |
| inputDataBuilder.AlignTo(4); // Input.data.footer alignment |
| inputDataBuilder.AddFixedU32(kDataFooterCode); // Input.data.footer |
| inputDataBuilder.AlignTo(field.GetAlign()); // Input.data padding |
| } |
| inputDataBuilder.AlignTo(footerAlign); // Input.footer @align |
| inputDataBuilder.AddFixedU32(kInputFooterCode); // Input.footer |
| inputDataBuilder.AlignTo(256); // Input padding |
| } |
| |
| MemoryDataBuilder expectedDataBuilder; // The expected data to be copied by the shader |
| expectedDataBuilder.AddSubBuilder(field.GetDataBuilder()); |
| expectedDataBuilder.AlignTo(std::max<size_t>(field.GetAlign(), 4u)); |
| |
| // Expectation and input buffer have identical data bytes but different padding bytes. |
| // Initializes the dst buffer with data bytes different from input and expectation, and padding |
| // bytes identical to expectation but different from input. |
| constexpr uint8_t dataKeyForInputAndExpectation = 0x00u; |
| constexpr uint8_t dataKeyForDstInit = 0xffu; |
| constexpr uint8_t paddingKeyForInput = 0x3fu; |
| constexpr uint8_t paddingKeyForDstInitAndExpectation = 0x77u; |
| |
| std::vector<uint8_t> inputData = inputDataBuilder.CreateBufferAndApplyOperations( |
| dataKeyForInputAndExpectation, paddingKeyForInput); |
| std::vector<uint8_t> expectedData = expectedDataBuilder.CreateBufferAndApplyOperations( |
| dataKeyForInputAndExpectation, paddingKeyForDstInitAndExpectation); |
| std::vector<uint8_t> initData = expectedDataBuilder.CreateBufferAndApplyOperations( |
| dataKeyForDstInit, paddingKeyForDstInitAndExpectation); |
| |
| // Set up input storage buffer |
| wgpu::Buffer inputBuf = utils::CreateBufferFromData( |
| device, inputData.data(), inputData.size(), |
| wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst | |
| (isUniform ? wgpu::BufferUsage::Uniform : wgpu::BufferUsage::Storage)); |
| |
| // Set up output storage buffer |
| wgpu::Buffer outputBuf = utils::CreateBufferFromData( |
| device, initData.data(), initData.size(), |
| wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst); |
| |
| // Set up status storage buffer |
| wgpu::BufferDescriptor statusDesc; |
| statusDesc.size = 4u; |
| statusDesc.usage = |
| wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst; |
| wgpu::Buffer statusBuf = device.CreateBuffer(&statusDesc); |
| |
| RunComputeShaderWithBuffers(device, queue, shader, {inputBuf, outputBuf, statusBuf}); |
| |
| // Check the status |
| EXPECT_BUFFER_U32_EQ(kStatusOk, statusBuf, 0) << "status code error" << std::endl |
| << "Shader: " << shader; |
| |
| // Check the data. Note that MemoryDataBuilder avoid generating NaN and Inf floating point data, |
| // whose bit pattern will not get preserved when reading from buffer (arbitrary NaNs may be |
| // silently transformed into a quiet NaN). Having NaN and Inf floating point data in input may |
| // result in bitwise mismatch. |
| field.CheckData([&](uint32_t offset, uint32_t size) { |
| EXPECT_BUFFER_U8_RANGE_EQ(expectedData.data() + offset, outputBuf, offset, size) |
| << "offset: " << offset << "\n Input buffer:" << inputData << "Shader:\n" |
| << shader << "\n"; |
| }); |
| } |
| |
| // Test different types that used directly as buffer type |
| TEST_P(ComputeLayoutMemoryBufferTests, NonStructMember) { |
| // TODO(crbug.com/dawn/1606): find out why these tests fail on Windows for OpenGL. |
| DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES() && IsWindows()); |
| |
| const bool isUniform = GetParam().mAddressSpace == AddressSpace::Uniform; |
| |
| auto params = GetParam(); |
| |
| Field& field = params.mField; |
| |
| // @size and @align attribute only apply to struct members, skip them |
| if (field.HasSizeAttribute() || field.HasAlignAttribute()) { |
| return; |
| } |
| |
| if (field.IsRequireF16Feature() && !device.HasFeature(wgpu::FeatureName::ShaderF16)) { |
| return; |
| } |
| |
| std::string shader = std::string(field.IsRequireF16Feature() ? "enable f16;" : "") + |
| R"( |
| @group(0) @binding(0) var<{input_qualifiers}> input : {field_type}; |
| @group(0) @binding(1) var<storage, read_write> output : {field_type}; |
| |
| @compute @workgroup_size(1,1,1) |
| fn main() { |
| output = input; |
| })"; |
| |
| shader = ReplaceAll(shader, "{field_type}", field.GetWGSLType()); |
| shader = ReplaceAll(shader, "{input_qualifiers}", |
| isUniform ? "uniform" // |
| : "storage, read_write"); |
| |
| // Build the input and expected data. |
| MemoryDataBuilder dataBuilder; |
| dataBuilder.AddSubBuilder(field.GetDataBuilder()); |
| dataBuilder.AlignTo(4); // Storage buffer size must be a multiple of 4 |
| |
| // Expectation and input buffer have identical data bytes but different padding bytes. |
| // Initializes the dst buffer with data bytes different from input and expectation, and |
| // padding bytes identical to expectation but different from input. |
| constexpr uint8_t dataKeyForInputAndExpectation = 0x00u; |
| constexpr uint8_t dataKeyForDstInit = 0xffu; |
| constexpr uint8_t paddingKeyForInput = 0x3fu; |
| constexpr uint8_t paddingKeyForDstInitAndExpectation = 0x77u; |
| |
| std::vector<uint8_t> inputData = dataBuilder.CreateBufferAndApplyOperations( |
| dataKeyForInputAndExpectation, paddingKeyForInput); |
| std::vector<uint8_t> expectedData = dataBuilder.CreateBufferAndApplyOperations( |
| dataKeyForInputAndExpectation, paddingKeyForDstInitAndExpectation); |
| std::vector<uint8_t> initData = dataBuilder.CreateBufferAndApplyOperations( |
| dataKeyForDstInit, paddingKeyForDstInitAndExpectation); |
| |
| // Set up input storage buffer |
| wgpu::Buffer inputBuf = utils::CreateBufferFromData( |
| device, inputData.data(), inputData.size(), |
| wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst | |
| (isUniform ? wgpu::BufferUsage::Uniform : wgpu::BufferUsage::Storage)); |
| EXPECT_BUFFER_U8_RANGE_EQ(inputData.data(), inputBuf, 0, inputData.size()); |
| |
| // Set up output storage buffer |
| wgpu::Buffer outputBuf = utils::CreateBufferFromData( |
| device, initData.data(), initData.size(), |
| wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst); |
| EXPECT_BUFFER_U8_RANGE_EQ(initData.data(), outputBuf, 0, initData.size()); |
| |
| RunComputeShaderWithBuffers(device, queue, shader, {inputBuf, outputBuf}); |
| |
| // Check the data. Note that MemoryDataBuilder avoid generating NaN and Inf floating point data, |
| // whose bit pattern will not get preserved when reading from buffer (arbitrary NaNs may be |
| // silently transformed into a quiet NaN). Having NaN and Inf floating point data in input may |
| // result in bitwise mismatch. |
| field.CheckData([&](uint32_t offset, uint32_t size) { |
| EXPECT_BUFFER_U8_RANGE_EQ(expectedData.data() + offset, outputBuf, offset, size) |
| << "offset: " << offset << "\n Input buffer:" << inputData << "Shader:\n" |
| << shader << "\n"; |
| }); |
| } |
| |
| auto GenerateParams() { |
| auto params = MakeParamGenerator<ComputeLayoutMemoryBufferTestParams>( |
| { |
| D3D11Backend(), |
| D3D12Backend(), |
| D3D12Backend({"use_dxc"}), |
| MetalBackend(), |
| VulkanBackend(), |
| OpenGLBackend(), |
| OpenGLESBackend(), |
| }, |
| {AddressSpace::Storage, AddressSpace::Uniform}, |
| { |
| // See https://www.w3.org/TR/WGSL/#alignment-and-size |
| // Scalar types with no custom alignment or size |
| Field::Scalar(ScalarType::f32), |
| Field::Scalar(ScalarType::i32), |
| Field::Scalar(ScalarType::u32), |
| Field::Scalar(ScalarType::f16), |
| |
| // Scalar types with custom alignment |
| Field::Scalar(ScalarType::f32).AlignAttribute(16), |
| Field::Scalar(ScalarType::i32).AlignAttribute(16), |
| Field::Scalar(ScalarType::u32).AlignAttribute(16), |
| Field::Scalar(ScalarType::f16).AlignAttribute(16), |
| |
| // Scalar types with custom size |
| Field::Scalar(ScalarType::f32).SizeAttribute(24), |
| Field::Scalar(ScalarType::i32).SizeAttribute(24), |
| Field::Scalar(ScalarType::u32).SizeAttribute(24), |
| Field::Scalar(ScalarType::f16).SizeAttribute(24), |
| |
| // Vector types with no custom alignment or size |
| Field::Vector(2, ScalarType::f32), |
| Field::Vector(3, ScalarType::f32), |
| Field::Vector(4, ScalarType::f32), |
| Field::Vector(2, ScalarType::i32), |
| Field::Vector(3, ScalarType::i32), |
| Field::Vector(4, ScalarType::i32), |
| Field::Vector(2, ScalarType::u32), |
| Field::Vector(3, ScalarType::u32), |
| Field::Vector(4, ScalarType::u32), |
| Field::Vector(2, ScalarType::f16), |
| Field::Vector(3, ScalarType::f16), |
| Field::Vector(4, ScalarType::f16), |
| |
| // Vector types with custom alignment |
| Field::Vector(2, ScalarType::f32).AlignAttribute(32), |
| Field::Vector(3, ScalarType::f32).AlignAttribute(32), |
| Field::Vector(4, ScalarType::f32).AlignAttribute(32), |
| Field::Vector(2, ScalarType::i32).AlignAttribute(32), |
| Field::Vector(3, ScalarType::i32).AlignAttribute(32), |
| Field::Vector(4, ScalarType::i32).AlignAttribute(32), |
| Field::Vector(2, ScalarType::u32).AlignAttribute(32), |
| Field::Vector(3, ScalarType::u32).AlignAttribute(32), |
| Field::Vector(4, ScalarType::u32).AlignAttribute(32), |
| Field::Vector(2, ScalarType::f16).AlignAttribute(32), |
| Field::Vector(3, ScalarType::f16).AlignAttribute(32), |
| Field::Vector(4, ScalarType::f16).AlignAttribute(32), |
| |
| // Vector types with custom size |
| Field::Vector(2, ScalarType::f32).SizeAttribute(24), |
| Field::Vector(3, ScalarType::f32).SizeAttribute(24), |
| Field::Vector(4, ScalarType::f32).SizeAttribute(24), |
| Field::Vector(2, ScalarType::i32).SizeAttribute(24), |
| Field::Vector(3, ScalarType::i32).SizeAttribute(24), |
| Field::Vector(4, ScalarType::i32).SizeAttribute(24), |
| Field::Vector(2, ScalarType::u32).SizeAttribute(24), |
| Field::Vector(3, ScalarType::u32).SizeAttribute(24), |
| Field::Vector(4, ScalarType::u32).SizeAttribute(24), |
| Field::Vector(2, ScalarType::f16).SizeAttribute(24), |
| Field::Vector(3, ScalarType::f16).SizeAttribute(24), |
| Field::Vector(4, ScalarType::f16).SizeAttribute(24), |
| |
| // Matrix types with no custom alignment or size |
| Field::Matrix(2, 2, ScalarType::f32), |
| Field::Matrix(3, 2, ScalarType::f32), |
| Field::Matrix(4, 2, ScalarType::f32), |
| Field::Matrix(2, 3, ScalarType::f32), |
| Field::Matrix(3, 3, ScalarType::f32), |
| Field::Matrix(4, 3, ScalarType::f32), |
| Field::Matrix(2, 4, ScalarType::f32), |
| Field::Matrix(3, 4, ScalarType::f32), |
| Field::Matrix(4, 4, ScalarType::f32), |
| Field::Matrix(2, 2, ScalarType::f16), |
| Field::Matrix(3, 2, ScalarType::f16), |
| Field::Matrix(4, 2, ScalarType::f16), |
| Field::Matrix(2, 3, ScalarType::f16), |
| Field::Matrix(3, 3, ScalarType::f16), |
| Field::Matrix(4, 3, ScalarType::f16), |
| Field::Matrix(2, 4, ScalarType::f16), |
| Field::Matrix(3, 4, ScalarType::f16), |
| Field::Matrix(4, 4, ScalarType::f16), |
| |
| // Matrix types with custom alignment |
| Field::Matrix(2, 2, ScalarType::f32).AlignAttribute(32), |
| Field::Matrix(3, 2, ScalarType::f32).AlignAttribute(32), |
| Field::Matrix(4, 2, ScalarType::f32).AlignAttribute(32), |
| Field::Matrix(2, 3, ScalarType::f32).AlignAttribute(32), |
| Field::Matrix(3, 3, ScalarType::f32).AlignAttribute(32), |
| Field::Matrix(4, 3, ScalarType::f32).AlignAttribute(32), |
| Field::Matrix(2, 4, ScalarType::f32).AlignAttribute(32), |
| Field::Matrix(3, 4, ScalarType::f32).AlignAttribute(32), |
| Field::Matrix(4, 4, ScalarType::f32).AlignAttribute(32), |
| Field::Matrix(2, 2, ScalarType::f16).AlignAttribute(32), |
| Field::Matrix(3, 2, ScalarType::f16).AlignAttribute(32), |
| Field::Matrix(4, 2, ScalarType::f16).AlignAttribute(32), |
| Field::Matrix(2, 3, ScalarType::f16).AlignAttribute(32), |
| Field::Matrix(3, 3, ScalarType::f16).AlignAttribute(32), |
| Field::Matrix(4, 3, ScalarType::f16).AlignAttribute(32), |
| Field::Matrix(2, 4, ScalarType::f16).AlignAttribute(32), |
| Field::Matrix(3, 4, ScalarType::f16).AlignAttribute(32), |
| Field::Matrix(4, 4, ScalarType::f16).AlignAttribute(32), |
| |
| // Matrix types with custom size |
| Field::Matrix(2, 2, ScalarType::f32).SizeAttribute(128), |
| Field::Matrix(3, 2, ScalarType::f32).SizeAttribute(128), |
| Field::Matrix(4, 2, ScalarType::f32).SizeAttribute(128), |
| Field::Matrix(2, 3, ScalarType::f32).SizeAttribute(128), |
| Field::Matrix(3, 3, ScalarType::f32).SizeAttribute(128), |
| Field::Matrix(4, 3, ScalarType::f32).SizeAttribute(128), |
| Field::Matrix(2, 4, ScalarType::f32).SizeAttribute(128), |
| Field::Matrix(3, 4, ScalarType::f32).SizeAttribute(128), |
| Field::Matrix(4, 4, ScalarType::f32).SizeAttribute(128), |
| Field::Matrix(2, 2, ScalarType::f16).SizeAttribute(128), |
| Field::Matrix(3, 2, ScalarType::f16).SizeAttribute(128), |
| Field::Matrix(4, 2, ScalarType::f16).SizeAttribute(128), |
| Field::Matrix(2, 3, ScalarType::f16).SizeAttribute(128), |
| Field::Matrix(3, 3, ScalarType::f16).SizeAttribute(128), |
| Field::Matrix(4, 3, ScalarType::f16).SizeAttribute(128), |
| Field::Matrix(2, 4, ScalarType::f16).SizeAttribute(128), |
| Field::Matrix(3, 4, ScalarType::f16).SizeAttribute(128), |
| Field::Matrix(4, 4, ScalarType::f16).SizeAttribute(128), |
| |
| // Array types with no custom alignment or size. |
| // Note: The use of StorageBufferOnly() is due to UBOs requiring 16 byte |
| // alignment of array elements. See |
| // https://www.w3.org/TR/WGSL/#storage-class-constraints |
| Field("array<u32, 1>", /* align */ 4, /* size */ 4, /* requireF16Feature */ false) |
| .StorageBufferOnly(), |
| Field("array<u32, 2>", /* align */ 4, /* size */ 8, /* requireF16Feature */ false) |
| .StorageBufferOnly(), |
| Field("array<u32, 3>", /* align */ 4, /* size */ 12, /* requireF16Feature */ false) |
| .StorageBufferOnly(), |
| Field("array<u32, 4>", /* align */ 4, /* size */ 16, /* requireF16Feature */ false) |
| .StorageBufferOnly(), |
| Field("array<vec2u, 1>", /* align */ 8, /* size */ 8, /* requireF16Feature */ false) |
| .StorageBufferOnly(), |
| Field("array<vec2u, 2>", /* align */ 8, /* size */ 16, |
| /* requireF16Feature */ false) |
| .StorageBufferOnly(), |
| Field("array<vec2u, 3>", /* align */ 8, /* size */ 24, |
| /* requireF16Feature */ false) |
| .StorageBufferOnly(), |
| Field("array<vec2u, 4>", /* align */ 8, /* size */ 32, |
| /* requireF16Feature */ false) |
| .StorageBufferOnly(), |
| Field("array<vec3u, 1>", /* align */ 16, /* size */ 16, |
| /* requireF16Feature */ false) |
| .Strided(12, 4), |
| Field("array<vec3u, 2>", /* align */ 16, /* size */ 32, |
| /* requireF16Feature */ false) |
| .Strided(12, 4), |
| Field("array<vec3u, 3>", /* align */ 16, /* size */ 48, |
| /* requireF16Feature */ false) |
| .Strided(12, 4), |
| Field("array<vec3u, 4>", /* align */ 16, /* size */ 64, |
| /* requireF16Feature */ false) |
| .Strided(12, 4), |
| Field("array<vec4u, 1>", /* align */ 16, /* size */ 16, |
| /* requireF16Feature */ false), |
| Field("array<vec4u, 2>", /* align */ 16, /* size */ 32, |
| /* requireF16Feature */ false), |
| Field("array<vec4u, 3>", /* align */ 16, /* size */ 48, |
| /* requireF16Feature */ false), |
| Field("array<vec4u, 4>", /* align */ 16, /* size */ 64, |
| /* requireF16Feature */ false), |
| |
| // Array types with custom alignment |
| Field("array<u32, 1>", /* align */ 4, /* size */ 4, /* requireF16Feature */ false) |
| .AlignAttribute(32) |
| .StorageBufferOnly(), |
| Field("array<u32, 2>", /* align */ 4, /* size */ 8, /* requireF16Feature */ false) |
| .AlignAttribute(32) |
| .StorageBufferOnly(), |
| Field("array<u32, 3>", /* align */ 4, /* size */ 12, /* requireF16Feature */ false) |
| .AlignAttribute(32) |
| .StorageBufferOnly(), |
| Field("array<u32, 4>", /* align */ 4, /* size */ 16, /* requireF16Feature */ false) |
| .AlignAttribute(32) |
| .StorageBufferOnly(), |
| Field("array<vec2u, 1>", /* align */ 8, /* size */ 8, /* requireF16Feature */ false) |
| .AlignAttribute(32) |
| .StorageBufferOnly(), |
| Field("array<vec2u, 2>", /* align */ 8, /* size */ 16, |
| /* requireF16Feature */ false) |
| .AlignAttribute(32) |
| .StorageBufferOnly(), |
| Field("array<vec2u, 3>", /* align */ 8, /* size */ 24, |
| /* requireF16Feature */ false) |
| .AlignAttribute(32) |
| .StorageBufferOnly(), |
| Field("array<vec2u, 4>", /* align */ 8, /* size */ 32, |
| /* requireF16Feature */ false) |
| .AlignAttribute(32) |
| .StorageBufferOnly(), |
| Field("array<vec3u, 1>", /* align */ 16, /* size */ 16, |
| /* requireF16Feature */ false) |
| .AlignAttribute(32) |
| .Strided(12, 4), |
| Field("array<vec3u, 2>", /* align */ 16, /* size */ 32, |
| /* requireF16Feature */ false) |
| .AlignAttribute(32) |
| .Strided(12, 4), |
| Field("array<vec3u, 3>", /* align */ 16, /* size */ 48, |
| /* requireF16Feature */ false) |
| .AlignAttribute(32) |
| .Strided(12, 4), |
| Field("array<vec3u, 4>", /* align */ 16, /* size */ 64, |
| /* requireF16Feature */ false) |
| .AlignAttribute(32) |
| .Strided(12, 4), |
| Field("array<vec4u, 1>", /* align */ 16, /* size */ 16, |
| /* requireF16Feature */ false) |
| .AlignAttribute(32), |
| Field("array<vec4u, 2>", /* align */ 16, /* size */ 32, |
| /* requireF16Feature */ false) |
| .AlignAttribute(32), |
| Field("array<vec4u, 3>", /* align */ 16, /* size */ 48, |
| /* requireF16Feature */ false) |
| .AlignAttribute(32), |
| Field("array<vec4u, 4>", /* align */ 16, /* size */ 64, |
| /* requireF16Feature */ false) |
| .AlignAttribute(32), |
| |
| // Array types with custom size |
| Field("array<u32, 1>", /* align */ 4, /* size */ 4, /* requireF16Feature */ false) |
| .SizeAttribute(128) |
| .StorageBufferOnly(), |
| Field("array<u32, 2>", /* align */ 4, /* size */ 8, /* requireF16Feature */ false) |
| .SizeAttribute(128) |
| .StorageBufferOnly(), |
| Field("array<u32, 3>", /* align */ 4, /* size */ 12, /* requireF16Feature */ false) |
| .SizeAttribute(128) |
| .StorageBufferOnly(), |
| Field("array<u32, 4>", /* align */ 4, /* size */ 16, /* requireF16Feature */ false) |
| .SizeAttribute(128) |
| .StorageBufferOnly(), |
| Field("array<vec3u, 4>", /* align */ 16, /* size */ 64, |
| /* requireF16Feature */ false) |
| .SizeAttribute(128) |
| .Strided(12, 4), |
| |
| // Array of f32 matrix |
| Field("array<mat2x2<f32>, 3>", /* align */ 8, /* size */ 48, |
| /* requireF16Feature */ false) |
| .StorageBufferOnly(), |
| // Uniform scope require the array alignment round up to 16. |
| Field("array<mat2x2<f32>, 3>", /* align */ 8, /* size */ 48, |
| /* requireF16Feature */ false) |
| .AlignAttribute(16), |
| Field("array<mat2x3<f32>, 3>", /* align */ 16, /* size */ 96, |
| /* requireF16Feature */ false) |
| .Strided(12, 4), |
| Field("array<mat2x4<f32>, 3>", /* align */ 16, /* size */ 96, |
| /* requireF16Feature */ false), |
| Field("array<mat3x2<f32>, 3>", /* align */ 8, /* size */ 72, |
| /* requireF16Feature */ false) |
| .StorageBufferOnly(), |
| // `mat3x2<f16>` can not be the element type of a uniform array, because its size 24 is |
| // not a multiple of 16. |
| Field("array<mat3x2<f32>, 3>", /* align */ 8, /* size */ 72, |
| /* requireF16Feature */ false) |
| .AlignAttribute(16) |
| .StorageBufferOnly(), |
| Field("array<mat3x3<f32>, 3>", /* align */ 16, /* size */ 144, |
| /* requireF16Feature */ false) |
| .Strided(12, 4), |
| Field("array<mat3x4<f32>, 3>", /* align */ 16, /* size */ 144, |
| /* requireF16Feature */ false), |
| Field("array<mat4x2<f32>, 3>", /* align */ 8, /* size */ 96, |
| /* requireF16Feature */ false) |
| .StorageBufferOnly(), |
| Field("array<mat4x2<f32>, 3>", /* align */ 8, /* size */ 96, |
| /* requireF16Feature */ false) |
| .AlignAttribute(16), |
| Field("array<mat4x3<f32>, 3>", /* align */ 16, /* size */ 192, |
| /* requireF16Feature */ false) |
| .Strided(12, 4), |
| Field("array<mat4x4<f32>, 3>", /* align */ 16, /* size */ 192, |
| /* requireF16Feature */ false), |
| |
| // Array of f16 matrix |
| Field("array<mat2x2<f16>, 3>", /* align */ 4, /* size */ 24, |
| /* requireF16Feature */ true) |
| .StorageBufferOnly(), |
| Field("array<mat2x3<f16>, 3>", /* align */ 8, /* size */ 48, |
| /* requireF16Feature */ true) |
| .Strided(6, 2) |
| .StorageBufferOnly(), |
| Field("array<mat2x4<f16>, 3>", /* align */ 8, /* size */ 48, |
| /* requireF16Feature */ true) |
| .StorageBufferOnly(), |
| Field("array<mat3x2<f16>, 3>", /* align */ 4, /* size */ 36, |
| /* requireF16Feature */ true) |
| .StorageBufferOnly(), |
| Field("array<mat3x3<f16>, 3>", /* align */ 8, /* size */ 72, |
| /* requireF16Feature */ true) |
| .Strided(6, 2) |
| .StorageBufferOnly(), |
| Field("array<mat3x4<f16>, 3>", /* align */ 8, /* size */ 72, |
| /* requireF16Feature */ true) |
| .StorageBufferOnly(), |
| Field("array<mat4x2<f16>, 3>", /* align */ 4, /* size */ 48, |
| /* requireF16Feature */ true) |
| .StorageBufferOnly(), |
| Field("array<mat4x3<f16>, 3>", /* align */ 8, /* size */ 96, |
| /* requireF16Feature */ true) |
| .Strided(6, 2) |
| .StorageBufferOnly(), |
| Field("array<mat4x4<f16>, 3>", /* align */ 8, /* size */ 96, |
| /* requireF16Feature */ true) |
| .StorageBufferOnly(), |
| // Uniform scope require the array alignment round up to 16, and array element size a |
| // multiple of 16. |
| Field("array<mat2x2<f16>, 3>", /* align */ 4, /* size */ 24, |
| /* requireF16Feature */ true) |
| .AlignAttribute(16) |
| .StorageBufferOnly(), |
| Field("array<mat2x3<f16>, 3>", /* align */ 8, /* size */ 48, |
| /* requireF16Feature */ true) |
| .AlignAttribute(16) |
| .Strided(6, 2), |
| Field("array<mat2x4<f16>, 3>", /* align */ 8, /* size */ 48, |
| /* requireF16Feature */ true) |
| .AlignAttribute(16), |
| Field("array<mat3x2<f16>, 3>", /* align */ 4, /* size */ 36, |
| /* requireF16Feature */ true) |
| .AlignAttribute(16) |
| .StorageBufferOnly(), |
| Field("array<mat3x3<f16>, 3>", /* align */ 8, /* size */ 72, |
| /* requireF16Feature */ true) |
| .AlignAttribute(16) |
| .Strided(6, 2) |
| .StorageBufferOnly(), |
| Field("array<mat3x4<f16>, 3>", /* align */ 8, /* size */ 72, |
| /* requireF16Feature */ true) |
| .AlignAttribute(16) |
| .StorageBufferOnly(), |
| Field("array<mat4x2<f16>, 3>", /* align */ 4, /* size */ 48, |
| /* requireF16Feature */ true) |
| .AlignAttribute(16), |
| Field("array<mat4x3<f16>, 3>", /* align */ 8, /* size */ 96, |
| /* requireF16Feature */ true) |
| .AlignAttribute(16) |
| .Strided(6, 2), |
| Field("array<mat4x4<f16>, 3>", /* align */ 8, /* size */ 96, |
| /* requireF16Feature */ true) |
| .AlignAttribute(16), |
| }); |
| |
| std::vector<ComputeLayoutMemoryBufferTestParams> filtered; |
| for (auto param : params) { |
| if (param.mAddressSpace != AddressSpace::Storage && param.mField.IsStorageBufferOnly()) { |
| continue; |
| } |
| filtered.emplace_back(param); |
| } |
| return filtered; |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(, |
| ComputeLayoutMemoryBufferTests, |
| ::testing::ValuesIn(GenerateParams()), |
| DawnTestBase::PrintToStringParamName("ComputeLayoutMemoryBufferTests")); |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ComputeLayoutMemoryBufferTests); |
| |
| } // anonymous namespace |
| } // namespace dawn |