blob: 8f89016846185ca2c5b3092958535083ef640b9a [file] [log] [blame] [edit]
// Copyright 2021 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 <unordered_set>
#include <vector>
#include "dawn/tests/MockCallback.h"
#include "dawn/tests/unittests/wire/WireTest.h"
#include "dawn/wire/WireClient.h"
#include "dawn/wire/WireServer.h"
#include "webgpu/webgpu_cpp.h"
namespace dawn::wire {
namespace {
using testing::_;
using testing::Invoke;
using testing::InvokeWithoutArgs;
using testing::MockCallback;
using testing::NotNull;
using testing::Return;
using testing::SaveArg;
using testing::StrEq;
using testing::WithArg;
class WireAdapterTests : public WireTest {
protected:
// Bootstrap the tests and create a fake adapter.
void SetUp() override {
WireTest::SetUp();
auto reservation = GetWireClient()->ReserveInstance();
instance = wgpu::Instance::Acquire(reservation.instance);
WGPUInstance apiInstance = api.GetNewInstance();
EXPECT_CALL(api, InstanceReference(apiInstance));
EXPECT_TRUE(
GetWireServer()->InjectInstance(apiInstance, reservation.id, reservation.generation));
wgpu::RequestAdapterOptions options = {};
MockCallback<WGPURequestAdapterCallback> cb;
auto* userdata = cb.MakeUserdata(this);
instance.RequestAdapter(&options, cb.Callback(), userdata);
// Expect the server to receive the message. Then, mock a fake reply.
apiAdapter = api.GetNewAdapter();
EXPECT_CALL(api, OnInstanceRequestAdapter(apiInstance, NotNull(), NotNull(), NotNull()))
.WillOnce(InvokeWithoutArgs([&] {
EXPECT_CALL(api, AdapterGetProperties(apiAdapter, NotNull()))
.WillOnce(WithArg<1>(Invoke([&](WGPUAdapterProperties* properties) {
*properties = {};
properties->vendorName = "";
properties->architecture = "";
properties->name = "";
properties->driverDescription = "";
})));
EXPECT_CALL(api, AdapterGetLimits(apiAdapter, NotNull()))
.WillOnce(WithArg<1>(Invoke([&](WGPUSupportedLimits* limits) {
*limits = {};
return true;
})));
EXPECT_CALL(api, AdapterEnumerateFeatures(apiAdapter, nullptr))
.WillOnce(Return(0))
.WillOnce(Return(0));
api.CallInstanceRequestAdapterCallback(
apiInstance, WGPURequestAdapterStatus_Success, apiAdapter, nullptr);
}));
FlushClient();
// Expect the callback in the client.
WGPUAdapter cAdapter;
EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Success, NotNull(), nullptr, this))
.WillOnce(SaveArg<1>(&cAdapter));
FlushServer();
EXPECT_NE(cAdapter, nullptr);
adapter = wgpu::Adapter::Acquire(cAdapter);
}
void TearDown() override {
adapter = nullptr;
instance = nullptr;
WireTest::TearDown();
}
WGPUAdapter apiAdapter;
wgpu::Instance instance;
wgpu::Adapter adapter;
};
// Test that the DeviceDescriptor is passed from the client to the server.
TEST_F(WireAdapterTests, RequestDevicePassesDescriptor) {
MockCallback<WGPURequestDeviceCallback> cb;
auto* userdata = cb.MakeUserdata(this);
// Test an empty descriptor
{
wgpu::DeviceDescriptor desc = {};
adapter.RequestDevice(&desc, cb.Callback(), userdata);
EXPECT_CALL(api, OnAdapterRequestDevice(apiAdapter, NotNull(), NotNull(), NotNull()))
.WillOnce(WithArg<1>(Invoke([](const WGPUDeviceDescriptor* apiDesc) {
EXPECT_EQ(apiDesc->label, nullptr);
EXPECT_EQ(apiDesc->requiredFeaturesCount, 0u);
EXPECT_EQ(apiDesc->requiredLimits, nullptr);
})));
FlushClient();
}
// Test a non-empty descriptor
{
wgpu::RequiredLimits limits = {};
limits.limits.maxStorageTexturesPerShaderStage = 5;
std::vector<wgpu::FeatureName> features = {wgpu::FeatureName::TextureCompressionETC2,
wgpu::FeatureName::TextureCompressionASTC};
wgpu::DeviceDescriptor desc = {};
desc.label = "hello device";
desc.requiredLimits = &limits;
desc.requiredFeaturesCount = features.size();
desc.requiredFeatures = features.data();
adapter.RequestDevice(&desc, cb.Callback(), userdata);
EXPECT_CALL(api, OnAdapterRequestDevice(apiAdapter, NotNull(), NotNull(), NotNull()))
.WillOnce(WithArg<1>(Invoke([&](const WGPUDeviceDescriptor* apiDesc) {
EXPECT_STREQ(apiDesc->label, desc.label);
ASSERT_EQ(apiDesc->requiredFeaturesCount, features.size());
for (uint32_t i = 0; i < features.size(); ++i) {
EXPECT_EQ(apiDesc->requiredFeatures[i],
static_cast<WGPUFeatureName>(features[i]));
}
ASSERT_NE(apiDesc->requiredLimits, nullptr);
EXPECT_EQ(apiDesc->requiredLimits->nextInChain, nullptr);
EXPECT_EQ(apiDesc->requiredLimits->limits.maxStorageTexturesPerShaderStage,
limits.limits.maxStorageTexturesPerShaderStage);
})));
FlushClient();
}
// Delete the adapter now, or it'll call the mock callback after it's deleted.
adapter = nullptr;
}
static void DeviceLostCallback(WGPUDeviceLostReason reason, const char* message, void* userdata) {}
// Test that the DeviceDescriptor is not allowed to pass a device lost callback from the client to
// the server.
TEST_F(WireAdapterTests, RequestDeviceAssertsOnLostCallbackPointer) {
MockCallback<WGPURequestDeviceCallback> cb;
auto* userdata = cb.MakeUserdata(this);
wgpu::DeviceDescriptor desc = {};
desc.deviceLostCallback = DeviceLostCallback;
desc.deviceLostUserdata = userdata;
adapter.RequestDevice(&desc, cb.Callback(), userdata);
EXPECT_CALL(api, OnAdapterRequestDevice(apiAdapter, NotNull(), NotNull(), NotNull()))
.WillOnce(WithArg<1>(Invoke([&](const WGPUDeviceDescriptor* apiDesc) {
EXPECT_STREQ(apiDesc->label, desc.label);
// The callback should not be passed through to the server.
ASSERT_EQ(apiDesc->deviceLostCallback, nullptr);
ASSERT_EQ(apiDesc->deviceLostUserdata, nullptr);
})));
FlushClient();
// Delete the adapter now, or it'll call the mock callback after it's deleted.
adapter = nullptr;
}
// Test that RequestDevice forwards the device information to the client.
TEST_F(WireAdapterTests, RequestDeviceSuccess) {
MockCallback<WGPURequestDeviceCallback> cb;
auto* userdata = cb.MakeUserdata(this);
wgpu::SupportedLimits fakeLimits = {};
fakeLimits.limits.maxTextureDimension1D = 433;
fakeLimits.limits.maxVertexAttributes = 1243;
std::initializer_list<wgpu::FeatureName> fakeFeatures = {
wgpu::FeatureName::Depth32FloatStencil8,
wgpu::FeatureName::TextureCompressionBC,
};
wgpu::DeviceDescriptor desc = {};
adapter.RequestDevice(&desc, cb.Callback(), userdata);
// Expect the server to receive the message. Then, mock a fake reply.
WGPUDevice apiDevice = api.GetNewDevice();
// The backend device should not be known by the wire server.
EXPECT_FALSE(GetWireServer()->IsDeviceKnown(apiDevice));
wgpu::Device device;
EXPECT_CALL(api, OnAdapterRequestDevice(apiAdapter, NotNull(), NotNull(), NotNull()))
.WillOnce(InvokeWithoutArgs([&] {
// Set on device creation to forward callbacks to the client.
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(apiDevice, NotNull(), NotNull()))
.Times(1);
EXPECT_CALL(api, OnDeviceSetLoggingCallback(apiDevice, NotNull(), NotNull())).Times(1);
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(apiDevice, NotNull(), NotNull()))
.Times(1);
EXPECT_CALL(api, DeviceGetLimits(apiDevice, NotNull()))
.WillOnce(WithArg<1>(Invoke([&](WGPUSupportedLimits* limits) {
*reinterpret_cast<wgpu::SupportedLimits*>(limits) = fakeLimits;
return true;
})));
EXPECT_CALL(api, DeviceEnumerateFeatures(apiDevice, nullptr))
.WillOnce(Return(fakeFeatures.size()));
EXPECT_CALL(api, DeviceEnumerateFeatures(apiDevice, NotNull()))
.WillOnce(WithArg<1>(Invoke([&](WGPUFeatureName* features) {
for (wgpu::FeatureName feature : fakeFeatures) {
*(features++) = static_cast<WGPUFeatureName>(feature);
}
return fakeFeatures.size();
})));
// The backend device should still not be known by the wire server since the
// callback has not been called yet.
EXPECT_FALSE(GetWireServer()->IsDeviceKnown(apiDevice));
api.CallAdapterRequestDeviceCallback(apiAdapter, WGPURequestDeviceStatus_Success,
apiDevice, nullptr);
// After the callback is called, the backend device is now known by the server.
EXPECT_TRUE(GetWireServer()->IsDeviceKnown(apiDevice));
}));
FlushClient();
// Expect the callback in the client and all the device information to match.
EXPECT_CALL(cb, Call(WGPURequestDeviceStatus_Success, NotNull(), nullptr, this))
.WillOnce(WithArg<1>(Invoke([&](WGPUDevice cDevice) {
device = wgpu::Device::Acquire(cDevice);
wgpu::SupportedLimits limits;
EXPECT_TRUE(device.GetLimits(&limits));
EXPECT_EQ(limits.limits.maxTextureDimension1D, fakeLimits.limits.maxTextureDimension1D);
EXPECT_EQ(limits.limits.maxVertexAttributes, fakeLimits.limits.maxVertexAttributes);
std::vector<wgpu::FeatureName> features;
features.resize(device.EnumerateFeatures(nullptr));
ASSERT_EQ(features.size(), fakeFeatures.size());
EXPECT_EQ(device.EnumerateFeatures(&features[0]), features.size());
std::unordered_set<wgpu::FeatureName> featureSet(fakeFeatures);
for (wgpu::FeatureName feature : features) {
EXPECT_EQ(featureSet.erase(feature), 1u);
}
})));
FlushServer();
// Test that callbacks can propagate from server to client.
MockCallback<WGPUErrorCallback> errorCb;
device.SetUncapturedErrorCallback(errorCb.Callback(), errorCb.MakeUserdata(this));
api.CallDeviceSetUncapturedErrorCallbackCallback(apiDevice, WGPUErrorType_Validation,
"Some error message");
EXPECT_CALL(errorCb, Call(WGPUErrorType_Validation, StrEq("Some error message"), this))
.Times(1);
FlushServer();
device = nullptr;
// Cleared when the device is destroyed.
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(apiDevice, nullptr, nullptr)).Times(1);
EXPECT_CALL(api, OnDeviceSetLoggingCallback(apiDevice, nullptr, nullptr)).Times(1);
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(apiDevice, nullptr, nullptr)).Times(1);
EXPECT_CALL(api, DeviceRelease(apiDevice));
// Server has not recevied the release yet, so the device should be known.
EXPECT_TRUE(GetWireServer()->IsDeviceKnown(apiDevice));
FlushClient();
// After receiving the release call, the device is no longer known by the server.
EXPECT_FALSE(GetWireServer()->IsDeviceKnown(apiDevice));
}
// Test that features requested that the implementation supports, but not the
// wire reject the callback.
TEST_F(WireAdapterTests, RequestFeatureUnsupportedByWire) {
MockCallback<WGPURequestDeviceCallback> cb;
auto* userdata = cb.MakeUserdata(this);
std::initializer_list<wgpu::FeatureName> fakeFeatures = {
// Some value that is not a valid feature
static_cast<wgpu::FeatureName>(-2),
wgpu::FeatureName::TextureCompressionASTC,
};
wgpu::DeviceDescriptor desc = {};
adapter.RequestDevice(&desc, cb.Callback(), userdata);
// Expect the server to receive the message. Then, mock a fake reply.
// The reply contains features that the device implementation supports, but the
// wire does not.
WGPUDevice apiDevice = api.GetNewDevice();
EXPECT_CALL(api, OnAdapterRequestDevice(apiAdapter, NotNull(), NotNull(), NotNull()))
.WillOnce(InvokeWithoutArgs([&] {
EXPECT_CALL(api, DeviceEnumerateFeatures(apiDevice, nullptr))
.WillOnce(Return(fakeFeatures.size()));
EXPECT_CALL(api, DeviceEnumerateFeatures(apiDevice, NotNull()))
.WillOnce(WithArg<1>(Invoke([&](WGPUFeatureName* features) {
for (wgpu::FeatureName feature : fakeFeatures) {
*(features++) = static_cast<WGPUFeatureName>(feature);
}
return fakeFeatures.size();
})));
// The device was actually created, but the wire didn't support its features.
// Expect it to be released.
EXPECT_CALL(api, DeviceRelease(apiDevice));
// Fake successful creation. The client still receives a failure due to
// unsupported features.
api.CallAdapterRequestDeviceCallback(apiAdapter, WGPURequestDeviceStatus_Success,
apiDevice, nullptr);
}));
FlushClient();
// Expect an error callback since the feature is not supported.
EXPECT_CALL(cb, Call(WGPURequestDeviceStatus_Error, nullptr, NotNull(), this)).Times(1);
FlushServer();
}
// Test that RequestDevice errors forward to the client.
TEST_F(WireAdapterTests, RequestDeviceError) {
MockCallback<WGPURequestDeviceCallback> cb;
auto* userdata = cb.MakeUserdata(this);
wgpu::DeviceDescriptor desc = {};
adapter.RequestDevice(&desc, cb.Callback(), userdata);
// Expect the server to receive the message. Then, mock an error.
EXPECT_CALL(api, OnAdapterRequestDevice(apiAdapter, NotNull(), NotNull(), NotNull()))
.WillOnce(InvokeWithoutArgs([&] {
api.CallAdapterRequestDeviceCallback(apiAdapter, WGPURequestDeviceStatus_Error, nullptr,
"Request device failed");
}));
FlushClient();
// Expect the callback in the client.
EXPECT_CALL(cb,
Call(WGPURequestDeviceStatus_Error, nullptr, StrEq("Request device failed"), this))
.Times(1);
FlushServer();
}
// Test that RequestDevice receives unknown status if the adapter is deleted
// before the callback happens.
TEST_F(WireAdapterTests, RequestDeviceAdapterDestroyedBeforeCallback) {
MockCallback<WGPURequestDeviceCallback> cb;
auto* userdata = cb.MakeUserdata(this);
wgpu::DeviceDescriptor desc = {};
adapter.RequestDevice(&desc, cb.Callback(), userdata);
EXPECT_CALL(cb, Call(WGPURequestDeviceStatus_Unknown, nullptr, NotNull(), this)).Times(1);
adapter = nullptr;
}
// Test that RequestDevice receives unknown status if the wire is disconnected
// before the callback happens.
TEST_F(WireAdapterTests, RequestDeviceWireDisconnectedBeforeCallback) {
MockCallback<WGPURequestDeviceCallback> cb;
auto* userdata = cb.MakeUserdata(this);
wgpu::DeviceDescriptor desc = {};
adapter.RequestDevice(&desc, cb.Callback(), userdata);
EXPECT_CALL(cb, Call(WGPURequestDeviceStatus_Unknown, nullptr, NotNull(), this)).Times(1);
GetWireClient()->Disconnect();
}
} // anonymous namespace
} // namespace dawn::wire