| // Copyright 2023 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 <gmock/gmock.h> |
| #include <webgpu/webgpu.h> |
| |
| #include <algorithm> |
| #include <atomic> |
| #include <chrono> |
| #include <cstdint> |
| #include <utility> |
| #include <vector> |
| |
| #include "dawn/common/FutureUtils.h" |
| #include "dawn/tests/DawnTest.h" |
| #include "dawn/utils/SystemUtils.h" |
| #include "dawn/utils/WGPUHelpers.h" |
| #include "dawn/utils/WireHelper.h" |
| |
| namespace dawn { |
| namespace { |
| |
| using testing::AnyOf; |
| using testing::Eq; |
| |
| wgpu::Device CreateExtraDevice(utils::WireHelper* wireHelper, wgpu::Instance instance) { |
| // IMPORTANT: DawnTest overrides RequestAdapter and RequestDevice and mixes |
| // up the two instances. We use these to bypass the override. |
| auto* requestAdapter = reinterpret_cast<WGPUProcInstanceRequestAdapter>( |
| wgpu::GetProcAddress("wgpuInstanceRequestAdapter")); |
| auto* requestDevice = reinterpret_cast<WGPUProcAdapterRequestDevice>( |
| wgpu::GetProcAddress("wgpuAdapterRequestDevice")); |
| |
| bool flushSuccess = false; |
| |
| wgpu::Adapter adapter2; |
| requestAdapter(instance.Get(), nullptr, |
| {nullptr, WGPUCallbackMode_AllowSpontaneous, |
| [](WGPURequestAdapterStatus status, WGPUAdapter adapter, WGPUStringView, void*, |
| void* result) { |
| *reinterpret_cast<wgpu::Adapter*>(result) = wgpu::Adapter::Acquire(adapter); |
| }, |
| nullptr, &adapter2}); |
| flushSuccess = wireHelper->FlushClient(); |
| DAWN_ASSERT(flushSuccess); |
| DAWN_ASSERT(adapter2); |
| |
| wgpu::Device device2; |
| requestDevice(adapter2.Get(), nullptr, |
| {nullptr, WGPUCallbackMode_AllowSpontaneous, |
| [](WGPURequestDeviceStatus status, WGPUDevice device, WGPUStringView, void*, |
| void* result) { |
| ASSERT_EQ(status, WGPURequestDeviceStatus_Success); |
| *reinterpret_cast<wgpu::Device*>(result) = wgpu::Device::Acquire(device); |
| }, |
| nullptr, &device2}); |
| flushSuccess = wireHelper->FlushClient(); |
| DAWN_ASSERT(flushSuccess); |
| DAWN_ASSERT(device2); |
| |
| return device2; |
| } |
| |
| std::pair<wgpu::Instance, wgpu::Device> CreateExtraInstance(utils::WireHelper* wireHelper, |
| wgpu::InstanceDescriptor* desc) { |
| auto [instance2, nativeInstance] = wireHelper->CreateInstances(desc, desc); |
| |
| wgpu::Device device2 = CreateExtraDevice(wireHelper, instance2); |
| DAWN_ASSERT(device2); |
| |
| return std::pair(std::move(instance2), std::move(device2)); |
| } |
| |
| // EventCompletionTests |
| |
| enum class WaitType { |
| TimedWaitAny, |
| SpinWaitAny, |
| SpinProcessEvents, |
| Spin, |
| }; |
| |
| enum class WaitTypeAndCallbackMode { |
| TimedWaitAny_WaitAnyOnly, |
| TimedWaitAny_AllowSpontaneous, |
| SpinWaitAny_WaitAnyOnly, |
| SpinWaitAny_AllowSpontaneous, |
| SpinProcessEvents_AllowProcessEvents, |
| SpinProcessEvents_AllowSpontaneous, |
| Spin_AllowSpontaneous, |
| }; |
| |
| std::ostream& operator<<(std::ostream& o, WaitTypeAndCallbackMode waitMode) { |
| switch (waitMode) { |
| case WaitTypeAndCallbackMode::TimedWaitAny_WaitAnyOnly: |
| return o << "TimedWaitAny_WaitAnyOnly"; |
| case WaitTypeAndCallbackMode::SpinWaitAny_WaitAnyOnly: |
| return o << "SpinWaitAny_WaitAnyOnly"; |
| case WaitTypeAndCallbackMode::SpinProcessEvents_AllowProcessEvents: |
| return o << "SpinProcessEvents_AllowProcessEvents"; |
| case WaitTypeAndCallbackMode::TimedWaitAny_AllowSpontaneous: |
| return o << "TimedWaitAny_AllowSpontaneous"; |
| case WaitTypeAndCallbackMode::SpinWaitAny_AllowSpontaneous: |
| return o << "SpinWaitAny_AllowSpontaneous"; |
| case WaitTypeAndCallbackMode::SpinProcessEvents_AllowSpontaneous: |
| return o << "SpinProcessEvents_AllowSpontaneous"; |
| case WaitTypeAndCallbackMode::Spin_AllowSpontaneous: |
| return o << "Spin_AllowSpontaneous"; |
| } |
| } |
| |
| DAWN_TEST_PARAM_STRUCT(EventCompletionTestParams, WaitTypeAndCallbackMode); |
| |
| class EventCompletionTests : public DawnTestWithParams<EventCompletionTestParams> { |
| protected: |
| wgpu::Instance testInstance; |
| wgpu::Device testDevice; |
| wgpu::Queue testQueue; |
| std::vector<wgpu::FutureWaitInfo> mFutures; |
| std::atomic<uint64_t> mCallbacksCompletedCount = 0; |
| uint64_t mCallbacksIssuedCount = 0; |
| uint64_t mCallbacksWaitedCount = 0; |
| |
| void SetUp() override { |
| DawnTestWithParams::SetUp(); |
| WaitTypeAndCallbackMode mode = GetParam().mWaitTypeAndCallbackMode; |
| // TODO(crbug.com/412761228): Once spontaneous events are supported in the other |
| // backends, enable relevant tests for them as well. |
| if (!IsMetal()) { |
| // Spontaneous is only supported on Metal at the moment. |
| DAWN_TEST_UNSUPPORTED_IF(mode == WaitTypeAndCallbackMode::Spin_AllowSpontaneous); |
| if (UsesWire()) { |
| // Timed wait any is only supported on the wire if the native backend supports |
| // spontaneous. |
| DAWN_TEST_UNSUPPORTED_IF( |
| mode == WaitTypeAndCallbackMode::TimedWaitAny_WaitAnyOnly || |
| mode == WaitTypeAndCallbackMode::TimedWaitAny_AllowSpontaneous); |
| } |
| } |
| testInstance = GetInstance(); |
| testDevice = device; |
| testQueue = queue; |
| // Make sure these aren't used accidentally (unfortunately can't do the same for instance): |
| device = nullptr; |
| queue = nullptr; |
| } |
| |
| void UseSecondInstance() { |
| wgpu::InstanceDescriptor desc; |
| static constexpr auto kTimedWaitAny = wgpu::InstanceFeatureName::TimedWaitAny; |
| desc.requiredFeatureCount = 1; |
| desc.requiredFeatures = &kTimedWaitAny; |
| std::tie(testInstance, testDevice) = CreateExtraInstance(GetWireHelper(), &desc); |
| testQueue = testDevice.GetQueue(); |
| } |
| |
| void LoseTestDevice() { |
| EXPECT_CALL(mDeviceLostCallback, |
| Call(CHandleIs(testDevice.Get()), wgpu::DeviceLostReason::Unknown, testing::_)) |
| .Times(1); |
| testDevice.ForceLoss(wgpu::DeviceLostReason::Unknown, "Device lost for testing"); |
| testInstance.ProcessEvents(); |
| } |
| |
| void TrivialSubmit() { |
| wgpu::CommandBuffer cb = testDevice.CreateCommandEncoder().Finish(); |
| testQueue.Submit(1, &cb); |
| } |
| |
| wgpu::CallbackMode GetCallbackMode() { |
| switch (GetParam().mWaitTypeAndCallbackMode) { |
| case WaitTypeAndCallbackMode::TimedWaitAny_WaitAnyOnly: |
| case WaitTypeAndCallbackMode::SpinWaitAny_WaitAnyOnly: |
| return wgpu::CallbackMode::WaitAnyOnly; |
| case WaitTypeAndCallbackMode::SpinProcessEvents_AllowProcessEvents: |
| return wgpu::CallbackMode::AllowProcessEvents; |
| case WaitTypeAndCallbackMode::TimedWaitAny_AllowSpontaneous: |
| case WaitTypeAndCallbackMode::SpinWaitAny_AllowSpontaneous: |
| case WaitTypeAndCallbackMode::SpinProcessEvents_AllowSpontaneous: |
| case WaitTypeAndCallbackMode::Spin_AllowSpontaneous: |
| return wgpu::CallbackMode::AllowSpontaneous; |
| } |
| } |
| WaitType GetWaitType() { |
| switch (GetParam().mWaitTypeAndCallbackMode) { |
| case WaitTypeAndCallbackMode::TimedWaitAny_WaitAnyOnly: |
| case WaitTypeAndCallbackMode::TimedWaitAny_AllowSpontaneous: |
| return WaitType::TimedWaitAny; |
| case WaitTypeAndCallbackMode::SpinWaitAny_WaitAnyOnly: |
| case WaitTypeAndCallbackMode::SpinWaitAny_AllowSpontaneous: |
| return WaitType::SpinWaitAny; |
| case WaitTypeAndCallbackMode::SpinProcessEvents_AllowProcessEvents: |
| case WaitTypeAndCallbackMode::SpinProcessEvents_AllowSpontaneous: |
| return WaitType::SpinProcessEvents; |
| case WaitTypeAndCallbackMode::Spin_AllowSpontaneous: |
| return WaitType::Spin; |
| } |
| } |
| |
| bool IsSpontaneous() { return GetCallbackMode() == wgpu::CallbackMode::AllowSpontaneous; } |
| |
| void TrackForTest(wgpu::Future future) { |
| mCallbacksIssuedCount++; |
| |
| switch (GetParam().mWaitTypeAndCallbackMode) { |
| case WaitTypeAndCallbackMode::TimedWaitAny_WaitAnyOnly: |
| case WaitTypeAndCallbackMode::TimedWaitAny_AllowSpontaneous: |
| case WaitTypeAndCallbackMode::SpinWaitAny_WaitAnyOnly: |
| case WaitTypeAndCallbackMode::SpinWaitAny_AllowSpontaneous: |
| mFutures.push_back(wgpu::FutureWaitInfo{future, false}); |
| break; |
| case WaitTypeAndCallbackMode::SpinProcessEvents_AllowProcessEvents: |
| case WaitTypeAndCallbackMode::SpinProcessEvents_AllowSpontaneous: |
| case WaitTypeAndCallbackMode::Spin_AllowSpontaneous: |
| break; |
| } |
| } |
| |
| wgpu::Future OnSubmittedWorkDone(wgpu::QueueWorkDoneStatus expectedStatus) { |
| return testQueue.OnSubmittedWorkDone( |
| GetCallbackMode(), |
| [this, expectedStatus](wgpu::QueueWorkDoneStatus status, wgpu::StringView) { |
| mCallbacksCompletedCount++; |
| ASSERT_EQ(status, expectedStatus); |
| }); |
| } |
| |
| void TestWaitAll(bool loopOnlyOnce = false) { |
| switch (GetParam().mWaitTypeAndCallbackMode) { |
| case WaitTypeAndCallbackMode::TimedWaitAny_WaitAnyOnly: |
| case WaitTypeAndCallbackMode::TimedWaitAny_AllowSpontaneous: |
| return TestWaitImpl(WaitType::TimedWaitAny, loopOnlyOnce); |
| case WaitTypeAndCallbackMode::SpinWaitAny_WaitAnyOnly: |
| case WaitTypeAndCallbackMode::SpinWaitAny_AllowSpontaneous: |
| return TestWaitImpl(WaitType::SpinWaitAny, loopOnlyOnce); |
| case WaitTypeAndCallbackMode::SpinProcessEvents_AllowProcessEvents: |
| case WaitTypeAndCallbackMode::SpinProcessEvents_AllowSpontaneous: |
| return TestWaitImpl(WaitType::SpinProcessEvents, loopOnlyOnce); |
| case WaitTypeAndCallbackMode::Spin_AllowSpontaneous: |
| return TestWaitImpl(WaitType::Spin, loopOnlyOnce); |
| } |
| } |
| |
| private: |
| void TestWaitImpl(WaitType waitType, bool loopOnlyOnce = false) { |
| uint64_t oldCompletedCount = mCallbacksCompletedCount; |
| |
| const auto start = std::chrono::high_resolution_clock::now(); |
| auto testTimeExceeded = [=]() -> bool { |
| return std::chrono::high_resolution_clock::now() - start > std::chrono::seconds(5); |
| }; |
| |
| switch (waitType) { |
| case WaitType::TimedWaitAny: { |
| bool emptyWait = mFutures.size() == 0; |
| // Loop at least once so we can test it with 0 futures. |
| do { |
| ASSERT_FALSE(testTimeExceeded()); |
| wgpu::WaitStatus status; |
| |
| uint64_t oldCompletionCount = mCallbacksCompletedCount; |
| FlushWire(); |
| // Any futures should succeed within a few milliseconds at most. |
| status = testInstance.WaitAny(mFutures.size(), mFutures.data(), UINT64_MAX); |
| ASSERT_EQ(status, wgpu::WaitStatus::Success); |
| bool mayHaveCompletedEarly = IsSpontaneous(); |
| if (!mayHaveCompletedEarly && !emptyWait) { |
| ASSERT_GT(mCallbacksCompletedCount, oldCompletionCount); |
| } |
| |
| // Verify this succeeds instantly because some futures completed already. |
| status = testInstance.WaitAny(mFutures.size(), mFutures.data(), 0); |
| ASSERT_EQ(status, wgpu::WaitStatus::Success); |
| |
| RemoveCompletedFutures(); |
| if (loopOnlyOnce) { |
| break; |
| } |
| } while (mFutures.size() > 0); |
| } break; |
| case WaitType::SpinWaitAny: { |
| bool emptyWait = mFutures.size() == 0; |
| // Loop at least once so we can test it with 0 futures. |
| do { |
| ASSERT_FALSE(testTimeExceeded()); |
| |
| uint64_t oldCompletionCount = mCallbacksCompletedCount; |
| FlushWire(); |
| auto status = testInstance.WaitAny(mFutures.size(), mFutures.data(), 0); |
| if (status == wgpu::WaitStatus::TimedOut) { |
| continue; |
| } |
| ASSERT_TRUE(status == wgpu::WaitStatus::Success); |
| bool mayHaveCompletedEarly = IsSpontaneous(); |
| if (!mayHaveCompletedEarly && !emptyWait) { |
| ASSERT_GT(mCallbacksCompletedCount, oldCompletionCount); |
| } |
| |
| RemoveCompletedFutures(); |
| if (loopOnlyOnce) { |
| break; |
| } |
| } while (mFutures.size() > 0); |
| } break; |
| case WaitType::SpinProcessEvents: { |
| do { |
| ASSERT_FALSE(testTimeExceeded()); |
| |
| FlushWire(); |
| testInstance.ProcessEvents(); |
| |
| if (loopOnlyOnce) { |
| break; |
| } |
| } while (mCallbacksCompletedCount < mCallbacksIssuedCount); |
| } break; |
| case WaitType::Spin: { |
| do { |
| ASSERT_FALSE(testTimeExceeded()); |
| FlushWire(); |
| utils::USleep(100); |
| } while (mCallbacksCompletedCount < mCallbacksIssuedCount); |
| } break; |
| } |
| |
| if (!IsSpontaneous()) { |
| ASSERT_EQ(mCallbacksCompletedCount - oldCompletedCount, |
| mCallbacksIssuedCount - mCallbacksWaitedCount); |
| } |
| ASSERT_EQ(mCallbacksCompletedCount, mCallbacksIssuedCount); |
| mCallbacksWaitedCount = mCallbacksCompletedCount; |
| } |
| |
| void RemoveCompletedFutures() { |
| size_t oldSize = mFutures.size(); |
| if (oldSize > 0) { |
| std::erase_if(mFutures, |
| [](const wgpu::FutureWaitInfo& info) { return info.completed; }); |
| ASSERT_LT(mFutures.size(), oldSize); |
| } |
| } |
| }; |
| |
| // Wait when no events have been requested. |
| TEST_P(EventCompletionTests, NoEvents) { |
| TestWaitAll(); |
| } |
| |
| // WorkDone event after submitting some trivial work. |
| TEST_P(EventCompletionTests, WorkDoneSimple) { |
| TrivialSubmit(); |
| TrackForTest(OnSubmittedWorkDone(wgpu::QueueWorkDoneStatus::Success)); |
| TestWaitAll(); |
| } |
| |
| // WorkDone event before device loss, wait afterward. |
| TEST_P(EventCompletionTests, WorkDoneAcrossDeviceLoss) { |
| // TODO(crbug.com/469831341): Flaky on Snapdragon X Elite devices w/ D3D11. |
| DAWN_SUPPRESS_TEST_IF(IsWindows() && IsQualcomm() && IsD3D11()); |
| |
| TrivialSubmit(); |
| TrackForTest(OnSubmittedWorkDone(wgpu::QueueWorkDoneStatus::Success)); |
| TestWaitAll(); |
| } |
| |
| // WorkDone event after device loss. |
| TEST_P(EventCompletionTests, WorkDoneAfterDeviceLoss) { |
| TrivialSubmit(); |
| LoseTestDevice(); |
| TrackForTest(OnSubmittedWorkDone(wgpu::QueueWorkDoneStatus::Success)); |
| TestWaitAll(); |
| } |
| |
| // WorkDone event twice after submitting some trivial work. |
| TEST_P(EventCompletionTests, WorkDoneTwice) { |
| // TODO(crbug.com/413053623): Investigate crash on WebGPU on Metal. |
| DAWN_SUPPRESS_TEST_IF(IsWebGPUOn(wgpu::BackendType::Metal) && |
| GetParam().mWaitTypeAndCallbackMode == |
| WaitTypeAndCallbackMode::Spin_AllowSpontaneous); |
| |
| // TODO(crbug.com/469831341): Flaky on Snapdragon X Elite devices w/ D3D11. |
| DAWN_SUPPRESS_TEST_IF(IsWindows() && IsQualcomm() && IsD3D11()); |
| |
| TrivialSubmit(); |
| TrackForTest(OnSubmittedWorkDone(wgpu::QueueWorkDoneStatus::Success)); |
| TrackForTest(OnSubmittedWorkDone(wgpu::QueueWorkDoneStatus::Success)); |
| TestWaitAll(); |
| } |
| |
| // WorkDone event without ever having submitted any work. |
| TEST_P(EventCompletionTests, WorkDoneNoWork) { |
| TrackForTest(OnSubmittedWorkDone(wgpu::QueueWorkDoneStatus::Success)); |
| TestWaitAll(); |
| TrackForTest(OnSubmittedWorkDone(wgpu::QueueWorkDoneStatus::Success)); |
| TrackForTest(OnSubmittedWorkDone(wgpu::QueueWorkDoneStatus::Success)); |
| TestWaitAll(); |
| } |
| |
| // WorkDone event after all work has completed already. |
| TEST_P(EventCompletionTests, WorkDoneAlreadyCompleted) { |
| // TODO(crbug.com/469831341): Flaky on Snapdragon X Elite devices w/ D3D11. |
| DAWN_SUPPRESS_TEST_IF(IsWindows() && IsQualcomm() && IsD3D11()); |
| |
| TrivialSubmit(); |
| TrackForTest(OnSubmittedWorkDone(wgpu::QueueWorkDoneStatus::Success)); |
| TestWaitAll(); |
| TrackForTest(OnSubmittedWorkDone(wgpu::QueueWorkDoneStatus::Success)); |
| TestWaitAll(); |
| } |
| |
| // WorkDone events waited in reverse order. |
| TEST_P(EventCompletionTests, WorkDoneOutOfOrder) { |
| // With ProcessEvents or Spontaneous we can't control the order of completion. |
| DAWN_TEST_UNSUPPORTED_IF(GetCallbackMode() != wgpu::CallbackMode::WaitAnyOnly); |
| |
| TrivialSubmit(); |
| wgpu::Future f1 = OnSubmittedWorkDone(wgpu::QueueWorkDoneStatus::Success); |
| TrivialSubmit(); |
| wgpu::Future f2 = OnSubmittedWorkDone(wgpu::QueueWorkDoneStatus::Success); |
| |
| // When using WaitAny, normally callback ordering guarantees would guarantee f1 completes before |
| // f2. But if we wait on f2 first, then f2 is allowed to complete first because f1 still hasn't |
| // had an opportunity to complete. |
| TrackForTest(f2); |
| TestWaitAll(); |
| TrackForTest(f1); |
| TestWaitAll(/*loopOnlyOnce=*/true); |
| } |
| |
| // TODO(crbug.com/dawn/1987): |
| // - Test any reentrancy guarantees (for ProcessEvents or WaitAny inside a callback), |
| // to make sure things don't blow up and we don't attempt to hold locks recursively. |
| // - Other tests? |
| |
| DAWN_INSTANTIATE_TEST_P(EventCompletionTests, |
| {D3D11Backend(), D3D11Backend({"d3d11_use_unmonitored_fence"}), |
| D3D11Backend({"d3d11_disable_fence"}), |
| D3D11Backend({"d3d11_delay_flush_to_gpu"}), D3D12Backend(), MetalBackend(), |
| VulkanBackend(), OpenGLBackend(), OpenGLESBackend()}, |
| { |
| WaitTypeAndCallbackMode::TimedWaitAny_WaitAnyOnly, |
| WaitTypeAndCallbackMode::TimedWaitAny_AllowSpontaneous, |
| WaitTypeAndCallbackMode::SpinWaitAny_WaitAnyOnly, |
| WaitTypeAndCallbackMode::SpinWaitAny_AllowSpontaneous, |
| WaitTypeAndCallbackMode::SpinProcessEvents_AllowProcessEvents, |
| WaitTypeAndCallbackMode::SpinProcessEvents_AllowSpontaneous, |
| WaitTypeAndCallbackMode::Spin_AllowSpontaneous, |
| |
| // TODO(crbug.com/dawn/2059): The cases with the Spontaneous flag |
| // enabled were added before we implemented all of the spontaneous |
| // completions. They might accidentally be overly strict. |
| |
| // TODO(crbug.com/dawn/2059): Make guarantees that Spontaneous callbacks |
| // get called (as long as you're hitting "checkpoints"), and add the |
| // corresponding tests, for example: |
| // - SpinProcessEvents_Spontaneous, |
| // - SpinSubmit_Spontaneous, |
| // - SpinCheckpoint_Spontaneous (if wgpuDeviceCheckpoint is added). |
| // - (Note we don't want to guarantee Tick will process events - we |
| // could even test that it doesn't, if we make that true.) |
| }); |
| |
| // WaitAnyTests |
| |
| class WaitAnyTests : public DawnTest {}; |
| |
| TEST_P(WaitAnyTests, UnsupportedTimeout) { |
| wgpu::Instance instance2; |
| wgpu::Device device2; |
| |
| // When not using the wire, DawnTest will unconditionally enable TimedWaitAny since it's |
| // useful for other tests. For this test, we need it to be false to test validation. |
| wgpu::InstanceDescriptor desc; |
| std::tie(instance2, device2) = CreateExtraInstance(GetWireHelper(), &desc); |
| |
| // UnsupportedTimeout is still validated if no futures are passed. |
| for (uint64_t timeout : {uint64_t(1), uint64_t(0), UINT64_MAX}) { |
| ASSERT_EQ(instance2.WaitAny(0, nullptr, timeout), |
| timeout > 0 ? wgpu::WaitStatus::Error : wgpu::WaitStatus::Success); |
| } |
| |
| for (uint64_t timeout : {uint64_t(1), uint64_t(0), UINT64_MAX}) { |
| wgpu::WaitStatus status = |
| instance2.WaitAny(device2.GetQueue().OnSubmittedWorkDone( |
| wgpu::CallbackMode::WaitAnyOnly, |
| [](wgpu::QueueWorkDoneStatus, wgpu::StringView) {}), |
| timeout); |
| if (timeout == 0) { |
| ASSERT_TRUE(status == wgpu::WaitStatus::Success || |
| status == wgpu::WaitStatus::TimedOut); |
| } else { |
| ASSERT_EQ(status, wgpu::WaitStatus::Error); |
| } |
| } |
| } |
| |
| TEST_P(WaitAnyTests, UnsupportedCount) { |
| // TODO(crbug.com/474391710): Flaky on Snapdragon X Elite w/ D3D11. |
| DAWN_SUPPRESS_TEST_IF(IsWindows() && IsQualcomm() && IsD3D11()); |
| |
| for (uint64_t timeout : {uint64_t(0), uint64_t(1)}) { |
| // We don't support values higher than the default (64), and if you ask for lower than 64 |
| // you still get 64. DawnTest doesn't request anything (so requests 0) so gets 64. |
| for (size_t count : {kTimedWaitAnyMaxCountDefault, kTimedWaitAnyMaxCountDefault + 1}) { |
| std::vector<wgpu::FutureWaitInfo> infos; |
| for (size_t i = 0; i < count; ++i) { |
| infos.push_back({queue.OnSubmittedWorkDone( |
| wgpu::CallbackMode::WaitAnyOnly, |
| [](wgpu::QueueWorkDoneStatus, wgpu::StringView) {})}); |
| } |
| FlushWire(); |
| wgpu::WaitStatus status = instance.WaitAny(infos.size(), infos.data(), timeout); |
| if (timeout == 0) { |
| ASSERT_NE(status, wgpu::WaitStatus::Error); |
| } else if (count <= 64) { |
| ASSERT_NE(status, wgpu::WaitStatus::Error); |
| } else { |
| ASSERT_EQ(status, wgpu::WaitStatus::Error); |
| } |
| } |
| } |
| } |
| |
| TEST_P(WaitAnyTests, UnsupportedMixedSources) { |
| wgpu::Device device1 = device; |
| wgpu::Device device2 = CreateDevice(); |
| |
| wgpu::Queue queue1 = queue; |
| wgpu::Queue queue2 = device2.GetQueue(); |
| |
| for (uint64_t timeout : {uint64_t(0), uint64_t(1)}) { |
| std::vector<wgpu::FutureWaitInfo> infos{{ |
| {queue1.OnSubmittedWorkDone(wgpu::CallbackMode::WaitAnyOnly, |
| [](wgpu::QueueWorkDoneStatus, wgpu::StringView) {})}, |
| {queue2.OnSubmittedWorkDone(wgpu::CallbackMode::WaitAnyOnly, |
| [](wgpu::QueueWorkDoneStatus, wgpu::StringView) {})}, |
| }}; |
| FlushWire(); |
| wgpu::WaitStatus status = instance.WaitAny(infos.size(), infos.data(), timeout); |
| if (timeout == 0) { |
| ASSERT_TRUE(status == wgpu::WaitStatus::Success || |
| status == wgpu::WaitStatus::TimedOut); |
| } else if (UsesWire()) { |
| // Wire supports mixed source waiting. |
| ASSERT_TRUE(status == wgpu::WaitStatus::Success || |
| status == wgpu::WaitStatus::TimedOut); |
| } else { |
| ASSERT_EQ(status, wgpu::WaitStatus::Error); |
| } |
| } |
| } |
| |
| // Test that submitting multiple heavy works then waiting one by one works. |
| // This is a regression test for crbug.com/dawn/415561579 |
| TEST_P(WaitAnyTests, WaitHeavyWorksOneByOne) { |
| // Wire doesn't support timeouts unless its the Metal backend. |
| // TODO(crbug.com/412761228): Once spontaneous events are supported in the other backends, |
| // enable this test for them as well. |
| DAWN_TEST_UNSUPPORTED_IF(UsesWire() && !IsMetal()); |
| |
| wgpu::Buffer countBuffer; |
| wgpu::Buffer ssbo; |
| { |
| wgpu::BufferDescriptor descriptor; |
| descriptor.size = 4; |
| descriptor.usage = wgpu::BufferUsage::Storage; |
| ssbo = device.CreateBuffer(&descriptor); |
| |
| descriptor.usage = wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst; |
| countBuffer = device.CreateBuffer(&descriptor); |
| } |
| |
| wgpu::ComputePipeline pipeline; |
| { |
| wgpu::ComputePipelineDescriptor csDesc; |
| csDesc.compute.module = utils::CreateShaderModule(device, R"( |
| @group(0) @binding(0) var<uniform> count : u32; |
| @group(0) @binding(1) var<storage, read_write> ssbo : u32; |
| |
| @compute @workgroup_size(1) fn main() { |
| for (var i : u32 = 0; i < count; i++) { |
| ssbo += 1u; |
| } |
| })"); |
| |
| pipeline = device.CreateComputePipeline(&csDesc); |
| } |
| |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), |
| {{0, countBuffer, 0, 4}, {1, ssbo, 0, 4}}); |
| |
| auto HeavySubmit = [&]() { |
| uint32_t count = 1000000; |
| queue.WriteBuffer(countBuffer, 0, &count, 4); |
| |
| auto encoder = device.CreateCommandEncoder(); |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); |
| |
| pass.SetBindGroup(0, bindGroup); |
| pass.SetPipeline(pipeline); |
| pass.DispatchWorkgroups(1); |
| |
| pass.End(); |
| wgpu::CommandBuffer cb = encoder.Finish(); |
| queue.Submit(1, &cb); |
| }; |
| |
| std::vector<wgpu::Future> futures(5); |
| for (auto& future : futures) { |
| HeavySubmit(); |
| future = queue.OnSubmittedWorkDone(wgpu::CallbackMode::WaitAnyOnly, |
| [](wgpu::QueueWorkDoneStatus, wgpu::StringView) {}); |
| } |
| FlushWire(); |
| |
| for (const auto& future : futures) { |
| wgpu::WaitStatus status = instance.WaitAny(future, UINT64_MAX); |
| ASSERT_EQ(status, wgpu::WaitStatus::Success); |
| } |
| } |
| |
| DAWN_INSTANTIATE_TEST(WaitAnyTests, |
| D3D11Backend(), |
| D3D11Backend({"d3d11_use_unmonitored_fence"}), |
| D3D11Backend({"d3d11_disable_fence"}), |
| D3D11Backend({"d3d11_delay_flush_to_gpu"}), |
| D3D12Backend(), |
| MetalBackend(), |
| VulkanBackend(), |
| OpenGLBackend(), |
| OpenGLESBackend()); |
| |
| class FutureTests : public DawnTest {}; |
| |
| // Regression test for crbug.com/dawn/2460 where when we have mixed source futures in a process |
| // events call we were crashing. |
| TEST_P(FutureTests, MixedSourcePolling) { |
| // OnSubmittedWorkDone is implemented via a queue serial. |
| device.GetQueue().OnSubmittedWorkDone(wgpu::CallbackMode::AllowProcessEvents, |
| [](wgpu::QueueWorkDoneStatus, wgpu::StringView) {}); |
| |
| // PopErrorScope is implemented via a signal. |
| device.PushErrorScope(wgpu::ErrorFilter::Validation); |
| device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents, |
| [](wgpu::PopErrorScopeStatus, wgpu::ErrorType, wgpu::StringView) {}); |
| |
| instance.ProcessEvents(); |
| } |
| |
| DAWN_INSTANTIATE_TEST(FutureTests, |
| D3D11Backend(), |
| D3D11Backend({"d3d11_use_unmonitored_fence"}), |
| D3D11Backend({"d3d11_disable_fence"}), |
| D3D11Backend({"d3d11_delay_flush_to_gpu"}), |
| D3D12Backend(), |
| MetalBackend(), |
| VulkanBackend(), |
| OpenGLBackend(), |
| OpenGLESBackend()); |
| |
| } // anonymous namespace |
| } // namespace dawn |