Handle DeviceLost error

Handle DeviceLostCallback once DeviceLost error occurs.
Disallow any other commands or actions on device to happen after device
has been lost.

Bug: dawn:68
Change-Id: Icbbbadf278cae5e6213050d00439118789c863dc
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/12801
Commit-Queue: Natasha Lee <natlee@microsoft.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index b6201ce..0bfede2 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -204,6 +204,7 @@
     "src/dawn_native/DynamicUploader.h",
     "src/dawn_native/EncodingContext.cpp",
     "src/dawn_native/EncodingContext.h",
+    "src/dawn_native/Error.cpp",
     "src/dawn_native/Error.h",
     "src/dawn_native/ErrorData.cpp",
     "src/dawn_native/ErrorData.h",
@@ -916,6 +917,7 @@
     "src/tests/end2end/DebugMarkerTests.cpp",
     "src/tests/end2end/DepthStencilStateTests.cpp",
     "src/tests/end2end/DestroyTests.cpp",
+    "src/tests/end2end/DeviceLostTests.cpp",
     "src/tests/end2end/DrawIndexedIndirectTests.cpp",
     "src/tests/end2end/DrawIndexedTests.cpp",
     "src/tests/end2end/DrawIndirectTests.cpp",
diff --git a/dawn.json b/dawn.json
index c8a318ae..5a6fa82 100644
--- a/dawn.json
+++ b/dawn.json
@@ -612,6 +612,9 @@
                 "TODO": "enga@: Make this a Dawn extension"
             },
             {
+                "name": "lose for testing"
+            },
+            {
                 "name": "tick"
             },
             {
diff --git a/src/dawn_native/Buffer.cpp b/src/dawn_native/Buffer.cpp
index c57d23a..8e72bd7 100644
--- a/src/dawn_native/Buffer.cpp
+++ b/src/dawn_native/Buffer.cpp
@@ -17,6 +17,7 @@
 #include "common/Assert.h"
 #include "dawn_native/Device.h"
 #include "dawn_native/DynamicUploader.h"
+#include "dawn_native/ErrorData.h"
 #include "dawn_native/ValidationUtils_autogen.h"
 
 #include <cstdio>
@@ -350,6 +351,7 @@
     }
 
     MaybeError BufferBase::ValidateSetSubData(uint32_t start, uint32_t count) const {
+        DAWN_TRY(GetDevice()->ValidateIsAlive());
         DAWN_TRY(GetDevice()->ValidateObject(this));
 
         switch (mState) {
@@ -388,6 +390,7 @@
     }
 
     MaybeError BufferBase::ValidateMap(wgpu::BufferUsage requiredUsage) const {
+        DAWN_TRY(GetDevice()->ValidateIsAlive());
         DAWN_TRY(GetDevice()->ValidateObject(this));
 
         switch (mState) {
@@ -407,6 +410,7 @@
     }
 
     MaybeError BufferBase::ValidateUnmap() const {
+        DAWN_TRY(GetDevice()->ValidateIsAlive());
         DAWN_TRY(GetDevice()->ValidateObject(this));
 
         switch (mState) {
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index f5609dc..d2aab65 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -95,15 +95,20 @@
     }
 
     void DeviceBase::BaseDestructor() {
-        MaybeError err = WaitForIdleForDestruction();
-        if (err.IsError()) {
-            // Assert that errors are device loss so that we can continue with destruction
-            ASSERT(err.AcquireError()->GetType() == wgpu::ErrorType::DeviceLost);
+        if (mLossStatus != LossStatus::Alive) {
+            return;
         }
+        // Assert that errors are device loss so that we can continue with destruction
+        AssertAndIgnoreDeviceLossError(WaitForIdleForDestruction());
         Destroy();
+        mLossStatus = LossStatus::AlreadyLost;
     }
 
     void DeviceBase::HandleError(wgpu::ErrorType type, const char* message) {
+        if (type == wgpu::ErrorType::DeviceLost) {
+            HandleLoss(message);
+        }
+        // Still forward device loss to error scope so it can reject them all
         mCurrentErrorScope->HandleError(type, message);
     }
 
@@ -165,6 +170,33 @@
         return {};
     }
 
+    MaybeError DeviceBase::ValidateIsAlive() const {
+        if (DAWN_LIKELY(mLossStatus == LossStatus::Alive)) {
+            return {};
+        }
+        return DAWN_DEVICE_LOST_ERROR("Device is lost");
+    }
+
+    void DeviceBase::HandleLoss(const char* message) {
+        if (mLossStatus == LossStatus::AlreadyLost) {
+            return;
+        }
+
+        Destroy();
+        mLossStatus = LossStatus::AlreadyLost;
+
+        if (mDeviceLostCallback) {
+            mDeviceLostCallback(message, mDeviceLostUserdata);
+        }
+    }
+
+    void DeviceBase::LoseForTesting() {
+        mLossStatus = LossStatus::BeingLost;
+        // Assert that errors are device loss so that we can continue with destruction
+        AssertAndIgnoreDeviceLossError(WaitForIdleForDestruction());
+        HandleError(wgpu::ErrorType::DeviceLost, "Device lost for testing");
+    }
+
     AdapterBase* DeviceBase::GetAdapter() const {
         return mAdapter;
     }
@@ -563,8 +595,12 @@
     // Other Device API methods
 
     void DeviceBase::Tick() {
-        if (ConsumedError(TickImpl()))
+        if (ConsumedError(ValidateIsAlive())) {
             return;
+        }
+        if (ConsumedError(TickImpl())) {
+            return;
+        }
 
         {
             auto deferredResults = std::move(mDeferredCreateBufferMappedAsyncResults);
@@ -651,6 +687,7 @@
 
     MaybeError DeviceBase::CreateBindGroupInternal(BindGroupBase** result,
                                                    const BindGroupDescriptor* descriptor) {
+        DAWN_TRY(ValidateIsAlive());
         if (IsValidationEnabled()) {
             DAWN_TRY(ValidateBindGroupDescriptor(this, descriptor));
         }
@@ -661,6 +698,7 @@
     MaybeError DeviceBase::CreateBindGroupLayoutInternal(
         BindGroupLayoutBase** result,
         const BindGroupLayoutDescriptor* descriptor) {
+        DAWN_TRY(ValidateIsAlive());
         if (IsValidationEnabled()) {
             DAWN_TRY(ValidateBindGroupLayoutDescriptor(this, descriptor));
         }
@@ -670,6 +708,7 @@
 
     MaybeError DeviceBase::CreateBufferInternal(BufferBase** result,
                                                 const BufferDescriptor* descriptor) {
+        DAWN_TRY(ValidateIsAlive());
         if (IsValidationEnabled()) {
             DAWN_TRY(ValidateBufferDescriptor(this, descriptor));
         }
@@ -680,6 +719,7 @@
     MaybeError DeviceBase::CreateComputePipelineInternal(
         ComputePipelineBase** result,
         const ComputePipelineDescriptor* descriptor) {
+        DAWN_TRY(ValidateIsAlive());
         if (IsValidationEnabled()) {
             DAWN_TRY(ValidateComputePipelineDescriptor(this, descriptor));
         }
@@ -704,6 +744,7 @@
     MaybeError DeviceBase::CreatePipelineLayoutInternal(
         PipelineLayoutBase** result,
         const PipelineLayoutDescriptor* descriptor) {
+        DAWN_TRY(ValidateIsAlive());
         if (IsValidationEnabled()) {
             DAWN_TRY(ValidatePipelineLayoutDescriptor(this, descriptor));
         }
@@ -712,6 +753,7 @@
     }
 
     MaybeError DeviceBase::CreateQueueInternal(QueueBase** result) {
+        DAWN_TRY(ValidateIsAlive());
         DAWN_TRY_ASSIGN(*result, CreateQueueImpl());
         return {};
     }
@@ -719,6 +761,7 @@
     MaybeError DeviceBase::CreateRenderBundleEncoderInternal(
         RenderBundleEncoder** result,
         const RenderBundleEncoderDescriptor* descriptor) {
+        DAWN_TRY(ValidateIsAlive());
         if (IsValidationEnabled()) {
             DAWN_TRY(ValidateRenderBundleEncoderDescriptor(this, descriptor));
         }
@@ -729,6 +772,7 @@
     MaybeError DeviceBase::CreateRenderPipelineInternal(
         RenderPipelineBase** result,
         const RenderPipelineDescriptor* descriptor) {
+        DAWN_TRY(ValidateIsAlive());
         if (IsValidationEnabled()) {
             DAWN_TRY(ValidateRenderPipelineDescriptor(this, descriptor));
         }
@@ -761,6 +805,7 @@
 
     MaybeError DeviceBase::CreateSamplerInternal(SamplerBase** result,
                                                  const SamplerDescriptor* descriptor) {
+        DAWN_TRY(ValidateIsAlive());
         if (IsValidationEnabled()) {
             DAWN_TRY(ValidateSamplerDescriptor(this, descriptor));
         }
@@ -770,6 +815,7 @@
 
     MaybeError DeviceBase::CreateShaderModuleInternal(ShaderModuleBase** result,
                                                       const ShaderModuleDescriptor* descriptor) {
+        DAWN_TRY(ValidateIsAlive());
         if (IsValidationEnabled()) {
             DAWN_TRY(ValidateShaderModuleDescriptor(this, descriptor));
         }
@@ -779,6 +825,7 @@
 
     MaybeError DeviceBase::CreateSwapChainInternal(SwapChainBase** result,
                                                    const SwapChainDescriptor* descriptor) {
+        DAWN_TRY(ValidateIsAlive());
         if (IsValidationEnabled()) {
             DAWN_TRY(ValidateSwapChainDescriptor(this, descriptor));
         }
@@ -788,6 +835,7 @@
 
     MaybeError DeviceBase::CreateTextureInternal(TextureBase** result,
                                                  const TextureDescriptor* descriptor) {
+        DAWN_TRY(ValidateIsAlive());
         if (IsValidationEnabled()) {
             DAWN_TRY(ValidateTextureDescriptor(this, descriptor));
         }
@@ -798,6 +846,7 @@
     MaybeError DeviceBase::CreateTextureViewInternal(TextureViewBase** result,
                                                      TextureBase* texture,
                                                      const TextureViewDescriptor* descriptor) {
+        DAWN_TRY(ValidateIsAlive());
         DAWN_TRY(ValidateObject(texture));
         TextureViewDescriptor desc = GetTextureViewDescriptorWithDefaults(texture, descriptor);
         if (IsValidationEnabled()) {
diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h
index 9d0947d..6e27c3b 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -29,9 +29,6 @@
 #include <memory>
 
 namespace dawn_native {
-
-    using ErrorCallback = void (*)(const char* errorMessage, void* userData);
-
     class AdapterBase;
     class AttachmentState;
     class AttachmentStateBlueprint;
@@ -167,6 +164,9 @@
         void SetUncapturedErrorCallback(wgpu::ErrorCallback callback, void* userdata);
         void PushErrorScope(wgpu::ErrorFilter filter);
         bool PopErrorScope(wgpu::ErrorCallback callback, void* userdata);
+
+        MaybeError ValidateIsAlive() const;
+
         ErrorScope* GetCurrentErrorScope();
 
         void Reference();
@@ -189,6 +189,7 @@
         bool IsValidationEnabled() const;
         size_t GetLazyClearCountForTesting();
         void IncrementLazyClearCountForTesting();
+        void LoseForTesting();
 
       protected:
         void SetToggle(Toggle toggle, bool isEnabled);
@@ -196,6 +197,13 @@
         void BaseDestructor();
 
         std::unique_ptr<DynamicUploader> mDynamicUploader;
+        // LossStatus::Alive means the device is alive and can be used normally.
+        // LossStatus::BeingLost means the device is in the process of being lost and should not
+        //              accept any new commands.
+        // LossStatus::AlreadyLost means the device has been lost and can no longer be used,
+        //             all resources have been freed.
+        enum class LossStatus { Alive, BeingLost, AlreadyLost };
+        LossStatus mLossStatus = LossStatus::Alive;
 
       private:
         virtual ResultOrError<BindGroupBase*> CreateBindGroupImpl(
@@ -263,6 +271,7 @@
         // resources.
         virtual MaybeError WaitForIdleForDestruction() = 0;
 
+        void HandleLoss(const char* message);
         wgpu::DeviceLostCallback mDeviceLostCallback = nullptr;
         void* mDeviceLostUserdata;
 
diff --git a/src/dawn_native/Error.cpp b/src/dawn_native/Error.cpp
new file mode 100644
index 0000000..d1ca233
--- /dev/null
+++ b/src/dawn_native/Error.cpp
@@ -0,0 +1,27 @@
+// Copyright 2018 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 "dawn_native/Error.h"
+
+#include "dawn_native/ErrorData.h"
+#include "dawn_native/dawn_platform.h"
+
+namespace dawn_native {
+    void AssertAndIgnoreDeviceLossError(MaybeError maybeError) {
+        if (maybeError.IsError()) {
+            std::unique_ptr<ErrorData> errorData = maybeError.AcquireError();
+            ASSERT(errorData->GetType() == wgpu::ErrorType::DeviceLost);
+        }
+    }
+}  // namespace dawn_native
\ No newline at end of file
diff --git a/src/dawn_native/Error.h b/src/dawn_native/Error.h
index 7f9dbcb..87ac540 100644
--- a/src/dawn_native/Error.h
+++ b/src/dawn_native/Error.h
@@ -81,6 +81,9 @@
     for (;;)                                                                  \
     break
 
+    // Assert that errors are device loss so that we can continue with destruction
+    void AssertAndIgnoreDeviceLossError(MaybeError maybeError);
+
 }  // namespace dawn_native
 
 #endif  // DAWNNATIVE_ERROR_H_
diff --git a/src/dawn_native/ErrorData.h b/src/dawn_native/ErrorData.h
index 27004de..5d74d36 100644
--- a/src/dawn_native/ErrorData.h
+++ b/src/dawn_native/ErrorData.h
@@ -28,7 +28,6 @@
 }
 
 namespace dawn_native {
-
     enum class InternalErrorType : uint32_t;
 
     class ErrorData {
diff --git a/src/dawn_native/Queue.cpp b/src/dawn_native/Queue.cpp
index 0fbcdc7..803f299 100644
--- a/src/dawn_native/Queue.cpp
+++ b/src/dawn_native/Queue.cpp
@@ -34,6 +34,11 @@
 
     void QueueBase::Submit(uint32_t commandCount, CommandBufferBase* const* commands) {
         DeviceBase* device = GetDevice();
+        if (device->ConsumedError(device->ValidateIsAlive())) {
+            // If device is lost, don't let any commands be submitted
+            return;
+        }
+
         TRACE_EVENT0(device->GetPlatform(), General, "Queue::Submit");
         if (device->IsValidationEnabled() &&
             device->ConsumedError(ValidateSubmit(commandCount, commands))) {
diff --git a/src/dawn_native/Texture.cpp b/src/dawn_native/Texture.cpp
index 7b264c1..5ec3b1f2 100644
--- a/src/dawn_native/Texture.cpp
+++ b/src/dawn_native/Texture.cpp
@@ -503,6 +503,7 @@
     }
 
     MaybeError TextureBase::ValidateDestroy() const {
+        DAWN_TRY(GetDevice()->ValidateIsAlive());
         DAWN_TRY(GetDevice()->ValidateObject(this));
         return {};
     }
diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp
index 352fe00..6b76544 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -403,6 +403,8 @@
     }
 
     void Device::Destroy() {
+        ASSERT(mLossStatus != LossStatus::AlreadyLost);
+
         // Immediately forget about all pending commands
         mPendingCommands.Release();
 
diff --git a/src/dawn_native/metal/DeviceMTL.mm b/src/dawn_native/metal/DeviceMTL.mm
index 77faa4e..6fa63d3 100644
--- a/src/dawn_native/metal/DeviceMTL.mm
+++ b/src/dawn_native/metal/DeviceMTL.mm
@@ -277,6 +277,8 @@
     }
 
     void Device::Destroy() {
+        ASSERT(mLossStatus != LossStatus::AlreadyLost);
+
         [mCommandContext.AcquireCommands() release];
 
         mMapTracker = nullptr;
diff --git a/src/dawn_native/null/DeviceNull.cpp b/src/dawn_native/null/DeviceNull.cpp
index be074b6..c93de3f 100644
--- a/src/dawn_native/null/DeviceNull.cpp
+++ b/src/dawn_native/null/DeviceNull.cpp
@@ -166,6 +166,8 @@
     }
 
     void Device::Destroy() {
+        ASSERT(mLossStatus != LossStatus::AlreadyLost);
+
         mDynamicUploader = nullptr;
 
         mPendingOperations.clear();
diff --git a/src/dawn_native/opengl/DeviceGL.cpp b/src/dawn_native/opengl/DeviceGL.cpp
index d44bd52..b794ed7 100644
--- a/src/dawn_native/opengl/DeviceGL.cpp
+++ b/src/dawn_native/opengl/DeviceGL.cpp
@@ -162,6 +162,8 @@
     }
 
     void Device::Destroy() {
+        ASSERT(mLossStatus != LossStatus::AlreadyLost);
+
         // Some operations might have been started since the last submit and waiting
         // on a serial that doesn't have a corresponding fence enqueued. Force all
         // operations to look as if they were completed (because they were).
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index 487e7ef..6b44d49 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -733,6 +733,8 @@
     }
 
     void Device::Destroy() {
+        ASSERT(mLossStatus != LossStatus::AlreadyLost);
+
         // Immediately tag the recording context as unused so we don't try to submit it in Tick.
         mRecordingContext.used = false;
         fn.DestroyCommandPool(mVkDevice, mRecordingContext.commandPool, nullptr);
@@ -741,7 +743,9 @@
         // on a serial that doesn't have a corresponding fence enqueued. Force all
         // operations to look as if they were completed (because they were).
         mCompletedSerial = mLastSubmittedSerial + 1;
-        Tick();
+
+        // Assert that errors are device loss so that we can continue with destruction
+        AssertAndIgnoreDeviceLossError(TickImpl());
 
         ASSERT(mCommandsInFlight.Empty());
         for (const CommandPoolAndBuffer& commands : mUnusedCommands) {
diff --git a/src/tests/DawnTest.cpp b/src/tests/DawnTest.cpp
index da7e575..5f5dba4 100644
--- a/src/tests/DawnTest.cpp
+++ b/src/tests/DawnTest.cpp
@@ -582,6 +582,7 @@
     queue = device.CreateQueue();
 
     device.SetUncapturedErrorCallback(OnDeviceError, this);
+    device.SetDeviceLostCallback(OnDeviceLost, this);
 }
 
 void DawnTestBase::TearDown() {
@@ -618,6 +619,10 @@
     self->mError = true;
 }
 
+void DawnTestBase::OnDeviceLost(const char* message, void* userdata) {
+    FAIL() << "Device Lost during test: " << message;
+}
+
 std::ostringstream& DawnTestBase::AddBufferExpectation(const char* file,
                                                        int line,
                                                        const wgpu::Buffer& buffer,
diff --git a/src/tests/DawnTest.h b/src/tests/DawnTest.h
index fb8825d..4378ac2 100644
--- a/src/tests/DawnTest.h
+++ b/src/tests/DawnTest.h
@@ -249,6 +249,7 @@
 
     // Tracking for validation errors
     static void OnDeviceError(WGPUErrorType type, const char* message, void* userdata);
+    static void OnDeviceLost(const char* message, void* userdata);
     bool mExpectError = false;
     bool mError = false;
 
diff --git a/src/tests/end2end/DeviceLostTests.cpp b/src/tests/end2end/DeviceLostTests.cpp
new file mode 100644
index 0000000..ab8f260
--- /dev/null
+++ b/src/tests/end2end/DeviceLostTests.cpp
@@ -0,0 +1,192 @@
+// Copyright 2019 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/DawnTest.h"
+
+#include <gmock/gmock.h>
+#include "utils/ComboRenderPipelineDescriptor.h"
+#include "utils/WGPUHelpers.h"
+
+#include <cstring>
+
+using namespace testing;
+
+class MockDeviceLostCallback {
+  public:
+    MOCK_METHOD2(Call, void(const char* message, void* userdata));
+};
+
+static std::unique_ptr<MockDeviceLostCallback> mockDeviceLostCallback;
+static void ToMockDeviceLostCallback(const char* message, void* userdata) {
+    mockDeviceLostCallback->Call(message, userdata);
+    DawnTestBase* self = static_cast<DawnTestBase*>(userdata);
+    self->StartExpectDeviceError();
+}
+
+class DeviceLostTest : public DawnTest {
+  protected:
+    void TestSetUp() override {
+        DAWN_SKIP_TEST_IF(UsesWire());
+        DawnTest::TestSetUp();
+        mockDeviceLostCallback = std::make_unique<MockDeviceLostCallback>();
+    }
+
+    void TearDown() override {
+        DawnTest::TearDown();
+        mockDeviceLostCallback = nullptr;
+    }
+
+    void SetCallbackAndLoseForTesting() {
+        device.SetDeviceLostCallback(ToMockDeviceLostCallback, this);
+        EXPECT_CALL(*mockDeviceLostCallback, Call(_, this)).Times(1);
+        device.LoseForTesting();
+    }
+};
+
+// Test that DeviceLostCallback is invoked when LostForTestimg is called
+TEST_P(DeviceLostTest, DeviceLostCallbackIsCalled) {
+    SetCallbackAndLoseForTesting();
+}
+
+// Test that submit fails when device is lost
+TEST_P(DeviceLostTest, SubmitFails) {
+    wgpu::CommandBuffer commands;
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    commands = encoder.Finish();
+
+    SetCallbackAndLoseForTesting();
+    ASSERT_DEVICE_ERROR(queue.Submit(0, &commands));
+}
+
+// Test that CreateBindGroupLayout fails when device is lost
+TEST_P(DeviceLostTest, CreateBindGroupLayoutFails) {
+    SetCallbackAndLoseForTesting();
+
+    wgpu::BindGroupLayoutBinding binding = {0, wgpu::ShaderStage::None,
+                                            wgpu::BindingType::UniformBuffer};
+    wgpu::BindGroupLayoutDescriptor descriptor;
+    descriptor.bindingCount = 1;
+    descriptor.bindings = &binding;
+    ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&descriptor));
+}
+
+// Test that CreateBindGroup fails when device is lost
+TEST_P(DeviceLostTest, CreateBindGroupFails) {
+    SetCallbackAndLoseForTesting();
+
+    wgpu::BindGroupBinding binding;
+    binding.binding = 0;
+    binding.sampler = nullptr;
+    binding.textureView = nullptr;
+    binding.buffer = nullptr;
+    binding.offset = 0;
+    binding.size = 0;
+
+    wgpu::BindGroupDescriptor descriptor;
+    descriptor.layout = nullptr;
+    descriptor.bindingCount = 1;
+    descriptor.bindings = &binding;
+    ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor));
+}
+
+// Test that CreatePipelineLayout fails when device is lost
+TEST_P(DeviceLostTest, CreatePipelineLayoutFails) {
+    SetCallbackAndLoseForTesting();
+
+    wgpu::PipelineLayoutDescriptor descriptor;
+    descriptor.bindGroupLayoutCount = 0;
+    descriptor.bindGroupLayouts = nullptr;
+    ASSERT_DEVICE_ERROR(device.CreatePipelineLayout(&descriptor));
+}
+
+// Tests that CreateRenderBundleEncoder fails when device is lost
+TEST_P(DeviceLostTest, CreateRenderBundleEncoderFails) {
+    SetCallbackAndLoseForTesting();
+
+    wgpu::RenderBundleEncoderDescriptor descriptor;
+    descriptor.colorFormatsCount = 0;
+    descriptor.colorFormats = nullptr;
+    ASSERT_DEVICE_ERROR(device.CreateRenderBundleEncoder(&descriptor));
+}
+
+// Tests that CreateComputePipeline fails when device is lost
+TEST_P(DeviceLostTest, CreateComputePipelineFails) {
+    SetCallbackAndLoseForTesting();
+
+    wgpu::ComputePipelineDescriptor descriptor;
+    descriptor.layout = nullptr;
+    descriptor.computeStage.module = nullptr;
+    descriptor.nextInChain = nullptr;
+    ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&descriptor));
+}
+
+// Tests that CreateRenderPipeline fails when device is lost
+TEST_P(DeviceLostTest, CreateRenderPipelineFails) {
+    SetCallbackAndLoseForTesting();
+
+    utils::ComboRenderPipelineDescriptor descriptor(device);
+    ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+}
+
+// Tests that CreateSampler fails when device is lost
+TEST_P(DeviceLostTest, CreateSamplerFails) {
+    SetCallbackAndLoseForTesting();
+
+    wgpu::SamplerDescriptor descriptor = utils::GetDefaultSamplerDescriptor();
+    ASSERT_DEVICE_ERROR(device.CreateSampler(&descriptor));
+}
+
+// Tests that CreateShaderModule fails when device is lost
+TEST_P(DeviceLostTest, CreateShaderModuleFails) {
+    SetCallbackAndLoseForTesting();
+
+    ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
+                #version 450
+                layout(location = 0) in vec4 color;
+                layout(location = 0) out vec4 fragColor;
+                void main() {
+                    fragColor = color;
+                })"));
+}
+
+// Tests that CreateSwapChain fails when device is lost
+TEST_P(DeviceLostTest, CreateSwapChainFails) {
+    SetCallbackAndLoseForTesting();
+
+    wgpu::SwapChainDescriptor descriptor;
+    descriptor.nextInChain = nullptr;
+    ASSERT_DEVICE_ERROR(device.CreateSwapChain(&descriptor));
+}
+
+// Tests that CreateTexture fails when device is lost
+TEST_P(DeviceLostTest, CreateTextureFails) {
+    SetCallbackAndLoseForTesting();
+
+    wgpu::TextureDescriptor descriptor;
+    descriptor.size.width = 4;
+    descriptor.size.height = 4;
+    descriptor.size.depth = 1;
+    descriptor.arrayLayerCount = 1;
+    descriptor.mipLevelCount = 1;
+    descriptor.dimension = wgpu::TextureDimension::e2D;
+    descriptor.usage = wgpu::TextureUsage::OutputAttachment;
+
+    ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
+}
+
+TEST_P(DeviceLostTest, TickFails) {
+    SetCallbackAndLoseForTesting();
+    ASSERT_DEVICE_ERROR(device.Tick());
+}
+DAWN_INSTANTIATE_TEST(DeviceLostTest, D3D12Backend, VulkanBackend);
\ No newline at end of file