| // 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 "tests/MockCallback.h" |
| #include "tests/unittests/wire/WireTest.h" |
| |
| #include "dawn_wire/WireClient.h" |
| #include "dawn_wire/WireServer.h" |
| |
| #include <webgpu/webgpu_cpp.h> |
| #include <unordered_set> |
| #include <vector> |
| |
| namespace { |
| |
| using namespace testing; |
| using namespace dawn_wire; |
| |
| 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->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; |
| } |
| |
| // 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(); |
| 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(); |
| }))); |
| |
| api.CallAdapterRequestDeviceCallback(apiAdapter, WGPURequestDeviceStatus_Success, |
| apiDevice, nullptr); |
| })); |
| 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) { |
| wgpu::Device 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(); |
| |
| // 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); |
| } |
| |
| // 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 |