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>
This commit is contained in:
Yan, Shaobo 2019-02-27 02:46:27 +00:00 committed by Shaobo Yan
parent d56f8d2e05
commit 738567f148
8 changed files with 104 additions and 22 deletions

View File

@ -267,6 +267,16 @@ namespace dawn_native {
return DAWN_VALIDATION_ERROR("Buffer subdata with too much data"); 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 // Note that no overflow can happen because we already checked for GetSize() >= count
if (start > GetSize() - count) { if (start > GetSize() - count) {
return DAWN_VALIDATION_ERROR("Buffer subdata out of range"); return DAWN_VALIDATION_ERROR("Buffer subdata out of range");

View File

@ -76,6 +76,23 @@ namespace dawn_native {
return {}; 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) { MaybeError ValidateTexelBufferOffset(TextureBase* texture, const BufferCopy& bufferCopy) {
uint32_t texelSize = uint32_t texelSize =
static_cast<uint32_t>(TextureFormatPixelSize(texture->GetFormat())); static_cast<uint32_t>(TextureFormatPixelSize(texture->GetFormat()));
@ -575,6 +592,8 @@ namespace dawn_native {
DAWN_TRY(GetDevice()->ValidateObject(copy->destination.buffer.Get())); DAWN_TRY(GetDevice()->ValidateObject(copy->destination.buffer.Get()));
DAWN_TRY(ValidateCopySizeFitsInBuffer(copy->source, copy->size)); DAWN_TRY(ValidateCopySizeFitsInBuffer(copy->source, copy->size));
DAWN_TRY(ValidateCopySizeFitsInBuffer(copy->destination, 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_TRY(ValidateCanUseAs(copy->source.buffer.Get(),
dawn::BufferUsageBit::TransferSrc)); dawn::BufferUsageBit::TransferSrc));

View File

@ -31,10 +31,6 @@
AddBufferExpectation(__FILE__, __LINE__, buffer, offset, sizeof(uint32_t) * count, \ AddBufferExpectation(__FILE__, __LINE__, buffer, offset, sizeof(uint32_t) * count, \
new detail::ExpectEq<uint32_t>(expected, 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. // Test a pixel of the mip level 0 of a 2D texture.
#define EXPECT_PIXEL_RGBA8_EQ(expected, texture, x, y) \ #define EXPECT_PIXEL_RGBA8_EQ(expected, texture, x, y) \
AddTextureExpectation(__FILE__, __LINE__, texture, x, y, 1, 1, 0, 0, sizeof(RGBA8), \ AddTextureExpectation(__FILE__, __LINE__, texture, x, y, 1, 1, 0, 0, sizeof(RGBA8), \

View File

@ -27,10 +27,10 @@ TEST_P(BasicTests, BufferSetSubData) {
descriptor.usage = dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst; descriptor.usage = dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst;
dawn::Buffer buffer = device.CreateBuffer(&descriptor); dawn::Buffer buffer = device.CreateBuffer(&descriptor);
uint8_t value = 187; uint32_t value = 0x01020304;
buffer.SetSubData(0, sizeof(value), &value); 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); DAWN_INSTANTIATE_TEST(BasicTests, D3D12Backend, MetalBackend, OpenGLBackend, VulkanBackend);

View File

@ -47,15 +47,15 @@ class BufferMapReadTests : public DawnTest {
// Test that the simplest map read works. // Test that the simplest map read works.
TEST_P(BufferMapReadTests, SmallReadAtZero) { TEST_P(BufferMapReadTests, SmallReadAtZero) {
dawn::BufferDescriptor descriptor; dawn::BufferDescriptor descriptor;
descriptor.size = 1; descriptor.size = 4;
descriptor.usage = dawn::BufferUsageBit::MapRead | dawn::BufferUsageBit::TransferDst; descriptor.usage = dawn::BufferUsageBit::MapRead | dawn::BufferUsageBit::TransferDst;
dawn::Buffer buffer = device.CreateBuffer(&descriptor); dawn::Buffer buffer = device.CreateBuffer(&descriptor);
uint8_t myData = 187; uint32_t myData = 0x01020304;
buffer.SetSubData(0, sizeof(myData), &myData); buffer.SetSubData(0, sizeof(myData), reinterpret_cast<uint8_t*>(&myData));
const void* mappedData = MapReadAsyncAndWait(buffer); 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(); buffer.Unmap();
} }
@ -151,17 +151,17 @@ DAWN_INSTANTIATE_TEST(BufferMapWriteTests, D3D12Backend, MetalBackend, OpenGLBac
class BufferSetSubDataTests : public DawnTest { 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) { TEST_P(BufferSetSubDataTests, SmallDataAtZero) {
dawn::BufferDescriptor descriptor; dawn::BufferDescriptor descriptor;
descriptor.size = 1; descriptor.size = 4;
descriptor.usage = dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst; descriptor.usage = dawn::BufferUsageBit::TransferSrc | dawn::BufferUsageBit::TransferDst;
dawn::Buffer buffer = device.CreateBuffer(&descriptor); dawn::Buffer buffer = device.CreateBuffer(&descriptor);
uint8_t value = 171; uint32_t value = 0x01020304;
buffer.SetSubData(0, sizeof(value), &value); 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. // Test that SetSubData offset works.
@ -172,10 +172,10 @@ TEST_P(BufferSetSubDataTests, SmallDataAtOffset) {
dawn::Buffer buffer = device.CreateBuffer(&descriptor); dawn::Buffer buffer = device.CreateBuffer(&descriptor);
constexpr uint32_t kOffset = 2000; constexpr uint32_t kOffset = 2000;
uint8_t value = 231; uint32_t value = 0x01020304;
buffer.SetSubData(kOffset, sizeof(value), &value); 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 // Stress test for many calls to SetSubData

View File

@ -197,6 +197,8 @@ TEST_P(IndexFormatTest, Uint16PrimitiveRestart) {
}); });
dawn::Buffer indexBuffer = utils::CreateBufferFromData<uint16_t>(device, dawn::BufferUsageBit::Index, { dawn::Buffer indexBuffer = utils::CreateBufferFromData<uint16_t>(device, dawn::BufferUsageBit::Index, {
0, 1, 2, 0xFFFFu, 3, 4, 2, 0, 1, 2, 0xFFFFu, 3, 4, 2,
// This value is for padding.
0xFFFFu,
}); });
uint32_t zeroOffset = 0; uint32_t zeroOffset = 0;

View File

@ -433,10 +433,10 @@ TEST_F(BufferValidationTest, DestroyInsideMapWriteCallback) {
// Test the success case for Buffer::SetSubData // Test the success case for Buffer::SetSubData
TEST_F(BufferValidationTest, SetSubDataSuccess) { TEST_F(BufferValidationTest, SetSubDataSuccess) {
dawn::Buffer buf = CreateSetSubDataBuffer(1); dawn::Buffer buf = CreateSetSubDataBuffer(4);
uint8_t foo = 0; uint32_t foo = 0x01020304;
buf.SetSubData(0, sizeof(foo), &foo); buf.SetSubData(0, sizeof(foo), reinterpret_cast<uint8_t*>(&foo));
} }
// Test error case for SetSubData out of bounds // Test error case for SetSubData out of bounds
@ -472,6 +472,31 @@ TEST_F(BufferValidationTest, SetSubDataWrongUsage) {
ASSERT_DEVICE_ERROR(buf.SetSubData(0, sizeof(foo), &foo)); 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 that it is valid to destroy an unmapped buffer
TEST_F(BufferValidationTest, DestroyUnmappedBuffer) { TEST_F(BufferValidationTest, DestroyUnmappedBuffer) {
{ {

View File

@ -170,6 +170,36 @@ TEST_F(CopyCommandTest_B2B, BadUsage) {
} }
} }
// 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 { class CopyCommandTest_B2T : public CopyCommandTest {
}; };