| // Copyright 2020 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/unittests/wire/WireTest.h" |
| |
| #include "common/Assert.h" |
| #include "dawn/dawn_proc.h" |
| #include "dawn_wire/WireClient.h" |
| #include "dawn_wire/WireServer.h" |
| #include "tests/MockCallback.h" |
| #include "utils/TerribleCommandBuffer.h" |
| |
| #include <array> |
| |
| using namespace testing; |
| using namespace dawn_wire; |
| |
| class WireMultipleDeviceTests : public testing::Test { |
| protected: |
| void SetUp() override { |
| dawnProcSetProcs(&dawn_wire::client::GetProcs()); |
| } |
| |
| void TearDown() override { |
| dawnProcSetProcs(nullptr); |
| } |
| |
| class WireHolder { |
| public: |
| WireHolder() { |
| DawnProcTable mockProcs; |
| mApi.GetProcTableAndDevice(&mockProcs, &mServerDevice); |
| |
| // Ignore Tick() |
| EXPECT_CALL(mApi, DeviceTick(_)).Times(AnyNumber()); |
| |
| // This SetCallback call cannot be ignored because it is done as soon as we start the |
| // server |
| EXPECT_CALL(mApi, OnDeviceSetUncapturedErrorCallback(_, _, _)).Times(Exactly(1)); |
| EXPECT_CALL(mApi, OnDeviceSetDeviceLostCallback(_, _, _)).Times(Exactly(1)); |
| |
| mS2cBuf = std::make_unique<utils::TerribleCommandBuffer>(); |
| mC2sBuf = std::make_unique<utils::TerribleCommandBuffer>(); |
| |
| WireServerDescriptor serverDesc = {}; |
| serverDesc.device = mServerDevice; |
| serverDesc.procs = &mockProcs; |
| serverDesc.serializer = mS2cBuf.get(); |
| |
| EXPECT_CALL(mApi, DeviceReference(mServerDevice)); |
| mWireServer.reset(new WireServer(serverDesc)); |
| mC2sBuf->SetHandler(mWireServer.get()); |
| |
| WireClientDescriptor clientDesc = {}; |
| clientDesc.serializer = mC2sBuf.get(); |
| |
| mWireClient.reset(new WireClient(clientDesc)); |
| mS2cBuf->SetHandler(mWireClient.get()); |
| |
| mClientDevice = mWireClient->GetDevice(); |
| |
| // The GetDefaultQueue is done on WireClient startup so we expect it now. |
| mClientQueue = wgpuDeviceGetDefaultQueue(mClientDevice); |
| mServerQueue = mApi.GetNewQueue(); |
| EXPECT_CALL(mApi, DeviceGetDefaultQueue(mServerDevice)).WillOnce(Return(mServerQueue)); |
| FlushClient(); |
| } |
| |
| ~WireHolder() { |
| mApi.IgnoreAllReleaseCalls(); |
| mWireClient = nullptr; |
| |
| // These are called on server destruction to clear the callbacks. They must not be |
| // called after the server is destroyed. |
| EXPECT_CALL(mApi, OnDeviceSetUncapturedErrorCallback(mServerDevice, nullptr, nullptr)) |
| .Times(Exactly(1)); |
| EXPECT_CALL(mApi, OnDeviceSetDeviceLostCallback(mServerDevice, nullptr, nullptr)) |
| .Times(Exactly(1)); |
| mWireServer = nullptr; |
| } |
| |
| void FlushClient(bool success = true) { |
| ASSERT_EQ(mC2sBuf->Flush(), success); |
| } |
| |
| void FlushServer(bool success = true) { |
| ASSERT_EQ(mS2cBuf->Flush(), success); |
| } |
| |
| testing::StrictMock<MockProcTable>* Api() { |
| return &mApi; |
| } |
| |
| WGPUDevice ClientDevice() { |
| return mClientDevice; |
| } |
| |
| WGPUDevice ServerDevice() { |
| return mServerDevice; |
| } |
| |
| WGPUQueue ClientQueue() { |
| return mClientQueue; |
| } |
| |
| WGPUQueue ServerQueue() { |
| return mServerQueue; |
| } |
| |
| private: |
| testing::StrictMock<MockProcTable> mApi; |
| std::unique_ptr<dawn_wire::WireServer> mWireServer; |
| std::unique_ptr<dawn_wire::WireClient> mWireClient; |
| std::unique_ptr<utils::TerribleCommandBuffer> mS2cBuf; |
| std::unique_ptr<utils::TerribleCommandBuffer> mC2sBuf; |
| WGPUDevice mServerDevice; |
| WGPUDevice mClientDevice; |
| WGPUQueue mServerQueue; |
| WGPUQueue mClientQueue; |
| }; |
| |
| void ExpectInjectedError(WireHolder* wire) { |
| std::string errorMessage; |
| EXPECT_CALL(*wire->Api(), |
| DeviceInjectError(wire->ServerDevice(), WGPUErrorType_Validation, _)) |
| .WillOnce(Invoke([&](WGPUDevice device, WGPUErrorType type, const char* message) { |
| errorMessage = message; |
| // Mock the call to the error callback. |
| wire->Api()->CallDeviceSetUncapturedErrorCallbackCallback(device, type, message); |
| })); |
| wire->FlushClient(); |
| |
| // The error callback should be forwarded to the client. |
| StrictMock<MockCallback<WGPUErrorCallback>> mockErrorCallback; |
| wgpuDeviceSetUncapturedErrorCallback(wire->ClientDevice(), mockErrorCallback.Callback(), |
| mockErrorCallback.MakeUserdata(this)); |
| |
| EXPECT_CALL(mockErrorCallback, Call(WGPUErrorType_Validation, StrEq(errorMessage), this)) |
| .Times(1); |
| wire->FlushServer(); |
| } |
| }; |
| |
| // Test that using objects from a different device is a validation error. |
| TEST_F(WireMultipleDeviceTests, ValidatesSameDevice) { |
| WireHolder wireA; |
| WireHolder wireB; |
| |
| // Create the fence |
| WGPUFenceDescriptor desc = {}; |
| WGPUFence fenceA = wgpuQueueCreateFence(wireA.ClientQueue(), &desc); |
| |
| // Signal with a fence from a different wire. |
| wgpuQueueSignal(wireB.ClientQueue(), fenceA, 1u); |
| |
| // We should inject an error into the server. |
| ExpectInjectedError(&wireB); |
| } |
| |
| // Test that objects created from mixed devices are an error to use. |
| TEST_F(WireMultipleDeviceTests, DifferentDeviceObjectCreationIsError) { |
| WireHolder wireA; |
| WireHolder wireB; |
| |
| // Create a bind group layout on wire A. |
| WGPUBindGroupLayoutDescriptor bglDesc = {}; |
| WGPUBindGroupLayout bglA = wgpuDeviceCreateBindGroupLayout(wireA.ClientDevice(), &bglDesc); |
| EXPECT_CALL(*wireA.Api(), DeviceCreateBindGroupLayout(wireA.ServerDevice(), _)) |
| .WillOnce(Return(wireA.Api()->GetNewBindGroupLayout())); |
| |
| wireA.FlushClient(); |
| |
| std::array<WGPUBindGroupEntry, 2> entries = {}; |
| |
| // Create a buffer on wire A. |
| WGPUBufferDescriptor bufferDesc = {}; |
| entries[0].buffer = wgpuDeviceCreateBuffer(wireA.ClientDevice(), &bufferDesc); |
| EXPECT_CALL(*wireA.Api(), DeviceCreateBuffer(wireA.ServerDevice(), _)) |
| .WillOnce(Return(wireA.Api()->GetNewBuffer())); |
| |
| wireA.FlushClient(); |
| |
| // Create a sampler on wire B. |
| WGPUSamplerDescriptor samplerDesc = {}; |
| entries[1].sampler = wgpuDeviceCreateSampler(wireB.ClientDevice(), &samplerDesc); |
| EXPECT_CALL(*wireB.Api(), DeviceCreateSampler(wireB.ServerDevice(), _)) |
| .WillOnce(Return(wireB.Api()->GetNewSampler())); |
| |
| wireB.FlushClient(); |
| |
| // Create a bind group on wire A using the bgl (A), buffer (A), and sampler (B). |
| WGPUBindGroupDescriptor bgDesc = {}; |
| bgDesc.layout = bglA; |
| bgDesc.entryCount = entries.size(); |
| bgDesc.entries = entries.data(); |
| WGPUBindGroup bindGroupA = wgpuDeviceCreateBindGroup(wireA.ClientDevice(), &bgDesc); |
| |
| // It should inject an error because the sampler is from a different device. |
| ExpectInjectedError(&wireA); |
| |
| // The bind group was never created on a server because it failed device validation. |
| // Any commands that use it should error. |
| wgpuBindGroupRelease(bindGroupA); |
| wireA.FlushClient(false); |
| } |
| |
| // Test that using objects, included in an extension struct, |
| // from a difference device is a validation error. |
| TEST_F(WireMultipleDeviceTests, ValidatesSameDeviceInExtensionStruct) { |
| WireHolder wireA; |
| WireHolder wireB; |
| |
| WGPUShaderModuleDescriptor shaderModuleDesc = {}; |
| WGPUShaderModule shaderModuleA = |
| wgpuDeviceCreateShaderModule(wireA.ClientDevice(), &shaderModuleDesc); |
| |
| // Flush on wire A. We should see the shader module created. |
| EXPECT_CALL(*wireA.Api(), DeviceCreateShaderModule(wireA.ServerDevice(), _)) |
| .WillOnce(Return(wireA.Api()->GetNewShaderModule())); |
| wireA.FlushClient(); |
| |
| WGPURenderPipelineDescriptorDummyExtension extDesc = {}; |
| extDesc.chain.sType = WGPUSType_RenderPipelineDescriptorDummyExtension; |
| extDesc.dummyStage.entryPoint = "main"; |
| extDesc.dummyStage.module = shaderModuleA; |
| |
| WGPURenderPipelineDescriptor pipelineDesc = {}; |
| pipelineDesc.nextInChain = &extDesc.chain; |
| WGPURenderPipeline pipelineB = |
| wgpuDeviceCreateRenderPipeline(wireB.ClientDevice(), &pipelineDesc); |
| |
| // We should inject an error into the server. |
| ExpectInjectedError(&wireB); |
| |
| // The pipeline was never created on a server because it failed device validation. |
| // Any commands that use it should error. |
| wgpuRenderPipelineRelease(pipelineB); |
| wireB.FlushClient(false); |
| } |
| |
| // Test that using objects, included in a chained extension struct, |
| // from a different device is a validation error. |
| TEST_F(WireMultipleDeviceTests, ValidatesSameDeviceSecondInExtensionStructChain) { |
| WireHolder wireA; |
| WireHolder wireB; |
| |
| WGPUShaderModuleDescriptor shaderModuleDesc = {}; |
| WGPUShaderModule shaderModuleA = |
| wgpuDeviceCreateShaderModule(wireA.ClientDevice(), &shaderModuleDesc); |
| |
| // Flush on wire A. We should see the shader module created. |
| EXPECT_CALL(*wireA.Api(), DeviceCreateShaderModule(wireA.ServerDevice(), _)) |
| .WillOnce(Return(wireA.Api()->GetNewShaderModule())); |
| wireA.FlushClient(); |
| |
| WGPUShaderModule shaderModuleB = |
| wgpuDeviceCreateShaderModule(wireB.ClientDevice(), &shaderModuleDesc); |
| |
| // Flush on wire B. We should see the shader module created. |
| EXPECT_CALL(*wireB.Api(), DeviceCreateShaderModule(wireB.ServerDevice(), _)) |
| .WillOnce(Return(wireB.Api()->GetNewShaderModule())); |
| wireB.FlushClient(); |
| |
| WGPURenderPipelineDescriptorDummyExtension extDescA = {}; |
| extDescA.chain.sType = WGPUSType_RenderPipelineDescriptorDummyExtension; |
| extDescA.dummyStage.entryPoint = "main"; |
| extDescA.dummyStage.module = shaderModuleA; |
| |
| WGPURenderPipelineDescriptorDummyExtension extDescB = {}; |
| extDescB.chain.sType = WGPUSType_RenderPipelineDescriptorDummyExtension; |
| extDescB.chain.next = &extDescA.chain; |
| extDescB.dummyStage.entryPoint = "main"; |
| extDescB.dummyStage.module = shaderModuleB; |
| |
| // The first extension struct is from Device B, and the second is from A. |
| // We should validate the second struct, is from the same device. |
| WGPURenderPipelineDescriptor pipelineDesc = {}; |
| pipelineDesc.nextInChain = &extDescB.chain; |
| WGPURenderPipeline pipelineB = |
| wgpuDeviceCreateRenderPipeline(wireB.ClientDevice(), &pipelineDesc); |
| |
| // We should inject an error into the server. |
| ExpectInjectedError(&wireB); |
| |
| // The pipeline was never created on a server because it failed device validation. |
| // Any commands that use it should error. |
| wgpuRenderPipelineRelease(pipelineB); |
| wireB.FlushClient(false); |
| } |