blob: cce160728b3737e33ccc72758cfeac136c02855f [file] [log] [blame]
// Copyright 2017 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 <array>
#include <cstring>
#include <limits>
#include <vector>
#include "dawn/tests/DawnTest.h"
namespace dawn {
namespace {
class BufferMappingTests : public DawnTest {
protected:
void MapAsyncAndWait(const wgpu::Buffer& buffer,
wgpu::MapMode mode,
size_t offset,
size_t size) {
bool done = false;
buffer.MapAsync(
mode, offset, size,
[](WGPUBufferMapAsyncStatus status, void* userdata) {
ASSERT_EQ(WGPUBufferMapAsyncStatus_Success, status);
*static_cast<bool*>(userdata) = true;
},
&done);
while (!done) {
WaitABit();
}
}
wgpu::Buffer CreateMapReadBuffer(uint64_t size) {
wgpu::BufferDescriptor descriptor;
descriptor.size = size;
descriptor.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
return device.CreateBuffer(&descriptor);
}
wgpu::Buffer CreateMapWriteBuffer(uint64_t size) {
wgpu::BufferDescriptor descriptor;
descriptor.size = size;
descriptor.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc;
return device.CreateBuffer(&descriptor);
}
};
void CheckMapping(const void* actual, const void* expected, size_t size) {
EXPECT_NE(actual, nullptr);
if (actual != nullptr) {
EXPECT_EQ(0, memcmp(actual, expected, size));
}
}
// Test that the simplest map read works
TEST_P(BufferMappingTests, MapRead_Basic) {
wgpu::Buffer buffer = CreateMapReadBuffer(4);
uint32_t myData = 0x01020304;
constexpr size_t kSize = sizeof(myData);
queue.WriteBuffer(buffer, 0, &myData, kSize);
MapAsyncAndWait(buffer, wgpu::MapMode::Read, 0, 4);
CheckMapping(buffer.GetConstMappedRange(), &myData, kSize);
CheckMapping(buffer.GetConstMappedRange(0, kSize), &myData, kSize);
buffer.Unmap();
}
// Test map-reading a zero-sized buffer.
TEST_P(BufferMappingTests, MapRead_ZeroSized) {
wgpu::Buffer buffer = CreateMapReadBuffer(0);
MapAsyncAndWait(buffer, wgpu::MapMode::Read, 0, wgpu::kWholeMapSize);
ASSERT_NE(buffer.GetConstMappedRange(), nullptr);
buffer.Unmap();
}
// Test map-reading with a non-zero offset
TEST_P(BufferMappingTests, MapRead_NonZeroOffset) {
wgpu::Buffer buffer = CreateMapReadBuffer(12);
uint32_t myData[3] = {0x01020304, 0x05060708, 0x090A0B0C};
queue.WriteBuffer(buffer, 0, &myData, sizeof(myData));
MapAsyncAndWait(buffer, wgpu::MapMode::Read, 8, 4);
ASSERT_EQ(myData[2], *static_cast<const uint32_t*>(buffer.GetConstMappedRange(8)));
buffer.Unmap();
}
// Map read and unmap twice. Test that both of these two iterations work.
TEST_P(BufferMappingTests, MapRead_Twice) {
wgpu::Buffer buffer = CreateMapReadBuffer(4);
uint32_t myData = 0x01020304;
queue.WriteBuffer(buffer, 0, &myData, sizeof(myData));
MapAsyncAndWait(buffer, wgpu::MapMode::Read, 0, 4);
ASSERT_EQ(myData, *static_cast<const uint32_t*>(buffer.GetConstMappedRange()));
buffer.Unmap();
myData = 0x05060708;
queue.WriteBuffer(buffer, 0, &myData, sizeof(myData));
MapAsyncAndWait(buffer, wgpu::MapMode::Read, 0, 4);
ASSERT_EQ(myData, *static_cast<const uint32_t*>(buffer.GetConstMappedRange()));
buffer.Unmap();
}
// Map read and test multiple get mapped range data
TEST_P(BufferMappingTests, MapRead_MultipleMappedRange) {
wgpu::Buffer buffer = CreateMapReadBuffer(12);
uint32_t myData[] = {0x00010203, 0x04050607, 0x08090a0b};
queue.WriteBuffer(buffer, 0, &myData, 12);
MapAsyncAndWait(buffer, wgpu::MapMode::Read, 0, 12);
ASSERT_EQ(myData[0], *static_cast<const uint32_t*>(buffer.GetConstMappedRange(0)));
ASSERT_EQ(myData[1], *(static_cast<const uint32_t*>(buffer.GetConstMappedRange(0)) + 1));
ASSERT_EQ(myData[2], *(static_cast<const uint32_t*>(buffer.GetConstMappedRange(0)) + 2));
ASSERT_EQ(myData[2], *static_cast<const uint32_t*>(buffer.GetConstMappedRange(8)));
buffer.Unmap();
}
// Test map-reading a large buffer.
TEST_P(BufferMappingTests, MapRead_Large) {
constexpr uint32_t kDataSize = 1000 * 1000;
constexpr size_t kByteSize = kDataSize * sizeof(uint32_t);
wgpu::Buffer buffer = CreateMapReadBuffer(kByteSize);
std::vector<uint32_t> myData;
for (uint32_t i = 0; i < kDataSize; ++i) {
myData.push_back(i);
}
queue.WriteBuffer(buffer, 0, myData.data(), kByteSize);
MapAsyncAndWait(buffer, wgpu::MapMode::Read, 0, kByteSize);
EXPECT_EQ(nullptr, buffer.GetConstMappedRange(0, kByteSize + 4));
EXPECT_EQ(0, memcmp(buffer.GetConstMappedRange(), myData.data(), kByteSize));
EXPECT_EQ(0, memcmp(buffer.GetConstMappedRange(8), myData.data() + 2, kByteSize - 8));
EXPECT_EQ(
0, memcmp(buffer.GetConstMappedRange(8, kByteSize - 8), myData.data() + 2, kByteSize - 8));
buffer.Unmap();
MapAsyncAndWait(buffer, wgpu::MapMode::Read, 16, kByteSize - 16);
// Size is too big.
EXPECT_EQ(nullptr, buffer.GetConstMappedRange(16, kByteSize - 12));
// Offset defaults to 0 which is less than 16
EXPECT_EQ(nullptr, buffer.GetConstMappedRange());
// Offset less than 8 is less than 16
EXPECT_EQ(nullptr, buffer.GetConstMappedRange(8));
// Test a couple values.
EXPECT_EQ(0, memcmp(buffer.GetConstMappedRange(16), myData.data() + 4, kByteSize - 16));
EXPECT_EQ(0, memcmp(buffer.GetConstMappedRange(24), myData.data() + 6, kByteSize - 24));
buffer.Unmap();
}
// Test that GetConstMappedRange works inside map-read callback
TEST_P(BufferMappingTests, MapRead_InCallback) {
constexpr size_t kBufferSize = 12;
wgpu::Buffer buffer = CreateMapReadBuffer(kBufferSize);
uint32_t myData[3] = {0x01020304, 0x05060708, 0x090A0B0C};
static constexpr size_t kSize = sizeof(myData);
queue.WriteBuffer(buffer, 0, &myData, kSize);
struct UserData {
bool done;
wgpu::Buffer buffer;
void* expected;
};
UserData user{false, buffer, &myData};
buffer.MapAsync(
wgpu::MapMode::Read, 0, kBufferSize,
[](WGPUBufferMapAsyncStatus status, void* userdata) {
UserData* user = static_cast<UserData*>(userdata);
EXPECT_EQ(WGPUBufferMapAsyncStatus_Success, status);
if (status == WGPUBufferMapAsyncStatus_Success) {
CheckMapping(user->buffer.GetConstMappedRange(), user->expected, kSize);
CheckMapping(user->buffer.GetConstMappedRange(0, kSize), user->expected, kSize);
CheckMapping(user->buffer.GetConstMappedRange(8, 4),
static_cast<const uint32_t*>(user->expected) + 2, sizeof(uint32_t));
user->buffer.Unmap();
}
user->done = true;
},
&user);
while (!user.done) {
WaitABit();
}
}
// Test that the simplest map write works.
TEST_P(BufferMappingTests, MapWrite_Basic) {
wgpu::Buffer buffer = CreateMapWriteBuffer(4);
uint32_t myData = 2934875;
MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, 4);
ASSERT_NE(nullptr, buffer.GetMappedRange());
ASSERT_NE(nullptr, buffer.GetConstMappedRange());
memcpy(buffer.GetMappedRange(), &myData, sizeof(myData));
buffer.Unmap();
EXPECT_BUFFER_U32_EQ(myData, buffer, 0);
}
// Test that the simplest map write works with a range.
TEST_P(BufferMappingTests, MapWrite_BasicRange) {
wgpu::Buffer buffer = CreateMapWriteBuffer(4);
uint32_t myData = 2934875;
MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, 4);
ASSERT_NE(nullptr, buffer.GetMappedRange(0, 4));
ASSERT_NE(nullptr, buffer.GetConstMappedRange(0, 4));
memcpy(buffer.GetMappedRange(), &myData, sizeof(myData));
buffer.Unmap();
EXPECT_BUFFER_U32_EQ(myData, buffer, 0);
}
// Test map-writing a zero-sized buffer.
TEST_P(BufferMappingTests, MapWrite_ZeroSized) {
wgpu::Buffer buffer = CreateMapWriteBuffer(0);
MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, wgpu::kWholeMapSize);
ASSERT_NE(buffer.GetConstMappedRange(), nullptr);
ASSERT_NE(buffer.GetMappedRange(), nullptr);
buffer.Unmap();
}
// Test map-writing with a non-zero offset.
TEST_P(BufferMappingTests, MapWrite_NonZeroOffset) {
wgpu::Buffer buffer = CreateMapWriteBuffer(12);
uint32_t myData = 2934875;
MapAsyncAndWait(buffer, wgpu::MapMode::Write, 8, 4);
memcpy(buffer.GetMappedRange(8), &myData, sizeof(myData));
buffer.Unmap();
EXPECT_BUFFER_U32_EQ(myData, buffer, 8);
}
// Map, write and unmap twice. Test that both of these two iterations work.
TEST_P(BufferMappingTests, MapWrite_Twice) {
wgpu::Buffer buffer = CreateMapWriteBuffer(4);
uint32_t myData = 2934875;
MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, 4);
memcpy(buffer.GetMappedRange(), &myData, sizeof(myData));
buffer.Unmap();
EXPECT_BUFFER_U32_EQ(myData, buffer, 0);
myData = 9999999;
MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, 4);
memcpy(buffer.GetMappedRange(), &myData, sizeof(myData));
buffer.Unmap();
EXPECT_BUFFER_U32_EQ(myData, buffer, 0);
}
// Map write and unmap twice with different ranges and make sure the first write is preserved
TEST_P(BufferMappingTests, MapWrite_TwicePreserve) {
wgpu::Buffer buffer = CreateMapWriteBuffer(12);
uint32_t data1 = 0x08090a0b;
size_t offset1 = 8;
MapAsyncAndWait(buffer, wgpu::MapMode::Write, offset1, wgpu::kWholeMapSize);
memcpy(buffer.GetMappedRange(offset1), &data1, sizeof(data1));
buffer.Unmap();
uint32_t data2 = 0x00010203;
size_t offset2 = 0;
MapAsyncAndWait(buffer, wgpu::MapMode::Write, offset2, wgpu::kWholeMapSize);
memcpy(buffer.GetMappedRange(offset2), &data2, sizeof(data2));
buffer.Unmap();
EXPECT_BUFFER_U32_EQ(data1, buffer, offset1);
EXPECT_BUFFER_U32_EQ(data2, buffer, offset2);
}
// Map write and unmap twice with overlapping ranges and make sure data is updated correctly
TEST_P(BufferMappingTests, MapWrite_TwiceRangeOverlap) {
wgpu::Buffer buffer = CreateMapWriteBuffer(16);
uint32_t data1[] = {0x01234567, 0x89abcdef};
size_t offset1 = 8;
MapAsyncAndWait(buffer, wgpu::MapMode::Write, offset1, 8);
memcpy(buffer.GetMappedRange(offset1, 8), data1, 8);
buffer.Unmap();
EXPECT_BUFFER_U32_EQ(0x00000000, buffer, 0);
EXPECT_BUFFER_U32_EQ(0x00000000, buffer, 4);
EXPECT_BUFFER_U32_EQ(0x01234567, buffer, 8);
EXPECT_BUFFER_U32_EQ(0x89abcdef, buffer, 12);
uint32_t data2[] = {0x01234567, 0x89abcdef, 0x55555555};
size_t offset2 = 0;
MapAsyncAndWait(buffer, wgpu::MapMode::Write, offset2, 12);
memcpy(buffer.GetMappedRange(offset2, 12), data2, 12);
buffer.Unmap();
EXPECT_BUFFER_U32_EQ(0x01234567, buffer, 0);
EXPECT_BUFFER_U32_EQ(0x89abcdef, buffer, 4);
EXPECT_BUFFER_U32_EQ(0x55555555, buffer, 8);
EXPECT_BUFFER_U32_EQ(0x89abcdef, buffer, 12);
}
// Map write and test multiple mapped range data get updated correctly
TEST_P(BufferMappingTests, MapWrite_MultipleMappedRange) {
wgpu::Buffer buffer = CreateMapWriteBuffer(12);
uint32_t data1 = 0x08090a0b;
size_t offset1 = 8;
uint32_t data2 = 0x00010203;
size_t offset2 = 0;
MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, 12);
memcpy(buffer.GetMappedRange(offset1), &data1, sizeof(data1));
memcpy(buffer.GetMappedRange(offset2), &data2, sizeof(data2));
buffer.Unmap();
EXPECT_BUFFER_U32_EQ(data1, buffer, offset1);
EXPECT_BUFFER_U32_EQ(data2, buffer, offset2);
}
// Test mapping a large buffer.
TEST_P(BufferMappingTests, MapWrite_Large) {
constexpr uint32_t kDataSize = 1000 * 1000;
constexpr size_t kByteSize = kDataSize * sizeof(uint32_t);
wgpu::Buffer buffer = CreateMapWriteBuffer(kDataSize * sizeof(uint32_t));
std::vector<uint32_t> myData;
for (uint32_t i = 0; i < kDataSize; ++i) {
myData.push_back(i);
}
MapAsyncAndWait(buffer, wgpu::MapMode::Write, 16, kByteSize - 20);
EXPECT_EQ(nullptr, buffer.GetMappedRange());
EXPECT_EQ(nullptr, buffer.GetMappedRange(0));
EXPECT_EQ(nullptr, buffer.GetMappedRange(8));
EXPECT_EQ(nullptr, buffer.GetMappedRange(16, kByteSize - 8));
memcpy(buffer.GetMappedRange(16, kByteSize - 20), myData.data(), kByteSize - 20);
buffer.Unmap();
EXPECT_BUFFER_U32_RANGE_EQ(myData.data(), buffer, 16, kDataSize - 5);
}
// Stress test mapping many buffers.
TEST_P(BufferMappingTests, MapWrite_ManySimultaneous) {
constexpr uint32_t kDataSize = 1000;
std::vector<uint32_t> myData;
for (uint32_t i = 0; i < kDataSize; ++i) {
myData.push_back(i);
}
constexpr uint32_t kBuffers = 100;
std::array<wgpu::Buffer, kBuffers> buffers;
uint32_t mapCompletedCount = 0;
// Create buffers and request mapping them.
wgpu::BufferDescriptor descriptor;
descriptor.size = static_cast<uint32_t>(kDataSize * sizeof(uint32_t));
descriptor.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc;
for (uint32_t i = 0; i < kBuffers; ++i) {
buffers[i] = device.CreateBuffer(&descriptor);
buffers[i].MapAsync(
wgpu::MapMode::Write, 0, descriptor.size,
[](WGPUBufferMapAsyncStatus status, void* userdata) {
ASSERT_EQ(WGPUBufferMapAsyncStatus_Success, status);
(*static_cast<uint32_t*>(userdata))++;
},
&mapCompletedCount);
}
// Wait for all mappings to complete
while (mapCompletedCount != kBuffers) {
WaitABit();
}
// All buffers are mapped, write into them and unmap them all.
for (uint32_t i = 0; i < kBuffers; ++i) {
memcpy(buffers[i].GetMappedRange(0, descriptor.size), myData.data(), descriptor.size);
buffers[i].Unmap();
}
// Check the content of the buffers.
for (uint32_t i = 0; i < kBuffers; ++i) {
EXPECT_BUFFER_U32_RANGE_EQ(myData.data(), buffers[i], 0, kDataSize);
}
}
// Test that the map offset isn't updated when the call is an error.
TEST_P(BufferMappingTests, OffsetNotUpdatedOnError) {
uint32_t data[3] = {0xCA7, 0xB0A7, 0xBA7};
wgpu::Buffer buffer = CreateMapReadBuffer(sizeof(data));
queue.WriteBuffer(buffer, 0, data, sizeof(data));
// Map the buffer but do not wait on the result yet.
bool done1 = false;
bool done2 = false;
buffer.MapAsync(
wgpu::MapMode::Read, 8, 4,
[](WGPUBufferMapAsyncStatus status, void* userdata) {
ASSERT_EQ(WGPUBufferMapAsyncStatus_Success, status);
*static_cast<bool*>(userdata) = true;
},
&done1);
// Call MapAsync another time, the callback will be rejected with error status
// (but doesn't produce a validation error) and mMapOffset is not updated
// because the buffer is already being mapped and it doesn't allow multiple
// MapAsync requests.
buffer.MapAsync(
wgpu::MapMode::Read, 0, 4,
[](WGPUBufferMapAsyncStatus status, void* userdata) {
ASSERT_EQ(WGPUBufferMapAsyncStatus_MappingAlreadyPending, status);
*static_cast<bool*>(userdata) = true;
},
&done2);
while (!done1 || !done2) {
WaitABit();
}
// mMapOffset has not been updated so it should still be 4, which is data[1]
ASSERT_EQ(0, memcmp(buffer.GetConstMappedRange(8), &data[2], sizeof(uint32_t)));
}
// Test that Get(Const)MappedRange work inside map-write callback.
TEST_P(BufferMappingTests, MapWrite_InCallbackDefault) {
wgpu::Buffer buffer = CreateMapWriteBuffer(4);
static constexpr uint32_t myData = 2934875;
static constexpr size_t kSize = sizeof(myData);
struct UserData {
bool done;
wgpu::Buffer buffer;
};
UserData user{false, buffer};
buffer.MapAsync(
wgpu::MapMode::Write, 0, kSize,
[](WGPUBufferMapAsyncStatus status, void* userdata) {
UserData* user = static_cast<UserData*>(userdata);
EXPECT_EQ(WGPUBufferMapAsyncStatus_Success, status);
if (status == WGPUBufferMapAsyncStatus_Success) {
EXPECT_NE(nullptr, user->buffer.GetConstMappedRange());
void* ptr = user->buffer.GetMappedRange();
EXPECT_NE(nullptr, ptr);
if (ptr != nullptr) {
uint32_t data = myData;
memcpy(ptr, &data, kSize);
}
user->buffer.Unmap();
}
user->done = true;
},
&user);
while (!user.done) {
WaitABit();
}
EXPECT_BUFFER_U32_EQ(myData, buffer, 0);
}
// Test that Get(Const)MappedRange with range work inside map-write callback.
TEST_P(BufferMappingTests, MapWrite_InCallbackRange) {
wgpu::Buffer buffer = CreateMapWriteBuffer(4);
static constexpr uint32_t myData = 2934875;
static constexpr size_t kSize = sizeof(myData);
struct UserData {
bool done;
wgpu::Buffer buffer;
};
UserData user{false, buffer};
buffer.MapAsync(
wgpu::MapMode::Write, 0, kSize,
[](WGPUBufferMapAsyncStatus status, void* userdata) {
UserData* user = static_cast<UserData*>(userdata);
EXPECT_EQ(WGPUBufferMapAsyncStatus_Success, status);
if (status == WGPUBufferMapAsyncStatus_Success) {
EXPECT_NE(nullptr, user->buffer.GetConstMappedRange(0, kSize));
void* ptr = user->buffer.GetMappedRange(0, kSize);
EXPECT_NE(nullptr, ptr);
if (ptr != nullptr) {
uint32_t data = myData;
memcpy(ptr, &data, kSize);
}
user->buffer.Unmap();
}
user->done = true;
},
&user);
while (!user.done) {
WaitABit();
}
EXPECT_BUFFER_U32_EQ(myData, buffer, 0);
}
// Regression test for crbug.com/dawn/969 where this test
// produced invalid barriers.
TEST_P(BufferMappingTests, MapWrite_ZeroSizedTwice) {
wgpu::Buffer buffer = CreateMapWriteBuffer(0);
MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, wgpu::kWholeMapSize);
buffer.Unmap();
MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, wgpu::kWholeMapSize);
}
// Regression test for crbug.com/1421170 where dropping a buffer which needs
// padding bytes initialization resulted in a use-after-free.
TEST_P(BufferMappingTests, RegressChromium1421170) {
// Create a mappable buffer of size 7. It will be internally
// aligned such that the padding bytes need to be zero initialized.
wgpu::BufferDescriptor descriptor;
descriptor.size = 7;
descriptor.usage = wgpu::BufferUsage::MapWrite;
descriptor.mappedAtCreation = false;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
// Drop the buffer. The pending commands to zero initialize the
// padding bytes should stay valid.
buffer = nullptr;
// Flush pending commands.
device.Tick();
}
DAWN_INSTANTIATE_TEST(BufferMappingTests,
D3D11Backend(),
D3D12Backend(),
MetalBackend(),
OpenGLBackend(),
OpenGLESBackend(),
VulkanBackend());
class BufferMappingCallbackTests : public BufferMappingTests {
protected:
void SubmitCommandBuffer(wgpu::Buffer buffer) {
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
{
// Record enough commands to make sure the submission cannot be completed by GPU too
// quick.
constexpr int kRepeatCount = 50;
constexpr int kBufferSize = 1024 * 1024 * 10;
wgpu::Buffer tempWriteBuffer = CreateMapWriteBuffer(kBufferSize);
wgpu::Buffer tempReadBuffer = CreateMapReadBuffer(kBufferSize);
for (int i = 0; i < kRepeatCount; ++i) {
encoder.CopyBufferToBuffer(tempWriteBuffer, 0, tempReadBuffer, 0, kBufferSize);
}
}
if (buffer) {
if (buffer.GetUsage() & wgpu::BufferUsage::CopyDst) {
encoder.ClearBuffer(buffer);
} else {
wgpu::Buffer tempBuffer = CreateMapReadBuffer(buffer.GetSize());
encoder.CopyBufferToBuffer(buffer, 0, tempBuffer, 0, buffer.GetSize());
}
}
wgpu::CommandBuffer commandBuffer = encoder.Finish();
queue.Submit(1, &commandBuffer);
}
void Wait(std::vector<bool>& done) {
do {
WaitABit();
} while (std::any_of(done.begin(), done.end(), [](bool done) { return !done; }));
}
};
TEST_P(BufferMappingCallbackTests, EmptySubmissionAndThenMap) {
wgpu::Buffer buffer = CreateMapWriteBuffer(4);
MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, wgpu::kWholeMapSize);
buffer.Unmap();
std::vector<bool> done = {false, false};
// 1. submission without using buffer.
SubmitCommandBuffer({});
queue.OnSubmittedWorkDone(
0,
[](WGPUQueueWorkDoneStatus status, void* userdata) {
EXPECT_EQ(status, WGPUQueueWorkDoneStatus_Success);
auto& done = *static_cast<std::vector<bool>*>(userdata);
done[0] = true;
// Step 2 callback should be called first, this is the second.
const std::vector<bool> kExpected = {true, true};
EXPECT_EQ(done, kExpected);
},
&done);
// 2.
buffer.MapAsync(
wgpu::MapMode::Write, 0, wgpu::kWholeMapSize,
[](WGPUBufferMapAsyncStatus status, void* userdata) {
EXPECT_EQ(status, WGPUBufferMapAsyncStatus_Success);
auto& done = *static_cast<std::vector<bool>*>(userdata);
done[1] = true;
// The buffer is not used by step 1, so this callback is called first.
const std::vector<bool> kExpected = {false, true};
EXPECT_EQ(done, kExpected);
},
&done);
Wait(done);
}
TEST_P(BufferMappingCallbackTests, UseTheBufferAndThenMap) {
wgpu::Buffer buffer = CreateMapWriteBuffer(4);
MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, wgpu::kWholeMapSize);
buffer.Unmap();
std::vector<bool> done = {false, false};
// 1. Submit a command buffer which uses the buffer
SubmitCommandBuffer(buffer);
queue.OnSubmittedWorkDone(
0,
[](WGPUQueueWorkDoneStatus status, void* userdata) {
EXPECT_EQ(status, WGPUQueueWorkDoneStatus_Success);
auto& done = *static_cast<std::vector<bool>*>(userdata);
done[0] = true;
// This callback should be called first
const std::vector<bool> kExpected = {true, false};
EXPECT_EQ(done, kExpected);
},
&done);
// 2.
buffer.MapAsync(
wgpu::MapMode::Write, 0, wgpu::kWholeMapSize,
[](WGPUBufferMapAsyncStatus status, void* userdata) {
EXPECT_EQ(status, WGPUBufferMapAsyncStatus_Success);
auto& done = *static_cast<std::vector<bool>*>(userdata);
done[1] = true;
// The buffer is used by step 1, so this callback is called second.
const std::vector<bool> kExpected = {true, true};
EXPECT_EQ(done, kExpected);
},
&done);
Wait(done);
buffer.Unmap();
}
TEST_P(BufferMappingCallbackTests, EmptySubmissionWriteAndThenMap) {
wgpu::Buffer buffer = CreateMapReadBuffer(4);
MapAsyncAndWait(buffer, wgpu::MapMode::Read, 0, wgpu::kWholeMapSize);
buffer.Unmap();
std::vector<bool> done = {false, false};
// 1. submission without using buffer.
SubmitCommandBuffer({});
queue.OnSubmittedWorkDone(
0,
[](WGPUQueueWorkDoneStatus status, void* userdata) {
EXPECT_EQ(status, WGPUQueueWorkDoneStatus_Success);
auto& done = *static_cast<std::vector<bool>*>(userdata);
done[0] = true;
// Step 2 callback should be called first, this is the second.
const std::vector<bool> kExpected = {true, false};
EXPECT_EQ(done, kExpected);
},
&done);
int32_t data = 0x12345678;
queue.WriteBuffer(buffer, 0, &data, sizeof(data));
// 2.
buffer.MapAsync(
wgpu::MapMode::Read, 0, wgpu::kWholeMapSize,
[](WGPUBufferMapAsyncStatus status, void* userdata) {
EXPECT_EQ(status, WGPUBufferMapAsyncStatus_Success);
auto& done = *static_cast<std::vector<bool>*>(userdata);
done[1] = true;
// The buffer is not used by step 1, so this callback is called first.
const std::vector<bool> kExpected = {true, true};
EXPECT_EQ(done, kExpected);
},
&done);
Wait(done);
buffer.Unmap();
}
DAWN_INSTANTIATE_TEST(BufferMappingCallbackTests,
D3D11Backend(),
D3D12Backend(),
MetalBackend(),
VulkanBackend());
class BufferMappedAtCreationTests : public DawnTest {
protected:
static void MapCallback(WGPUBufferMapAsyncStatus status, void* userdata) {
EXPECT_EQ(WGPUBufferMapAsyncStatus_Success, status);
*static_cast<bool*>(userdata) = true;
}
const void* MapAsyncAndWait(const wgpu::Buffer& buffer, wgpu::MapMode mode, size_t size) {
bool done = false;
buffer.MapAsync(mode, 0, size, MapCallback, &done);
while (!done) {
WaitABit();
}
return buffer.GetConstMappedRange(0, size);
}
void UnmapBuffer(const wgpu::Buffer& buffer) { buffer.Unmap(); }
wgpu::Buffer BufferMappedAtCreation(wgpu::BufferUsage usage, uint64_t size) {
wgpu::BufferDescriptor descriptor;
descriptor.size = size;
descriptor.usage = usage;
descriptor.mappedAtCreation = true;
return device.CreateBuffer(&descriptor);
}
wgpu::Buffer BufferMappedAtCreationWithData(wgpu::BufferUsage usage,
const std::vector<uint32_t>& data) {
size_t byteLength = data.size() * sizeof(uint32_t);
wgpu::Buffer buffer = BufferMappedAtCreation(usage, byteLength);
memcpy(buffer.GetMappedRange(), data.data(), byteLength);
return buffer;
}
};
// Test that the simplest mappedAtCreation works for MapWrite buffers.
TEST_P(BufferMappedAtCreationTests, MapWriteUsageSmall) {
uint32_t myData = 230502;
wgpu::Buffer buffer = BufferMappedAtCreationWithData(
wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc, {myData});
UnmapBuffer(buffer);
EXPECT_BUFFER_U32_EQ(myData, buffer, 0);
}
// Test that the simplest mappedAtCreation works for MapRead buffers.
TEST_P(BufferMappedAtCreationTests, MapReadUsageSmall) {
uint32_t myData = 230502;
wgpu::Buffer buffer = BufferMappedAtCreationWithData(wgpu::BufferUsage::MapRead, {myData});
UnmapBuffer(buffer);
const void* mappedData = MapAsyncAndWait(buffer, wgpu::MapMode::Read, 4);
ASSERT_EQ(myData, *reinterpret_cast<const uint32_t*>(mappedData));
UnmapBuffer(buffer);
}
// Test that the simplest mappedAtCreation works for non-mappable buffers.
TEST_P(BufferMappedAtCreationTests, NonMappableUsageSmall) {
uint32_t myData = 4239;
wgpu::Buffer buffer = BufferMappedAtCreationWithData(wgpu::BufferUsage::CopySrc, {myData});
UnmapBuffer(buffer);
EXPECT_BUFFER_U32_EQ(myData, buffer, 0);
}
// Test mappedAtCreation for a large MapWrite buffer
TEST_P(BufferMappedAtCreationTests, MapWriteUsageLarge) {
constexpr uint64_t kDataSize = 1000 * 1000;
std::vector<uint32_t> myData;
for (uint32_t i = 0; i < kDataSize; ++i) {
myData.push_back(i);
}
wgpu::Buffer buffer = BufferMappedAtCreationWithData(
wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc, {myData});
UnmapBuffer(buffer);
EXPECT_BUFFER_U32_RANGE_EQ(myData.data(), buffer, 0, kDataSize);
}
// Test mappedAtCreation for a large MapRead buffer
TEST_P(BufferMappedAtCreationTests, MapReadUsageLarge) {
constexpr uint64_t kDataSize = 1000 * 1000;
std::vector<uint32_t> myData;
for (uint32_t i = 0; i < kDataSize; ++i) {
myData.push_back(i);
}
wgpu::Buffer buffer = BufferMappedAtCreationWithData(wgpu::BufferUsage::MapRead, myData);
UnmapBuffer(buffer);
const void* mappedData =
MapAsyncAndWait(buffer, wgpu::MapMode::Read, kDataSize * sizeof(uint32_t));
ASSERT_EQ(0, memcmp(mappedData, myData.data(), kDataSize * sizeof(uint32_t)));
UnmapBuffer(buffer);
}
// Test mappedAtCreation for a large non-mappable buffer
TEST_P(BufferMappedAtCreationTests, NonMappableUsageLarge) {
constexpr uint64_t kDataSize = 1000 * 1000;
std::vector<uint32_t> myData;
for (uint32_t i = 0; i < kDataSize; ++i) {
myData.push_back(i);
}
wgpu::Buffer buffer = BufferMappedAtCreationWithData(wgpu::BufferUsage::CopySrc, {myData});
UnmapBuffer(buffer);
EXPECT_BUFFER_U32_RANGE_EQ(myData.data(), buffer, 0, kDataSize);
}
// Test destroying a non-mappable buffer mapped at creation.
// This is a regression test for an issue where the D3D12 backend thought the buffer was actually
// mapped and tried to unlock the heap residency (when actually the buffer was using a staging
// buffer)
TEST_P(BufferMappedAtCreationTests, DestroyNonMappableWhileMappedForCreation) {
wgpu::Buffer buffer = BufferMappedAtCreation(wgpu::BufferUsage::CopySrc, 4);
buffer.Destroy();
}
// Test destroying a mappable buffer mapped at creation.
TEST_P(BufferMappedAtCreationTests, DestroyMappableWhileMappedForCreation) {
wgpu::Buffer buffer = BufferMappedAtCreation(wgpu::BufferUsage::MapRead, 4);
buffer.Destroy();
}
// Test that mapping a buffer is valid after mappedAtCreation and Unmap
TEST_P(BufferMappedAtCreationTests, CreateThenMapSuccess) {
static uint32_t myData = 230502;
wgpu::Buffer buffer = BufferMappedAtCreationWithData(
wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc, {myData});
UnmapBuffer(buffer);
EXPECT_BUFFER_U32_EQ(myData, buffer, 0);
bool done = false;
buffer.MapAsync(
wgpu::MapMode::Write, 0, 4,
[](WGPUBufferMapAsyncStatus status, void* userdata) {
ASSERT_EQ(WGPUBufferMapAsyncStatus_Success, status);
*static_cast<bool*>(userdata) = true;
},
&done);
while (!done) {
WaitABit();
}
UnmapBuffer(buffer);
}
// Test that is is invalid to map a buffer twice when using mappedAtCreation
TEST_P(BufferMappedAtCreationTests, CreateThenMapBeforeUnmapFailure) {
uint32_t myData = 230502;
wgpu::Buffer buffer = BufferMappedAtCreationWithData(
wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc, {myData});
ASSERT_DEVICE_ERROR([&] {
bool done = false;
buffer.MapAsync(
wgpu::MapMode::Write, 0, 4,
[](WGPUBufferMapAsyncStatus status, void* userdata) {
ASSERT_EQ(WGPUBufferMapAsyncStatus_ValidationError, status);
*static_cast<bool*>(userdata) = true;
},
&done);
while (!done) {
WaitABit();
}
}());
// mappedAtCreation is unaffected by the MapWrite error.
UnmapBuffer(buffer);
}
// Test that creating a zero-sized buffer mapped is allowed.
TEST_P(BufferMappedAtCreationTests, ZeroSized) {
wgpu::BufferDescriptor descriptor;
descriptor.size = 0;
descriptor.usage = wgpu::BufferUsage::Vertex;
descriptor.mappedAtCreation = true;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
ASSERT_NE(nullptr, buffer.GetMappedRange());
// Check that unmapping the buffer works too.
UnmapBuffer(buffer);
}
// Test that creating a zero-sized mapppable buffer mapped. (it is a different code path)
TEST_P(BufferMappedAtCreationTests, ZeroSizedMappableBuffer) {
wgpu::BufferDescriptor descriptor;
descriptor.size = 0;
descriptor.usage = wgpu::BufferUsage::MapWrite;
descriptor.mappedAtCreation = true;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
ASSERT_NE(nullptr, buffer.GetMappedRange());
// Check that unmapping the buffer works too.
UnmapBuffer(buffer);
}
// Test that creating a zero-sized error buffer mapped. (it is a different code path)
TEST_P(BufferMappedAtCreationTests, ZeroSizedErrorBuffer) {
DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("skip_validation"));
wgpu::BufferDescriptor descriptor;
descriptor.size = 0;
descriptor.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::Storage;
descriptor.mappedAtCreation = true;
wgpu::Buffer buffer;
ASSERT_DEVICE_ERROR(buffer = device.CreateBuffer(&descriptor));
ASSERT_NE(nullptr, buffer.GetMappedRange());
}
// Test the result of GetMappedRange when mapped at creation.
TEST_P(BufferMappedAtCreationTests, GetMappedRange) {
wgpu::BufferDescriptor descriptor;
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::CopyDst;
descriptor.mappedAtCreation = true;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
ASSERT_EQ(buffer.GetMappedRange(), buffer.GetConstMappedRange());
ASSERT_NE(buffer.GetMappedRange(), nullptr);
buffer.Unmap();
}
// Test the result of GetMappedRange when mapped at creation for a zero-sized buffer.
TEST_P(BufferMappedAtCreationTests, GetMappedRangeZeroSized) {
wgpu::BufferDescriptor descriptor;
descriptor.size = 0;
descriptor.usage = wgpu::BufferUsage::CopyDst;
descriptor.mappedAtCreation = true;
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
ASSERT_EQ(buffer.GetMappedRange(), buffer.GetConstMappedRange());
ASSERT_NE(buffer.GetMappedRange(), nullptr);
buffer.Unmap();
}
DAWN_INSTANTIATE_TEST(BufferMappedAtCreationTests,
D3D11Backend(),
D3D12Backend(),
D3D12Backend({}, {"use_d3d12_resource_heap_tier2"}),
MetalBackend(),
OpenGLBackend(),
OpenGLESBackend(),
VulkanBackend());
class BufferTests : public DawnTest {};
// Test that creating a zero-buffer is allowed.
TEST_P(BufferTests, ZeroSizedBuffer) {
wgpu::BufferDescriptor desc;
desc.size = 0;
desc.usage = wgpu::BufferUsage::CopyDst;
device.CreateBuffer(&desc);
}
// Test that creating a very large buffers fails gracefully.
TEST_P(BufferTests, CreateBufferOOM) {
// TODO(http://crbug.com/dawn/749): Missing support.
DAWN_TEST_UNSUPPORTED_IF(IsOpenGL());
DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES());
DAWN_TEST_UNSUPPORTED_IF(IsAsan());
DAWN_TEST_UNSUPPORTED_IF(IsTsan());
wgpu::BufferDescriptor descriptor;
descriptor.usage = wgpu::BufferUsage::CopyDst;
descriptor.size = std::numeric_limits<uint64_t>::max();
ASSERT_DEVICE_ERROR(device.CreateBuffer(&descriptor));
// UINT64_MAX may be special cased. Test a smaller, but really large buffer also fails
descriptor.size = 1ull << 50;
ASSERT_DEVICE_ERROR(device.CreateBuffer(&descriptor));
// Validation errors should always be prior to OOM.
descriptor.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::Uniform;
ASSERT_DEVICE_ERROR(device.CreateBuffer(&descriptor));
}
// Test that a very large buffer mappedAtCreation fails gracefully.
TEST_P(BufferTests, BufferMappedAtCreationOOM) {
// TODO(crbug.com/dawn/1506): new (std::nothrow) crashes on OOM on Mac ARM64 because libunwind
// doesn't see the previous catchall try-catch.
DAWN_SUPPRESS_TEST_IF(DAWN_PLATFORM_IS(MACOS) && DAWN_PLATFORM_IS(ARM64));
// TODO(http://crbug.com/dawn/749): Missing support.
DAWN_TEST_UNSUPPORTED_IF(IsOpenGL());
DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES());
DAWN_TEST_UNSUPPORTED_IF(IsAsan());
DAWN_TEST_UNSUPPORTED_IF(IsTsan());
// Test non-mappable buffer
{
wgpu::BufferDescriptor descriptor;
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::CopyDst;
descriptor.mappedAtCreation = true;
// Control: test a small buffer works.
device.CreateBuffer(&descriptor);
// Test an enormous buffer fails
descriptor.size = std::numeric_limits<uint64_t>::max();
if (UsesWire()) {
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
ASSERT_EQ(nullptr, buffer.Get());
} else {
ASSERT_DEVICE_ERROR(device.CreateBuffer(&descriptor));
}
// UINT64_MAX may be special cased. Test a smaller, but really large buffer also fails
descriptor.size = 1ull << 50;
if (UsesWire()) {
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
ASSERT_EQ(nullptr, buffer.Get());
} else {
ASSERT_DEVICE_ERROR(device.CreateBuffer(&descriptor));
}
}
// Test mappable buffer
{
wgpu::BufferDescriptor descriptor;
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::MapWrite;
descriptor.mappedAtCreation = true;
// Control: test a small buffer works.
device.CreateBuffer(&descriptor);
// Test an enormous buffer fails
descriptor.size = std::numeric_limits<uint64_t>::max();
if (UsesWire()) {
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
ASSERT_EQ(nullptr, buffer.Get());
} else {
ASSERT_DEVICE_ERROR(device.CreateBuffer(&descriptor));
}
// UINT64_MAX may be special cased. Test a smaller, but really large buffer also fails
descriptor.size = 1ull << 50;
if (UsesWire()) {
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
ASSERT_EQ(nullptr, buffer.Get());
} else {
ASSERT_DEVICE_ERROR(device.CreateBuffer(&descriptor));
}
}
}
// Test that mapping an OOM buffer fails gracefully
TEST_P(BufferTests, CreateBufferOOMMapAsync) {
// TODO(http://crbug.com/dawn/749): Missing support.
DAWN_TEST_UNSUPPORTED_IF(IsOpenGL());
DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES());
DAWN_TEST_UNSUPPORTED_IF(IsAsan());
DAWN_TEST_UNSUPPORTED_IF(IsTsan());
auto RunTest = [this](const wgpu::BufferDescriptor& descriptor) {
wgpu::Buffer buffer;
ASSERT_DEVICE_ERROR(buffer = device.CreateBuffer(&descriptor));
bool done = false;
ASSERT_DEVICE_ERROR(buffer.MapAsync(
wgpu::MapMode::Write, 0, 4,
[](WGPUBufferMapAsyncStatus status, void* userdata) {
EXPECT_EQ(status, WGPUBufferMapAsyncStatus_ValidationError);
*static_cast<bool*>(userdata) = true;
},
&done));
while (!done) {
WaitABit();
}
};
wgpu::BufferDescriptor descriptor;
descriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::MapWrite;
// Test an enormous buffer
descriptor.size = std::numeric_limits<uint64_t>::max();
RunTest(descriptor);
// UINT64_MAX may be special cased. Test a smaller, but really large buffer also fails
descriptor.size = 1ull << 50;
RunTest(descriptor);
}
DAWN_INSTANTIATE_TEST(BufferTests,
D3D11Backend(),
D3D12Backend(),
MetalBackend(),
OpenGLBackend(),
OpenGLESBackend(),
VulkanBackend());
class BufferNoSuballocationTests : public DawnTest {};
// Regression test for crbug.com/1313172
// This tests a buffer. It then performs writeBuffer and immediately destroys
// it. Though writeBuffer references a destroyed buffer, it should not crash.
TEST_P(BufferNoSuballocationTests, WriteBufferThenDestroy) {
uint32_t myData = 0x01020304;
wgpu::BufferDescriptor desc;
desc.size = 1024;
desc.usage = wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&desc);
// Enqueue a pending write into the buffer.
constexpr size_t kSize = sizeof(myData);
queue.WriteBuffer(buffer, 0, &myData, kSize);
// Destroy the buffer.
buffer.Destroy();
// Flush and wait for all commands.
queue.Submit(0, nullptr);
WaitForAllOperations();
}
DAWN_INSTANTIATE_TEST(BufferNoSuballocationTests,
D3D11Backend({"disable_resource_suballocation"}),
D3D12Backend({"disable_resource_suballocation"}),
MetalBackend({"disable_resource_suballocation"}),
OpenGLBackend({"disable_resource_suballocation"}),
OpenGLESBackend({"disable_resource_suballocation"}),
VulkanBackend({"disable_resource_suballocation"}));
} // anonymous namespace
} // namespace dawn