mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-05-13 10:51:35 +00:00
When calling unmap on a mapped buffer for which the callback hasn't fired yet, the callback should be called with UNKNOWN. The code marked the callback as called only after calling it, causing problems with re-entrancy where the callback would be called twice. This could also get triggered by destroying the buffer inside the callback. Fix this in backend::Buffer and the WireClient and add test for both.
350 lines
12 KiB
C++
350 lines
12 KiB
C++
// Copyright 2017 The NXT 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 "tests/unittests/validation/ValidationTest.h"
|
|
|
|
#include <gmock/gmock.h>
|
|
|
|
using namespace testing;
|
|
|
|
class MockBufferMapReadCallback {
|
|
public:
|
|
MOCK_METHOD3(Call, void(nxtBufferMapReadStatus status, const uint32_t* ptr, nxtCallbackUserdata userdata));
|
|
};
|
|
|
|
static MockBufferMapReadCallback* mockBufferMapReadCallback = nullptr;
|
|
static void ToMockBufferMapReadCallback(nxtBufferMapReadStatus status, const void* ptr, nxtCallbackUserdata userdata) {
|
|
// Assume the data is uint32_t to make writing matchers easier
|
|
mockBufferMapReadCallback->Call(status, reinterpret_cast<const uint32_t*>(ptr), userdata);
|
|
}
|
|
|
|
class BufferValidationTest : public ValidationTest {
|
|
protected:
|
|
nxt::Buffer CreateMapReadBuffer(uint32_t size) {
|
|
return device.CreateBufferBuilder()
|
|
.SetSize(size)
|
|
.SetAllowedUsage(nxt::BufferUsageBit::MapRead)
|
|
.SetInitialUsage(nxt::BufferUsageBit::MapRead)
|
|
.GetResult();
|
|
}
|
|
nxt::Buffer CreateSetSubDataBuffer(uint32_t size) {
|
|
return device.CreateBufferBuilder()
|
|
.SetSize(size)
|
|
.SetAllowedUsage(nxt::BufferUsageBit::TransferDst)
|
|
.SetInitialUsage(nxt::BufferUsageBit::TransferDst)
|
|
.GetResult();
|
|
}
|
|
|
|
nxt::Queue queue;
|
|
|
|
private:
|
|
void SetUp() override {
|
|
ValidationTest::SetUp();
|
|
|
|
mockBufferMapReadCallback = new MockBufferMapReadCallback;
|
|
queue = device.CreateQueueBuilder().GetResult();
|
|
}
|
|
|
|
void TearDown() override {
|
|
delete mockBufferMapReadCallback;
|
|
|
|
ValidationTest::TearDown();
|
|
}
|
|
};
|
|
|
|
// Test case where creation should succeed
|
|
TEST_F(BufferValidationTest, CreationSuccess) {
|
|
// Success
|
|
{
|
|
nxt::Buffer buf = AssertWillBeSuccess(device.CreateBufferBuilder())
|
|
.SetSize(4)
|
|
.SetAllowedUsage(nxt::BufferUsageBit::Uniform)
|
|
.SetInitialUsage(nxt::BufferUsageBit::Uniform)
|
|
.GetResult();
|
|
}
|
|
|
|
// Success, when no initial usage is set
|
|
{
|
|
nxt::Buffer buf = AssertWillBeSuccess(device.CreateBufferBuilder())
|
|
.SetSize(4)
|
|
.SetAllowedUsage(nxt::BufferUsageBit::Uniform)
|
|
.GetResult();
|
|
}
|
|
}
|
|
|
|
// Test failure when specifying properties multiple times
|
|
TEST_F(BufferValidationTest, CreationDuplicates) {
|
|
// When size is specified multiple times
|
|
{
|
|
nxt::Buffer buf = AssertWillBeError(device.CreateBufferBuilder())
|
|
.SetSize(4)
|
|
.SetSize(3)
|
|
.SetAllowedUsage(nxt::BufferUsageBit::Uniform)
|
|
.SetInitialUsage(nxt::BufferUsageBit::Uniform)
|
|
.GetResult();
|
|
}
|
|
|
|
// When allowed usage is specified multiple times
|
|
{
|
|
nxt::Buffer buf = AssertWillBeError(device.CreateBufferBuilder())
|
|
.SetSize(4)
|
|
.SetAllowedUsage(nxt::BufferUsageBit::Vertex)
|
|
.SetAllowedUsage(nxt::BufferUsageBit::Uniform)
|
|
.SetInitialUsage(nxt::BufferUsageBit::Uniform)
|
|
.GetResult();
|
|
}
|
|
|
|
// When initial usage is specified multiple times
|
|
{
|
|
nxt::Buffer buf = AssertWillBeError(device.CreateBufferBuilder())
|
|
.SetSize(4)
|
|
.SetAllowedUsage(nxt::BufferUsageBit::Uniform | nxt::BufferUsageBit::Vertex)
|
|
.SetInitialUsage(nxt::BufferUsageBit::Uniform)
|
|
.SetInitialUsage(nxt::BufferUsageBit::Vertex)
|
|
.GetResult();
|
|
}
|
|
}
|
|
|
|
// Test failure when the initial usage isn't a subset of the allowed usage
|
|
TEST_F(BufferValidationTest, CreationInitialNotSubsetOfAllowed) {
|
|
nxt::Buffer buf = AssertWillBeError(device.CreateBufferBuilder())
|
|
.SetSize(4)
|
|
.SetAllowedUsage(nxt::BufferUsageBit::Uniform)
|
|
.SetInitialUsage(nxt::BufferUsageBit::Vertex)
|
|
.GetResult();
|
|
}
|
|
|
|
// Test failure when required properties are missing
|
|
TEST_F(BufferValidationTest, CreationMissingProperties) {
|
|
// When allowed usage is missing
|
|
{
|
|
nxt::Buffer buf = AssertWillBeError(device.CreateBufferBuilder())
|
|
.SetSize(4)
|
|
.GetResult();
|
|
}
|
|
|
|
// When size is missing
|
|
{
|
|
nxt::Buffer buf = AssertWillBeError(device.CreateBufferBuilder())
|
|
.SetAllowedUsage(nxt::BufferUsageBit::Vertex)
|
|
.GetResult();
|
|
}
|
|
}
|
|
|
|
// Test restriction on usages allowed with MapRead and MapWrite
|
|
TEST_F(BufferValidationTest, CreationMapUsageRestrictions) {
|
|
// MapRead with TransferDst is ok
|
|
{
|
|
nxt::Buffer buf = AssertWillBeSuccess(device.CreateBufferBuilder(), "1")
|
|
.SetAllowedUsage(nxt::BufferUsageBit::MapRead | nxt::BufferUsageBit::TransferDst)
|
|
.SetSize(4)
|
|
.GetResult();
|
|
}
|
|
|
|
// MapRead with something else is an error
|
|
{
|
|
nxt::Buffer buf = AssertWillBeError(device.CreateBufferBuilder(), "2")
|
|
.SetAllowedUsage(nxt::BufferUsageBit::MapRead | nxt::BufferUsageBit::Uniform)
|
|
.SetSize(4)
|
|
.GetResult();
|
|
}
|
|
|
|
// MapWrite with TransferSrc is ok
|
|
{
|
|
nxt::Buffer buf = AssertWillBeSuccess(device.CreateBufferBuilder(), "3")
|
|
.SetAllowedUsage(nxt::BufferUsageBit::MapWrite | nxt::BufferUsageBit::TransferSrc)
|
|
.SetSize(4)
|
|
.GetResult();
|
|
}
|
|
|
|
// MapWrite with something else is an error
|
|
{
|
|
nxt::Buffer buf = AssertWillBeError(device.CreateBufferBuilder(), "4")
|
|
.SetAllowedUsage(nxt::BufferUsageBit::MapWrite | nxt::BufferUsageBit::Uniform)
|
|
.SetSize(4)
|
|
.GetResult();
|
|
}
|
|
}
|
|
|
|
// Test the success cause for mapping buffer for reading
|
|
TEST_F(BufferValidationTest, MapReadSuccess) {
|
|
nxt::Buffer buf = CreateMapReadBuffer(4);
|
|
|
|
nxt::CallbackUserdata userdata = 40598;
|
|
buf.MapReadAsync(0, 4, ToMockBufferMapReadCallback, userdata);
|
|
|
|
EXPECT_CALL(*mockBufferMapReadCallback, Call(NXT_BUFFER_MAP_READ_STATUS_SUCCESS, Ne(nullptr), userdata))
|
|
.Times(1);
|
|
queue.Submit(0, nullptr);
|
|
|
|
buf.Unmap();
|
|
}
|
|
|
|
// Test map reading out of range causes an error
|
|
TEST_F(BufferValidationTest, MapReadOutOfRange) {
|
|
nxt::Buffer buf = CreateMapReadBuffer(4);
|
|
|
|
nxt::CallbackUserdata userdata = 40599;
|
|
EXPECT_CALL(*mockBufferMapReadCallback, Call(NXT_BUFFER_MAP_READ_STATUS_ERROR, nullptr, userdata))
|
|
.Times(1);
|
|
|
|
ASSERT_DEVICE_ERROR(buf.MapReadAsync(0, 5, ToMockBufferMapReadCallback, userdata));
|
|
}
|
|
|
|
// Test map reading a buffer with wrong current usage
|
|
TEST_F(BufferValidationTest, MapReadWrongUsage) {
|
|
nxt::Buffer buf = device.CreateBufferBuilder()
|
|
.SetSize(4)
|
|
.SetAllowedUsage(nxt::BufferUsageBit::MapRead | nxt::BufferUsageBit::TransferDst)
|
|
.SetInitialUsage(nxt::BufferUsageBit::TransferDst)
|
|
.GetResult();
|
|
|
|
nxt::CallbackUserdata userdata = 40600;
|
|
EXPECT_CALL(*mockBufferMapReadCallback, Call(NXT_BUFFER_MAP_READ_STATUS_ERROR, nullptr, userdata))
|
|
.Times(1);
|
|
|
|
ASSERT_DEVICE_ERROR(buf.MapReadAsync(0, 4, ToMockBufferMapReadCallback, userdata));
|
|
}
|
|
|
|
// Test map reading a buffer that is already mapped
|
|
TEST_F(BufferValidationTest, MapReadAlreadyMapped) {
|
|
nxt::Buffer buf = CreateMapReadBuffer(4);
|
|
|
|
nxt::CallbackUserdata userdata1 = 40601;
|
|
buf.MapReadAsync(0, 4, ToMockBufferMapReadCallback, userdata1);
|
|
EXPECT_CALL(*mockBufferMapReadCallback, Call(NXT_BUFFER_MAP_READ_STATUS_SUCCESS, Ne(nullptr), userdata1))
|
|
.Times(1);
|
|
|
|
nxt::CallbackUserdata userdata2 = 40602;
|
|
EXPECT_CALL(*mockBufferMapReadCallback, Call(NXT_BUFFER_MAP_READ_STATUS_ERROR, nullptr, userdata2))
|
|
.Times(1);
|
|
ASSERT_DEVICE_ERROR(buf.MapReadAsync(0, 4, ToMockBufferMapReadCallback, userdata2));
|
|
|
|
queue.Submit(0, nullptr);
|
|
}
|
|
|
|
// Test unmapping before having the result gives UNKNOWN
|
|
TEST_F(BufferValidationTest, MapReadUnmapBeforeResult) {
|
|
nxt::Buffer buf = CreateMapReadBuffer(4);
|
|
|
|
nxt::CallbackUserdata userdata = 40603;
|
|
buf.MapReadAsync(0, 4, ToMockBufferMapReadCallback, userdata);
|
|
|
|
EXPECT_CALL(*mockBufferMapReadCallback, Call(NXT_BUFFER_MAP_READ_STATUS_UNKNOWN, nullptr, userdata))
|
|
.Times(1);
|
|
buf.Unmap();
|
|
|
|
// Submitting the queue makes the null backend process map request, but the callback shouldn't
|
|
// be called again
|
|
queue.Submit(0, nullptr);
|
|
}
|
|
|
|
// Test destroying the buffer before having the result gives UNKNOWN
|
|
// TODO(cwallez@chromium.org) currently this doesn't work because the buffer doesn't know
|
|
// when its external ref count reaches 0.
|
|
TEST_F(BufferValidationTest, DISABLED_MapReadDestroyBeforeResult) {
|
|
{
|
|
nxt::Buffer buf = CreateMapReadBuffer(4);
|
|
|
|
nxt::CallbackUserdata userdata = 40604;
|
|
buf.MapReadAsync(0, 4, ToMockBufferMapReadCallback, userdata);
|
|
|
|
EXPECT_CALL(*mockBufferMapReadCallback, Call(NXT_BUFFER_MAP_READ_STATUS_UNKNOWN, nullptr, userdata))
|
|
.Times(1);
|
|
}
|
|
|
|
// Submitting the queue makes the null backend process map request, but the callback shouldn't
|
|
// be called again
|
|
queue.Submit(0, nullptr);
|
|
}
|
|
|
|
// When a request is cancelled with Unmap it might still be in flight, test doing a new request
|
|
// works as expected and we don't get the cancelled request's data.
|
|
TEST_F(BufferValidationTest, MapReadUnmapBeforeResultThenMapAgain) {
|
|
nxt::Buffer buf = CreateMapReadBuffer(4);
|
|
|
|
nxt::CallbackUserdata userdata = 40605;
|
|
buf.MapReadAsync(0, 4, ToMockBufferMapReadCallback, userdata);
|
|
|
|
EXPECT_CALL(*mockBufferMapReadCallback, Call(NXT_BUFFER_MAP_READ_STATUS_UNKNOWN, nullptr, userdata))
|
|
.Times(1);
|
|
buf.Unmap();
|
|
|
|
userdata ++;
|
|
|
|
buf.MapReadAsync(0, 4, ToMockBufferMapReadCallback, userdata);
|
|
|
|
EXPECT_CALL(*mockBufferMapReadCallback, Call(NXT_BUFFER_MAP_READ_STATUS_SUCCESS, Ne(nullptr), userdata))
|
|
.Times(1);
|
|
queue.Submit(0, nullptr);
|
|
}
|
|
|
|
// Test that the MapReadCallback isn't fired twice when unmap() is called inside the callback
|
|
TEST_F(BufferValidationTest, UnmapInsideMapReadCallback) {
|
|
nxt::Buffer buf = CreateMapReadBuffer(4);
|
|
|
|
nxt::CallbackUserdata userdata = 40678;
|
|
buf.MapReadAsync(0, 4, ToMockBufferMapReadCallback, userdata);
|
|
|
|
EXPECT_CALL(*mockBufferMapReadCallback, Call(NXT_BUFFER_MAP_READ_STATUS_SUCCESS, Ne(nullptr), userdata))
|
|
.WillOnce(InvokeWithoutArgs([&]() {
|
|
buf.Unmap();
|
|
}));
|
|
|
|
queue.Submit(0, nullptr);
|
|
}
|
|
|
|
// Test that the MapReadCallback isn't fired twice the buffer external refcount reaches 0 in the callback
|
|
TEST_F(BufferValidationTest, DestroyInsideMapReadCallback) {
|
|
nxt::Buffer buf = CreateMapReadBuffer(4);
|
|
|
|
nxt::CallbackUserdata userdata = 40679;
|
|
buf.MapReadAsync(0, 4, ToMockBufferMapReadCallback, userdata);
|
|
|
|
EXPECT_CALL(*mockBufferMapReadCallback, Call(NXT_BUFFER_MAP_READ_STATUS_SUCCESS, Ne(nullptr), userdata))
|
|
.WillOnce(InvokeWithoutArgs([&]() {
|
|
buf = nxt::Buffer();
|
|
}));
|
|
|
|
queue.Submit(0, nullptr);
|
|
}
|
|
|
|
// Test the success case for Buffer::SetSubData
|
|
TEST_F(BufferValidationTest, SetSubDataSuccess) {
|
|
nxt::Buffer buf = CreateSetSubDataBuffer(4);
|
|
|
|
uint32_t foo = 0;
|
|
buf.SetSubData(0, 1, &foo);
|
|
}
|
|
|
|
// Test error case for SetSubData out of bounds
|
|
TEST_F(BufferValidationTest, SetSubDataOutOfBounds) {
|
|
nxt::Buffer buf = CreateSetSubDataBuffer(4);
|
|
|
|
uint32_t foo = 0;
|
|
ASSERT_DEVICE_ERROR(buf.SetSubData(0, 2, &foo));
|
|
}
|
|
|
|
// Test error case for SetSubData with the wrong usage
|
|
TEST_F(BufferValidationTest, SetSubDataWrongUsage) {
|
|
nxt::Buffer buf = device.CreateBufferBuilder()
|
|
.SetSize(4)
|
|
.SetAllowedUsage(nxt::BufferUsageBit::TransferDst | nxt::BufferUsageBit::Vertex)
|
|
.SetInitialUsage(nxt::BufferUsageBit::Vertex)
|
|
.GetResult();
|
|
|
|
uint32_t foo = 0;
|
|
ASSERT_DEVICE_ERROR(buf.SetSubData(0, 1, &foo));
|
|
}
|