dawn E2E: Refactor and add non-struct-member tests in ComputeLayoutMemoryBufferTests
This CL refactor the end-to-end test suit ComputeLayoutMemoryBufferTests and add tests for non-struct-member scalar, vector, matrix, and array of vectors and matrices types. This test suit is also intend to test f16 buffer read/write after it is implemented. Bug: tint:1673 Change-Id: Iea4d3f70897d196ea00e3a3e0189a0372afe0382 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/102800 Kokoro: Kokoro <noreply+kokoro@google.com> Reviewed-by: Corentin Wallez <cwallez@chromium.org> Commit-Queue: Zhaoming Jiang <zhaoming.jiang@intel.com>
This commit is contained in:
parent
0c71e44aa0
commit
5e22e46a63
|
@ -34,77 +34,6 @@ std::string ReplaceAll(std::string str, const std::string& substr, const std::st
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)>;
|
|
||||||
|
|
||||||
// DataMatcher is a function pointer to a data matching function.
|
|
||||||
// size is the total number of bytes being considered for matching.
|
|
||||||
// The callback may be called once or multiple times, and may only consider
|
|
||||||
// part of the interval [0, size)
|
|
||||||
using DataMatcher = void (*)(uint32_t size, DataMatcherCallback);
|
|
||||||
|
|
||||||
// FullDataMatcher is a DataMatcher that calls callback with the interval
|
|
||||||
// [0, size)
|
|
||||||
void FullDataMatcher(uint32_t size, DataMatcherCallback callback) {
|
|
||||||
callback(0, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
// StridedDataMatcher is a DataMatcher that calls callback with the strided
|
|
||||||
// intervals of length BYTES_TO_MATCH, skipping BYTES_TO_SKIP.
|
|
||||||
// For example: StridedDataMatcher<2, 4>(18, callback) will call callback
|
|
||||||
// with the intervals: [0, 2), [6, 8), [12, 14)
|
|
||||||
template <int BYTES_TO_MATCH, int BYTES_TO_SKIP>
|
|
||||||
void StridedDataMatcher(uint32_t size, DataMatcherCallback callback) {
|
|
||||||
uint32_t offset = 0;
|
|
||||||
while (offset < size) {
|
|
||||||
callback(offset, BYTES_TO_MATCH);
|
|
||||||
offset += BYTES_TO_MATCH + BYTES_TO_SKIP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Align returns the WGSL decoration for an explicit structure field alignment
|
|
||||||
std::string AlignDeco(uint32_t value) {
|
|
||||||
return "@align(" + std::to_string(value) + ") ";
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
// Field holds test parameters for ComputeLayoutMemoryBufferTests.Fields
|
|
||||||
struct Field {
|
|
||||||
const char* type; // Type of the field
|
|
||||||
uint32_t align; // Alignment of the type in bytes
|
|
||||||
uint32_t size; // Natural size of the type in bytes
|
|
||||||
|
|
||||||
uint32_t padded_size = 0; // Decorated (extended) size of the type in bytes
|
|
||||||
DataMatcher matcher = &FullDataMatcher; // The matching method
|
|
||||||
bool storage_buffer_only = false; // This should only be used for storage buffer tests
|
|
||||||
|
|
||||||
// Sets the padded_size to value.
|
|
||||||
// Returns this Field so calls can be chained.
|
|
||||||
Field& PaddedSize(uint32_t value) {
|
|
||||||
padded_size = value;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sets the matcher to a StridedDataMatcher<BYTES_TO_MATCH, BYTES_TO_SKIP>.
|
|
||||||
// Returns this Field so calls can be chained.
|
|
||||||
template <int BYTES_TO_MATCH, int BYTES_TO_SKIP>
|
|
||||||
Field& Strided() {
|
|
||||||
matcher = &StridedDataMatcher<BYTES_TO_MATCH, BYTES_TO_SKIP>;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marks that this should only be used for storage buffer tests.
|
|
||||||
// Returns this Field so calls can be chained.
|
|
||||||
Field& StorageBufferOnly() {
|
|
||||||
storage_buffer_only = true;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// StorageClass is an enumerator of storage classes used by ComputeLayoutMemoryBufferTests.Fields
|
// StorageClass is an enumerator of storage classes used by ComputeLayoutMemoryBufferTests.Fields
|
||||||
enum class StorageClass {
|
enum class StorageClass {
|
||||||
Uniform,
|
Uniform,
|
||||||
|
@ -123,12 +52,395 @@ std::ostream& operator<<(std::ostream& o, StorageClass storageClass) {
|
||||||
return o;
|
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 MSB set to 0 while padding bytes 1.
|
||||||
|
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;
|
||||||
|
// Get a data byte with MSB set to 0.
|
||||||
|
auto NextDataByte = [&]() {
|
||||||
|
dataByte += 0x11u;
|
||||||
|
return static_cast<uint8_t>((dataByte ^ dataXorKey) & 0x7fu);
|
||||||
|
};
|
||||||
|
// Get a padding byte with MSB set to 1, distinguished from data bytes.
|
||||||
|
auto NextPaddingByte = [&]() {
|
||||||
|
paddingByte += 0x13u;
|
||||||
|
return static_cast<uint8_t>((paddingByte ^ paddingXorKey) | 0x80u);
|
||||||
|
};
|
||||||
|
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`, `vec2<f32>`, `mat4x4<f32>` or
|
||||||
|
// `array<f32, 5>`, or have a fixed data stride, e.g. `mat3x3<f32>` or `array<vec3<f32>, 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)
|
||||||
|
: mWGSLType(wgslType),
|
||||||
|
mAlign(align),
|
||||||
|
mSize(size),
|
||||||
|
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; }
|
||||||
|
|
||||||
|
// 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 continious 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 introduced by @size attribute are not tested.
|
||||||
|
void CheckData(DataMatcherCallback callback) const {
|
||||||
|
// Calls `callback` with the strided intervals of length mStrideDataBytes, skipping
|
||||||
|
// mStridePaddingBytes. For example, for a field of mSize = 18, mStrideDataBytes = 2,
|
||||||
|
// and mStridePaddingBytes = 4, calls `callback` with the intervals: [0, 2), [6, 8),
|
||||||
|
// [12, 14). If the data is continious, 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);
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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};
|
||||||
|
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
|
||||||
|
|
||||||
|
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) {
|
std::ostream& operator<<(std::ostream& o, Field field) {
|
||||||
o << "@align(" << field.align << ") @size("
|
o << "@align(" << field.GetAlign() << ") @size(" << field.GetPaddedSize() << ") "
|
||||||
<< (field.padded_size > 0 ? field.padded_size : field.size) << ") " << field.type;
|
<< field.GetWGSLType();
|
||||||
return o;
|
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, StorageClass, Field);
|
DAWN_TEST_PARAM_STRUCT(ComputeLayoutMemoryBufferTestParams, StorageClass, Field);
|
||||||
|
|
||||||
class ComputeLayoutMemoryBufferTests
|
class ComputeLayoutMemoryBufferTests
|
||||||
|
@ -136,7 +448,13 @@ class ComputeLayoutMemoryBufferTests
|
||||||
void SetUp() override { DawnTestBase::SetUp(); }
|
void SetUp() override { DawnTestBase::SetUp(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_P(ComputeLayoutMemoryBufferTests, Fields) {
|
// 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) {
|
||||||
// Sentinel value markers codes used to check that the start and end of
|
// Sentinel value markers codes used to check that the start and end of
|
||||||
// structures are correctly aligned. Each of these codes are distinct and
|
// structures are correctly aligned. Each of these codes are distinct and
|
||||||
// are not likely to be confused with data.
|
// are not likely to be confused with data.
|
||||||
|
@ -145,15 +463,6 @@ TEST_P(ComputeLayoutMemoryBufferTests, Fields) {
|
||||||
constexpr uint32_t kInputHeaderCode = 0x91827364u;
|
constexpr uint32_t kInputHeaderCode = 0x91827364u;
|
||||||
constexpr uint32_t kInputFooterCode = 0x19283764u;
|
constexpr uint32_t kInputFooterCode = 0x19283764u;
|
||||||
|
|
||||||
// Byte codes used for field padding. The MSB is set for each of these.
|
|
||||||
// The field data has the MSB 0.
|
|
||||||
constexpr uint8_t kDataAlignPaddingCode = 0xfeu;
|
|
||||||
constexpr uint8_t kFieldAlignPaddingCode = 0xfdu;
|
|
||||||
constexpr uint8_t kFieldSizePaddingCode = 0xdcu;
|
|
||||||
constexpr uint8_t kDataSizePaddingCode = 0xdbu;
|
|
||||||
constexpr uint8_t kInputFooterAlignPaddingCode = 0xdau;
|
|
||||||
constexpr uint8_t kInputTailPaddingCode = 0xd9u;
|
|
||||||
|
|
||||||
// Status codes returned by the shader.
|
// Status codes returned by the shader.
|
||||||
constexpr uint32_t kStatusBadInputHeader = 100u;
|
constexpr uint32_t kStatusBadInputHeader = 100u;
|
||||||
constexpr uint32_t kStatusBadInputFooter = 101u;
|
constexpr uint32_t kStatusBadInputFooter = 101u;
|
||||||
|
@ -210,7 +519,7 @@ fn main() {
|
||||||
// Structure size: roundUp(AlignOf(S), OffsetOf(S, L) + SizeOf(S, L))
|
// Structure size: roundUp(AlignOf(S), OffsetOf(S, L) + SizeOf(S, L))
|
||||||
// https://www.w3.org/TR/WGSL/#storage-class-constraints
|
// https://www.w3.org/TR/WGSL/#storage-class-constraints
|
||||||
// RequiredAlignOf(S, uniform): roundUp(16, max(AlignOf(T0), ..., AlignOf(TN)))
|
// RequiredAlignOf(S, uniform): roundUp(16, max(AlignOf(T0), ..., AlignOf(TN)))
|
||||||
uint32_t dataAlign = isUniform ? std::max(16u, field.align) : field.align;
|
uint32_t dataAlign = isUniform ? std::max(size_t(16u), field.GetAlign()) : field.GetAlign();
|
||||||
|
|
||||||
// https://www.w3.org/TR/WGSL/#structure-layout-rules
|
// 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
|
// Note: When underlying the target is a Vulkan device, we assume the device does not support
|
||||||
|
@ -219,11 +528,10 @@ fn main() {
|
||||||
uint32_t footerAlign = isUniform ? 16 : 4;
|
uint32_t footerAlign = isUniform ? 16 : 4;
|
||||||
|
|
||||||
shader = ReplaceAll(shader, "{data_align}", isUniform ? AlignDeco(dataAlign) : "");
|
shader = ReplaceAll(shader, "{data_align}", isUniform ? AlignDeco(dataAlign) : "");
|
||||||
shader = ReplaceAll(shader, "{field_align}", std::to_string(field.align));
|
shader = ReplaceAll(shader, "{field_align}", std::to_string(field.GetAlign()));
|
||||||
shader = ReplaceAll(shader, "{footer_align}", isUniform ? AlignDeco(footerAlign) : "");
|
shader = ReplaceAll(shader, "{footer_align}", isUniform ? AlignDeco(footerAlign) : "");
|
||||||
shader = ReplaceAll(shader, "{field_size}",
|
shader = ReplaceAll(shader, "{field_size}", std::to_string(field.GetPaddedSize()));
|
||||||
std::to_string(field.padded_size > 0 ? field.padded_size : field.size));
|
shader = ReplaceAll(shader, "{field_type}", field.GetWGSLType());
|
||||||
shader = ReplaceAll(shader, "{field_type}", field.type);
|
|
||||||
shader = ReplaceAll(shader, "{input_header_code}", std::to_string(kInputHeaderCode));
|
shader = ReplaceAll(shader, "{input_header_code}", std::to_string(kInputHeaderCode));
|
||||||
shader = ReplaceAll(shader, "{input_footer_code}", std::to_string(kInputFooterCode));
|
shader = ReplaceAll(shader, "{input_footer_code}", std::to_string(kInputFooterCode));
|
||||||
shader = ReplaceAll(shader, "{data_header_code}", std::to_string(kDataHeaderCode));
|
shader = ReplaceAll(shader, "{data_header_code}", std::to_string(kDataHeaderCode));
|
||||||
|
@ -237,55 +545,40 @@ fn main() {
|
||||||
isUniform ? "uniform" //
|
isUniform ? "uniform" //
|
||||||
: "storage, read_write");
|
: "storage, read_write");
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// Build the input and expected data.
|
// Build the input and expected data.
|
||||||
std::vector<uint8_t> inputData; // The whole SSBO data
|
MemoryDataBuilder inputDataBuilder; // The whole SSBO data
|
||||||
std::vector<uint8_t> expectedData; // The expected data to be copied by the shader
|
|
||||||
{
|
{
|
||||||
auto PushU32 = [&inputData](uint32_t u32) {
|
inputDataBuilder.AddFixedU32(kInputHeaderCode); // Input.header
|
||||||
inputData.emplace_back((u32 >> 0) & 0xff);
|
inputDataBuilder.AlignTo(dataAlign); // Input.data
|
||||||
inputData.emplace_back((u32 >> 8) & 0xff);
|
|
||||||
inputData.emplace_back((u32 >> 16) & 0xff);
|
|
||||||
inputData.emplace_back((u32 >> 24) & 0xff);
|
|
||||||
};
|
|
||||||
auto AlignTo = [&inputData](uint32_t alignment, uint8_t code) {
|
|
||||||
uint32_t target = Align(inputData.size(), alignment);
|
|
||||||
uint32_t bytes = target - inputData.size();
|
|
||||||
for (uint32_t i = 0; i < bytes; i++) {
|
|
||||||
inputData.emplace_back(code);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
PushU32(kInputHeaderCode); // Input.header
|
|
||||||
AlignTo(dataAlign, kDataAlignPaddingCode); // Input.data
|
|
||||||
{
|
{
|
||||||
PushU32(kDataHeaderCode); // Input.data.header
|
inputDataBuilder.AddFixedU32(kDataHeaderCode); // Input.data.header
|
||||||
AlignTo(field.align, kFieldAlignPaddingCode); // Input.data.field
|
inputDataBuilder.AddSubBuilder(field.GetDataBuilder()); // Input.data.field
|
||||||
for (uint32_t i = 0; i < field.size; i++) {
|
inputDataBuilder.AddFixedU32(kDataFooterCode); // Input.data.footer
|
||||||
// The data has the MSB cleared to distinguish it from the
|
inputDataBuilder.AlignTo(field.GetAlign()); // Input.data padding
|
||||||
// padding codes.
|
|
||||||
uint8_t code = i & 0x7f;
|
|
||||||
inputData.emplace_back(code); // Input.data.field
|
|
||||||
expectedData.emplace_back(code);
|
|
||||||
}
|
|
||||||
for (uint32_t i = field.size; i < field.padded_size; i++) {
|
|
||||||
inputData.emplace_back(kFieldSizePaddingCode); // Input.data.field padding
|
|
||||||
}
|
|
||||||
PushU32(kDataFooterCode); // Input.data.footer
|
|
||||||
AlignTo(field.align, kDataSizePaddingCode); // Input.data padding
|
|
||||||
}
|
}
|
||||||
AlignTo(footerAlign, kInputFooterAlignPaddingCode); // Input.footer @align
|
inputDataBuilder.AlignTo(footerAlign); // Input.footer @align
|
||||||
PushU32(kInputFooterCode); // Input.footer
|
inputDataBuilder.AddFixedU32(kInputFooterCode); // Input.footer
|
||||||
AlignTo(256, kInputTailPaddingCode); // Input padding
|
inputDataBuilder.AlignTo(256); // Input padding
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MemoryDataBuilder expectedDataBuilder; // The expected data to be copied by the shader
|
||||||
|
expectedDataBuilder.AddSubBuilder(field.GetDataBuilder());
|
||||||
|
|
||||||
|
// 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
|
// Set up input storage buffer
|
||||||
wgpu::Buffer inputBuf = utils::CreateBufferFromData(
|
wgpu::Buffer inputBuf = utils::CreateBufferFromData(
|
||||||
device, inputData.data(), inputData.size(),
|
device, inputData.data(), inputData.size(),
|
||||||
|
@ -293,11 +586,9 @@ fn main() {
|
||||||
(isUniform ? wgpu::BufferUsage::Uniform : wgpu::BufferUsage::Storage));
|
(isUniform ? wgpu::BufferUsage::Uniform : wgpu::BufferUsage::Storage));
|
||||||
|
|
||||||
// Set up output storage buffer
|
// Set up output storage buffer
|
||||||
wgpu::BufferDescriptor outputDesc;
|
wgpu::Buffer outputBuf = utils::CreateBufferFromData(
|
||||||
outputDesc.size = field.size;
|
device, initData.data(), initData.size(),
|
||||||
outputDesc.usage =
|
wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst);
|
||||||
wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
|
|
||||||
wgpu::Buffer outputBuf = device.CreateBuffer(&outputDesc);
|
|
||||||
|
|
||||||
// Set up status storage buffer
|
// Set up status storage buffer
|
||||||
wgpu::BufferDescriptor statusDesc;
|
wgpu::BufferDescriptor statusDesc;
|
||||||
|
@ -306,40 +597,84 @@ fn main() {
|
||||||
wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
|
wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
|
||||||
wgpu::Buffer statusBuf = device.CreateBuffer(&statusDesc);
|
wgpu::Buffer statusBuf = device.CreateBuffer(&statusDesc);
|
||||||
|
|
||||||
// Set up bind group and issue dispatch
|
RunComputeShaderWithBuffers(device, queue, shader, {inputBuf, outputBuf, statusBuf});
|
||||||
wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
|
|
||||||
{
|
|
||||||
{0, inputBuf},
|
|
||||||
{1, outputBuf},
|
|
||||||
{2, statusBuf},
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Check the status
|
// Check the status
|
||||||
EXPECT_BUFFER_U32_EQ(kStatusOk, statusBuf, 0) << "status code error" << std::endl
|
EXPECT_BUFFER_U32_EQ(kStatusOk, statusBuf, 0) << "status code error" << std::endl
|
||||||
<< "Shader: " << shader;
|
<< "Shader: " << shader;
|
||||||
|
|
||||||
// Check the data
|
// Check the data
|
||||||
field.matcher(field.size, [&](uint32_t offset, uint32_t size) {
|
field.CheckData([&](uint32_t offset, uint32_t size) {
|
||||||
EXPECT_BUFFER_U8_RANGE_EQ(expectedData.data() + offset, outputBuf, offset, size)
|
EXPECT_BUFFER_U8_RANGE_EQ(expectedData.data() + offset, outputBuf, offset, size)
|
||||||
<< "offset: " << offset;
|
<< "offset: " << offset;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
// Test different types that used directly as buffer type
|
||||||
|
TEST_P(ComputeLayoutMemoryBufferTests, NonStructMember) {
|
||||||
|
auto params = GetParam();
|
||||||
|
Field& field = params.mField;
|
||||||
|
// @size and @align attribute only apply to struct members, skip them
|
||||||
|
if (field.HasSizeAttribute() || field.HasAlignAttribute()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool isUniform = GetParam().mStorageClass == StorageClass::Uniform;
|
||||||
|
|
||||||
|
std::string shader = 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());
|
||||||
|
|
||||||
|
// 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
|
||||||
|
field.CheckData([&](uint32_t offset, uint32_t size) {
|
||||||
|
EXPECT_BUFFER_U8_RANGE_EQ(expectedData.data() + offset, outputBuf, offset, size)
|
||||||
|
<< "offset: " << offset;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
auto GenerateParams() {
|
auto GenerateParams() {
|
||||||
auto params = MakeParamGenerator<ComputeLayoutMemoryBufferTestParams>(
|
auto params = MakeParamGenerator<ComputeLayoutMemoryBufferTestParams>(
|
||||||
|
@ -354,127 +689,169 @@ auto GenerateParams() {
|
||||||
{
|
{
|
||||||
// See https://www.w3.org/TR/WGSL/#alignment-and-size
|
// See https://www.w3.org/TR/WGSL/#alignment-and-size
|
||||||
// Scalar types with no custom alignment or size
|
// Scalar types with no custom alignment or size
|
||||||
Field{"i32", /* align */ 4, /* size */ 4},
|
Field::Scalar(ScalarType::f32),
|
||||||
Field{"u32", /* align */ 4, /* size */ 4},
|
Field::Scalar(ScalarType::i32),
|
||||||
Field{"f32", /* align */ 4, /* size */ 4},
|
Field::Scalar(ScalarType::u32),
|
||||||
|
|
||||||
// Scalar types with custom alignment
|
// Scalar types with custom alignment
|
||||||
Field{"i32", /* align */ 16, /* size */ 4},
|
Field::Scalar(ScalarType::f32).AlignAttribute(16),
|
||||||
Field{"u32", /* align */ 16, /* size */ 4},
|
Field::Scalar(ScalarType::i32).AlignAttribute(16),
|
||||||
Field{"f32", /* align */ 16, /* size */ 4},
|
Field::Scalar(ScalarType::u32).AlignAttribute(16),
|
||||||
|
|
||||||
// Scalar types with custom size
|
// Scalar types with custom size
|
||||||
Field{"i32", /* align */ 4, /* size */ 4}.PaddedSize(24),
|
Field::Scalar(ScalarType::f32).SizeAttribute(24),
|
||||||
Field{"u32", /* align */ 4, /* size */ 4}.PaddedSize(24),
|
Field::Scalar(ScalarType::i32).SizeAttribute(24),
|
||||||
Field{"f32", /* align */ 4, /* size */ 4}.PaddedSize(24),
|
Field::Scalar(ScalarType::u32).SizeAttribute(24),
|
||||||
|
|
||||||
// Vector types with no custom alignment or size
|
// Vector types with no custom alignment or size
|
||||||
Field{"vec2<i32>", /* align */ 8, /* size */ 8},
|
Field::Vector(2, ScalarType::f32),
|
||||||
Field{"vec2<u32>", /* align */ 8, /* size */ 8},
|
Field::Vector(3, ScalarType::f32),
|
||||||
Field{"vec2<f32>", /* align */ 8, /* size */ 8},
|
Field::Vector(4, ScalarType::f32),
|
||||||
Field{"vec3<i32>", /* align */ 16, /* size */ 12},
|
Field::Vector(2, ScalarType::i32),
|
||||||
Field{"vec3<u32>", /* align */ 16, /* size */ 12},
|
Field::Vector(3, ScalarType::i32),
|
||||||
Field{"vec3<f32>", /* align */ 16, /* size */ 12},
|
Field::Vector(4, ScalarType::i32),
|
||||||
Field{"vec4<i32>", /* align */ 16, /* size */ 16},
|
Field::Vector(2, ScalarType::u32),
|
||||||
Field{"vec4<u32>", /* align */ 16, /* size */ 16},
|
Field::Vector(3, ScalarType::u32),
|
||||||
Field{"vec4<f32>", /* align */ 16, /* size */ 16},
|
Field::Vector(4, ScalarType::u32),
|
||||||
|
|
||||||
// Vector types with custom alignment
|
// Vector types with custom alignment
|
||||||
Field{"vec2<i32>", /* align */ 32, /* size */ 8},
|
Field::Vector(2, ScalarType::f32).AlignAttribute(32),
|
||||||
Field{"vec2<u32>", /* align */ 32, /* size */ 8},
|
Field::Vector(3, ScalarType::f32).AlignAttribute(32),
|
||||||
Field{"vec2<f32>", /* align */ 32, /* size */ 8},
|
Field::Vector(4, ScalarType::f32).AlignAttribute(32),
|
||||||
Field{"vec3<i32>", /* align */ 32, /* size */ 12},
|
Field::Vector(2, ScalarType::i32).AlignAttribute(32),
|
||||||
Field{"vec3<u32>", /* align */ 32, /* size */ 12},
|
Field::Vector(3, ScalarType::i32).AlignAttribute(32),
|
||||||
Field{"vec3<f32>", /* align */ 32, /* size */ 12},
|
Field::Vector(4, ScalarType::i32).AlignAttribute(32),
|
||||||
Field{"vec4<i32>", /* align */ 32, /* size */ 16},
|
Field::Vector(2, ScalarType::u32).AlignAttribute(32),
|
||||||
Field{"vec4<u32>", /* align */ 32, /* size */ 16},
|
Field::Vector(3, ScalarType::u32).AlignAttribute(32),
|
||||||
Field{"vec4<f32>", /* align */ 32, /* size */ 16},
|
Field::Vector(4, ScalarType::u32).AlignAttribute(32),
|
||||||
|
|
||||||
// Vector types with custom size
|
// Vector types with custom size
|
||||||
Field{"vec2<i32>", /* align */ 8, /* size */ 8}.PaddedSize(24),
|
Field::Vector(2, ScalarType::f32).SizeAttribute(24),
|
||||||
Field{"vec2<u32>", /* align */ 8, /* size */ 8}.PaddedSize(24),
|
Field::Vector(3, ScalarType::f32).SizeAttribute(24),
|
||||||
Field{"vec2<f32>", /* align */ 8, /* size */ 8}.PaddedSize(24),
|
Field::Vector(4, ScalarType::f32).SizeAttribute(24),
|
||||||
Field{"vec3<i32>", /* align */ 16, /* size */ 12}.PaddedSize(24),
|
Field::Vector(2, ScalarType::i32).SizeAttribute(24),
|
||||||
Field{"vec3<u32>", /* align */ 16, /* size */ 12}.PaddedSize(24),
|
Field::Vector(3, ScalarType::i32).SizeAttribute(24),
|
||||||
Field{"vec3<f32>", /* align */ 16, /* size */ 12}.PaddedSize(24),
|
Field::Vector(4, ScalarType::i32).SizeAttribute(24),
|
||||||
Field{"vec4<i32>", /* align */ 16, /* size */ 16}.PaddedSize(24),
|
Field::Vector(2, ScalarType::u32).SizeAttribute(24),
|
||||||
Field{"vec4<u32>", /* align */ 16, /* size */ 16}.PaddedSize(24),
|
Field::Vector(3, ScalarType::u32).SizeAttribute(24),
|
||||||
Field{"vec4<f32>", /* align */ 16, /* size */ 16}.PaddedSize(24),
|
Field::Vector(4, ScalarType::u32).SizeAttribute(24),
|
||||||
|
|
||||||
// Matrix types with no custom alignment or size
|
// Matrix types with no custom alignment or size
|
||||||
Field{"mat2x2<f32>", /* align */ 8, /* size */ 16},
|
Field::Matrix(2, 2, ScalarType::f32),
|
||||||
Field{"mat3x2<f32>", /* align */ 8, /* size */ 24},
|
Field::Matrix(3, 2, ScalarType::f32),
|
||||||
Field{"mat4x2<f32>", /* align */ 8, /* size */ 32},
|
Field::Matrix(4, 2, ScalarType::f32),
|
||||||
Field{"mat2x3<f32>", /* align */ 16, /* size */ 32}.Strided<12, 4>(),
|
Field::Matrix(2, 3, ScalarType::f32),
|
||||||
Field{"mat3x3<f32>", /* align */ 16, /* size */ 48}.Strided<12, 4>(),
|
Field::Matrix(3, 3, ScalarType::f32),
|
||||||
Field{"mat4x3<f32>", /* align */ 16, /* size */ 64}.Strided<12, 4>(),
|
Field::Matrix(4, 3, ScalarType::f32),
|
||||||
Field{"mat2x4<f32>", /* align */ 16, /* size */ 32},
|
Field::Matrix(2, 4, ScalarType::f32),
|
||||||
Field{"mat3x4<f32>", /* align */ 16, /* size */ 48},
|
Field::Matrix(3, 4, ScalarType::f32),
|
||||||
Field{"mat4x4<f32>", /* align */ 16, /* size */ 64},
|
Field::Matrix(4, 4, ScalarType::f32),
|
||||||
|
|
||||||
// Matrix types with custom alignment
|
// Matrix types with custom alignment
|
||||||
Field{"mat2x2<f32>", /* align */ 32, /* size */ 16},
|
Field::Matrix(2, 2, ScalarType::f32).AlignAttribute(32),
|
||||||
Field{"mat3x2<f32>", /* align */ 32, /* size */ 24},
|
Field::Matrix(3, 2, ScalarType::f32).AlignAttribute(32),
|
||||||
Field{"mat4x2<f32>", /* align */ 32, /* size */ 32},
|
Field::Matrix(4, 2, ScalarType::f32).AlignAttribute(32),
|
||||||
Field{"mat2x3<f32>", /* align */ 32, /* size */ 32}.Strided<12, 4>(),
|
Field::Matrix(2, 3, ScalarType::f32).AlignAttribute(32),
|
||||||
Field{"mat3x3<f32>", /* align */ 32, /* size */ 48}.Strided<12, 4>(),
|
Field::Matrix(3, 3, ScalarType::f32).AlignAttribute(32),
|
||||||
Field{"mat4x3<f32>", /* align */ 32, /* size */ 64}.Strided<12, 4>(),
|
Field::Matrix(4, 3, ScalarType::f32).AlignAttribute(32),
|
||||||
Field{"mat2x4<f32>", /* align */ 32, /* size */ 32},
|
Field::Matrix(2, 4, ScalarType::f32).AlignAttribute(32),
|
||||||
Field{"mat3x4<f32>", /* align */ 32, /* size */ 48},
|
Field::Matrix(3, 4, ScalarType::f32).AlignAttribute(32),
|
||||||
Field{"mat4x4<f32>", /* align */ 32, /* size */ 64},
|
Field::Matrix(4, 4, ScalarType::f32).AlignAttribute(32),
|
||||||
|
|
||||||
// Matrix types with custom size
|
// Matrix types with custom size
|
||||||
Field{"mat2x2<f32>", /* align */ 8, /* size */ 16}.PaddedSize(128),
|
Field::Matrix(2, 2, ScalarType::f32).SizeAttribute(128),
|
||||||
Field{"mat3x2<f32>", /* align */ 8, /* size */ 24}.PaddedSize(128),
|
Field::Matrix(3, 2, ScalarType::f32).SizeAttribute(128),
|
||||||
Field{"mat4x2<f32>", /* align */ 8, /* size */ 32}.PaddedSize(128),
|
Field::Matrix(4, 2, ScalarType::f32).SizeAttribute(128),
|
||||||
Field{"mat2x3<f32>", /* align */ 16, /* size */ 32}.PaddedSize(128).Strided<12, 4>(),
|
Field::Matrix(2, 3, ScalarType::f32).SizeAttribute(128),
|
||||||
Field{"mat3x3<f32>", /* align */ 16, /* size */ 48}.PaddedSize(128).Strided<12, 4>(),
|
Field::Matrix(3, 3, ScalarType::f32).SizeAttribute(128),
|
||||||
Field{"mat4x3<f32>", /* align */ 16, /* size */ 64}.PaddedSize(128).Strided<12, 4>(),
|
Field::Matrix(4, 3, ScalarType::f32).SizeAttribute(128),
|
||||||
Field{"mat2x4<f32>", /* align */ 16, /* size */ 32}.PaddedSize(128),
|
Field::Matrix(2, 4, ScalarType::f32).SizeAttribute(128),
|
||||||
Field{"mat3x4<f32>", /* align */ 16, /* size */ 48}.PaddedSize(128),
|
Field::Matrix(3, 4, ScalarType::f32).SizeAttribute(128),
|
||||||
Field{"mat4x4<f32>", /* align */ 16, /* size */ 64}.PaddedSize(128),
|
Field::Matrix(4, 4, ScalarType::f32).SizeAttribute(128),
|
||||||
|
|
||||||
// Array types with no custom alignment or size.
|
// Array types with no custom alignment or size.
|
||||||
// Note: The use of StorageBufferOnly() is due to UBOs requiring 16 byte alignment
|
// 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
|
// of array elements. See https://www.w3.org/TR/WGSL/#storage-class-constraints
|
||||||
Field{"array<u32, 1>", /* align */ 4, /* size */ 4}.StorageBufferOnly(),
|
Field("array<u32, 1>", /* align */ 4, /* size */ 4).StorageBufferOnly(),
|
||||||
Field{"array<u32, 2>", /* align */ 4, /* size */ 8}.StorageBufferOnly(),
|
Field("array<u32, 2>", /* align */ 4, /* size */ 8).StorageBufferOnly(),
|
||||||
Field{"array<u32, 3>", /* align */ 4, /* size */ 12}.StorageBufferOnly(),
|
Field("array<u32, 3>", /* align */ 4, /* size */ 12).StorageBufferOnly(),
|
||||||
Field{"array<u32, 4>", /* align */ 4, /* size */ 16}.StorageBufferOnly(),
|
Field("array<u32, 4>", /* align */ 4, /* size */ 16).StorageBufferOnly(),
|
||||||
Field{"array<vec4<u32>, 1>", /* align */ 16, /* size */ 16},
|
Field("array<vec2<u32>, 1>", /* align */ 8, /* size */ 8).StorageBufferOnly(),
|
||||||
Field{"array<vec4<u32>, 2>", /* align */ 16, /* size */ 32},
|
Field("array<vec2<u32>, 2>", /* align */ 8, /* size */ 16).StorageBufferOnly(),
|
||||||
Field{"array<vec4<u32>, 3>", /* align */ 16, /* size */ 48},
|
Field("array<vec2<u32>, 3>", /* align */ 8, /* size */ 24).StorageBufferOnly(),
|
||||||
Field{"array<vec4<u32>, 4>", /* align */ 16, /* size */ 64},
|
Field("array<vec2<u32>, 4>", /* align */ 8, /* size */ 32).StorageBufferOnly(),
|
||||||
Field{"array<vec3<u32>, 4>", /* align */ 16, /* size */ 64}.Strided<12, 4>(),
|
Field("array<vec3<u32>, 1>", /* align */ 16, /* size */ 16).Strided(12, 4),
|
||||||
|
Field("array<vec3<u32>, 2>", /* align */ 16, /* size */ 32).Strided(12, 4),
|
||||||
|
Field("array<vec3<u32>, 3>", /* align */ 16, /* size */ 48).Strided(12, 4),
|
||||||
|
Field("array<vec3<u32>, 4>", /* align */ 16, /* size */ 64).Strided(12, 4),
|
||||||
|
Field("array<vec4<u32>, 1>", /* align */ 16, /* size */ 16),
|
||||||
|
Field("array<vec4<u32>, 2>", /* align */ 16, /* size */ 32),
|
||||||
|
Field("array<vec4<u32>, 3>", /* align */ 16, /* size */ 48),
|
||||||
|
Field("array<vec4<u32>, 4>", /* align */ 16, /* size */ 64),
|
||||||
|
|
||||||
// Array types with custom alignment
|
// Array types with custom alignment
|
||||||
Field{"array<u32, 1>", /* align */ 32, /* size */ 4}.StorageBufferOnly(),
|
Field("array<u32, 1>", /* align */ 4, /* size */ 4)
|
||||||
Field{"array<u32, 2>", /* align */ 32, /* size */ 8}.StorageBufferOnly(),
|
.AlignAttribute(32)
|
||||||
Field{"array<u32, 3>", /* align */ 32, /* size */ 12}.StorageBufferOnly(),
|
.StorageBufferOnly(),
|
||||||
Field{"array<u32, 4>", /* align */ 32, /* size */ 16}.StorageBufferOnly(),
|
Field("array<u32, 2>", /* align */ 4, /* size */ 8)
|
||||||
Field{"array<vec4<u32>, 1>", /* align */ 32, /* size */ 16},
|
.AlignAttribute(32)
|
||||||
Field{"array<vec4<u32>, 2>", /* align */ 32, /* size */ 32},
|
.StorageBufferOnly(),
|
||||||
Field{"array<vec4<u32>, 3>", /* align */ 32, /* size */ 48},
|
Field("array<u32, 3>", /* align */ 4, /* size */ 12)
|
||||||
Field{"array<vec4<u32>, 4>", /* align */ 32, /* size */ 64},
|
.AlignAttribute(32)
|
||||||
Field{"array<vec3<u32>, 4>", /* align */ 32, /* size */ 64}.Strided<12, 4>(),
|
.StorageBufferOnly(),
|
||||||
|
Field("array<u32, 4>", /* align */ 4, /* size */ 16)
|
||||||
|
.AlignAttribute(32)
|
||||||
|
.StorageBufferOnly(),
|
||||||
|
Field("array<vec2<u32>, 1>", /* align */ 8, /* size */ 8)
|
||||||
|
.AlignAttribute(32)
|
||||||
|
.StorageBufferOnly(),
|
||||||
|
Field("array<vec2<u32>, 2>", /* align */ 8, /* size */ 16)
|
||||||
|
.AlignAttribute(32)
|
||||||
|
.StorageBufferOnly(),
|
||||||
|
Field("array<vec2<u32>, 3>", /* align */ 8, /* size */ 24)
|
||||||
|
.AlignAttribute(32)
|
||||||
|
.StorageBufferOnly(),
|
||||||
|
Field("array<vec2<u32>, 4>", /* align */ 8, /* size */ 32)
|
||||||
|
.AlignAttribute(32)
|
||||||
|
.StorageBufferOnly(),
|
||||||
|
Field("array<vec3<u32>, 1>", /* align */ 16, /* size */ 16)
|
||||||
|
.AlignAttribute(32)
|
||||||
|
.Strided(12, 4),
|
||||||
|
Field("array<vec3<u32>, 2>", /* align */ 16, /* size */ 32)
|
||||||
|
.AlignAttribute(32)
|
||||||
|
.Strided(12, 4),
|
||||||
|
Field("array<vec3<u32>, 3>", /* align */ 16, /* size */ 48)
|
||||||
|
.AlignAttribute(32)
|
||||||
|
.Strided(12, 4),
|
||||||
|
Field("array<vec3<u32>, 4>", /* align */ 16, /* size */ 64)
|
||||||
|
.AlignAttribute(32)
|
||||||
|
.Strided(12, 4),
|
||||||
|
Field("array<vec4<u32>, 1>", /* align */ 16, /* size */ 16).AlignAttribute(32),
|
||||||
|
Field("array<vec4<u32>, 2>", /* align */ 16, /* size */ 32).AlignAttribute(32),
|
||||||
|
Field("array<vec4<u32>, 3>", /* align */ 16, /* size */ 48).AlignAttribute(32),
|
||||||
|
Field("array<vec4<u32>, 4>", /* align */ 16, /* size */ 64).AlignAttribute(32),
|
||||||
|
|
||||||
// Array types with custom size
|
// Array types with custom size
|
||||||
Field{"array<u32, 1>", /* align */ 4, /* size */ 4}.PaddedSize(128).StorageBufferOnly(),
|
Field("array<u32, 1>", /* align */ 4, /* size */ 4)
|
||||||
Field{"array<u32, 2>", /* align */ 4, /* size */ 8}.PaddedSize(128).StorageBufferOnly(),
|
.SizeAttribute(128)
|
||||||
Field{"array<u32, 3>", /* align */ 4, /* size */ 12}
|
|
||||||
.PaddedSize(128)
|
|
||||||
.StorageBufferOnly(),
|
.StorageBufferOnly(),
|
||||||
Field{"array<u32, 4>", /* align */ 4, /* size */ 16}
|
Field("array<u32, 2>", /* align */ 4, /* size */ 8)
|
||||||
.PaddedSize(128)
|
.SizeAttribute(128)
|
||||||
.StorageBufferOnly(),
|
.StorageBufferOnly(),
|
||||||
Field{"array<vec3<u32>, 4>", /* align */ 16, /* size */ 64}
|
Field("array<u32, 3>", /* align */ 4, /* size */ 12)
|
||||||
.PaddedSize(128)
|
.SizeAttribute(128)
|
||||||
.Strided<12, 4>(),
|
.StorageBufferOnly(),
|
||||||
|
Field("array<u32, 4>", /* align */ 4, /* size */ 16)
|
||||||
|
.SizeAttribute(128)
|
||||||
|
.StorageBufferOnly(),
|
||||||
|
Field("array<vec3<u32>, 4>", /* align */ 16, /* size */ 64)
|
||||||
|
.SizeAttribute(128)
|
||||||
|
.Strided(12, 4),
|
||||||
});
|
});
|
||||||
|
|
||||||
std::vector<ComputeLayoutMemoryBufferTestParams> filtered;
|
std::vector<ComputeLayoutMemoryBufferTestParams> filtered;
|
||||||
for (auto param : params) {
|
for (auto param : params) {
|
||||||
if (param.mStorageClass != StorageClass::Storage && param.mField.storage_buffer_only) {
|
if (param.mStorageClass != StorageClass::Storage && param.mField.IsStorageBufferOnly()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
filtered.emplace_back(param);
|
filtered.emplace_back(param);
|
||||||
|
|
Loading…
Reference in New Issue