Implement the webgpu.h swapchains in the frontend and Null backend.

The state-tracking of the webgpu.h swapchain is a bit complicated
because contrary to implementation-based swapchains, they have more
guarantees and a "replacing mechanism". For example instead of hoping
the implementation-based swapchain resize automatically, the
surface-based swapchain needs to be replaced by a new swapchain and
invalidated.

This mechanism of invalidation also needs to be triggered when the last
reference to the surface is lost because we don't want to risk the
application destroying the window from under us.

Adds tests for all the cases of invalidation I could think of apart from
device loss.

Bug: dawn:269

Change-Id: Id515dbb640e13c6e30bb1f1e93b8e54f1e2bba4b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/15400
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index 391c393..81a3177 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -35,6 +35,7 @@
 #include "dawn_native/RenderPipeline.h"
 #include "dawn_native/Sampler.h"
 #include "dawn_native/ShaderModule.h"
+#include "dawn_native/Surface.h"
 #include "dawn_native/SwapChain.h"
 #include "dawn_native/Texture.h"
 #include "dawn_native/ValidationUtils_autogen.h"
@@ -833,7 +834,19 @@
             DAWN_TRY_ASSIGN(*result, CreateSwapChainImpl(descriptor));
         } else {
             ASSERT(descriptor->implementation == 0);
-            DAWN_TRY_ASSIGN(*result, CreateSwapChainImpl(surface, descriptor));
+
+            NewSwapChainBase* previousSwapChain = surface->GetAttachedSwapChain();
+            NewSwapChainBase* newSwapChain;
+            DAWN_TRY_ASSIGN(newSwapChain,
+                            CreateSwapChainImpl(surface, previousSwapChain, descriptor));
+
+            if (previousSwapChain != nullptr) {
+                ASSERT(!previousSwapChain->IsAttached());
+            }
+            ASSERT(newSwapChain->IsAttached());
+
+            surface->SetAttachedSwapChain(newSwapChain);
+            *result = newSwapChain;
         }
         return {};
     }
diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h
index cffb169..622c436 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -224,8 +224,10 @@
             const ShaderModuleDescriptor* descriptor) = 0;
         virtual ResultOrError<SwapChainBase*> CreateSwapChainImpl(
             const SwapChainDescriptor* descriptor) = 0;
-        virtual ResultOrError<SwapChainBase*> CreateSwapChainImpl(
+        // Note that previousSwapChain may be nullptr, or come from a different backend.
+        virtual ResultOrError<NewSwapChainBase*> CreateSwapChainImpl(
             Surface* surface,
+            NewSwapChainBase* previousSwapChain,
             const SwapChainDescriptor* descriptor) = 0;
         virtual ResultOrError<TextureBase*> CreateTextureImpl(
             const TextureDescriptor* descriptor) = 0;
diff --git a/src/dawn_native/Forward.h b/src/dawn_native/Forward.h
index 538c3f3..05538e8 100644
--- a/src/dawn_native/Forward.h
+++ b/src/dawn_native/Forward.h
@@ -42,6 +42,7 @@
     class ShaderModuleBase;
     class StagingBufferBase;
     class SwapChainBase;
+    class NewSwapChainBase;
     class TextureBase;
     class TextureViewBase;
 
diff --git a/src/dawn_native/Surface.cpp b/src/dawn_native/Surface.cpp
index fa90d78..eee9335 100644
--- a/src/dawn_native/Surface.cpp
+++ b/src/dawn_native/Surface.cpp
@@ -16,6 +16,7 @@
 
 #include "common/Platform.h"
 #include "dawn_native/Instance.h"
+#include "dawn_native/SwapChain.h"
 
 #if defined(DAWN_PLATFORM_WINDOWS)
 #    include "common/windows_with_undefs.h"
@@ -137,7 +138,20 @@
         }
     }
 
-    Surface::~Surface() = default;
+    Surface::~Surface() {
+        if (mSwapChain != nullptr) {
+            mSwapChain->DetachFromSurface();
+            mSwapChain = nullptr;
+        }
+    }
+
+    NewSwapChainBase* Surface::GetAttachedSwapChain() const {
+        return mSwapChain;
+    }
+
+    void Surface::SetAttachedSwapChain(NewSwapChainBase* swapChain) {
+        mSwapChain = swapChain;
+    }
 
     InstanceBase* Surface::GetInstance() {
         return mInstance.Get();
diff --git a/src/dawn_native/Surface.h b/src/dawn_native/Surface.h
index 8f9ca14..b33cf8a 100644
--- a/src/dawn_native/Surface.h
+++ b/src/dawn_native/Surface.h
@@ -29,11 +29,16 @@
     // A surface is a sum types of all the kind of windows Dawn supports. The OS-specific types
     // aren't used because they would cause compilation errors on other OSes (or require
     // ObjectiveC).
+    // The surface is also used to store the current swapchain so that we can detach it when it is
+    // replaced.
     class Surface final : public RefCounted {
       public:
         Surface(InstanceBase* instance, const SurfaceDescriptor* descriptor);
         ~Surface();
 
+        void SetAttachedSwapChain(NewSwapChainBase* swapChain);
+        NewSwapChainBase* GetAttachedSwapChain() const;
+
         // These are valid to call on all Surfaces.
         enum class Type { MetalLayer, WindowsHWND, Xlib };
         Type GetType() const;
@@ -54,6 +59,9 @@
         Ref<InstanceBase> mInstance;
         Type mType;
 
+        // The swapchain will set this to null when it is destroyed.
+        NewSwapChainBase* mSwapChain = nullptr;
+
         // MetalLayer
         void* mMetalLayer = nullptr;
 
diff --git a/src/dawn_native/SwapChain.cpp b/src/dawn_native/SwapChain.cpp
index 7118e94..1053aaf 100644
--- a/src/dawn_native/SwapChain.cpp
+++ b/src/dawn_native/SwapChain.cpp
@@ -15,6 +15,7 @@
 #include "dawn_native/SwapChain.h"
 
 #include "common/Constants.h"
+#include "dawn_native/Adapter.h"
 #include "dawn_native/Device.h"
 #include "dawn_native/Surface.h"
 #include "dawn_native/Texture.h"
@@ -96,6 +97,19 @@
         return {};
     }
 
+    TextureDescriptor GetSwapChainBaseTextureDescriptor(NewSwapChainBase* swapChain) {
+        TextureDescriptor desc;
+        desc.usage = swapChain->GetUsage();
+        desc.dimension = wgpu::TextureDimension::e2D;
+        desc.size = {swapChain->GetWidth(), swapChain->GetHeight(), 1};
+        desc.arrayLayerCount = 1;
+        desc.format = swapChain->GetFormat();
+        desc.mipLevelCount = 1;
+        desc.sampleCount = 1;
+
+        return desc;
+    }
+
     // SwapChainBase
 
     SwapChainBase::SwapChainBase(DeviceBase* device) : ObjectBase(device) {
@@ -246,6 +260,7 @@
                                        Surface* surface,
                                        const SwapChainDescriptor* descriptor)
         : SwapChainBase(device),
+          mAttached(true),
           mWidth(descriptor->width),
           mHeight(descriptor->height),
           mFormat(descriptor->format),
@@ -253,6 +268,25 @@
           mSurface(surface) {
     }
 
+    NewSwapChainBase::~NewSwapChainBase() {
+        if (mCurrentTextureView.Get() != nullptr) {
+            ASSERT(mCurrentTextureView->GetTexture()->GetTextureState() ==
+                   TextureBase::TextureState::Destroyed);
+        }
+
+        ASSERT(!mAttached);
+        ASSERT(mSurface == nullptr);
+    }
+
+    void NewSwapChainBase::DetachFromSurface() {
+        if (mAttached) {
+            DetachFromSurfaceImpl();
+            GetSurface()->SetAttachedSwapChain(nullptr);
+            mSurface = nullptr;
+            mAttached = false;
+        }
+    }
+
     void NewSwapChainBase::Configure(wgpu::TextureFormat format,
                                      wgpu::TextureUsage allowedUsage,
                                      uint32_t width,
@@ -262,14 +296,48 @@
     }
 
     TextureViewBase* NewSwapChainBase::GetCurrentTextureView() {
-        GetDevice()->ConsumedError(DAWN_VALIDATION_ERROR(
-            "GetCurrentTextureView not implemented yet for surface-based swapchains"));
-        return TextureViewBase::MakeError(GetDevice());
+        if (GetDevice()->ConsumedError(ValidateGetCurrentTextureView())) {
+            return TextureViewBase::MakeError(GetDevice());
+        }
+
+        if (mCurrentTextureView.Get() != nullptr) {
+            // Calling GetCurrentTextureView always returns a new reference so add it even when
+            // reusing the existing texture view.
+            mCurrentTextureView->Reference();
+            return mCurrentTextureView.Get();
+        }
+
+        TextureViewBase* view = nullptr;
+        if (GetDevice()->ConsumedError(GetCurrentTextureViewImpl(), &view)) {
+            return TextureViewBase::MakeError(GetDevice());
+        }
+
+        // Check that the return texture view matches exactly what was given for this descriptor.
+        ASSERT(view->GetTexture()->GetFormat().format == mFormat);
+        ASSERT((view->GetTexture()->GetUsage() & mUsage) == mUsage);
+        ASSERT(view->GetLevelCount() == 1);
+        ASSERT(view->GetLayerCount() == 1);
+        ASSERT(view->GetDimension() == wgpu::TextureViewDimension::e2D);
+        ASSERT(view->GetTexture()->GetMipLevelVirtualSize(view->GetBaseMipLevel()).width == mWidth);
+        ASSERT(view->GetTexture()->GetMipLevelVirtualSize(view->GetBaseMipLevel()).height ==
+               mHeight);
+
+        mCurrentTextureView = view;
+        return view;
     }
 
     void NewSwapChainBase::Present() {
-        GetDevice()->ConsumedError(
-            DAWN_VALIDATION_ERROR("Present not implemented yet for surface-based swapchains"));
+        if (GetDevice()->ConsumedError(ValidatePresent())) {
+            return;
+        }
+
+        if (GetDevice()->ConsumedError(PresentImpl())) {
+            return;
+        }
+
+        ASSERT(mCurrentTextureView->GetTexture()->GetTextureState() ==
+               TextureBase::TextureState::Destroyed);
+        mCurrentTextureView = nullptr;
     }
 
     uint32_t NewSwapChainBase::GetWidth() const {
@@ -289,7 +357,41 @@
     }
 
     Surface* NewSwapChainBase::GetSurface() {
-        return mSurface.Get();
+        return mSurface;
+    }
+
+    bool NewSwapChainBase::IsAttached() const {
+        return mAttached;
+    }
+
+    wgpu::BackendType NewSwapChainBase::GetBackendType() const {
+        return GetDevice()->GetAdapter()->GetBackendType();
+    }
+
+    MaybeError NewSwapChainBase::ValidatePresent() const {
+        DAWN_TRY(GetDevice()->ValidateIsAlive());
+        DAWN_TRY(GetDevice()->ValidateObject(this));
+
+        if (!mAttached) {
+            return DAWN_VALIDATION_ERROR("Presenting on detached swapchain");
+        }
+
+        if (mCurrentTextureView.Get() == nullptr) {
+            return DAWN_VALIDATION_ERROR("Presenting without prior GetCurrentTextureView");
+        }
+
+        return {};
+    }
+
+    MaybeError NewSwapChainBase::ValidateGetCurrentTextureView() const {
+        DAWN_TRY(GetDevice()->ValidateIsAlive());
+        DAWN_TRY(GetDevice()->ValidateObject(this));
+
+        if (!mAttached) {
+            return DAWN_VALIDATION_ERROR("Getting view on detached swapchain");
+        }
+
+        return {};
     }
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/SwapChain.h b/src/dawn_native/SwapChain.h
index cade83d..ff16e02 100644
--- a/src/dawn_native/SwapChain.h
+++ b/src/dawn_native/SwapChain.h
@@ -28,6 +28,8 @@
                                            const Surface* surface,
                                            const SwapChainDescriptor* descriptor);
 
+    TextureDescriptor GetSwapChainBaseTextureDescriptor(NewSwapChainBase* swapChain);
+
     class SwapChainBase : public ObjectBase {
       public:
         SwapChainBase(DeviceBase* device);
@@ -91,7 +93,24 @@
         NewSwapChainBase(DeviceBase* device,
                          Surface* surface,
                          const SwapChainDescriptor* descriptor);
+        ~NewSwapChainBase() override;
 
+        // This is called when the swapchain is detached for any reason:
+        //
+        //  - The swapchain is being destroyed.
+        //  - The surface it is attached to is being destroyed.
+        //  - The swapchain is being replaced by another one on the surface.
+        //
+        // The call for the old swapchain being replaced should be called inside the backend
+        // implementation of SwapChains. This is to allow them to acquire any resources before
+        // calling detach to make a seamless transition from the previous swapchain.
+        //
+        // Likewise the call for the swapchain being destroyed must be done in the backend's
+        // swapchain's destructor since C++ says it is UB to call virtual methods in the base class
+        // destructor.
+        void DetachFromSurface();
+
+        // Dawn API
         void Configure(wgpu::TextureFormat format,
                        wgpu::TextureUsage allowedUsage,
                        uint32_t width,
@@ -104,14 +123,36 @@
         wgpu::TextureFormat GetFormat() const;
         wgpu::TextureUsage GetUsage() const;
         Surface* GetSurface();
+        bool IsAttached() const;
+        wgpu::BackendType GetBackendType() const;
 
       private:
+        bool mAttached;
         uint32_t mWidth;
         uint32_t mHeight;
         wgpu::TextureFormat mFormat;
         wgpu::TextureUsage mUsage;
 
-        Ref<Surface> mSurface;
+        // This is a weak reference to the surface. If the surface is destroyed it will call
+        // DetachFromSurface and mSurface will be updated to nullptr.
+        Surface* mSurface = nullptr;
+        Ref<TextureViewBase> mCurrentTextureView;
+
+        MaybeError ValidatePresent() const;
+        MaybeError ValidateGetCurrentTextureView() const;
+
+        // GetCurrentTextureViewImpl and PresentImpl are guaranteed to be called in an interleaved
+        // manner, starting with GetCurrentTextureViewImpl.
+
+        // The returned texture view must match the swapchain descriptor exactly.
+        virtual ResultOrError<TextureViewBase*> GetCurrentTextureViewImpl() = 0;
+        // The call to present must destroy the current view's texture so further access to it are
+        // invalid.
+        virtual MaybeError PresentImpl() = 0;
+
+        // Guaranteed to be called exactly once during the lifetime of the SwapChain. After it is
+        // called no other virtual method can be called.
+        virtual void DetachFromSurfaceImpl() = 0;
     };
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp
index c019879..18734e6 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -254,8 +254,9 @@
         const SwapChainDescriptor* descriptor) {
         return new SwapChain(this, descriptor);
     }
-    ResultOrError<SwapChainBase*> Device::CreateSwapChainImpl(
+    ResultOrError<NewSwapChainBase*> Device::CreateSwapChainImpl(
         Surface* surface,
+        NewSwapChainBase* previousSwapChain,
         const SwapChainDescriptor* descriptor) {
         return DAWN_VALIDATION_ERROR("New swapchains not implemented.");
     }
diff --git a/src/dawn_native/d3d12/DeviceD3D12.h b/src/dawn_native/d3d12/DeviceD3D12.h
index 62a70c4..65edada 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.h
+++ b/src/dawn_native/d3d12/DeviceD3D12.h
@@ -122,8 +122,9 @@
             const ShaderModuleDescriptor* descriptor) override;
         ResultOrError<SwapChainBase*> CreateSwapChainImpl(
             const SwapChainDescriptor* descriptor) override;
-        ResultOrError<SwapChainBase*> CreateSwapChainImpl(
+        ResultOrError<NewSwapChainBase*> CreateSwapChainImpl(
             Surface* surface,
+            NewSwapChainBase* previousSwapChain,
             const SwapChainDescriptor* descriptor) override;
         ResultOrError<TextureBase*> CreateTextureImpl(const TextureDescriptor* descriptor) override;
         ResultOrError<TextureViewBase*> CreateTextureViewImpl(
diff --git a/src/dawn_native/metal/DeviceMTL.h b/src/dawn_native/metal/DeviceMTL.h
index e2000d0..d881ea0 100644
--- a/src/dawn_native/metal/DeviceMTL.h
+++ b/src/dawn_native/metal/DeviceMTL.h
@@ -85,8 +85,9 @@
             const ShaderModuleDescriptor* descriptor) override;
         ResultOrError<SwapChainBase*> CreateSwapChainImpl(
             const SwapChainDescriptor* descriptor) override;
-        ResultOrError<SwapChainBase*> CreateSwapChainImpl(
+        ResultOrError<NewSwapChainBase*> CreateSwapChainImpl(
             Surface* surface,
+            NewSwapChainBase* previousSwapChain,
             const SwapChainDescriptor* descriptor) override;
         ResultOrError<TextureBase*> CreateTextureImpl(const TextureDescriptor* descriptor) override;
         ResultOrError<TextureViewBase*> CreateTextureViewImpl(
diff --git a/src/dawn_native/metal/DeviceMTL.mm b/src/dawn_native/metal/DeviceMTL.mm
index d4a4e5c..830b99a 100644
--- a/src/dawn_native/metal/DeviceMTL.mm
+++ b/src/dawn_native/metal/DeviceMTL.mm
@@ -116,8 +116,9 @@
         const SwapChainDescriptor* descriptor) {
         return new SwapChain(this, descriptor);
     }
-    ResultOrError<SwapChainBase*> Device::CreateSwapChainImpl(
+    ResultOrError<NewSwapChainBase*> Device::CreateSwapChainImpl(
         Surface* surface,
+        NewSwapChainBase* previousSwapChain,
         const SwapChainDescriptor* descriptor) {
         return DAWN_VALIDATION_ERROR("New swapchains not implemented.");
     }
diff --git a/src/dawn_native/null/DeviceNull.cpp b/src/dawn_native/null/DeviceNull.cpp
index 579aec1..2d29183 100644
--- a/src/dawn_native/null/DeviceNull.cpp
+++ b/src/dawn_native/null/DeviceNull.cpp
@@ -153,10 +153,11 @@
         const SwapChainDescriptor* descriptor) {
         return new OldSwapChain(this, descriptor);
     }
-    ResultOrError<SwapChainBase*> Device::CreateSwapChainImpl(
+    ResultOrError<NewSwapChainBase*> Device::CreateSwapChainImpl(
         Surface* surface,
+        NewSwapChainBase* previousSwapChain,
         const SwapChainDescriptor* descriptor) {
-        return new SwapChain(this, surface, descriptor);
+        return new SwapChain(this, surface, previousSwapChain, descriptor);
     }
     ResultOrError<TextureBase*> Device::CreateTextureImpl(const TextureDescriptor* descriptor) {
         return new Texture(this, descriptor, TextureBase::TextureState::OwnedInternal);
@@ -358,11 +359,42 @@
 
     // SwapChain
 
-    SwapChain::SwapChain(Device* device, Surface* surface, const SwapChainDescriptor* descriptor)
+    SwapChain::SwapChain(Device* device,
+                         Surface* surface,
+                         NewSwapChainBase* previousSwapChain,
+                         const SwapChainDescriptor* descriptor)
         : NewSwapChainBase(device, surface, descriptor) {
+        if (previousSwapChain != nullptr) {
+            // TODO(cwallez@chromium.org): figure out what should happen when surfaces are used by
+            // multiple backends one after the other. It probably needs to block until the backend
+            // and GPU are completely finished with the previous swapchain.
+            ASSERT(previousSwapChain->GetBackendType() == wgpu::BackendType::Null);
+            previousSwapChain->DetachFromSurface();
+        }
     }
 
     SwapChain::~SwapChain() {
+        DetachFromSurface();
+    }
+
+    MaybeError SwapChain::PresentImpl() {
+        mTexture->Destroy();
+        mTexture = nullptr;
+        return {};
+    }
+
+    ResultOrError<TextureViewBase*> SwapChain::GetCurrentTextureViewImpl() {
+        TextureDescriptor textureDesc = GetSwapChainBaseTextureDescriptor(this);
+        mTexture = AcquireRef(
+            new Texture(GetDevice(), &textureDesc, TextureBase::TextureState::OwnedInternal));
+        return mTexture->CreateView(nullptr);
+    }
+
+    void SwapChain::DetachFromSurfaceImpl() {
+        if (mTexture.Get() != nullptr) {
+            mTexture->Destroy();
+            mTexture = nullptr;
+        }
     }
 
     // OldSwapChain
diff --git a/src/dawn_native/null/DeviceNull.h b/src/dawn_native/null/DeviceNull.h
index f7b8d56..dee944a 100644
--- a/src/dawn_native/null/DeviceNull.h
+++ b/src/dawn_native/null/DeviceNull.h
@@ -125,8 +125,9 @@
             const ShaderModuleDescriptor* descriptor) override;
         ResultOrError<SwapChainBase*> CreateSwapChainImpl(
             const SwapChainDescriptor* descriptor) override;
-        ResultOrError<SwapChainBase*> CreateSwapChainImpl(
+        ResultOrError<NewSwapChainBase*> CreateSwapChainImpl(
             Surface* surface,
+            NewSwapChainBase* previousSwapChain,
             const SwapChainDescriptor* descriptor) override;
         ResultOrError<TextureBase*> CreateTextureImpl(const TextureDescriptor* descriptor) override;
         ResultOrError<TextureViewBase*> CreateTextureViewImpl(
@@ -202,8 +203,18 @@
 
     class SwapChain : public NewSwapChainBase {
       public:
-        SwapChain(Device* device, Surface* surface, const SwapChainDescriptor* descriptor);
-        ~SwapChain();
+        SwapChain(Device* device,
+                  Surface* surface,
+                  NewSwapChainBase* previousSwapChain,
+                  const SwapChainDescriptor* descriptor);
+        ~SwapChain() override;
+
+      private:
+        Ref<Texture> mTexture;
+
+        MaybeError PresentImpl() override;
+        ResultOrError<TextureViewBase*> GetCurrentTextureViewImpl() override;
+        void DetachFromSurfaceImpl() override;
     };
 
     class OldSwapChain : public OldSwapChainBase {
diff --git a/src/dawn_native/opengl/DeviceGL.cpp b/src/dawn_native/opengl/DeviceGL.cpp
index 38f0309..f6c2e1a 100644
--- a/src/dawn_native/opengl/DeviceGL.cpp
+++ b/src/dawn_native/opengl/DeviceGL.cpp
@@ -96,8 +96,9 @@
         const SwapChainDescriptor* descriptor) {
         return new SwapChain(this, descriptor);
     }
-    ResultOrError<SwapChainBase*> Device::CreateSwapChainImpl(
+    ResultOrError<NewSwapChainBase*> Device::CreateSwapChainImpl(
         Surface* surface,
+        NewSwapChainBase* previousSwapChain,
         const SwapChainDescriptor* descriptor) {
         return DAWN_VALIDATION_ERROR("New swapchains not implemented.");
     }
diff --git a/src/dawn_native/opengl/DeviceGL.h b/src/dawn_native/opengl/DeviceGL.h
index 9c2600f..7d0b803 100644
--- a/src/dawn_native/opengl/DeviceGL.h
+++ b/src/dawn_native/opengl/DeviceGL.h
@@ -80,8 +80,9 @@
             const ShaderModuleDescriptor* descriptor) override;
         ResultOrError<SwapChainBase*> CreateSwapChainImpl(
             const SwapChainDescriptor* descriptor) override;
-        ResultOrError<SwapChainBase*> CreateSwapChainImpl(
+        ResultOrError<NewSwapChainBase*> CreateSwapChainImpl(
             Surface* surface,
+            NewSwapChainBase* previousSwapChain,
             const SwapChainDescriptor* descriptor) override;
         ResultOrError<TextureBase*> CreateTextureImpl(const TextureDescriptor* descriptor) override;
         ResultOrError<TextureViewBase*> CreateTextureViewImpl(
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index f31ea96..bc4179a 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -161,8 +161,9 @@
         const SwapChainDescriptor* descriptor) {
         return SwapChain::Create(this, descriptor);
     }
-    ResultOrError<SwapChainBase*> Device::CreateSwapChainImpl(
+    ResultOrError<NewSwapChainBase*> Device::CreateSwapChainImpl(
         Surface* surface,
+        NewSwapChainBase* previousSwapChain,
         const SwapChainDescriptor* descriptor) {
         return DAWN_VALIDATION_ERROR("New swapchains not implemented.");
     }
diff --git a/src/dawn_native/vulkan/DeviceVk.h b/src/dawn_native/vulkan/DeviceVk.h
index bd8df74..7175710 100644
--- a/src/dawn_native/vulkan/DeviceVk.h
+++ b/src/dawn_native/vulkan/DeviceVk.h
@@ -119,8 +119,9 @@
             const ShaderModuleDescriptor* descriptor) override;
         ResultOrError<SwapChainBase*> CreateSwapChainImpl(
             const SwapChainDescriptor* descriptor) override;
-        ResultOrError<SwapChainBase*> CreateSwapChainImpl(
+        ResultOrError<NewSwapChainBase*> CreateSwapChainImpl(
             Surface* surface,
+            NewSwapChainBase* previousSwapChain,
             const SwapChainDescriptor* descriptor) override;
         ResultOrError<TextureBase*> CreateTextureImpl(const TextureDescriptor* descriptor) override;
         ResultOrError<TextureViewBase*> CreateTextureViewImpl(
diff --git a/src/tests/end2end/SwapChainValidationTests.cpp b/src/tests/end2end/SwapChainValidationTests.cpp
index 1a91958..1900604 100644
--- a/src/tests/end2end/SwapChainValidationTests.cpp
+++ b/src/tests/end2end/SwapChainValidationTests.cpp
@@ -16,6 +16,7 @@
 
 #include "common/Constants.h"
 #include "common/Log.h"
+#include "utils/ComboRenderPipelineDescriptor.h"
 #include "utils/GLFWUtils.h"
 #include "utils/WGPUHelpers.h"
 
@@ -69,11 +70,26 @@
         pass.EndPass();
         ASSERT_DEVICE_ERROR(encoder.Finish());
     }
+
+    // Checks that an OutputAttachment view is an error by trying to create a render pass on it.
+    void CheckTextureViewIsDestroyed(wgpu::TextureView view) {
+        utils::ComboRenderPassDescriptor renderPassDesc({view});
+
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc);
+        pass.EndPass();
+        wgpu::CommandBuffer commands = encoder.Finish();
+
+        wgpu::Queue queue = device.CreateQueue();
+        ASSERT_DEVICE_ERROR(queue.Submit(1, &commands));
+    }
 };
 
-// Control case for a successful swapchain creation.
+// Control case for a successful swapchain creation and presenting.
 TEST_F(SwapChainValidationTests, CreationSuccess) {
-    device.CreateSwapChain(surface, &goodDescriptor);
+    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
+    wgpu::TextureView view = swapchain.GetCurrentTextureView();
+    swapchain.Present();
 }
 
 // Checks that the creation size must be a valid 2D texture size.
@@ -144,3 +160,133 @@
 
     ASSERT_DEVICE_ERROR(swapchain.Present());
 }
+
+// Check it is invalid to call present without getting a current view.
+TEST_F(SwapChainValidationTests, PresentWithoutCurrentView) {
+    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
+
+    // Check it is invalid if we never called GetCurrentTextureView
+    ASSERT_DEVICE_ERROR(swapchain.Present());
+
+    // Check it is invalid if we never called since the last present.
+    swapchain.GetCurrentTextureView();
+    swapchain.Present();
+    ASSERT_DEVICE_ERROR(swapchain.Present());
+}
+
+// Check that the current view is in the destroyed state after the swapchain is destroyed.
+TEST_F(SwapChainValidationTests, ViewDestroyedAfterSwapChainDestruction) {
+    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
+    wgpu::TextureView view = swapchain.GetCurrentTextureView();
+    swapchain = nullptr;
+
+    CheckTextureViewIsDestroyed(view);
+}
+
+// Check that the current view is the destroyed state after present.
+TEST_F(SwapChainValidationTests, ViewDestroyedAfterPresent) {
+    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
+    wgpu::TextureView view = swapchain.GetCurrentTextureView();
+    swapchain.Present();
+
+    CheckTextureViewIsDestroyed(view);
+}
+
+// Check that returned view is of the current format / usage / dimension / size / sample count
+TEST_F(SwapChainValidationTests, ReturnedViewCharacteristics) {
+    utils::ComboRenderPipelineDescriptor pipelineDesc(device);
+    pipelineDesc.vertexStage.module =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
+                #version 450
+                void main() {
+                    gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
+                })");
+    pipelineDesc.cFragmentStage.module =
+        utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
+                #version 450
+                layout(location = 0) out vec4 fragColor;
+                void main() {
+                    fragColor = vec4(0.0, 1.0, 0.0, 1.0);
+                })");
+    // Validation will check that the sample count of the view matches this format.
+    pipelineDesc.sampleCount = 1;
+    pipelineDesc.colorStateCount = 2;
+    // Validation will check that the format of the view matches this format.
+    pipelineDesc.cColorStates[0].format = wgpu::TextureFormat::BGRA8Unorm;
+    pipelineDesc.cColorStates[1].format = wgpu::TextureFormat::R8Unorm;
+    device.CreateRenderPipeline(&pipelineDesc);
+
+    // Create a second texture to be used as render pass attachment. Validation will check that the
+    // size of the view matches the size of this texture.
+    wgpu::TextureDescriptor textureDesc;
+    textureDesc.usage = wgpu::TextureUsage::OutputAttachment;
+    textureDesc.dimension = wgpu::TextureDimension::e2D;
+    textureDesc.size = {1, 1, 1};
+    textureDesc.format = wgpu::TextureFormat::R8Unorm;
+    textureDesc.sampleCount = 1;
+    wgpu::Texture secondTexture = device.CreateTexture(&textureDesc);
+
+    // Get the swapchain view and try to use it in the render pass to trigger all the validation.
+    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
+    wgpu::TextureView view = swapchain.GetCurrentTextureView();
+
+    // Validation will also check the dimension of the view is 2D, and it's usage contains
+    // OutputAttachment
+    utils::ComboRenderPassDescriptor renderPassDesc({view, secondTexture.CreateView()});
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc);
+    pass.EndPass();
+    wgpu::CommandBuffer commands = encoder.Finish();
+
+    wgpu::Queue queue = device.CreateQueue();
+    queue.Submit(1, &commands);
+
+    // Check that view doesn't have extra formats like Sampled.
+    // TODO(cwallez@chromium.org): also check for [Readonly]Storage once that's implemented.
+    wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
+        device, {{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::SampledTexture}});
+    ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl, {{0, view}}));
+}
+
+// Check that failing to create a new swapchain doesn't replace the previous one.
+TEST_F(SwapChainValidationTests, ErrorSwapChainDoesntReplacePreviousOne) {
+    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
+    ASSERT_DEVICE_ERROR(device.CreateSwapChain(surface, &badDescriptor));
+
+    wgpu::TextureView view = swapchain.GetCurrentTextureView();
+    swapchain.Present();
+}
+
+// Check that after replacement, all swapchain operations are errors and the view is destroyed.
+TEST_F(SwapChainValidationTests, ReplacedSwapChainIsInvalid) {
+    {
+        wgpu::SwapChain replacedSwapChain = device.CreateSwapChain(surface, &goodDescriptor);
+        device.CreateSwapChain(surface, &goodDescriptor);
+        ASSERT_DEVICE_ERROR(replacedSwapChain.GetCurrentTextureView());
+    }
+
+    {
+        wgpu::SwapChain replacedSwapChain = device.CreateSwapChain(surface, &goodDescriptor);
+        wgpu::TextureView view = replacedSwapChain.GetCurrentTextureView();
+        device.CreateSwapChain(surface, &goodDescriptor);
+
+        CheckTextureViewIsDestroyed(view);
+        ASSERT_DEVICE_ERROR(replacedSwapChain.Present());
+    }
+}
+
+// Check that after surface destruction, all swapchain operations are errors and the view is
+// destroyed. The test is split in two to reset the wgpu::Surface in the middle.
+TEST_F(SwapChainValidationTests, SwapChainIsInvalidAfterSurfaceDestruction_GetView) {
+    wgpu::SwapChain replacedSwapChain = device.CreateSwapChain(surface, &goodDescriptor);
+    surface = nullptr;
+    ASSERT_DEVICE_ERROR(replacedSwapChain.GetCurrentTextureView());
+}
+TEST_F(SwapChainValidationTests, SwapChainIsInvalidAfterSurfaceDestruction_AfterGetView) {
+    wgpu::SwapChain replacedSwapChain = device.CreateSwapChain(surface, &goodDescriptor);
+    wgpu::TextureView view = replacedSwapChain.GetCurrentTextureView();
+    surface = nullptr;
+
+    CheckTextureViewIsDestroyed(view);
+    ASSERT_DEVICE_ERROR(replacedSwapChain.Present());
+}