Validate buffer to buffer copy size to be a multiple of 4 bytes
Metal needs buffer to buffer copy size must be a multiple of 4 bytes.
Adding validation to check this.
BUG=dawn:73
Change-Id: I9a4685d75439502017efa5455f7c2920a77f7a6d
Reviewed-on: https://dawn-review.googlesource.com/c/4900
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/Buffer.cpp b/src/dawn_native/Buffer.cpp
index b70ae11..d619fa4 100644
--- a/src/dawn_native/Buffer.cpp
+++ b/src/dawn_native/Buffer.cpp
@@ -267,6 +267,16 @@
return DAWN_VALIDATION_ERROR("Buffer subdata with too much data");
}
+ // Metal requests buffer to buffer copy size must be a multiple of 4 bytes on macOS
+ if (count % 4 != 0) {
+ return DAWN_VALIDATION_ERROR("Buffer subdata size must be a multiple of 4 bytes");
+ }
+
+ // Metal requests offset of buffer to buffer copy must be a multiple of 4 bytes on macOS
+ if (start % 4 != 0) {
+ return DAWN_VALIDATION_ERROR("Start position must be a multiple of 4 bytes");
+ }
+
// Note that no overflow can happen because we already checked for GetSize() >= count
if (start > GetSize() - count) {
return DAWN_VALIDATION_ERROR("Buffer subdata out of range");
diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp
index 2498820..0b85bfe 100644
--- a/src/dawn_native/CommandEncoder.cpp
+++ b/src/dawn_native/CommandEncoder.cpp
@@ -76,6 +76,23 @@
return {};
}
+ MaybeError ValidateB2BCopySizeAlignment(uint32_t dataSize,
+ uint32_t srcOffset,
+ uint32_t dstOffset) {
+ // Copy size must be a multiple of 4 bytes on macOS.
+ if (dataSize % 4 != 0) {
+ return DAWN_VALIDATION_ERROR("Copy size must be a multiple of 4 bytes");
+ }
+
+ // SourceOffset and destinationOffset must be multiples of 4 bytes on macOS.
+ if (srcOffset % 4 != 0 || dstOffset % 4 != 0) {
+ return DAWN_VALIDATION_ERROR(
+ "Source offset and destination offset must be multiples of 4 bytes");
+ }
+
+ return {};
+ }
+
MaybeError ValidateTexelBufferOffset(TextureBase* texture, const BufferCopy& bufferCopy) {
uint32_t texelSize =
static_cast<uint32_t>(TextureFormatPixelSize(texture->GetFormat()));
@@ -575,6 +592,8 @@
DAWN_TRY(GetDevice()->ValidateObject(copy->destination.buffer.Get()));
DAWN_TRY(ValidateCopySizeFitsInBuffer(copy->source, copy->size));
DAWN_TRY(ValidateCopySizeFitsInBuffer(copy->destination, copy->size));
+ DAWN_TRY(ValidateB2BCopySizeAlignment(copy->size, copy->source.offset,
+ copy->destination.offset));
DAWN_TRY(ValidateCanUseAs(copy->source.buffer.Get(),
dawn::BufferUsageBit::TransferSrc));
diff --git a/src/tests/DawnTest.h b/src/tests/DawnTest.h
index 72093fd..d2daa4b 100644
--- a/src/tests/DawnTest.h
+++ b/src/tests/DawnTest.h
@@ -31,10 +31,6 @@
AddBufferExpectation(__FILE__, __LINE__, buffer, offset, sizeof(uint32_t) * count, \
new detail::ExpectEq<uint32_t>(expected, count))
-#define EXPECT_BUFFER_U8_EQ(expected, buffer, offset) \
- AddBufferExpectation(__FILE__, __LINE__, buffer, offset, sizeof(uint8_t), \
- new detail::ExpectEq<uint8_t>(expected))
-
// Test a pixel of the mip level 0 of a 2D texture.
#define EXPECT_PIXEL_RGBA8_EQ(expected, texture, x, y) \
AddTextureExpectation(__FILE__, __LINE__, texture, x, y, 1, 1, 0, 0, sizeof(RGBA8), \
diff --git a/src/tests/end2end/BasicTests.cpp b/src/tests/end2end/BasicTests.cpp
index 83d0967..0400e06 100644
--- a/src/tests/end2end/BasicTests.cpp
+++ b/src/tests/end2end/BasicTests.cpp
@@ -27,10 +27,10 @@
descriptor.usage = dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst;
dawn::Buffer buffer = device.CreateBuffer(&descriptor);
- uint8_t value = 187;
- buffer.SetSubData(0, sizeof(value), &value);
+ uint32_t value = 0x01020304;
+ buffer.SetSubData(0, sizeof(value), reinterpret_cast<uint8_t*>(&value));
- EXPECT_BUFFER_U8_EQ(value, buffer, 0);
+ EXPECT_BUFFER_U32_EQ(value, buffer, 0);
}
DAWN_INSTANTIATE_TEST(BasicTests, D3D12Backend, MetalBackend, OpenGLBackend, VulkanBackend);
diff --git a/src/tests/end2end/BufferTests.cpp b/src/tests/end2end/BufferTests.cpp
index 2a1ab24..19e70db 100644
--- a/src/tests/end2end/BufferTests.cpp
+++ b/src/tests/end2end/BufferTests.cpp
@@ -47,15 +47,15 @@
// Test that the simplest map read works.
TEST_P(BufferMapReadTests, SmallReadAtZero) {
dawn::BufferDescriptor descriptor;
- descriptor.size = 1;
+ descriptor.size = 4;
descriptor.usage = dawn::BufferUsageBit::MapRead | dawn::BufferUsageBit::TransferDst;
dawn::Buffer buffer = device.CreateBuffer(&descriptor);
- uint8_t myData = 187;
- buffer.SetSubData(0, sizeof(myData), &myData);
+ uint32_t myData = 0x01020304;
+ buffer.SetSubData(0, sizeof(myData), reinterpret_cast<uint8_t*>(&myData));
const void* mappedData = MapReadAsyncAndWait(buffer);
- ASSERT_EQ(myData, *reinterpret_cast<const uint8_t*>(mappedData));
+ ASSERT_EQ(myData, *reinterpret_cast<const uint32_t*>(mappedData));
buffer.Unmap();
}
@@ -151,17 +151,17 @@
class BufferSetSubDataTests : public DawnTest {
};
-// Test the simplest set sub data: setting one u8 at offset 0.
+// Test the simplest set sub data: setting one u32 at offset 0.
TEST_P(BufferSetSubDataTests, SmallDataAtZero) {
dawn::BufferDescriptor descriptor;
- descriptor.size = 1;
+ descriptor.size = 4;
descriptor.usage = dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst;
dawn::Buffer buffer = device.CreateBuffer(&descriptor);
- uint8_t value = 171;
- buffer.SetSubData(0, sizeof(value), &value);
+ uint32_t value = 0x01020304;
+ buffer.SetSubData(0, sizeof(value), reinterpret_cast<uint8_t*>(&value));
- EXPECT_BUFFER_U8_EQ(value, buffer, 0);
+ EXPECT_BUFFER_U32_EQ(value, buffer, 0);
}
// Test that SetSubData offset works.
@@ -172,10 +172,10 @@
dawn::Buffer buffer = device.CreateBuffer(&descriptor);
constexpr uint32_t kOffset = 2000;
- uint8_t value = 231;
- buffer.SetSubData(kOffset, sizeof(value), &value);
+ uint32_t value = 0x01020304;
+ buffer.SetSubData(kOffset, sizeof(value), reinterpret_cast<uint8_t*>(&value));
- EXPECT_BUFFER_U8_EQ(value, buffer, kOffset);
+ EXPECT_BUFFER_U32_EQ(value, buffer, kOffset);
}
// Stress test for many calls to SetSubData
diff --git a/src/tests/end2end/IndexFormatTests.cpp b/src/tests/end2end/IndexFormatTests.cpp
index 1e4e441..2868cba 100644
--- a/src/tests/end2end/IndexFormatTests.cpp
+++ b/src/tests/end2end/IndexFormatTests.cpp
@@ -197,6 +197,8 @@
});
dawn::Buffer indexBuffer = utils::CreateBufferFromData<uint16_t>(device, dawn::BufferUsageBit::Index, {
0, 1, 2, 0xFFFFu, 3, 4, 2,
+ // This value is for padding.
+ 0xFFFFu,
});
uint32_t zeroOffset = 0;
diff --git a/src/tests/unittests/validation/BufferValidationTests.cpp b/src/tests/unittests/validation/BufferValidationTests.cpp
index 2b05026..e7900a3 100644
--- a/src/tests/unittests/validation/BufferValidationTests.cpp
+++ b/src/tests/unittests/validation/BufferValidationTests.cpp
@@ -433,10 +433,10 @@
// Test the success case for Buffer::SetSubData
TEST_F(BufferValidationTest, SetSubDataSuccess) {
- dawn::Buffer buf = CreateSetSubDataBuffer(1);
+ dawn::Buffer buf = CreateSetSubDataBuffer(4);
- uint8_t foo = 0;
- buf.SetSubData(0, sizeof(foo), &foo);
+ uint32_t foo = 0x01020304;
+ buf.SetSubData(0, sizeof(foo), reinterpret_cast<uint8_t*>(&foo));
}
// Test error case for SetSubData out of bounds
@@ -472,6 +472,31 @@
ASSERT_DEVICE_ERROR(buf.SetSubData(0, sizeof(foo), &foo));
}
+// Test SetSubData with unaligned size
+TEST_F(BufferValidationTest, SetSubDataWithUnalignedSize) {
+ dawn::BufferDescriptor descriptor;
+ descriptor.size = 4;
+ descriptor.usage = dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst;
+
+ dawn::Buffer buf = device.CreateBuffer(&descriptor);
+
+ uint8_t value = 123;
+ ASSERT_DEVICE_ERROR(buf.SetSubData(0, sizeof(value), &value));
+}
+
+// Test SetSubData with unaligned offset
+TEST_F(BufferValidationTest, SetSubDataWithUnalignedOffset) {
+ dawn::BufferDescriptor descriptor;
+ descriptor.size = 4000;
+ descriptor.usage = dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst;
+
+ dawn::Buffer buf = device.CreateBuffer(&descriptor);
+
+ uint32_t kOffset = 2999;
+ uint32_t value = 0x01020304;
+ ASSERT_DEVICE_ERROR(buf.SetSubData(kOffset, sizeof(value), reinterpret_cast<uint8_t*>(&value)));
+}
+
// Test that it is valid to destroy an unmapped buffer
TEST_F(BufferValidationTest, DestroyUnmappedBuffer) {
{
diff --git a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
index a9f0a6d..d0696ac 100644
--- a/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
+++ b/src/tests/unittests/validation/CopyCommandsValidationTests.cpp
@@ -170,6 +170,36 @@
}
}
+// Test B2B copies with unaligned data size
+TEST_F(CopyCommandTest_B2B, UnalignedSize) {
+ dawn::Buffer source = CreateBuffer(16, dawn::BufferUsageBit::TransferSrc);
+ dawn::Buffer destination = CreateBuffer(16, dawn::BufferUsageBit::TransferDst);
+
+ dawn::CommandEncoder encoder = device.CreateCommandEncoder();
+ encoder.CopyBufferToBuffer(source, 8, destination, 0, sizeof(uint8_t));
+ ASSERT_DEVICE_ERROR(encoder.Finish());
+}
+
+// Test B2B copies with unaligned offset
+TEST_F(CopyCommandTest_B2B, UnalignedOffset) {
+ dawn::Buffer source = CreateBuffer(16, dawn::BufferUsageBit::TransferSrc);
+ dawn::Buffer destination = CreateBuffer(16, dawn::BufferUsageBit::TransferDst);
+
+ // Unaligned source offset
+ {
+ dawn::CommandEncoder encoder = device.CreateCommandEncoder();
+ encoder.CopyBufferToBuffer(source, 9, destination, 0, 4);
+ ASSERT_DEVICE_ERROR(encoder.Finish());
+ }
+
+ // Unaligned destination offset
+ {
+ dawn::CommandEncoder encoder = device.CreateCommandEncoder();
+ encoder.CopyBufferToBuffer(source, 8, destination, 1, 4);
+ ASSERT_DEVICE_ERROR(encoder.Finish());
+ }
+}
+
class CopyCommandTest_B2T : public CopyCommandTest {
};