| // Copyright 2019 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 <cstring> | |
| #include <memory> | |
| #include <string> | |
| #include "dawn/native/DawnNative.h" | |
| #include "gmock/gmock.h" | |
| #include "src/dawn/tests/DawnTest.h" | |
| #include "src/dawn/tests/MockCallback.h" | |
| #include "src/dawn/tests/StringViewMatchers.h" | |
| #include "src/dawn/utils/ComboRenderPipelineDescriptor.h" | |
| #include "src/dawn/utils/WGPUHelpers.h" | |
| namespace dawn { | |
| namespace { | |
| using testing::_; | |
| using testing::EmptySizedString; | |
| using testing::HasSubstr; | |
| using testing::MockCppCallback; | |
| using MockMapAsyncCallback = MockCppCallback<wgpu::BufferMapCallback<void>*>; | |
| using MockQueueWorkDoneCallback = MockCppCallback<wgpu::QueueWorkDoneCallback<void>*>; | |
| class DeviceLostTest : public DawnTest { | |
| protected: | |
| void GetRequiredLimits(const dawn::utils::ComboLimits& supported, | |
| dawn::utils::ComboLimits& required) override { | |
| // TODO(crbug.com/383593270): Enable all the limits. | |
| required.maxStorageBuffersInFragmentStage = supported.maxStorageBuffersInFragmentStage; | |
| required.maxStorageBuffersPerShaderStage = supported.maxStorageBuffersPerShaderStage; | |
| } | |
| void SetUp() override { DawnTest::SetUp(); } | |
| void TearDown() override { | |
| WaitABit(); | |
| DawnTest::TearDown(); | |
| } | |
| template <typename T> | |
| void ExpectObjectIsError(const T& object) { | |
| EXPECT_TRUE(dawn::native::CheckIsErrorForTesting(object.Get())); | |
| } | |
| MockQueueWorkDoneCallback mWorkDoneCb; | |
| MockMapAsyncCallback mMapAsyncCb; | |
| }; | |
| // Test that DeviceLostCallback is invoked when LostForTesting is called | |
| TEST_P(DeviceLostTest, DeviceLostCallbackIsCalled) { | |
| LoseDeviceForTesting(); | |
| } | |
| // Test that submit fails after the device is lost | |
| TEST_P(DeviceLostTest, SubmitAfterDeviceLost) { | |
| wgpu::CommandBuffer commands; | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| commands = encoder.Finish(); | |
| LoseDeviceForTesting(); | |
| queue.Submit(0, &commands); | |
| } | |
| // Test that CreateBindGroupLayout fails when device is lost | |
| TEST_P(DeviceLostTest, CreateBindGroupLayoutFails) { | |
| LoseDeviceForTesting(); | |
| wgpu::BindGroupLayoutEntry entry; | |
| entry.binding = 0; | |
| entry.visibility = wgpu::ShaderStage::None; | |
| entry.buffer.type = wgpu::BufferBindingType::Uniform; | |
| wgpu::BindGroupLayoutDescriptor descriptor; | |
| descriptor.entryCount = 1; | |
| descriptor.entries = &entry; | |
| ExpectObjectIsError(device.CreateBindGroupLayout(&descriptor)); | |
| } | |
| // Test that GetBindGroupLayout fails when device is lost | |
| TEST_P(DeviceLostTest, GetBindGroupLayoutFails) { | |
| wgpu::ShaderModule csModule = utils::CreateShaderModule(device, R"( | |
| struct UniformBuffer { | |
| pos : vec4f | |
| } | |
| @group(0) @binding(0) var<uniform> ubo : UniformBuffer; | |
| @compute @workgroup_size(1) fn main() { | |
| })"); | |
| wgpu::ComputePipelineDescriptor descriptor; | |
| descriptor.layout = nullptr; | |
| descriptor.compute.module = csModule; | |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&descriptor); | |
| LoseDeviceForTesting(); | |
| ExpectObjectIsError(pipeline.GetBindGroupLayout(0)); | |
| } | |
| // Test that CreateBindGroup fails when device is lost | |
| TEST_P(DeviceLostTest, CreateBindGroupFails) { | |
| wgpu::BindGroupLayout layout; | |
| { | |
| wgpu::BindGroupLayoutEntry entry; | |
| entry.binding = 0; | |
| entry.visibility = wgpu::ShaderStage::None; | |
| entry.buffer.type = wgpu::BufferBindingType::Uniform; | |
| wgpu::BindGroupLayoutDescriptor descriptor; | |
| descriptor.entryCount = 1; | |
| descriptor.entries = &entry; | |
| layout = device.CreateBindGroupLayout(&descriptor); | |
| } | |
| WaitABit(); | |
| LoseDeviceForTesting(); | |
| { | |
| wgpu::BindGroupEntry entry; | |
| entry.binding = 0; | |
| entry.sampler = nullptr; | |
| entry.textureView = nullptr; | |
| entry.buffer = nullptr; | |
| entry.offset = 0; | |
| entry.size = 0; | |
| wgpu::BindGroupDescriptor descriptor; | |
| descriptor.layout = layout; | |
| descriptor.entryCount = 1; | |
| descriptor.entries = &entry; | |
| ExpectObjectIsError(device.CreateBindGroup(&descriptor)); | |
| } | |
| } | |
| // Test that CreatePipelineLayout fails when device is lost | |
| TEST_P(DeviceLostTest, CreatePipelineLayoutFails) { | |
| LoseDeviceForTesting(); | |
| wgpu::PipelineLayoutDescriptor descriptor; | |
| descriptor.bindGroupLayoutCount = 0; | |
| descriptor.bindGroupLayouts = nullptr; | |
| ExpectObjectIsError(device.CreatePipelineLayout(&descriptor)); | |
| } | |
| // Tests that CreateRenderBundleEncoder fails when device is lost | |
| TEST_P(DeviceLostTest, CreateRenderBundleEncoderFails) { | |
| LoseDeviceForTesting(); | |
| wgpu::RenderBundleEncoderDescriptor descriptor; | |
| descriptor.colorFormatCount = 0; | |
| descriptor.colorFormats = nullptr; | |
| ExpectObjectIsError(device.CreateRenderBundleEncoder(&descriptor)); | |
| } | |
| // Tests that CreateComputePipeline fails when device is lost | |
| TEST_P(DeviceLostTest, CreateComputePipelineFails) { | |
| wgpu::ShaderModule shader = utils::CreateShaderModule(device, ""); | |
| WaitABit(); | |
| LoseDeviceForTesting(); | |
| wgpu::ComputePipelineDescriptor descriptor = {}; | |
| descriptor.layout = nullptr; | |
| descriptor.compute.module = shader; | |
| ExpectObjectIsError(device.CreateComputePipeline(&descriptor)); | |
| } | |
| // Tests that CreateRenderPipeline fails when device is lost | |
| TEST_P(DeviceLostTest, CreateRenderPipelineFails) { | |
| wgpu::ShaderModule shader = utils::CreateShaderModule(device, ""); | |
| WaitABit(); | |
| LoseDeviceForTesting(); | |
| utils::ComboRenderPipelineDescriptor descriptor = {}; | |
| descriptor.vertex.module = shader; | |
| descriptor.fragment = nullptr; | |
| ExpectObjectIsError(device.CreateRenderPipeline(&descriptor)); | |
| } | |
| // Tests that CreateSampler fails when device is lost | |
| TEST_P(DeviceLostTest, CreateSamplerFails) { | |
| LoseDeviceForTesting(); | |
| ExpectObjectIsError(device.CreateSampler()); | |
| } | |
| // Tests that CreateShaderModule fails when device is lost | |
| TEST_P(DeviceLostTest, CreateShaderModuleFails) { | |
| LoseDeviceForTesting(); | |
| ExpectObjectIsError(utils::CreateShaderModule(device, R"( | |
| @fragment | |
| fn main(@location(0) color : vec4f) -> @location(0) vec4f { | |
| return color; | |
| })")); | |
| } | |
| // Note that no device lost tests are done for surface because it is awkward to create a | |
| // wgpu::Surface in this file. SurfaceTests.GetAfterDeviceLoss covers this validation. | |
| // Tests that CreateTexture fails when device is lost | |
| TEST_P(DeviceLostTest, CreateTextureFails) { | |
| LoseDeviceForTesting(); | |
| wgpu::TextureDescriptor descriptor; | |
| descriptor.size.width = 4; | |
| descriptor.size.height = 4; | |
| descriptor.size.depthOrArrayLayers = 1; | |
| descriptor.mipLevelCount = 1; | |
| descriptor.dimension = wgpu::TextureDimension::e2D; | |
| descriptor.usage = wgpu::TextureUsage::RenderAttachment; | |
| ExpectObjectIsError(device.CreateTexture(&descriptor)); | |
| } | |
| // Test that CreateBuffer fails when device is lost | |
| TEST_P(DeviceLostTest, CreateBufferFails) { | |
| LoseDeviceForTesting(); | |
| wgpu::BufferDescriptor bufferDescriptor; | |
| bufferDescriptor.size = sizeof(float); | |
| bufferDescriptor.usage = wgpu::BufferUsage::CopySrc; | |
| ExpectObjectIsError(device.CreateBuffer(&bufferDescriptor)); | |
| } | |
| // Test that buffer.MapAsync for writing fails after device is lost | |
| TEST_P(DeviceLostTest, BufferMapAsyncFailsForWriting) { | |
| wgpu::BufferDescriptor bufferDescriptor; | |
| bufferDescriptor.size = 4; | |
| bufferDescriptor.usage = wgpu::BufferUsage::MapWrite; | |
| wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor); | |
| LoseDeviceForTesting(); | |
| EXPECT_CALL(mMapAsyncCb, Call(wgpu::MapAsyncStatus::Aborted, | |
| HasSubstr(UsesWire() ? "destroyed before mapping" : "is lost"))) | |
| .Times(1); | |
| buffer.MapAsync(wgpu::MapMode::Write, 0, 4, wgpu::CallbackMode::AllowProcessEvents, | |
| mMapAsyncCb.Callback()); | |
| } | |
| // Test that BufferMapAsync for writing calls back with success when device lost after | |
| // mapping | |
| TEST_P(DeviceLostTest, BufferMapAsyncBeforeLossFailsForWriting) { | |
| wgpu::BufferDescriptor bufferDescriptor; | |
| bufferDescriptor.size = 4; | |
| bufferDescriptor.usage = wgpu::BufferUsage::MapWrite; | |
| wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor); | |
| EXPECT_CALL(mMapAsyncCb, Call(wgpu::MapAsyncStatus::Success, _)).Times(1); | |
| buffer.MapAsync(wgpu::MapMode::Write, 0, 4, wgpu::CallbackMode::AllowProcessEvents, | |
| mMapAsyncCb.Callback()); | |
| LoseDeviceForTesting(); | |
| WaitForAllOperations(); | |
| } | |
| // Test that buffer.Unmap after device is lost | |
| TEST_P(DeviceLostTest, BufferUnmapAfterDeviceLost) { | |
| wgpu::BufferDescriptor bufferDescriptor; | |
| bufferDescriptor.size = sizeof(float); | |
| bufferDescriptor.usage = wgpu::BufferUsage::MapWrite; | |
| bufferDescriptor.mappedAtCreation = true; | |
| wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor); | |
| LoseDeviceForTesting(); | |
| buffer.Unmap(); | |
| } | |
| // Test CreateBuffer behavior after device is lost or destroyed | |
| TEST_P(DeviceLostTest, CreateBuffer) { | |
| // Fails on TSAN due to allowing too much memory. TSAN max is `0x10000000000` and the test | |
| // allocates `0x8000000000000000` | |
| DAWN_TEST_UNSUPPORTED_IF(IsTsan()); | |
| uint64_t kStupidLarge = uint64_t(1) << uint64_t(63); | |
| LoseDeviceForTesting(); | |
| // Each test either expects null or an ErrorBuffer. | |
| auto Test = [&](bool expectNull, wgpu::BufferDescriptor* desc) { | |
| wgpu::Buffer buffer = device.CreateBuffer(desc); | |
| if (expectNull) { | |
| EXPECT_EQ(nullptr, buffer.Get()); | |
| } else { | |
| ExpectObjectIsError(buffer); | |
| // Even if it's an ErrorBuffer, mappedAtCreation means it can be mapped. | |
| if (desc->mappedAtCreation) { | |
| EXPECT_NE(nullptr, buffer.GetMappedRange()); | |
| } | |
| } | |
| }; | |
| auto Tests = [&]() { | |
| for (auto usage : {wgpu::BufferUsage::MapWrite, wgpu::BufferUsage::CopyDst}) { | |
| wgpu::BufferDescriptor bufferDescriptor; | |
| bufferDescriptor.usage = usage; | |
| // Not mapped. Error is that the device is lost. | |
| bufferDescriptor.size = 4; | |
| bufferDescriptor.mappedAtCreation = false; | |
| Test(false, &bufferDescriptor); | |
| // Not mapped. Error is that the device is lost AND the size is too big. | |
| bufferDescriptor.size = kStupidLarge; | |
| bufferDescriptor.mappedAtCreation = false; | |
| Test(false, &bufferDescriptor); | |
| // Mapped at creation. Error is that the device is lost. | |
| bufferDescriptor.size = 4; | |
| bufferDescriptor.mappedAtCreation = true; | |
| Test(false, &bufferDescriptor); | |
| // Mapped at creation. Error is that the device is lost AND the size is too big. | |
| bufferDescriptor.size = kStupidLarge; | |
| bufferDescriptor.mappedAtCreation = true; | |
| Test(true, &bufferDescriptor); | |
| } | |
| }; | |
| Tests(); | |
| device.Destroy(); | |
| Tests(); | |
| } | |
| // Test that BufferMapAsync for reading fails after device is lost | |
| TEST_P(DeviceLostTest, BufferMapAsyncFailsForReading) { | |
| wgpu::BufferDescriptor bufferDescriptor; | |
| bufferDescriptor.size = 4; | |
| bufferDescriptor.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst; | |
| wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor); | |
| LoseDeviceForTesting(); | |
| EXPECT_CALL(mMapAsyncCb, Call(wgpu::MapAsyncStatus::Aborted, | |
| HasSubstr(UsesWire() ? "destroyed before mapping" : "is lost"))) | |
| .Times(1); | |
| buffer.MapAsync(wgpu::MapMode::Read, 0, 4, wgpu::CallbackMode::AllowProcessEvents, | |
| mMapAsyncCb.Callback()); | |
| } | |
| // Test that BufferMapAsync for reading calls back with success when device lost after | |
| // mapping | |
| TEST_P(DeviceLostTest, BufferMapAsyncBeforeLossFailsForReading) { | |
| wgpu::BufferDescriptor bufferDescriptor; | |
| bufferDescriptor.size = sizeof(float); | |
| bufferDescriptor.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst; | |
| wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor); | |
| EXPECT_CALL(mMapAsyncCb, Call(wgpu::MapAsyncStatus::Success, _)).Times(1); | |
| buffer.MapAsync(wgpu::MapMode::Read, 0, 4, wgpu::CallbackMode::AllowProcessEvents, | |
| mMapAsyncCb.Callback()); | |
| LoseDeviceForTesting(); | |
| WaitForAllOperations(); | |
| } | |
| // Test that WriteBuffer after device is lost | |
| TEST_P(DeviceLostTest, WriteBufferAfterDeviceLost) { | |
| wgpu::BufferDescriptor bufferDescriptor; | |
| bufferDescriptor.size = sizeof(float); | |
| bufferDescriptor.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst; | |
| wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor); | |
| LoseDeviceForTesting(); | |
| float data = 12.0f; | |
| queue.WriteBuffer(buffer, 0, &data, sizeof(data)); | |
| } | |
| // Test it's possible to GetMappedRange on a buffer created mapped after device loss | |
| TEST_P(DeviceLostTest, GetMappedRange_CreateBufferMappedAtCreationAfterLoss) { | |
| LoseDeviceForTesting(); | |
| wgpu::BufferDescriptor desc; | |
| desc.size = 4; | |
| desc.usage = wgpu::BufferUsage::CopySrc; | |
| desc.mappedAtCreation = true; | |
| wgpu::Buffer buffer = device.CreateBuffer(&desc); | |
| ExpectObjectIsError(buffer); | |
| ASSERT_NE(buffer.GetMappedRange(), nullptr); | |
| // Write to the range as it should still point to valid memory. | |
| *static_cast<uint32_t*>(buffer.GetMappedRange()) = 42; | |
| } | |
| // Test that device loss doesn't change the result of GetMappedRange, mappedAtCreation version. | |
| TEST_P(DeviceLostTest, GetMappedRange_CreateBufferMappedAtCreationBeforeLoss) { | |
| wgpu::BufferDescriptor desc; | |
| desc.size = 4; | |
| desc.usage = wgpu::BufferUsage::CopySrc; | |
| desc.mappedAtCreation = true; | |
| wgpu::Buffer buffer = device.CreateBuffer(&desc); | |
| void* rangeBeforeLoss = buffer.GetMappedRange(); | |
| LoseDeviceForTesting(); | |
| ASSERT_NE(buffer.GetMappedRange(), nullptr); | |
| ASSERT_EQ(buffer.GetMappedRange(), rangeBeforeLoss); | |
| // Write to the range as it should still point to valid memory. | |
| *static_cast<uint32_t*>(buffer.GetMappedRange()) = 42; | |
| } | |
| // Test that device loss doesn't change the result of GetMappedRange, mapping for reading version. | |
| TEST_P(DeviceLostTest, GetMappedRange_MapAsyncReading) { | |
| wgpu::BufferDescriptor desc; | |
| desc.size = 4; | |
| desc.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst; | |
| wgpu::Buffer buffer = device.CreateBuffer(&desc); | |
| MapAsyncAndWait(buffer, wgpu::MapMode::Read, 0, 4); | |
| queue.Submit(0, nullptr); | |
| const void* rangeBeforeLoss = buffer.GetConstMappedRange(); | |
| LoseDeviceForTesting(); | |
| ASSERT_NE(buffer.GetConstMappedRange(), nullptr); | |
| ASSERT_EQ(buffer.GetConstMappedRange(), rangeBeforeLoss); | |
| if (!IsNull()) { | |
| // Read from the range as it should still point to valid memory. Also check the value to | |
| // force the compiler to keep the read. The null backend doesn't do zero init so we skip | |
| // there. | |
| uint32_t zero = *static_cast<const uint32_t*>(buffer.GetConstMappedRange()); | |
| ASSERT_EQ(zero, 0u); | |
| } | |
| } | |
| // Test that device loss doesn't change the result of GetMappedRange, mapping for writing version. | |
| TEST_P(DeviceLostTest, GetMappedRange_MapAsyncWriting) { | |
| wgpu::BufferDescriptor desc; | |
| desc.size = 4; | |
| desc.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc; | |
| wgpu::Buffer buffer = device.CreateBuffer(&desc); | |
| MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, 4); | |
| queue.Submit(0, nullptr); | |
| const void* rangeBeforeLoss = buffer.GetConstMappedRange(); | |
| LoseDeviceForTesting(); | |
| ASSERT_NE(buffer.GetConstMappedRange(), nullptr); | |
| ASSERT_EQ(buffer.GetConstMappedRange(), rangeBeforeLoss); | |
| // Write to the range as it should still point to valid memory. | |
| *static_cast<uint32_t*>(buffer.GetMappedRange()) = 42; | |
| } | |
| // TODO(dawn:929): mapasync read + resolve + loss getmappedrange != nullptr. | |
| // TODO(dawn:929): mapasync write + resolve + loss getmappedrange != nullptr. | |
| // Test that Command Encoder Finish fails when device lost | |
| TEST_P(DeviceLostTest, CommandEncoderFinishFails) { | |
| wgpu::CommandBuffer commands; | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| LoseDeviceForTesting(); | |
| ExpectObjectIsError(encoder.Finish()); | |
| } | |
| // Test QueueOnSubmittedWorkDone after device is lost. | |
| TEST_P(DeviceLostTest, QueueOnSubmittedWorkDoneAfterDeviceLost) { | |
| LoseDeviceForTesting(); | |
| // Callback should have success status | |
| EXPECT_CALL(mWorkDoneCb, Call(wgpu::QueueWorkDoneStatus::Success, EmptySizedString())); | |
| queue.OnSubmittedWorkDone(wgpu::CallbackMode::AllowProcessEvents, mWorkDoneCb.Callback()); | |
| WaitABit(); | |
| } | |
| // Test QueueOnSubmittedWorkDone when the device is lost after calling OnSubmittedWorkDone | |
| TEST_P(DeviceLostTest, QueueOnSubmittedWorkDoneBeforeLossFails) { | |
| // Callback should have success status | |
| EXPECT_CALL(mWorkDoneCb, Call(wgpu::QueueWorkDoneStatus::Success, EmptySizedString())); | |
| queue.OnSubmittedWorkDone(wgpu::CallbackMode::AllowProcessEvents, mWorkDoneCb.Callback()); | |
| LoseDeviceForTesting(); | |
| WaitABit(); | |
| } | |
| // Test that LostForTesting can only be called on one time | |
| TEST_P(DeviceLostTest, LoseDeviceForTestingOnce) { | |
| // First LoseDeviceForTesting call should occur normally. The callback is already set in SetUp. | |
| LoseDeviceForTesting(); | |
| device.ForceLoss(wgpu::DeviceLostReason::Unknown, "Device lost for testing"); | |
| FlushWire(); | |
| testing::Mock::VerifyAndClearExpectations(&mDeviceLostCallback); | |
| } | |
| TEST_P(DeviceLostTest, DeviceLostDoesntCallUncapturedError) { | |
| // Since the device has a default error callback set that fails if it is called, we just need | |
| // to lose the device and verify no failures. | |
| LoseDeviceForTesting(); | |
| } | |
| // Test that WGPUCreatePipelineAsyncStatus_Success is returned when device is lost | |
| // before the callback of Create*PipelineAsync() is called. | |
| TEST_P(DeviceLostTest, DeviceLostBeforeCreatePipelineAsyncCallback) { | |
| wgpu::ShaderModule csModule = utils::CreateShaderModule(device, R"( | |
| @compute @workgroup_size(1) fn main() { | |
| })"); | |
| wgpu::ComputePipelineDescriptor descriptor; | |
| descriptor.compute.module = csModule; | |
| device.CreateComputePipelineAsync(&descriptor, wgpu::CallbackMode::AllowProcessEvents, | |
| [](wgpu::CreatePipelineAsyncStatus status, | |
| wgpu::ComputePipeline pipeline, wgpu::StringView) { | |
| EXPECT_EQ(wgpu::CreatePipelineAsyncStatus::Success, | |
| status); | |
| EXPECT_NE(pipeline, nullptr); | |
| }); | |
| LoseDeviceForTesting(); | |
| } | |
| // This is a regression test for crbug.com/1212385 where Dawn didn't clean up all | |
| // references to bind group layouts such that the cache was non-empty at the end | |
| // of shut down. | |
| TEST_P(DeviceLostTest, FreeBindGroupAfterDeviceLossWithPendingCommands) { | |
| DAWN_TEST_UNSUPPORTED_IF(GetSupportedLimits().maxStorageBuffersInFragmentStage < 1); | |
| wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Storage}}); | |
| wgpu::BufferDescriptor bufferDesc; | |
| bufferDesc.size = sizeof(float); | |
| bufferDesc.usage = wgpu::BufferUsage::Storage; | |
| wgpu::Buffer buffer = device.CreateBuffer(&bufferDesc); | |
| wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer, 0, sizeof(float)}}); | |
| // Advance the pending command serial. We only a need a couple of these to repro the bug, | |
| // but include extra so this does not become a change-detecting test if the specific serial | |
| // value is sensitive. | |
| queue.Submit(0, nullptr); | |
| queue.Submit(0, nullptr); | |
| queue.Submit(0, nullptr); | |
| queue.Submit(0, nullptr); | |
| queue.Submit(0, nullptr); | |
| queue.Submit(0, nullptr); | |
| LoseDeviceForTesting(); | |
| // Releasing the bing group places the bind group layout into a queue in the Vulkan backend | |
| // for recycling of descriptor sets. So, after these release calls there is still one last | |
| // reference to the BGL which wouldn't be freed until the pending serial passes. | |
| // Since the device is lost, destruction will clean up immediately without waiting for the | |
| // serial. The implementation needs to be sure to clear these BGL references. At the end of | |
| // Device shut down, we DAWN_ASSERT that the BGL cache is empty. | |
| bgl = nullptr; | |
| bg = nullptr; | |
| } | |
| // This is a regression test for crbug.com/1365011 where ending a render pass with an indirect draw | |
| // in it after the device is lost would cause render commands to be leaked. | |
| TEST_P(DeviceLostTest, DeviceLostInRenderPassWithDrawIndirect) { | |
| utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 4u, 4u); | |
| utils::ComboRenderPipelineDescriptor desc; | |
| desc.vertex.module = utils::CreateShaderModule(device, R"( | |
| @vertex fn main(@builtin(vertex_index) i : u32) -> @builtin(position) vec4f { | |
| var pos = array( | |
| vec2f(-1.0, -1.0), | |
| vec2f(3.0, -1.0), | |
| vec2f(-1.0, 3.0)); | |
| return vec4f(pos[i], 0.0, 1.0); | |
| } | |
| )"); | |
| desc.cFragment.module = utils::CreateShaderModule(device, R"( | |
| @fragment fn main() -> @location(0) vec4f { | |
| return vec4f(0.0, 1.0, 0.0, 1.0); | |
| } | |
| )"); | |
| desc.cTargets[0].format = renderPass.colorFormat; | |
| wgpu::Buffer indirectBuffer = | |
| utils::CreateBufferFromData<uint32_t>(device, wgpu::BufferUsage::Indirect, {3, 1, 0, 0}); | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&desc); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); | |
| pass.SetPipeline(pipeline); | |
| pass.DrawIndirect(indirectBuffer, 0); | |
| LoseDeviceForTesting(); | |
| pass.End(); | |
| } | |
| // Attempting to set an object label after device loss should not cause an error. | |
| TEST_P(DeviceLostTest, SetLabelAfterDeviceLoss) { | |
| std::string label = "test"; | |
| wgpu::BufferDescriptor descriptor; | |
| descriptor.size = 4; | |
| descriptor.usage = wgpu::BufferUsage::Uniform; | |
| wgpu::Buffer buffer = device.CreateBuffer(&descriptor); | |
| LoseDeviceForTesting(); | |
| buffer.SetLabel(label.c_str()); | |
| } | |
| DAWN_INSTANTIATE_TEST(DeviceLostTest, | |
| D3D11Backend(), | |
| D3D12Backend(), | |
| MetalBackend(), | |
| NullBackend(), | |
| OpenGLBackend(), | |
| OpenGLESBackend(), | |
| VulkanBackend(), | |
| WebGPUBackend()); | |
| } // anonymous namespace | |
| } // namespace dawn |