| // Copyright 2021 The Dawn & Tint Authors |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // 1. Redistributions of source code must retain the above copyright notice, this |
| // list of conditions and the following disclaimer. |
| // |
| // 2. Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimer in the documentation |
| // and/or other materials provided with the distribution. |
| // |
| // 3. Neither the name of the copyright holder nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include <unordered_set> |
| #include <vector> |
| |
| #include "dawn/tests/MockCallback.h" |
| #include "dawn/tests/unittests/wire/WireFutureTest.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; |
| |
| using WireAdapterTestBase = WireFutureTestWithParams<WGPURequestDeviceCallback, |
| WGPURequestDeviceCallbackInfo, |
| wgpuAdapterRequestDevice, |
| wgpuAdapterRequestDeviceF>; |
| |
| class WireAdapterTests : public WireAdapterTestBase { |
| protected: |
| // Overriden version of wgpuAdapterRequestDevice that defers to the API call based on the |
| // test callback mode. |
| void AdapterRequestDevice(const wgpu::Adapter& a, |
| const wgpu::DeviceDescriptor* descriptor, |
| void* userdata = nullptr) { |
| CallImpl(userdata, a.Get(), reinterpret_cast<WGPUDeviceDescriptor const*>(descriptor)); |
| } |
| }; |
| DAWN_INSTANTIATE_WIRE_FUTURE_TEST_P(WireAdapterTests); |
| |
| // Test that an empty DeviceDescriptor is passed from the client to the server. |
| TEST_P(WireAdapterTests, RequestDeviceEmptyDescriptor) { |
| wgpu::DeviceDescriptor desc = {}; |
| AdapterRequestDevice(adapter, &desc); |
| |
| EXPECT_CALL(api, OnAdapterRequestDevice(apiAdapter, NotNull(), _)) |
| .WillOnce(WithArg<1>(Invoke([&](const WGPUDeviceDescriptor* apiDesc) { |
| EXPECT_EQ(apiDesc->label, nullptr); |
| EXPECT_EQ(apiDesc->requiredFeatureCount, 0u); |
| EXPECT_EQ(apiDesc->requiredLimits, nullptr); |
| |
| // Call the callback so the test doesn't wait indefinitely. |
| api.CallAdapterRequestDeviceCallback(apiAdapter, WGPURequestDeviceStatus_Error, nullptr, |
| nullptr); |
| }))); |
| FlushClient(); |
| FlushFutures(); |
| ExpectWireCallbacksWhen([&](auto& mockCb) { |
| EXPECT_CALL(mockCb, Call).Times(1); |
| |
| FlushCallbacks(); |
| }); |
| } |
| |
| // Test that a null DeviceDescriptor is passed from the client to the server as an empty one. |
| TEST_P(WireAdapterTests, RequestDeviceNullDescriptor) { |
| AdapterRequestDevice(adapter, nullptr); |
| |
| EXPECT_CALL(api, OnAdapterRequestDevice(apiAdapter, NotNull(), _)) |
| .WillOnce(WithArg<1>(Invoke([&](const WGPUDeviceDescriptor* apiDesc) { |
| EXPECT_EQ(apiDesc->label, nullptr); |
| EXPECT_EQ(apiDesc->requiredFeatureCount, 0u); |
| EXPECT_EQ(apiDesc->requiredLimits, nullptr); |
| |
| // Call the callback so the test doesn't wait indefinitely. |
| api.CallAdapterRequestDeviceCallback(apiAdapter, WGPURequestDeviceStatus_Error, nullptr, |
| nullptr); |
| }))); |
| FlushClient(); |
| FlushFutures(); |
| ExpectWireCallbacksWhen([&](auto& mockCb) { |
| EXPECT_CALL(mockCb, Call).Times(1); |
| |
| FlushCallbacks(); |
| }); |
| } |
| |
| static void DeviceLostCallback(WGPUDevice const* device, |
| 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_P(WireAdapterTests, RequestDeviceAssertsOnLostCallbackPointer) { |
| int userdata = 1337; |
| wgpu::DeviceDescriptor desc = {}; |
| desc.deviceLostCallbackInfo.callback = DeviceLostCallback; |
| desc.deviceLostCallbackInfo.userdata = &userdata; |
| |
| AdapterRequestDevice(adapter, &desc); |
| |
| EXPECT_CALL(api, OnAdapterRequestDevice(apiAdapter, NotNull(), _)) |
| .WillOnce(WithArg<1>(Invoke([&](const WGPUDeviceDescriptor* apiDesc) { |
| EXPECT_STREQ(apiDesc->label, desc.label); |
| |
| // The callback should not be passed through to the server, and it should be overridden. |
| ASSERT_NE(apiDesc->deviceLostCallbackInfo.callback, nullptr); |
| ASSERT_NE(apiDesc->deviceLostCallbackInfo.callback, &DeviceLostCallback); |
| ASSERT_NE(apiDesc->deviceLostCallbackInfo.userdata, nullptr); |
| ASSERT_NE(apiDesc->deviceLostCallbackInfo.userdata, &userdata); |
| |
| // Call the callback so the test doesn't wait indefinitely. |
| api.CallAdapterRequestDeviceCallback(apiAdapter, WGPURequestDeviceStatus_Error, nullptr, |
| nullptr); |
| }))); |
| FlushClient(); |
| FlushFutures(); |
| ExpectWireCallbacksWhen([&](auto& mockCb) { |
| EXPECT_CALL(mockCb, Call).Times(1); |
| |
| FlushCallbacks(); |
| }); |
| } |
| |
| // Test that RequestDevice forwards the device information to the client. |
| TEST_P(WireAdapterTests, RequestDeviceSuccess) { |
| 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 = {}; |
| AdapterRequestDevice(adapter, &desc, this); |
| |
| // 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)); |
| |
| EXPECT_CALL(api, OnAdapterRequestDevice(apiAdapter, 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, DeviceGetLimits(apiDevice, NotNull())) |
| .WillOnce(WithArg<1>(Invoke([&](WGPUSupportedLimits* limits) { |
| *reinterpret_cast<wgpu::SupportedLimits*>(limits) = fakeLimits; |
| return WGPUStatus_Success; |
| }))); |
| |
| 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(); |
| FlushFutures(); |
| |
| wgpu::Device device; |
| // Expect the callback in the client and all the device information to match. |
| ExpectWireCallbacksWhen([&](auto& mockCb) { |
| EXPECT_CALL(mockCb, Call(WGPURequestDeviceStatus_Success, NotNull(), nullptr, this)) |
| .WillOnce(WithArg<1>(Invoke([&](WGPUDevice cDevice) { |
| device = wgpu::Device::Acquire(cDevice); |
| |
| wgpu::SupportedLimits limits; |
| EXPECT_EQ(device.GetLimits(&limits), wgpu::Status::Success); |
| 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); |
| } |
| }))); |
| FlushCallbacks(); |
| }); |
| |
| // 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); |
| FlushCallbacks(); |
| |
| 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, 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_P(WireAdapterTests, RequestFeatureUnsupportedByWire) { |
| 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 = {}; |
| AdapterRequestDevice(adapter, &desc, this); |
| |
| // 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(), _)) |
| .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(); |
| FlushFutures(); |
| |
| // Expect an error callback since the feature is not supported. |
| ExpectWireCallbacksWhen([&](auto& mockCb) { |
| EXPECT_CALL(mockCb, Call(WGPURequestDeviceStatus_Error, nullptr, NotNull(), this)).Times(1); |
| FlushCallbacks(); |
| }); |
| } |
| |
| // Test that RequestDevice errors forward to the client. |
| TEST_P(WireAdapterTests, RequestDeviceError) { |
| wgpu::DeviceDescriptor desc = {}; |
| AdapterRequestDevice(adapter, &desc, this); |
| |
| // Expect the server to receive the message. Then, mock an error. |
| EXPECT_CALL(api, OnAdapterRequestDevice(apiAdapter, NotNull(), _)) |
| .WillOnce(InvokeWithoutArgs([&] { |
| api.CallAdapterRequestDeviceCallback(apiAdapter, WGPURequestDeviceStatus_Error, nullptr, |
| "Request device failed"); |
| })); |
| FlushClient(); |
| FlushFutures(); |
| |
| // Expect the callback in the client. |
| ExpectWireCallbacksWhen([&](auto& mockCb) { |
| EXPECT_CALL(mockCb, Call(WGPURequestDeviceStatus_Error, nullptr, |
| StrEq("Request device failed"), this)) |
| .Times(1); |
| FlushCallbacks(); |
| }); |
| } |
| |
| // Test that RequestDevice can complete successfully even if the adapter is deleted |
| // before the callback happens. |
| TEST_P(WireAdapterTests, RequestDeviceAdapterDestroyedBeforeCallback) { |
| wgpu::DeviceDescriptor desc = {}; |
| AdapterRequestDevice(adapter, &desc, this); |
| adapter = nullptr; |
| |
| wgpu::SupportedLimits fakeLimits = {}; |
| fakeLimits.limits.maxTextureDimension1D = 433; |
| fakeLimits.limits.maxVertexAttributes = 1243; |
| |
| std::initializer_list<wgpu::FeatureName> fakeFeatures = { |
| wgpu::FeatureName::Depth32FloatStencil8, |
| wgpu::FeatureName::TextureCompressionBC, |
| }; |
| |
| // Mock a reply from the server. |
| WGPUDevice apiDevice = api.GetNewDevice(); |
| EXPECT_CALL(api, OnAdapterRequestDevice(apiAdapter, 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, DeviceGetLimits(apiDevice, NotNull())) |
| .WillOnce(WithArg<1>(Invoke([&](WGPUSupportedLimits* limits) { |
| *reinterpret_cast<wgpu::SupportedLimits*>(limits) = fakeLimits; |
| return WGPUStatus_Success; |
| }))); |
| |
| 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); |
| })); |
| EXPECT_CALL(api, AdapterRelease(apiAdapter)); |
| FlushClient(); |
| FlushFutures(); |
| |
| wgpu::Device device; |
| // Expect the callback in the client. |
| ExpectWireCallbacksWhen([&](auto& mockCb) { |
| EXPECT_CALL(mockCb, Call(WGPURequestDeviceStatus_Success, NotNull(), nullptr, this)) |
| .WillOnce(WithArg<1>( |
| Invoke([&](WGPUDevice cDevice) { device = wgpu::Device::Acquire(cDevice); }))); |
| FlushCallbacks(); |
| }); |
| |
| 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, DeviceRelease(apiDevice)); |
| FlushClient(); |
| } |
| |
| // Test that RequestDevice receives unknown status if the wire is disconnected |
| // before the callback happens. |
| TEST_P(WireAdapterTests, RequestDeviceWireDisconnectedBeforeCallback) { |
| wgpu::DeviceDescriptor desc = {}; |
| AdapterRequestDevice(adapter, &desc, this); |
| |
| ExpectWireCallbacksWhen([&](auto& mockCb) { |
| EXPECT_CALL(mockCb, Call(WGPURequestDeviceStatus_InstanceDropped, nullptr, NotNull(), this)) |
| .Times(1); |
| |
| GetWireClient()->Disconnect(); |
| }); |
| } |
| |
| } // anonymous namespace |
| } // namespace dawn::wire |