Remove SwapChain from the API

This applies the switch from manipulating a SwapChain object to calling
Surface::configure that was brung to the standard webgpu.h a couple of
months ago.

I believe this needs a bit of extra work, but at this stage I need a
review from people more familiar with the codebase to tell me if I am
going in the right direction.

The SwapChain object is still here, but no longer present in the API.
Instances of the swap chain are now owned by the surface object. The
test for surface capabilities is partly calling a method of the
PhysicalDevice in order to have backend-specific capabilities.

So far I mostly tested with the Vulkan backend, though I tried to
provide a valid implementation for all backends.

Reland changes:
This fixes commit 167600, which was causing lock issues because the
surface was failing at locking the device before calling it's internal
SwapChain's methods like Present and GetTexture.

Bug: dawn:2320
Change-Id: I6773c9de069ad4f4f93ad47ace6a5773665f4e92
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/179580
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Élie Michel <elie.michel.fr@gmail.com>
diff --git a/generator/templates/dawn/wire/WireCmd.cpp b/generator/templates/dawn/wire/WireCmd.cpp
index a62906e..ec1faaa 100644
--- a/generator/templates/dawn/wire/WireCmd.cpp
+++ b/generator/templates/dawn/wire/WireCmd.cpp
@@ -220,7 +220,7 @@
                         //* Structures might contain more pointers so we need to add their extra size as well.
                         {% if member.type.category == "structure" %}
                             for (decltype(memberLength) i = 0; i < memberLength; ++i) {
-                                {% do assert(member.annotation == "const*", "unhandled annotation: " + member.annotation) %}
+                                {% do assert(member.annotation == "const*" or member.annotation == "*", "unhandled annotation: " + member.annotation)%}
                                 result += {{as_cType(member.type.name)}}GetExtraRequiredSize(record.{{as_varName(member.name)}}[i]);
                             }
                         {% endif %}
diff --git a/src/dawn/dawn.json b/src/dawn/dawn.json
index 9edf18c..11bd5df 100644
--- a/src/dawn/dawn.json
+++ b/src/dawn/dawn.json
@@ -406,15 +406,14 @@
     },
     "surface capabilities": {
         "category": "structure",
-        "tags": ["upstream"],
         "extensible": "out",
         "members": [
             {"name": "format count", "type": "size_t"},
-            {"name": "formats", "type": "texture format", "annotation": "*"},
+            {"name": "formats", "type": "texture format", "annotation": "const*"},
             {"name": "present mode count", "type": "size_t"},
-            {"name": "present modes", "type": "present mode", "annotation": "*"},
+            {"name": "present modes", "type": "present mode", "annotation": "const*"},
             {"name": "alpha mode count", "type": "size_t"},
-            {"name": "alpha modes", "type": "composite alpha mode", "annotation": "*"}
+            {"name": "alpha modes", "type": "composite alpha mode", "annotation": "const*"}
         ],
         "methods": [
             {
@@ -426,18 +425,17 @@
     },
     "surface configuration": {
         "category": "structure",
-        "tags": ["upstream"],
         "extensible": "in",
         "members": [
             {"name": "device", "type": "device"},
             {"name": "format", "type": "texture format"},
-            {"name": "usage", "type": "texture usage"},
-            {"name": "view format count", "type": "size_t"},
-            {"name": "view formats", "type": "texture format", "annotation": "const*"},
-            {"name": "alpha mode", "type": "composite alpha mode"},
+            {"name": "usage", "type": "texture usage", "default": "render attachment"},
+            {"name": "view format count", "type": "size_t", "default": 0},
+            {"name": "view formats", "type": "texture format", "annotation": "const*", "length": "view format count"},
+            {"name": "alpha mode", "type": "composite alpha mode", "default": "opaque"},
             {"name": "width", "type": "uint32_t"},
             {"name": "height", "type": "uint32_t"},
-            {"name": "present mode", "type": "present mode"}
+            {"name": "present mode", "type": "present mode", "default": "fifo"}
         ]
     },
     "external texture binding entry": {
@@ -1033,7 +1031,6 @@
     },
     "composite alpha mode": {
         "category": "enum",
-        "tags": ["upstream"],
         "values": [
             {"value": 0, "name": "auto"},
             {"value": 1, "name": "opaque"},
@@ -3452,7 +3449,6 @@
             {
                 "name": "configure",
                 "returns": "void",
-                "tags": ["upstream"],
                 "args": [
                     {"name": "config", "type": "surface configuration", "annotation": "const*"}
                 ]
@@ -3460,7 +3456,6 @@
             {
                 "name": "get capabilities",
                 "returns": "void",
-                "tags": ["upstream"],
                 "args": [
                     {"name": "adapter", "type": "adapter"},
                     {"name": "capabilities", "type": "surface capabilities", "annotation": "*"}
@@ -3469,7 +3464,6 @@
             {
                 "name": "get current texture",
                 "returns": "void",
-                "tags": ["upstream"],
                 "args": [
                     {"name": "surface texture", "type": "surface texture", "annotation": "*"}
                 ]
@@ -3484,14 +3478,20 @@
             {
                 "name": "present",
                 "returns": "void",
-                "tags": ["upstream"],
                 "args": []
             },
             {
                 "name": "unconfigure",
                 "returns": "void",
-                "tags": ["upstream"],
                 "args": []
+            },
+            {
+                "name": "set label",
+                "tags": [],
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"}
+                ]
             }
         ]
     },
@@ -3610,7 +3610,6 @@
     },
     "surface texture": {
         "category": "structure",
-        "tags": ["upstream"],
         "members": [
             {"name": "texture", "type": "texture"},
             {"name": "suboptimal", "type": "bool"},
@@ -3756,7 +3755,6 @@
     },
     "surface get current texture status": {
         "category": "enum",
-        "tags": ["upstream"],
         "values": [
             {"value": 0, "name": "success"},
             {"value": 1, "name": "timeout"},
diff --git a/src/dawn/dawn_wire.json b/src/dawn/dawn_wire.json
index 4b230a3..77eaa5a 100644
--- a/src/dawn/dawn_wire.json
+++ b/src/dawn/dawn_wire.json
@@ -241,6 +241,7 @@
             "QueueOnSubmittedWorkDoneF",
             "QueueWriteBuffer",
             "QueueWriteTexture",
+            "SurfaceGetCapabilities",
             "SurfaceGetPreferredFormat",
             "TextureGetWidth",
             "TextureGetHeight",
@@ -261,6 +262,8 @@
             "DeviceInjectError",
             "InstanceProcessEvents",
             "InstanceWaitAny",
+            "SurfaceConfigure",
+            "SurfaceGetCurrentTexture",
             "SwapChainGetCurrentTexture"
         ],
         "client_special_objects": [
@@ -272,6 +275,7 @@
             "Queue",
             "ShaderModule",
             "Surface",
+            "SurfaceCapabilities",
             "SwapChain",
             "Texture"
         ],
diff --git a/src/dawn/native/Adapter.cpp b/src/dawn/native/Adapter.cpp
index 801c7f6..e4113a3 100644
--- a/src/dawn/native/Adapter.cpp
+++ b/src/dawn/native/Adapter.cpp
@@ -29,6 +29,7 @@
 
 #include <algorithm>
 #include <memory>
+#include <string>
 #include <tuple>
 #include <utility>
 #include <vector>
@@ -70,6 +71,10 @@
     return mPhysicalDevice.Get();
 }
 
+const PhysicalDeviceBase* AdapterBase::GetPhysicalDevice() const {
+    return mPhysicalDevice.Get();
+}
+
 InstanceBase* AdapterBase::APIGetInstance() const {
     InstanceBase* instance = mPhysicalDevice->GetInstance();
     DAWN_ASSERT(instance != nullptr);
@@ -364,6 +369,10 @@
     return mFeatureLevel;
 }
 
+const std::string& AdapterBase::GetName() const {
+    return mPhysicalDevice->GetName();
+}
+
 std::vector<Ref<AdapterBase>> SortAdapters(std::vector<Ref<AdapterBase>> adapters,
                                            const RequestAdapterOptions* options) {
     const bool highPerformance =
diff --git a/src/dawn/native/Adapter.h b/src/dawn/native/Adapter.h
index 5b99069f..a90ed28 100644
--- a/src/dawn/native/Adapter.h
+++ b/src/dawn/native/Adapter.h
@@ -28,10 +28,12 @@
 #ifndef SRC_DAWN_NATIVE_ADAPTER_H_
 #define SRC_DAWN_NATIVE_ADAPTER_H_
 
+#include <string>
 #include <vector>
 
 #include "dawn/common/Ref.h"
 #include "dawn/common/RefCounted.h"
+#include "dawn/common/WeakRefSupport.h"
 #include "dawn/native/DawnNative.h"
 #include "dawn/native/PhysicalDevice.h"
 #include "dawn/native/dawn_platform.h"
@@ -42,7 +44,7 @@
 class TogglesState;
 struct SupportedLimits;
 
-class AdapterBase : public RefCounted {
+class AdapterBase : public RefCounted, public WeakRefSupport<AdapterBase> {
   public:
     AdapterBase(Ref<PhysicalDeviceBase> physicalDevice,
                 FeatureLevel featureLevel,
@@ -71,12 +73,16 @@
 
     // Return the underlying PhysicalDevice.
     PhysicalDeviceBase* GetPhysicalDevice();
+    const PhysicalDeviceBase* GetPhysicalDevice() const;
 
     // Get the actual toggles state of the adapter.
     const TogglesState& GetTogglesState() const;
 
     FeatureLevel GetFeatureLevel() const;
 
+    // Get a human readable label for the adapter (in practice, the physical device name)
+    const std::string& GetName() const;
+
   private:
     Ref<PhysicalDeviceBase> mPhysicalDevice;
     FeatureLevel mFeatureLevel;
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index c014fa0..f8010e3 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -1425,7 +1425,18 @@
     Ref<SwapChainBase> result;
     if (ConsumedError(CreateSwapChain(surface, descriptor), &result,
                       "calling %s.CreateSwapChain(%s).", this, descriptor)) {
-        result = SwapChainBase::MakeError(this, descriptor);
+        SurfaceConfiguration config;
+        config.nextInChain = descriptor->nextInChain;
+        config.device = this;
+        config.width = descriptor->width;
+        config.height = descriptor->height;
+        config.format = descriptor->format;
+        config.usage = descriptor->usage;
+        config.presentMode = descriptor->presentMode;
+        config.viewFormatCount = 0;
+        config.viewFormats = nullptr;
+        config.alphaMode = wgpu::CompositeAlphaMode::Opaque;
+        result = SwapChainBase::MakeError(this, &config);
     }
     return ReturnToAPI(std::move(result));
 }
@@ -2120,15 +2131,31 @@
 ResultOrError<Ref<SwapChainBase>> DeviceBase::CreateSwapChain(
     Surface* surface,
     const SwapChainDescriptor* descriptor) {
+    EmitDeprecationWarning(
+        "The explicit creation of a SwapChain object is deprecated and should be replaced by "
+        "Surface configuration.");
+
     DAWN_TRY(ValidateIsAlive());
     if (IsValidationEnabled()) {
         DAWN_TRY_CONTEXT(ValidateSwapChainDescriptor(this, surface, descriptor), "validating %s",
                          descriptor);
     }
 
+    SurfaceConfiguration config;
+    config.nextInChain = descriptor->nextInChain;
+    config.device = this;
+    config.width = descriptor->width;
+    config.height = descriptor->height;
+    config.format = descriptor->format;
+    config.usage = descriptor->usage;
+    config.presentMode = descriptor->presentMode;
+    config.viewFormatCount = 0;
+    config.viewFormats = nullptr;
+    config.alphaMode = wgpu::CompositeAlphaMode::Opaque;
+
     SwapChainBase* previousSwapChain = surface->GetAttachedSwapChain();
     ResultOrError<Ref<SwapChainBase>> maybeNewSwapChain =
-        CreateSwapChainImpl(surface, previousSwapChain, descriptor);
+        CreateSwapChainImpl(surface, previousSwapChain, &config);
 
     if (previousSwapChain != nullptr) {
         previousSwapChain->DetachFromSurface();
@@ -2142,6 +2169,13 @@
     return newSwapChain;
 }
 
+ResultOrError<Ref<SwapChainBase>> DeviceBase::CreateSwapChain(Surface* surface,
+                                                              SwapChainBase* previousSwapChain,
+                                                              const SurfaceConfiguration* config) {
+    // Nothing to validate here as it is done in Surface::Configure
+    return CreateSwapChainImpl(surface, previousSwapChain, config);
+}
+
 ResultOrError<Ref<TextureBase>> DeviceBase::CreateTexture(const TextureDescriptor* descriptorOrig) {
     DAWN_TRY(ValidateIsAlive());
 
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index 663ce83..174ec0f 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -266,8 +266,13 @@
     ResultOrError<Ref<ShaderModuleBase>> CreateShaderModule(
         const ShaderModuleDescriptor* descriptor,
         std::unique_ptr<OwnedCompilationMessages>* compilationMessages = nullptr);
+    // Deprecated: this was the way to create a SwapChain when it was explicitly manipulated by the
+    // end user.
     ResultOrError<Ref<SwapChainBase>> CreateSwapChain(Surface* surface,
                                                       const SwapChainDescriptor* descriptor);
+    ResultOrError<Ref<SwapChainBase>> CreateSwapChain(Surface* surface,
+                                                      SwapChainBase* previousSwapChain,
+                                                      const SurfaceConfiguration* config);
     ResultOrError<Ref<TextureBase>> CreateTexture(const TextureDescriptor* rawDescriptor);
     ResultOrError<Ref<TextureViewBase>> CreateTextureView(TextureBase* texture,
                                                           const TextureViewDescriptor* descriptor);
@@ -306,7 +311,9 @@
     ShaderModuleBase* APICreateShaderModule(const ShaderModuleDescriptor* descriptor);
     ShaderModuleBase* APICreateErrorShaderModule(const ShaderModuleDescriptor* descriptor,
                                                  const char* errorMessage);
-    SwapChainBase* APICreateSwapChain(Surface* surface, const SwapChainDescriptor* descriptor);
+    // TODO (dawn:2320) Remove after deprecation
+    SwapChainBase* APICreateSwapChain(Surface* surface,
+                                      const SwapChainDescriptor* descriptor);  // Deprecated
     TextureBase* APICreateTexture(const TextureDescriptor* descriptor);
 
     wgpu::TextureUsage APIGetSupportedSurfaceUsage(Surface* surface);
@@ -513,7 +520,7 @@
     virtual ResultOrError<Ref<SwapChainBase>> CreateSwapChainImpl(
         Surface* surface,
         SwapChainBase* previousSwapChain,
-        const SwapChainDescriptor* descriptor) = 0;
+        const SurfaceConfiguration* config) = 0;
     virtual ResultOrError<Ref<TextureBase>> CreateTextureImpl(
         const UnpackedPtr<TextureDescriptor>& descriptor) = 0;
     virtual ResultOrError<Ref<TextureViewBase>> CreateTextureViewImpl(
diff --git a/src/dawn/native/PhysicalDevice.h b/src/dawn/native/PhysicalDevice.h
index c2ac2a6..82a943d 100644
--- a/src/dawn/native/PhysicalDevice.h
+++ b/src/dawn/native/PhysicalDevice.h
@@ -48,6 +48,13 @@
 
 class DeviceBase;
 
+// Structure that holds surface capabilities for a (Surface, PhysicalDevice) pair.
+struct PhysicalDeviceSurfaceCapabilities {
+    std::vector<wgpu::TextureFormat> formats;
+    std::vector<wgpu::PresentMode> presentModes;
+    std::vector<wgpu::CompositeAlphaMode> alphaModes;
+};
+
 struct FeatureValidationResult {
     // Constructor of successful result
     FeatureValidationResult();
@@ -116,6 +123,9 @@
         wgpu::TextureFormat format,
         UnpackedPtr<FormatCapabilities>& capabilities) const;
 
+    virtual ResultOrError<PhysicalDeviceSurfaceCapabilities> GetSurfaceCapabilities(
+        const Surface* surface) const = 0;
+
   protected:
     uint32_t mVendorId = 0xFFFFFFFF;
     std::string mVendorName;
diff --git a/src/dawn/native/Surface.cpp b/src/dawn/native/Surface.cpp
index 8929dd6..5b1d37e 100644
--- a/src/dawn/native/Surface.cpp
+++ b/src/dawn/native/Surface.cpp
@@ -27,10 +27,17 @@
 
 #include "dawn/native/Surface.h"
 
+#include <memory>
+#include <string>
+#include <utility>
+
 #include "dawn/common/Platform.h"
 #include "dawn/native/ChainUtils.h"
+#include "dawn/native/Device.h"
 #include "dawn/native/Instance.h"
 #include "dawn/native/SwapChain.h"
+#include "dawn/native/ValidationUtils_autogen.h"
+#include "dawn/native/utils/WGPUHelpers.h"
 
 #if defined(DAWN_USE_WINDOWS_UI)
 #include <windows.ui.core.h>
@@ -186,6 +193,111 @@
     }
 }
 
+MaybeError ValidateSurfaceConfiguration(DeviceBase* device,
+                                        const PhysicalDeviceSurfaceCapabilities& capabilities,
+                                        const SurfaceConfiguration* config,
+                                        const Surface* surface) {
+    UnpackedPtr<SurfaceConfiguration> unpacked;
+    DAWN_TRY_ASSIGN(unpacked, ValidateAndUnpack(config));
+
+    DAWN_TRY(config->device->ValidateIsAlive());
+
+    const Format* format = nullptr;
+    DAWN_TRY_ASSIGN(format, device->GetInternalFormat(config->format));
+    DAWN_ASSERT(format != nullptr);
+
+    // TODO(crbug.com/dawn/160): Lift this restriction once
+    // wgpu::Instance::GetPreferredSurfaceFormat is implemented.
+    // TODO(dawn:286):
+#if DAWN_PLATFORM_IS(ANDROID)
+    constexpr wgpu::TextureFormat kRequireSwapChainFormat = wgpu::TextureFormat::RGBA8Unorm;
+#else
+    constexpr wgpu::TextureFormat kRequireSwapChainFormat = wgpu::TextureFormat::BGRA8Unorm;
+#endif  // !DAWN_PLATFORM_IS(ANDROID)
+    DAWN_INVALID_IF(config->format != kRequireSwapChainFormat,
+                    "Format (%s) is not %s, which is (currently) the only accepted format.",
+                    config->format, kRequireSwapChainFormat);
+
+    if (device->HasFeature(Feature::SurfaceCapabilities)) {
+        wgpu::TextureUsage validUsage;
+        DAWN_TRY_ASSIGN(validUsage, device->GetSupportedSurfaceUsage(surface));
+        DAWN_INVALID_IF(
+            !IsSubset(config->usage, validUsage),
+            "Usage (%s) is not supported, %s are (currently) the only accepted usage flags.",
+            config->usage, validUsage);
+    } else {
+        DAWN_INVALID_IF(config->usage != wgpu::TextureUsage::RenderAttachment,
+                        "Usage (%s) is not %s, which is (currently) the only accepted usage. Other "
+                        "usage flags require enabling %s",
+                        config->usage, wgpu::TextureUsage::RenderAttachment,
+                        wgpu::FeatureName::SurfaceCapabilities);
+    }
+
+    for (size_t i = 0; i < config->viewFormatCount; ++i) {
+        const wgpu::TextureFormat apiViewFormat = config->viewFormats[i];
+        const Format* viewFormat = nullptr;
+        DAWN_TRY_ASSIGN(viewFormat, device->GetInternalFormat(apiViewFormat));
+        DAWN_ASSERT(viewFormat != nullptr);
+
+        DAWN_INVALID_IF(std::find(capabilities.formats.begin(), capabilities.formats.end(),
+                                  apiViewFormat) == capabilities.formats.end(),
+                        "View format (%s) is not supported by the adapter (%s) for this surface.",
+                        apiViewFormat, device->GetAdapter());
+    }
+
+    DAWN_TRY(ValidatePresentMode(config->presentMode));
+
+    // Check that config matches capabilities
+    auto formatIt =
+        std::find(capabilities.formats.begin(), capabilities.formats.end(), config->format);
+    DAWN_INVALID_IF(formatIt == capabilities.formats.end(),
+                    "Format (%s) is not supported by the adapter (%s) for this surface.",
+                    config->format, config->device->GetAdapter());
+
+    auto presentModeIt = std::find(capabilities.presentModes.begin(),
+                                   capabilities.presentModes.end(), config->presentMode);
+    DAWN_INVALID_IF(presentModeIt == capabilities.presentModes.end(),
+                    "Present mode (%s) is not supported by the adapter (%s) for this surface.",
+                    config->format, config->device->GetAdapter());
+
+    auto alphaModeIt = std::find(capabilities.alphaModes.begin(), capabilities.alphaModes.end(),
+                                 config->alphaMode);
+    DAWN_INVALID_IF(alphaModeIt == capabilities.alphaModes.end(),
+                    "Alpha mode (%s) is not supported by the adapter (%s) for this surface.",
+                    config->format, config->device->GetAdapter());
+
+    DAWN_INVALID_IF(config->width == 0 || config->height == 0,
+                    "Surface configuration size (width: %u, height: %u) is empty.", config->width,
+                    config->height);
+
+    DAWN_INVALID_IF(
+        config->width > device->GetLimits().v1.maxTextureDimension2D ||
+            config->height > device->GetLimits().v1.maxTextureDimension2D,
+        "Surface configuration size (width: %u, height: %u) is greater than the maximum 2D texture "
+        "size (width: %u, height: %u).",
+        config->width, config->height, device->GetLimits().v1.maxTextureDimension2D,
+        device->GetLimits().v1.maxTextureDimension2D);
+
+    return {};
+}
+
+class AdapterSurfaceCapCache {
+  public:
+    template <typename F>
+    MaybeError WithAdapterCapabilities(AdapterBase* adapter, const Surface* surface, F f) {
+        if (mCachedCapabilitiesAdapter.Promote().Get() != adapter) {
+            const PhysicalDeviceBase* physicalDevice = adapter->GetPhysicalDevice();
+            DAWN_TRY_ASSIGN(mCachedCapabilities, physicalDevice->GetSurfaceCapabilities(surface));
+            mCachedCapabilitiesAdapter = GetWeakRef(adapter);
+        }
+        return f(mCachedCapabilities);
+    }
+
+  private:
+    WeakRef<AdapterBase> mCachedCapabilitiesAdapter = nullptr;
+    PhysicalDeviceSurfaceCapabilities mCachedCapabilities;
+};
+
 // static
 Ref<Surface> Surface::MakeError(InstanceBase* instance) {
     return AcquireRef(new Surface(instance, ErrorMonad::kError));
@@ -194,7 +306,13 @@
 Surface::Surface(InstanceBase* instance, ErrorTag tag) : ErrorMonad(tag), mInstance(instance) {}
 
 Surface::Surface(InstanceBase* instance, const UnpackedPtr<SurfaceDescriptor>& descriptor)
-    : ErrorMonad(), mInstance(instance) {
+    : ErrorMonad(),
+      mInstance(instance),
+      mCapabilityCache(std::make_unique<AdapterSurfaceCapCache>()) {
+    if (descriptor->label != nullptr && strlen(descriptor->label) != 0) {
+        mLabel = descriptor->label;
+    }
+
     // Type is validated in validation, otherwise this may crash with an assert failure.
     wgpu::SType type = descriptor
                            .ValidateBranches<Branch<SurfaceDescriptorFromAndroidNativeWindow>,
@@ -260,18 +378,29 @@
 
 Surface::~Surface() {
     if (mSwapChain != nullptr) {
-        mSwapChain->DetachFromSurface();
-        mSwapChain = nullptr;
+        [[maybe_unused]] bool error = mInstance->ConsumedError(Unconfigure());
+    }
+
+    if (mRecycledSwapChain != nullptr) {
+        mRecycledSwapChain->DetachFromSurface();
+        mRecycledSwapChain = nullptr;
     }
 }
 
 SwapChainBase* Surface::GetAttachedSwapChain() {
     DAWN_ASSERT(!IsError());
+    DAWN_ASSERT(mIsSwapChainManagedBySurface == ManagesSwapChain::Unknown ||
+                mIsSwapChainManagedBySurface == ManagesSwapChain::No);
+    mIsSwapChainManagedBySurface = ManagesSwapChain::No;
+
     return mSwapChain.Get();
 }
-
 void Surface::SetAttachedSwapChain(SwapChainBase* swapChain) {
     DAWN_ASSERT(!IsError());
+    DAWN_ASSERT(mIsSwapChainManagedBySurface == ManagesSwapChain::Unknown ||
+                mIsSwapChainManagedBySurface == ManagesSwapChain::No);
+    mIsSwapChainManagedBySurface = ManagesSwapChain::No;
+
     mSwapChain = swapChain;
 }
 
@@ -279,6 +408,10 @@
     return mInstance.Get();
 }
 
+DeviceBase* Surface::GetCurrentDevice() const {
+    return mCurrentDevice.Get();
+}
+
 Surface::Type Surface::GetType() const {
     DAWN_ASSERT(!IsError());
     return mType;
@@ -348,13 +481,203 @@
     return mXWindow;
 }
 
+MaybeError Surface::Configure(const SurfaceConfiguration* config) {
+    DAWN_INVALID_IF(IsError(), "%s is invalid.", this);
+    mCurrentDevice = config->device;  // next errors are routed to the new device
+    DAWN_INVALID_IF(mIsSwapChainManagedBySurface == ManagesSwapChain::No,
+                    "%s cannot be configured because it is used by legacy swapchain %s.", this,
+                    mSwapChain.Get());
+
+    mIsSwapChainManagedBySurface = ManagesSwapChain::Yes;
+
+    DAWN_TRY(mCapabilityCache->WithAdapterCapabilities(
+        GetCurrentDevice()->GetAdapter(), this,
+        [&](const PhysicalDeviceSurfaceCapabilities& caps) -> MaybeError {
+            return ValidateSurfaceConfiguration(GetCurrentDevice(), caps, config, this);
+        }));
+
+    // Reuse either the current swapchain, or the recycled swap chain
+    SwapChainBase* previousSwapChain = mSwapChain.Get();
+    if (previousSwapChain == nullptr && mRecycledSwapChain != nullptr &&
+        mRecycledSwapChain->GetDevice() == config->device) {
+        previousSwapChain = mRecycledSwapChain.Get();
+    }
+
+    {
+        auto deviceLock(GetCurrentDevice()->GetScopedLock());
+        ResultOrError<Ref<SwapChainBase>> maybeNewSwapChain =
+            GetCurrentDevice()->CreateSwapChain(this, previousSwapChain, config);
+
+        // Don't keep swap chains older than 1 call to Configure
+        if (mRecycledSwapChain) {
+            mRecycledSwapChain->DetachFromSurface();
+            mRecycledSwapChain = nullptr;
+        }
+
+        if (mSwapChain && maybeNewSwapChain.IsSuccess()) {
+            mSwapChain->DetachFromSurface();
+        }
+        DAWN_TRY_ASSIGN(mSwapChain, std::move(maybeNewSwapChain));
+
+        mSwapChain->SetIsAttached();
+    }
+
+    return {};
+}
+
+MaybeError Surface::Unconfigure() {
+    DAWN_INVALID_IF(IsError(), "%s is invalid.", this);
+    DAWN_INVALID_IF(!mSwapChain.Get(), "%s is not configured.", this);
+
+    if (mSwapChain != nullptr) {
+        if (mIsSwapChainManagedBySurface == ManagesSwapChain::Yes) {
+            if (mRecycledSwapChain != nullptr) {
+                mRecycledSwapChain->DetachFromSurface();
+                mRecycledSwapChain = nullptr;
+            }
+            mRecycledSwapChain = mSwapChain;
+        } else {
+            mSwapChain->DetachFromSurface();
+        }
+        mSwapChain = nullptr;
+    }
+
+    return {};
+}
+
+MaybeError Surface::GetCapabilities(AdapterBase* adapter, SurfaceCapabilities* capabilities) const {
+    DAWN_INVALID_IF(IsError(), "%s is invalid.", this);
+
+    DAWN_TRY(mCapabilityCache->WithAdapterCapabilities(
+        adapter, this,
+        [&capabilities](const PhysicalDeviceSurfaceCapabilities& caps) -> MaybeError {
+            capabilities->nextInChain = nullptr;
+            DAWN_TRY(utils::AllocateApiSeqFromStdVector(capabilities->formats,
+                                                        capabilities->formatCount, caps.formats));
+            DAWN_TRY(utils::AllocateApiSeqFromStdVector(
+                capabilities->presentModes, capabilities->presentModeCount, caps.presentModes));
+            DAWN_TRY(utils::AllocateApiSeqFromStdVector(
+                capabilities->alphaModes, capabilities->alphaModeCount, caps.alphaModes));
+            return {};
+        }));
+
+    return {};
+}
+
+void APISurfaceCapabilitiesFreeMembers(WGPUSurfaceCapabilities capabilities) {
+    utils::FreeApiSeq(capabilities.formats, capabilities.formatCount);
+    utils::FreeApiSeq(capabilities.presentModes, capabilities.presentModeCount);
+    utils::FreeApiSeq(capabilities.alphaModes, capabilities.alphaModeCount);
+}
+
+MaybeError Surface::GetCurrentTexture(SurfaceTexture* surfaceTexture) const {
+    DAWN_INVALID_IF(IsError(), "%s is invalid.", this);
+    DAWN_INVALID_IF(!mSwapChain.Get(), "%s is not configured.", this);
+
+    auto deviceLock(GetCurrentDevice()->GetScopedLock());
+    DAWN_TRY_ASSIGN(*surfaceTexture, mSwapChain->GetCurrentTexture());
+
+    return {};
+}
+
+ResultOrError<wgpu::TextureFormat> Surface::GetPreferredFormat(AdapterBase* adapter) const {
+    wgpu::TextureFormat format = wgpu::TextureFormat::Undefined;
+
+    DAWN_TRY(mCapabilityCache->WithAdapterCapabilities(
+        adapter, this, [&](const PhysicalDeviceSurfaceCapabilities& caps) -> MaybeError {
+            DAWN_INVALID_IF(caps.formats.empty(), "No format is supported by %s for %s.", adapter,
+                            this);
+            format = caps.formats.front();
+            return {};
+        }));
+
+    return format;
+}
+
+MaybeError Surface::Present() {
+    DAWN_INVALID_IF(IsError(), "%s is invalid.", this);
+    DAWN_INVALID_IF(!mSwapChain.Get(), "%s is not configured.", this);
+
+    auto deviceLock(GetCurrentDevice()->GetScopedLock());
+    mSwapChain->APIPresent();
+
+    return {};
+}
+
+const std::string& Surface::GetLabel() const {
+    return mLabel;
+}
+
+void Surface::APIConfigure(const SurfaceConfiguration* config) {
+    MaybeError maybeError = Configure(config);
+    if (!GetCurrentDevice()) {
+        mInstance->ConsumedError(std::move(maybeError));
+    } else {
+        [[maybe_unused]] bool error = GetCurrentDevice()->ConsumedError(
+            std::move(maybeError), "calling %s.Configure().", this);
+    }
+}
+
+void Surface::APIGetCapabilities(AdapterBase* adapter, SurfaceCapabilities* capabilities) const {
+    MaybeError maybeError = GetCapabilities(adapter, capabilities);
+    if (!GetCurrentDevice()) {
+        mInstance->ConsumedError(std::move(maybeError));
+    } else {
+        [[maybe_unused]] bool error = GetCurrentDevice()->ConsumedError(
+            std::move(maybeError), "calling %s.Configure().", this);
+    }
+}
+
+void Surface::APIGetCurrentTexture(SurfaceTexture* surfaceTexture) const {
+    MaybeError maybeError = GetCurrentTexture(surfaceTexture);
+    if (!GetCurrentDevice()) {
+        if (mInstance->ConsumedError(std::move(maybeError))) {
+            // TODO(dawn:2320) This is the closest status to "surface was not configured so there is
+            // no associated device" but SurfaceTexture may change soon upstream.
+            surfaceTexture->status = wgpu::SurfaceGetCurrentTextureStatus::DeviceLost;
+            surfaceTexture->suboptimal = true;
+            surfaceTexture->texture = nullptr;
+        }
+    } else {
+        [[maybe_unused]] bool error = GetCurrentDevice()->ConsumedError(std::move(maybeError));
+    }
+}
+
 wgpu::TextureFormat Surface::APIGetPreferredFormat(AdapterBase* adapter) const {
-    // This is the only supported format in native mode (see crbug.com/dawn/160).
-#if DAWN_PLATFORM_IS(ANDROID)
-    return wgpu::TextureFormat::RGBA8Unorm;
-#else
-    return wgpu::TextureFormat::BGRA8Unorm;
-#endif  // !DAWN_PLATFORM_IS(ANDROID)
+    ResultOrError<wgpu::TextureFormat> resultOrError = GetPreferredFormat(adapter);
+    wgpu::TextureFormat format;
+    if (!GetCurrentDevice()) {
+        if (mInstance->ConsumedError(std::move(resultOrError), &format)) {
+            return wgpu::TextureFormat::Undefined;
+        }
+    } else if (GetCurrentDevice()->ConsumedError(std::move(resultOrError), &format,
+                                                 "calling %s.GetPreferredFormat(%s).", this,
+                                                 adapter)) {
+        return wgpu::TextureFormat::Undefined;
+    }
+    return format;
+}
+
+void Surface::APIPresent() {
+    MaybeError maybeError = Present();
+    if (!GetCurrentDevice()) {
+        mInstance->ConsumedError(std::move(maybeError));
+    } else {
+        [[maybe_unused]] bool error = GetCurrentDevice()->ConsumedError(std::move(maybeError));
+    }
+}
+
+void Surface::APIUnconfigure() {
+    MaybeError maybeError = Unconfigure();
+    if (!GetCurrentDevice()) {
+        mInstance->ConsumedError(std::move(maybeError));
+    } else {
+        [[maybe_unused]] bool error = GetCurrentDevice()->ConsumedError(std::move(maybeError));
+    }
+}
+
+void Surface::APISetLabel(const char* label) {
+    mLabel = label;
 }
 
 }  // namespace dawn::native
diff --git a/src/dawn/native/Surface.h b/src/dawn/native/Surface.h
index 71abec4..662098a 100644
--- a/src/dawn/native/Surface.h
+++ b/src/dawn/native/Surface.h
@@ -28,6 +28,9 @@
 #ifndef SRC_DAWN_NATIVE_SURFACE_H_
 #define SRC_DAWN_NATIVE_SURFACE_H_
 
+#include <memory>
+#include <string>
+
 #include "dawn/native/Error.h"
 #include "dawn/native/Forward.h"
 #include "dawn/native/ObjectBase.h"
@@ -48,10 +51,20 @@
 
 namespace dawn::native {
 
+struct PhysicalDeviceSurfaceCapabilities;
+
+// Adapter surface capabilities are cached by the surface
+class AdapterSurfaceCapCache;
+
 ResultOrError<UnpackedPtr<SurfaceDescriptor>> ValidateSurfaceDescriptor(
     InstanceBase* instance,
     const SurfaceDescriptor* rawDescriptor);
 
+MaybeError ValidateSurfaceConfiguration(DeviceBase* device,
+                                        const PhysicalDeviceSurfaceCapabilities& capabilities,
+                                        const SurfaceConfiguration* config,
+                                        const Surface* surface);
+
 // 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).
@@ -63,9 +76,6 @@
 
     Surface(InstanceBase* instance, const UnpackedPtr<SurfaceDescriptor>& descriptor);
 
-    void SetAttachedSwapChain(SwapChainBase* swapChain);
-    SwapChainBase* GetAttachedSwapChain();
-
     // These are valid to call on all Surfaces.
     enum class Type {
         AndroidWindow,
@@ -78,6 +88,7 @@
     };
     Type GetType() const;
     InstanceBase* GetInstance() const;
+    DeviceBase* GetCurrentDevice() const;
 
     // Valid to call if the type is MetalLayer
     void* GetMetalLayer() const;
@@ -103,19 +114,61 @@
     void* GetXDisplay() const;
     uint32_t GetXWindow() const;
 
+    // TODO(dawn:2320) Remove these 2 accessors once the deprecation period is finished and
+    // Device::APICreateSwapChain gets dropped
+    SwapChainBase* GetAttachedSwapChain();
+    void SetAttachedSwapChain(SwapChainBase* swapChain);
+
+    const std::string& GetLabel() const;
+
     // Dawn API
+    void APIConfigure(const SurfaceConfiguration* config);
+    void APIGetCapabilities(AdapterBase* adapter, SurfaceCapabilities* capabilities) const;
+    void APIGetCurrentTexture(SurfaceTexture* surfaceTexture) const;
     wgpu::TextureFormat APIGetPreferredFormat(AdapterBase* adapter) const;
+    void APIPresent();
+    void APIUnconfigure();
+    void APISetLabel(const char* label);
 
   private:
     Surface(InstanceBase* instance, ErrorMonad::ErrorTag tag);
     ~Surface() override;
 
+    MaybeError Configure(const SurfaceConfiguration* config);
+    MaybeError Unconfigure();
+
+    MaybeError GetCapabilities(AdapterBase* adapter, SurfaceCapabilities* capabilities) const;
+    MaybeError GetCurrentTexture(SurfaceTexture* surfaceTexture) const;
+    ResultOrError<wgpu::TextureFormat> GetPreferredFormat(AdapterBase* adapter) const;
+    MaybeError Present();
+
     Ref<InstanceBase> mInstance;
     Type mType;
+    std::string mLabel;
 
-    // The swapchain will set this to null when it is destroyed.
+    // The surface has an associated device only when it is configured
+    Ref<DeviceBase> mCurrentDevice;
+
+    // The swapchain is created when configuring the surface.
     Ref<SwapChainBase> mSwapChain;
 
+    // We keep on storing the previous swap chain after Unconfigure in case we could reuse it
+    Ref<SwapChainBase> mRecycledSwapChain;
+
+    // This ensures that the user does not mix the legacy API (ManagesSwapChain::No, i.e., explicit
+    // call to CreateSwapChain) with the new API (ManagesSwapChain::Yes, i.e., surface.configure).
+    // TODO(dawn:2320) Remove and consider it is always Yes once Device::APICreateSwapChain gets
+    // dropped
+    enum class ManagesSwapChain {
+        Yes,
+        No,
+        Unknown,
+    };
+    ManagesSwapChain mIsSwapChainManagedBySurface = ManagesSwapChain::Unknown;
+
+    // A cache is mutable because potentially modified in const-qualified getters
+    std::unique_ptr<AdapterSurfaceCapCache> mCapabilityCache;
+
     // MetalLayer
     raw_ptr<void> mMetalLayer = nullptr;
 
diff --git a/src/dawn/native/SwapChain.cpp b/src/dawn/native/SwapChain.cpp
index 3c00d47..8f76306 100644
--- a/src/dawn/native/SwapChain.cpp
+++ b/src/dawn/native/SwapChain.cpp
@@ -28,6 +28,7 @@
 #include "dawn/native/SwapChain.h"
 
 #include <utility>
+#include <vector>
 
 #include "dawn/common/Constants.h"
 #include "dawn/native/Device.h"
@@ -43,11 +44,11 @@
 
 class ErrorSwapChain final : public SwapChainBase {
   public:
-    explicit ErrorSwapChain(DeviceBase* device, const SwapChainDescriptor* desc)
-        : SwapChainBase(device, desc, ObjectBase::kError) {}
+    explicit ErrorSwapChain(DeviceBase* device, const SurfaceConfiguration* config)
+        : SwapChainBase(device, config, ObjectBase::kError) {}
 
   private:
-    ResultOrError<Ref<TextureBase>> GetCurrentTextureImpl() override { DAWN_UNREACHABLE(); }
+    ResultOrError<SwapChainTextureInfo> GetCurrentTextureImpl() override { DAWN_UNREACHABLE(); }
     MaybeError PresentImpl() override { DAWN_UNREACHABLE(); }
     void DetachFromSurfaceImpl() override { DAWN_UNREACHABLE(); }
 };
@@ -109,6 +110,8 @@
     desc.dimension = wgpu::TextureDimension::e2D;
     desc.size = {swapChain->GetWidth(), swapChain->GetHeight(), 1};
     desc.format = swapChain->GetFormat();
+    desc.viewFormatCount = swapChain->GetViewFormats().size();
+    desc.viewFormats = swapChain->GetViewFormats().data();
     desc.mipLevelCount = 1;
     desc.sampleCount = 1;
 
@@ -117,38 +120,56 @@
 
 SwapChainBase::SwapChainBase(DeviceBase* device,
                              Surface* surface,
-                             const SwapChainDescriptor* descriptor)
+                             const SurfaceConfiguration* config)
     : ApiObjectBase(device, kLabelNotImplemented),
-      mWidth(descriptor->width),
-      mHeight(descriptor->height),
-      mFormat(descriptor->format),
-      mUsage(descriptor->usage),
-      mPresentMode(descriptor->presentMode),
+      mWidth(config->width),
+      mHeight(config->height),
+      mFormat(config->format),
+      mUsage(config->usage),
+      mPresentMode(config->presentMode),
+      mAlphaMode(config->alphaMode),
       mSurface(surface) {
     GetObjectTrackingList()->Track(this);
+    for (uint32_t i = 0; i < config->viewFormatCount; ++i) {
+        if (config->viewFormats[i] == config->format) {
+            // Skip our own format, like texture creations does.
+            continue;
+        }
+        mViewFormats.push_back(config->viewFormats[i]);
+    }
+}
+
+FormatSet SwapChainBase::ComputeViewFormatSet() const {
+    FormatSet viewFormatSet;
+    for (wgpu::TextureFormat format : mViewFormats) {
+        viewFormatSet[GetDevice()->GetValidInternalFormat(format)] = true;
+    }
+    return viewFormatSet;
 }
 
 SwapChainBase::~SwapChainBase() {
-    if (mCurrentTexture != nullptr) {
-        DAWN_ASSERT(mCurrentTexture->IsDestroyed());
+    if (mCurrentTextureInfo.texture != nullptr) {
+        DAWN_ASSERT(mCurrentTextureInfo.texture->IsDestroyed());
     }
 
     DAWN_ASSERT(!mAttached);
 }
 
 SwapChainBase::SwapChainBase(DeviceBase* device,
-                             const SwapChainDescriptor* descriptor,
+                             const SurfaceConfiguration* config,
                              ObjectBase::ErrorTag tag)
     : ApiObjectBase(device, tag),
-      mWidth(descriptor->width),
-      mHeight(descriptor->height),
-      mFormat(descriptor->format),
-      mUsage(descriptor->usage),
-      mPresentMode(descriptor->presentMode) {}
+      mWidth(config->width),
+      mHeight(config->height),
+      mFormat(config->format),
+      mUsage(config->usage),
+      mPresentMode(config->presentMode),
+      mAlphaMode(config->alphaMode) {}
 
 // static
-Ref<SwapChainBase> SwapChainBase::MakeError(DeviceBase* device, const SwapChainDescriptor* desc) {
-    return AcquireRef(new ErrorSwapChain(device, desc));
+Ref<SwapChainBase> SwapChainBase::MakeError(DeviceBase* device,
+                                            const SurfaceConfiguration* config) {
+    return AcquireRef(new ErrorSwapChain(device, config));
 }
 
 void SwapChainBase::DestroyImpl() {}
@@ -178,14 +199,15 @@
 }
 
 TextureBase* SwapChainBase::APIGetCurrentTexture() {
-    Ref<TextureBase> result;
+    SurfaceTexture result;
     if (GetDevice()->ConsumedError(GetCurrentTexture(), &result, "calling %s.GetCurrentTexture()",
                                    this)) {
         TextureDescriptor desc = GetSwapChainBaseTextureDescriptor(this);
-        result = TextureBase::MakeError(GetDevice(), &desc);
-        SetChildLabel(result.Get());
+        Ref<TextureBase> errorTexture = TextureBase::MakeError(GetDevice(), &desc);
+        SetChildLabel(errorTexture.Get());
+        result.texture = ReturnToAPI(std::move(errorTexture));
     }
-    return ReturnToAPI(std::move(result));
+    return result.texture;
 }
 
 TextureViewBase* SwapChainBase::APIGetCurrentTextureView() {
@@ -198,33 +220,36 @@
     return ReturnToAPI(std::move(result));
 }
 
-ResultOrError<Ref<TextureBase>> SwapChainBase::GetCurrentTexture() {
+ResultOrError<SurfaceTexture> SwapChainBase::GetCurrentTexture() {
     DAWN_TRY(ValidateGetCurrentTexture());
+    SurfaceTexture surfaceTexture;
 
-    if (mCurrentTexture != nullptr) {
-        // Calling GetCurrentTexture always returns a new reference.
-        return mCurrentTexture;
+    if (mCurrentTextureInfo.texture == nullptr) {
+        DAWN_TRY_ASSIGN(mCurrentTextureInfo, GetCurrentTextureImpl());
+        SetChildLabel(mCurrentTextureInfo.texture.Get());
+
+        // Check that the return texture matches exactly what was given for this descriptor.
+        DAWN_ASSERT(mCurrentTextureInfo.texture->GetFormat().format == mFormat);
+        DAWN_ASSERT(IsSubset(mUsage, mCurrentTextureInfo.texture->GetUsage()));
+        DAWN_ASSERT(mCurrentTextureInfo.texture->GetDimension() == wgpu::TextureDimension::e2D);
+        DAWN_ASSERT(mCurrentTextureInfo.texture->GetWidth(Aspect::Color) == mWidth);
+        DAWN_ASSERT(mCurrentTextureInfo.texture->GetHeight(Aspect::Color) == mHeight);
+        DAWN_ASSERT(mCurrentTextureInfo.texture->GetNumMipLevels() == 1);
+        DAWN_ASSERT(mCurrentTextureInfo.texture->GetArrayLayers() == 1);
+        DAWN_ASSERT(mCurrentTextureInfo.texture->GetViewFormats() == ComputeViewFormatSet());
     }
 
-    DAWN_TRY_ASSIGN(mCurrentTexture, GetCurrentTextureImpl());
-    SetChildLabel(mCurrentTexture.Get());
-
-    // Check that the return texture matches exactly what was given for this descriptor.
-    DAWN_ASSERT(mCurrentTexture->GetFormat().format == mFormat);
-    DAWN_ASSERT(IsSubset(mUsage, mCurrentTexture->GetUsage()));
-    DAWN_ASSERT(mCurrentTexture->GetDimension() == wgpu::TextureDimension::e2D);
-    DAWN_ASSERT(mCurrentTexture->GetWidth(Aspect::Color) == mWidth);
-    DAWN_ASSERT(mCurrentTexture->GetHeight(Aspect::Color) == mHeight);
-    DAWN_ASSERT(mCurrentTexture->GetNumMipLevels() == 1);
-    DAWN_ASSERT(mCurrentTexture->GetArrayLayers() == 1);
-
-    return mCurrentTexture;
+    // Calling GetCurrentTexture always returns a new reference.
+    surfaceTexture.texture = Ref<TextureBase>(mCurrentTextureInfo.texture).Detach();
+    surfaceTexture.suboptimal = mCurrentTextureInfo.suboptimal;
+    surfaceTexture.status = mCurrentTextureInfo.status;
+    return surfaceTexture;
 }
 
 ResultOrError<Ref<TextureViewBase>> SwapChainBase::GetCurrentTextureView() {
-    Ref<TextureBase> currentTexture;
-    DAWN_TRY_ASSIGN(currentTexture, GetCurrentTexture());
-    return currentTexture->CreateView();
+    SurfaceTexture surfaceTexture;
+    DAWN_TRY_ASSIGN(surfaceTexture, GetCurrentTexture());
+    return surfaceTexture.texture->CreateView();
 }
 
 void SwapChainBase::APIPresent() {
@@ -236,8 +261,8 @@
         return;
     }
 
-    DAWN_ASSERT(mCurrentTexture->IsDestroyed());
-    mCurrentTexture = nullptr;
+    DAWN_ASSERT(mCurrentTextureInfo.texture->IsDestroyed());
+    mCurrentTextureInfo.texture = nullptr;
 }
 
 uint32_t SwapChainBase::GetWidth() const {
@@ -252,6 +277,10 @@
     return mFormat;
 }
 
+const std::vector<wgpu::TextureFormat>& SwapChainBase::GetViewFormats() const {
+    return mViewFormats;
+}
+
 wgpu::TextureUsage SwapChainBase::GetUsage() const {
     return mUsage;
 }
@@ -260,6 +289,10 @@
     return mPresentMode;
 }
 
+wgpu::CompositeAlphaMode SwapChainBase::GetAlphaMode() const {
+    return mAlphaMode;
+}
+
 Surface* SwapChainBase::GetSurface() const {
     return mSurface;
 }
@@ -278,7 +311,7 @@
 
     DAWN_INVALID_IF(!mAttached, "Cannot call Present called on detached %s.", this);
 
-    DAWN_INVALID_IF(mCurrentTexture == nullptr,
+    DAWN_INVALID_IF(mCurrentTextureInfo.texture == nullptr,
                     "GetCurrentTexture was not called on %s this frame prior to calling Present.",
                     this);
 
diff --git a/src/dawn/native/SwapChain.h b/src/dawn/native/SwapChain.h
index 20f9ae3..09d1a52 100644
--- a/src/dawn/native/SwapChain.h
+++ b/src/dawn/native/SwapChain.h
@@ -28,7 +28,10 @@
 #ifndef SRC_DAWN_NATIVE_SWAPCHAIN_H_
 #define SRC_DAWN_NATIVE_SWAPCHAIN_H_
 
+#include <vector>
+
 #include "dawn/native/Error.h"
+#include "dawn/native/Format.h"
 #include "dawn/native/Forward.h"
 #include "dawn/native/ObjectBase.h"
 #include "dawn/native/dawn_platform.h"
@@ -36,17 +39,25 @@
 
 namespace dawn::native {
 
+// TODO(dawn:2320) Remove the SwapChainDescriptor once the deprecation period is finished and
+// APICreateSwapChain gets dropped
 MaybeError ValidateSwapChainDescriptor(const DeviceBase* device,
                                        const Surface* surface,
                                        const SwapChainDescriptor* descriptor);
 
 TextureDescriptor GetSwapChainBaseTextureDescriptor(SwapChainBase* swapChain);
 
+struct SwapChainTextureInfo {
+    Ref<TextureBase> texture;
+    wgpu::Bool suboptimal;
+    wgpu::SurfaceGetCurrentTextureStatus status;
+};
+
 class SwapChainBase : public ApiObjectBase {
   public:
-    SwapChainBase(DeviceBase* device, Surface* surface, const SwapChainDescriptor* descriptor);
+    SwapChainBase(DeviceBase* device, Surface* surface, const SurfaceConfiguration* config);
 
-    static Ref<SwapChainBase> MakeError(DeviceBase* device, const SwapChainDescriptor* descriptor);
+    static Ref<SwapChainBase> MakeError(DeviceBase* device, const SurfaceConfiguration* config);
     ObjectType GetType() const override;
 
     // This is called when the swapchain is detached when one of the following happens:
@@ -88,19 +99,27 @@
     uint32_t GetWidth() const;
     uint32_t GetHeight() const;
     wgpu::TextureFormat GetFormat() const;
+    const std::vector<wgpu::TextureFormat>& GetViewFormats() const;
     wgpu::TextureUsage GetUsage() const;
     wgpu::PresentMode GetPresentMode() const;
+    wgpu::CompositeAlphaMode GetAlphaMode() const;
     Surface* GetSurface() const;
     bool IsAttached() const;
     wgpu::BackendType GetBackendType() const;
 
+    // The returned texture must match the swapchain descriptor exactly.
+    ResultOrError<SurfaceTexture> GetCurrentTexture();
+
   protected:
-    SwapChainBase(DeviceBase* device, const SwapChainDescriptor* desc, ObjectBase::ErrorTag tag);
+    SwapChainBase(DeviceBase* device, const SurfaceConfiguration* config, ObjectBase::ErrorTag tag);
     ~SwapChainBase() override;
     void DestroyImpl() override;
 
   private:
     void SetChildLabel(ApiObjectBase* child) const;
+    // Get a format set from mViewFormats (equivalent information, but easier to validate the
+    // current texture)
+    FormatSet ComputeViewFormatSet() const;
 
     bool mAttached = false;
     uint32_t mWidth;
@@ -108,11 +127,15 @@
     wgpu::TextureFormat mFormat;
     wgpu::TextureUsage mUsage;
     wgpu::PresentMode mPresentMode;
+    // This is not stored as a FormatSet so that it can hold the data pointed to by the
+    // descriptor returned by GetSwapChainBaseTextureDescriptor():
+    std::vector<wgpu::TextureFormat> mViewFormats;
+    wgpu::CompositeAlphaMode mAlphaMode;
 
     // This is a weak reference to the surface. If the surface is destroyed it will call
     // DetachFromSurface and mSurface will be updated to nullptr.
     raw_ptr<Surface> mSurface = nullptr;
-    Ref<TextureBase> mCurrentTexture;
+    SwapChainTextureInfo mCurrentTextureInfo;
 
     MaybeError ValidatePresent() const;
     MaybeError ValidateGetCurrentTexture() const;
@@ -120,9 +143,7 @@
     // GetCurrentTextureImpl and PresentImpl are guaranteed to be called in an interleaved manner,
     // starting with GetCurrentTextureImpl.
 
-    // The returned texture must match the swapchain descriptor exactly.
-    ResultOrError<Ref<TextureBase>> GetCurrentTexture();
-    virtual ResultOrError<Ref<TextureBase>> GetCurrentTextureImpl() = 0;
+    virtual ResultOrError<SwapChainTextureInfo> GetCurrentTextureImpl() = 0;
 
     ResultOrError<Ref<TextureViewBase>> GetCurrentTextureView();
 
diff --git a/src/dawn/native/d3d/PhysicalDeviceD3D.cpp b/src/dawn/native/d3d/PhysicalDeviceD3D.cpp
index 926ef45..c9e2753 100644
--- a/src/dawn/native/d3d/PhysicalDeviceD3D.cpp
+++ b/src/dawn/native/d3d/PhysicalDeviceD3D.cpp
@@ -52,6 +52,34 @@
     return mBackend;
 }
 
+ResultOrError<PhysicalDeviceSurfaceCapabilities> PhysicalDevice::GetSurfaceCapabilities(
+    const Surface*) const {
+    PhysicalDeviceSurfaceCapabilities capabilities;
+
+    // Formats
+
+    // This is the only supported format in native mode (see crbug.com/dawn/160).
+    capabilities.formats.push_back(wgpu::TextureFormat::BGRA8Unorm);
+
+    // Present Modes
+
+    capabilities.presentModes = {
+        wgpu::PresentMode::Fifo,
+        wgpu::PresentMode::Immediate,
+        wgpu::PresentMode::Mailbox,
+    };
+
+    // Alpha Modes
+
+    capabilities.alphaModes = {
+        wgpu::CompositeAlphaMode::Opaque,
+        wgpu::CompositeAlphaMode::Premultiplied,
+        wgpu::CompositeAlphaMode::Auto,
+    };
+
+    return capabilities;
+}
+
 MaybeError PhysicalDevice::InitializeImpl() {
     DXGI_ADAPTER_DESC1 adapterDesc;
     GetHardwareAdapter()->GetDesc1(&adapterDesc);
diff --git a/src/dawn/native/d3d/PhysicalDeviceD3D.h b/src/dawn/native/d3d/PhysicalDeviceD3D.h
index 6f73b8d..4d69d184 100644
--- a/src/dawn/native/d3d/PhysicalDeviceD3D.h
+++ b/src/dawn/native/d3d/PhysicalDeviceD3D.h
@@ -47,6 +47,9 @@
     IDXGIAdapter3* GetHardwareAdapter() const;
     Backend* GetBackend() const;
 
+    ResultOrError<PhysicalDeviceSurfaceCapabilities> GetSurfaceCapabilities(
+        const Surface* surface) const override;
+
   protected:
     MaybeError InitializeImpl() override;
 
diff --git a/src/dawn/native/d3d11/DeviceD3D11.cpp b/src/dawn/native/d3d11/DeviceD3D11.cpp
index dc5b66d..62370c27 100644
--- a/src/dawn/native/d3d11/DeviceD3D11.cpp
+++ b/src/dawn/native/d3d11/DeviceD3D11.cpp
@@ -212,11 +212,10 @@
     return ShaderModule::Create(this, descriptor, parseResult, compilationMessages);
 }
 
-ResultOrError<Ref<SwapChainBase>> Device::CreateSwapChainImpl(
-    Surface* surface,
-    SwapChainBase* previousSwapChain,
-    const SwapChainDescriptor* descriptor) {
-    return SwapChain::Create(this, surface, previousSwapChain, descriptor);
+ResultOrError<Ref<SwapChainBase>> Device::CreateSwapChainImpl(Surface* surface,
+                                                              SwapChainBase* previousSwapChain,
+                                                              const SurfaceConfiguration* config) {
+    return SwapChain::Create(this, surface, previousSwapChain, config);
 }
 
 ResultOrError<Ref<TextureBase>> Device::CreateTextureImpl(
diff --git a/src/dawn/native/d3d11/DeviceD3D11.h b/src/dawn/native/d3d11/DeviceD3D11.h
index 21aca83..3a7aa86 100644
--- a/src/dawn/native/d3d11/DeviceD3D11.h
+++ b/src/dawn/native/d3d11/DeviceD3D11.h
@@ -128,7 +128,7 @@
     ResultOrError<Ref<SwapChainBase>> CreateSwapChainImpl(
         Surface* surface,
         SwapChainBase* previousSwapChain,
-        const SwapChainDescriptor* descriptor) override;
+        const SurfaceConfiguration* config) override;
     ResultOrError<Ref<TextureBase>> CreateTextureImpl(
         const UnpackedPtr<TextureDescriptor>& descriptor) override;
     ResultOrError<Ref<TextureViewBase>> CreateTextureViewImpl(
diff --git a/src/dawn/native/d3d11/SwapChainD3D11.cpp b/src/dawn/native/d3d11/SwapChainD3D11.cpp
index 8100240..8a83ac8 100644
--- a/src/dawn/native/d3d11/SwapChainD3D11.cpp
+++ b/src/dawn/native/d3d11/SwapChainD3D11.cpp
@@ -45,8 +45,8 @@
 ResultOrError<Ref<SwapChain>> SwapChain::Create(Device* device,
                                                 Surface* surface,
                                                 SwapChainBase* previousSwapChain,
-                                                const SwapChainDescriptor* descriptor) {
-    Ref<SwapChain> swapchain = AcquireRef(new SwapChain(device, surface, descriptor));
+                                                const SurfaceConfiguration* config) {
+    Ref<SwapChain> swapchain = AcquireRef(new SwapChain(device, surface, config));
     DAWN_TRY(swapchain->Initialize(previousSwapChain));
     return swapchain;
 }
@@ -84,12 +84,17 @@
     return {};
 }
 
-ResultOrError<Ref<TextureBase>> SwapChain::GetCurrentTextureImpl() {
+ResultOrError<SwapChainTextureInfo> SwapChain::GetCurrentTextureImpl() {
     // Create the API side objects for this use of the swapchain's buffer.
     TextureDescriptor descriptor = GetSwapChainBaseTextureDescriptor(this);
     DAWN_TRY_ASSIGN(mApiTexture,
                     Texture::Create(ToBackend(GetDevice()), Unpack(&descriptor), mBuffer));
-    return mApiTexture;
+    SwapChainTextureInfo info;
+    info.texture = mApiTexture;
+    info.status = wgpu::SurfaceGetCurrentTextureStatus::Success;
+    // TODO(dawn:2320) Check for optimality
+    info.suboptimal = false;
+    return info;
 }
 
 MaybeError SwapChain::DetachAndWaitForDeallocation() {
diff --git a/src/dawn/native/d3d11/SwapChainD3D11.h b/src/dawn/native/d3d11/SwapChainD3D11.h
index 58a7d4c..501183d 100644
--- a/src/dawn/native/d3d11/SwapChainD3D11.h
+++ b/src/dawn/native/d3d11/SwapChainD3D11.h
@@ -44,7 +44,7 @@
     static ResultOrError<Ref<SwapChain>> Create(Device* device,
                                                 Surface* surface,
                                                 SwapChainBase* previousSwapChain,
-                                                const SwapChainDescriptor* descriptor);
+                                                const SurfaceConfiguration* config);
 
   private:
     using Base = d3d::SwapChain;
@@ -53,7 +53,7 @@
 
     // SwapChainBase implementation
     MaybeError PresentImpl() override;
-    ResultOrError<Ref<TextureBase>> GetCurrentTextureImpl() override;
+    ResultOrError<SwapChainTextureInfo> GetCurrentTextureImpl() override;
     void DetachFromSurfaceImpl() override;
 
     // d3d::SwapChain implementation
diff --git a/src/dawn/native/d3d12/DeviceD3D12.cpp b/src/dawn/native/d3d12/DeviceD3D12.cpp
index 2bec840..b5baa5d 100644
--- a/src/dawn/native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn/native/d3d12/DeviceD3D12.cpp
@@ -397,11 +397,10 @@
     OwnedCompilationMessages* compilationMessages) {
     return ShaderModule::Create(this, descriptor, parseResult, compilationMessages);
 }
-ResultOrError<Ref<SwapChainBase>> Device::CreateSwapChainImpl(
-    Surface* surface,
-    SwapChainBase* previousSwapChain,
-    const SwapChainDescriptor* descriptor) {
-    return SwapChain::Create(this, surface, previousSwapChain, descriptor);
+ResultOrError<Ref<SwapChainBase>> Device::CreateSwapChainImpl(Surface* surface,
+                                                              SwapChainBase* previousSwapChain,
+                                                              const SurfaceConfiguration* config) {
+    return SwapChain::Create(this, surface, previousSwapChain, config);
 }
 ResultOrError<Ref<TextureBase>> Device::CreateTextureImpl(
     const UnpackedPtr<TextureDescriptor>& descriptor) {
diff --git a/src/dawn/native/d3d12/DeviceD3D12.h b/src/dawn/native/d3d12/DeviceD3D12.h
index c8040cc..93ebae8 100644
--- a/src/dawn/native/d3d12/DeviceD3D12.h
+++ b/src/dawn/native/d3d12/DeviceD3D12.h
@@ -207,7 +207,7 @@
     ResultOrError<Ref<SwapChainBase>> CreateSwapChainImpl(
         Surface* surface,
         SwapChainBase* previousSwapChain,
-        const SwapChainDescriptor* descriptor) override;
+        const SurfaceConfiguration* config) override;
     ResultOrError<Ref<TextureBase>> CreateTextureImpl(
         const UnpackedPtr<TextureDescriptor>& descriptor) override;
     ResultOrError<Ref<TextureViewBase>> CreateTextureViewImpl(
diff --git a/src/dawn/native/d3d12/SwapChainD3D12.cpp b/src/dawn/native/d3d12/SwapChainD3D12.cpp
index 0892149..2f5da77 100644
--- a/src/dawn/native/d3d12/SwapChainD3D12.cpp
+++ b/src/dawn/native/d3d12/SwapChainD3D12.cpp
@@ -46,8 +46,8 @@
 ResultOrError<Ref<SwapChain>> SwapChain::Create(Device* device,
                                                 Surface* surface,
                                                 SwapChainBase* previousSwapChain,
-                                                const SwapChainDescriptor* descriptor) {
-    Ref<SwapChain> swapchain = AcquireRef(new SwapChain(device, surface, descriptor));
+                                                const SurfaceConfiguration* config) {
+    Ref<SwapChain> swapchain = AcquireRef(new SwapChain(device, surface, config));
     DAWN_TRY(swapchain->Initialize(previousSwapChain));
     return swapchain;
 }
@@ -105,7 +105,7 @@
     return {};
 }
 
-ResultOrError<Ref<TextureBase>> SwapChain::GetCurrentTextureImpl() {
+ResultOrError<SwapChainTextureInfo> SwapChain::GetCurrentTextureImpl() {
     Queue* queue = ToBackend(GetDevice()->GetQueue());
 
     // Synchronously wait until previous operations on the next swapchain buffer are finished.
@@ -119,7 +119,13 @@
     TextureDescriptor descriptor = GetSwapChainBaseTextureDescriptor(this);
     DAWN_TRY_ASSIGN(mApiTexture, Texture::Create(ToBackend(GetDevice()), Unpack(&descriptor),
                                                  mBuffers[mCurrentBuffer]));
-    return mApiTexture;
+
+    SwapChainTextureInfo info;
+    info.texture = mApiTexture;
+    info.status = wgpu::SurfaceGetCurrentTextureStatus::Success;
+    // TODO(dawn:2320) Check for optimality
+    info.suboptimal = false;
+    return info;
 }
 
 MaybeError SwapChain::DetachAndWaitForDeallocation() {
diff --git a/src/dawn/native/d3d12/SwapChainD3D12.h b/src/dawn/native/d3d12/SwapChainD3D12.h
index eb0569b..b3ecedf 100644
--- a/src/dawn/native/d3d12/SwapChainD3D12.h
+++ b/src/dawn/native/d3d12/SwapChainD3D12.h
@@ -45,7 +45,7 @@
     static ResultOrError<Ref<SwapChain>> Create(Device* device,
                                                 Surface* surface,
                                                 SwapChainBase* previousSwapChain,
-                                                const SwapChainDescriptor* descriptor);
+                                                const SurfaceConfiguration* config);
 
   private:
     using Base = d3d::SwapChain;
@@ -54,7 +54,7 @@
 
     // SwapChainBase implementation
     MaybeError PresentImpl() override;
-    ResultOrError<Ref<TextureBase>> GetCurrentTextureImpl() override;
+    ResultOrError<SwapChainTextureInfo> GetCurrentTextureImpl() override;
     void DetachFromSurfaceImpl() override;
 
     // d3d::SwapChain implementation
diff --git a/src/dawn/native/metal/BackendMTL.mm b/src/dawn/native/metal/BackendMTL.mm
index 68a8b73..f5c51e7 100644
--- a/src/dawn/native/metal/BackendMTL.mm
+++ b/src/dawn/native/metal/BackendMTL.mm
@@ -303,6 +303,34 @@
 
     bool SupportsFeatureLevel(FeatureLevel) const override { return true; }
 
+    ResultOrError<PhysicalDeviceSurfaceCapabilities> GetSurfaceCapabilities(
+        const Surface*) const override {
+        PhysicalDeviceSurfaceCapabilities capabilities;
+
+        // Formats
+
+        // This is the only supported format in native mode (see crbug.com/dawn/160).
+        capabilities.formats.push_back(wgpu::TextureFormat::BGRA8Unorm);
+
+        // Present Modes
+
+        capabilities.presentModes = {
+            wgpu::PresentMode::Fifo,
+            wgpu::PresentMode::Immediate,
+            wgpu::PresentMode::Mailbox,
+        };
+
+        // Alpha Modes
+
+        capabilities.alphaModes = {
+            wgpu::CompositeAlphaMode::Opaque,
+            wgpu::CompositeAlphaMode::Premultiplied,
+            wgpu::CompositeAlphaMode::Auto,
+        };
+
+        return capabilities;
+    }
+
   private:
     ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(AdapterBase* adapter,
                                                     const UnpackedPtr<DeviceDescriptor>& descriptor,
diff --git a/src/dawn/native/metal/DeviceMTL.h b/src/dawn/native/metal/DeviceMTL.h
index fa40bbb..c273e0b 100644
--- a/src/dawn/native/metal/DeviceMTL.h
+++ b/src/dawn/native/metal/DeviceMTL.h
@@ -113,7 +113,7 @@
     ResultOrError<Ref<SwapChainBase>> CreateSwapChainImpl(
         Surface* surface,
         SwapChainBase* previousSwapChain,
-        const SwapChainDescriptor* descriptor) override;
+        const SurfaceConfiguration* config) override;
     ResultOrError<Ref<TextureBase>> CreateTextureImpl(
         const UnpackedPtr<TextureDescriptor>& descriptor) override;
     ResultOrError<Ref<TextureViewBase>> CreateTextureViewImpl(
diff --git a/src/dawn/native/metal/DeviceMTL.mm b/src/dawn/native/metal/DeviceMTL.mm
index 2ac4c5f..6158b98 100644
--- a/src/dawn/native/metal/DeviceMTL.mm
+++ b/src/dawn/native/metal/DeviceMTL.mm
@@ -224,11 +224,10 @@
     OwnedCompilationMessages* compilationMessages) {
     return ShaderModule::Create(this, descriptor, parseResult, compilationMessages);
 }
-ResultOrError<Ref<SwapChainBase>> Device::CreateSwapChainImpl(
-    Surface* surface,
-    SwapChainBase* previousSwapChain,
-    const SwapChainDescriptor* descriptor) {
-    return SwapChain::Create(this, surface, previousSwapChain, descriptor);
+ResultOrError<Ref<SwapChainBase>> Device::CreateSwapChainImpl(Surface* surface,
+                                                              SwapChainBase* previousSwapChain,
+                                                              const SurfaceConfiguration* config) {
+    return SwapChain::Create(this, surface, previousSwapChain, config);
 }
 ResultOrError<Ref<TextureBase>> Device::CreateTextureImpl(
     const UnpackedPtr<TextureDescriptor>& descriptor) {
diff --git a/src/dawn/native/metal/SwapChainMTL.h b/src/dawn/native/metal/SwapChainMTL.h
index 3bf596c..7786310 100644
--- a/src/dawn/native/metal/SwapChainMTL.h
+++ b/src/dawn/native/metal/SwapChainMTL.h
@@ -45,9 +45,9 @@
     static ResultOrError<Ref<SwapChain>> Create(Device* device,
                                                 Surface* surface,
                                                 SwapChainBase* previousSwapChain,
-                                                const SwapChainDescriptor* descriptor);
+                                                const SurfaceConfiguration* config);
 
-    SwapChain(DeviceBase* device, Surface* surface, const SwapChainDescriptor* descriptor);
+    SwapChain(DeviceBase* device, Surface* surface, const SurfaceConfiguration* config);
     ~SwapChain() override;
 
   private:
@@ -62,7 +62,7 @@
     Ref<Texture> mTexture;
 
     MaybeError PresentImpl() override;
-    ResultOrError<Ref<TextureBase>> GetCurrentTextureImpl() override;
+    ResultOrError<SwapChainTextureInfo> GetCurrentTextureImpl() override;
     void DetachFromSurfaceImpl() override;
 };
 
diff --git a/src/dawn/native/metal/SwapChainMTL.mm b/src/dawn/native/metal/SwapChainMTL.mm
index 981d415..ad9b9aa 100644
--- a/src/dawn/native/metal/SwapChainMTL.mm
+++ b/src/dawn/native/metal/SwapChainMTL.mm
@@ -40,14 +40,14 @@
 ResultOrError<Ref<SwapChain>> SwapChain::Create(Device* device,
                                                 Surface* surface,
                                                 SwapChainBase* previousSwapChain,
-                                                const SwapChainDescriptor* descriptor) {
-    Ref<SwapChain> swapchain = AcquireRef(new SwapChain(device, surface, descriptor));
+                                                const SurfaceConfiguration* config) {
+    Ref<SwapChain> swapchain = AcquireRef(new SwapChain(device, surface, config));
     DAWN_TRY(swapchain->Initialize(previousSwapChain));
     return swapchain;
 }
 
-SwapChain::SwapChain(DeviceBase* dev, Surface* sur, const SwapChainDescriptor* desc)
-    : SwapChainBase(dev, sur, desc) {}
+SwapChain::SwapChain(DeviceBase* dev, Surface* sur, const SurfaceConfiguration* config)
+    : SwapChainBase(dev, sur, config) {}
 
 SwapChain::~SwapChain() = default;
 
@@ -82,6 +82,9 @@
     [*mLayer setDevice:ToBackend(GetDevice())->GetMTLDevice()];
     [*mLayer setPixelFormat:MetalPixelFormat(GetDevice(), GetFormat())];
 
+    // TODO(dawn:2320) Check that this behaves as expected by the spec
+    [*mLayer setOpaque:(GetAlphaMode() != wgpu::CompositeAlphaMode::Premultiplied)];
+
 #if DAWN_PLATFORM_IS(MACOS)
     [*mLayer setDisplaySyncEnabled:(GetPresentMode() != wgpu::PresentMode::Immediate)];
 #endif  // DAWN_PLATFORM_IS(MACOS)
@@ -103,7 +106,7 @@
     return {};
 }
 
-ResultOrError<Ref<TextureBase>> SwapChain::GetCurrentTextureImpl() {
+ResultOrError<SwapChainTextureInfo> SwapChain::GetCurrentTextureImpl() {
     @autoreleasepool {
         DAWN_ASSERT(mCurrentDrawable == nullptr);
         mCurrentDrawable = [*mLayer nextDrawable];
@@ -112,7 +115,13 @@
 
         mTexture = Texture::CreateWrapping(ToBackend(GetDevice()), Unpack(&textureDesc),
                                            NSPRef<id<MTLTexture>>([*mCurrentDrawable texture]));
-        return mTexture;
+
+        SwapChainTextureInfo info;
+        info.texture = mTexture;
+        info.status = wgpu::SurfaceGetCurrentTextureStatus::Success;
+        // TODO(dawn:2320) Check for optimality
+        info.suboptimal = false;
+        return info;
     }
 }
 
diff --git a/src/dawn/native/null/DeviceNull.cpp b/src/dawn/native/null/DeviceNull.cpp
index db54784..6f6bffb 100644
--- a/src/dawn/native/null/DeviceNull.cpp
+++ b/src/dawn/native/null/DeviceNull.cpp
@@ -66,6 +66,15 @@
     return true;
 }
 
+ResultOrError<PhysicalDeviceSurfaceCapabilities> PhysicalDevice::GetSurfaceCapabilities(
+    const Surface* surface) const {
+    PhysicalDeviceSurfaceCapabilities capabilities;
+    capabilities.formats = {wgpu::TextureFormat::BGRA8Unorm};
+    capabilities.presentModes = {wgpu::PresentMode::Fifo};
+    capabilities.alphaModes = {wgpu::CompositeAlphaMode::Auto};
+    return capabilities;
+}
+
 MaybeError PhysicalDevice::InitializeImpl() {
     return {};
 }
@@ -223,11 +232,10 @@
     DAWN_TRY(module->Initialize(parseResult, compilationMessages));
     return module;
 }
-ResultOrError<Ref<SwapChainBase>> Device::CreateSwapChainImpl(
-    Surface* surface,
-    SwapChainBase* previousSwapChain,
-    const SwapChainDescriptor* descriptor) {
-    return SwapChain::Create(this, surface, previousSwapChain, descriptor);
+ResultOrError<Ref<SwapChainBase>> Device::CreateSwapChainImpl(Surface* surface,
+                                                              SwapChainBase* previousSwapChain,
+                                                              const SurfaceConfiguration* config) {
+    return SwapChain::Create(this, surface, previousSwapChain, config);
 }
 ResultOrError<Ref<TextureBase>> Device::CreateTextureImpl(
     const UnpackedPtr<TextureDescriptor>& descriptor) {
@@ -507,8 +515,8 @@
 ResultOrError<Ref<SwapChain>> SwapChain::Create(Device* device,
                                                 Surface* surface,
                                                 SwapChainBase* previousSwapChain,
-                                                const SwapChainDescriptor* descriptor) {
-    Ref<SwapChain> swapchain = AcquireRef(new SwapChain(device, surface, descriptor));
+                                                const SurfaceConfiguration* config) {
+    Ref<SwapChain> swapchain = AcquireRef(new SwapChain(device, surface, config));
     DAWN_TRY(swapchain->Initialize(previousSwapChain));
     return swapchain;
 }
@@ -533,10 +541,14 @@
     return {};
 }
 
-ResultOrError<Ref<TextureBase>> SwapChain::GetCurrentTextureImpl() {
+ResultOrError<SwapChainTextureInfo> SwapChain::GetCurrentTextureImpl() {
     TextureDescriptor textureDesc = GetSwapChainBaseTextureDescriptor(this);
     mTexture = AcquireRef(new Texture(GetDevice(), Unpack(&textureDesc)));
-    return mTexture;
+    SwapChainTextureInfo info;
+    info.texture = mTexture;
+    info.status = wgpu::SurfaceGetCurrentTextureStatus::Success;
+    info.suboptimal = false;
+    return info;
 }
 
 void SwapChain::DetachFromSurfaceImpl() {
diff --git a/src/dawn/native/null/DeviceNull.h b/src/dawn/native/null/DeviceNull.h
index db6b596..c338066 100644
--- a/src/dawn/native/null/DeviceNull.h
+++ b/src/dawn/native/null/DeviceNull.h
@@ -164,7 +164,7 @@
     ResultOrError<Ref<SwapChainBase>> CreateSwapChainImpl(
         Surface* surface,
         SwapChainBase* previousSwapChain,
-        const SwapChainDescriptor* descriptor) override;
+        const SurfaceConfiguration* config) override;
     ResultOrError<Ref<TextureBase>> CreateTextureImpl(
         const UnpackedPtr<TextureDescriptor>& descriptor) override;
     ResultOrError<Ref<TextureViewBase>> CreateTextureViewImpl(
@@ -194,6 +194,9 @@
 
     bool SupportsFeatureLevel(FeatureLevel featureLevel) const override;
 
+    ResultOrError<PhysicalDeviceSurfaceCapabilities> GetSurfaceCapabilities(
+        const Surface* surface) const override;
+
     // Used for the tests that intend to use an adapter without all features enabled.
     using PhysicalDeviceBase::SetSupportedFeaturesForTesting;
 
@@ -321,7 +324,7 @@
     static ResultOrError<Ref<SwapChain>> Create(Device* device,
                                                 Surface* surface,
                                                 SwapChainBase* previousSwapChain,
-                                                const SwapChainDescriptor* descriptor);
+                                                const SurfaceConfiguration* config);
     ~SwapChain() override;
 
   private:
@@ -331,7 +334,7 @@
     Ref<Texture> mTexture;
 
     MaybeError PresentImpl() override;
-    ResultOrError<Ref<TextureBase>> GetCurrentTextureImpl() override;
+    ResultOrError<SwapChainTextureInfo> GetCurrentTextureImpl() override;
     void DetachFromSurfaceImpl() override;
 };
 
diff --git a/src/dawn/native/opengl/DeviceGL.cpp b/src/dawn/native/opengl/DeviceGL.cpp
index 7c5b95f..dc3b37d 100644
--- a/src/dawn/native/opengl/DeviceGL.cpp
+++ b/src/dawn/native/opengl/DeviceGL.cpp
@@ -259,10 +259,9 @@
     OwnedCompilationMessages* compilationMessages) {
     return ShaderModule::Create(this, descriptor, parseResult, compilationMessages);
 }
-ResultOrError<Ref<SwapChainBase>> Device::CreateSwapChainImpl(
-    Surface* surface,
-    SwapChainBase* previousSwapChain,
-    const SwapChainDescriptor* descriptor) {
+ResultOrError<Ref<SwapChainBase>> Device::CreateSwapChainImpl(Surface* surface,
+                                                              SwapChainBase* previousSwapChain,
+                                                              const SurfaceConfiguration* config) {
     return DAWN_VALIDATION_ERROR("New swapchains not implemented.");
 }
 ResultOrError<Ref<TextureBase>> Device::CreateTextureImpl(
diff --git a/src/dawn/native/opengl/DeviceGL.h b/src/dawn/native/opengl/DeviceGL.h
index 2ad1e2e..6849720 100644
--- a/src/dawn/native/opengl/DeviceGL.h
+++ b/src/dawn/native/opengl/DeviceGL.h
@@ -130,7 +130,7 @@
     ResultOrError<Ref<SwapChainBase>> CreateSwapChainImpl(
         Surface* surface,
         SwapChainBase* previousSwapChain,
-        const SwapChainDescriptor* descriptor) override;
+        const SurfaceConfiguration* config) override;
     ResultOrError<Ref<TextureBase>> CreateTextureImpl(
         const UnpackedPtr<TextureDescriptor>& descriptor) override;
     ResultOrError<Ref<TextureViewBase>> CreateTextureViewImpl(
diff --git a/src/dawn/native/opengl/PhysicalDeviceGL.cpp b/src/dawn/native/opengl/PhysicalDeviceGL.cpp
index c92b41b..ed6656b 100644
--- a/src/dawn/native/opengl/PhysicalDeviceGL.cpp
+++ b/src/dawn/native/opengl/PhysicalDeviceGL.cpp
@@ -446,6 +446,37 @@
     return featureLevel == FeatureLevel::Compatibility;
 }
 
+ResultOrError<PhysicalDeviceSurfaceCapabilities> PhysicalDevice::GetSurfaceCapabilities(
+    const Surface*) const {
+    PhysicalDeviceSurfaceCapabilities capabilities;
+
+    // Formats
+
+    // This is the only supported format in native mode (see crbug.com/dawn/160).
+#if DAWN_PLATFORM_IS(ANDROID)
+    capabilities.formats.push_back(wgpu::TextureFormat::RGBA8Unorm);
+#else
+    capabilities.formats.push_back(wgpu::TextureFormat::BGRA8Unorm);
+#endif  // !DAWN_PLATFORM_IS(ANDROID)
+
+    // Present Modes
+
+    capabilities.presentModes = {
+        wgpu::PresentMode::Fifo,
+        wgpu::PresentMode::Immediate,
+        wgpu::PresentMode::Mailbox,
+    };
+
+    // Alpha Modes
+
+    capabilities.alphaModes = {
+        wgpu::CompositeAlphaMode::Opaque,
+        wgpu::CompositeAlphaMode::Auto,
+    };
+
+    return capabilities;
+}
+
 FeatureValidationResult PhysicalDevice::ValidateFeatureSupportedWithTogglesImpl(
     wgpu::FeatureName feature,
     const TogglesState& toggles) const {
diff --git a/src/dawn/native/opengl/PhysicalDeviceGL.h b/src/dawn/native/opengl/PhysicalDeviceGL.h
index e2f4771..47897a0 100644
--- a/src/dawn/native/opengl/PhysicalDeviceGL.h
+++ b/src/dawn/native/opengl/PhysicalDeviceGL.h
@@ -46,6 +46,8 @@
     // PhysicalDeviceBase Implementation
     bool SupportsExternalImages() const override;
     bool SupportsFeatureLevel(FeatureLevel featureLevel) const override;
+    ResultOrError<PhysicalDeviceSurfaceCapabilities> GetSurfaceCapabilities(
+        const Surface* surface) const override;
 
   private:
     PhysicalDevice(InstanceBase* instance, wgpu::BackendType backendType, EGLDisplay display);
diff --git a/src/dawn/native/utils/WGPUHelpers.h b/src/dawn/native/utils/WGPUHelpers.h
index 376f774..5790df7 100644
--- a/src/dawn/native/utils/WGPUHelpers.h
+++ b/src/dawn/native/utils/WGPUHelpers.h
@@ -136,6 +136,31 @@
 
 const char* GetLabelForTrace(const char* label);
 
+// Given a std vector, allocate an equivalent array that can be returned in an API's foos/fooCount
+// pair of fields. The apiData must eventually be freed using FreeApiSeq.
+template <typename T>
+inline MaybeError AllocateApiSeqFromStdVector(const T*& apiData,
+                                              size_t& apiSize,
+                                              const std::vector<T>& vector) {
+    apiSize = vector.size();
+    if (apiSize > 0) {
+        T* mutableData = new T[apiSize];
+        memcpy(mutableData, vector.data(), apiSize * sizeof(T));
+        apiData = mutableData;
+    } else {
+        apiData = nullptr;
+    }
+    return {};
+}
+
+// Free an API sequence that was allocated by AllocateApiSeqFromStdVector
+template <typename T>
+inline void FreeApiSeq(T*& apiData, size_t& apiSize) {
+    delete[] apiData;
+    apiData = nullptr;
+    apiSize = 0;
+}
+
 }  // namespace dawn::native::utils
 
 #endif  // SRC_DAWN_NATIVE_UTILS_WGPUHELPERS_H_
diff --git a/src/dawn/native/vulkan/DeviceVk.cpp b/src/dawn/native/vulkan/DeviceVk.cpp
index d88ee8f..f308a76 100644
--- a/src/dawn/native/vulkan/DeviceVk.cpp
+++ b/src/dawn/native/vulkan/DeviceVk.cpp
@@ -211,11 +211,10 @@
     OwnedCompilationMessages* compilationMessages) {
     return ShaderModule::Create(this, descriptor, parseResult, compilationMessages);
 }
-ResultOrError<Ref<SwapChainBase>> Device::CreateSwapChainImpl(
-    Surface* surface,
-    SwapChainBase* previousSwapChain,
-    const SwapChainDescriptor* descriptor) {
-    return SwapChain::Create(this, surface, previousSwapChain, descriptor);
+ResultOrError<Ref<SwapChainBase>> Device::CreateSwapChainImpl(Surface* surface,
+                                                              SwapChainBase* previousSwapChain,
+                                                              const SurfaceConfiguration* config) {
+    return SwapChain::Create(this, surface, previousSwapChain, config);
 }
 ResultOrError<Ref<TextureBase>> Device::CreateTextureImpl(
     const UnpackedPtr<TextureDescriptor>& descriptor) {
diff --git a/src/dawn/native/vulkan/DeviceVk.h b/src/dawn/native/vulkan/DeviceVk.h
index 82856ec..3007197 100644
--- a/src/dawn/native/vulkan/DeviceVk.h
+++ b/src/dawn/native/vulkan/DeviceVk.h
@@ -146,7 +146,7 @@
     ResultOrError<Ref<SwapChainBase>> CreateSwapChainImpl(
         Surface* surface,
         SwapChainBase* previousSwapChain,
-        const SwapChainDescriptor* descriptor) override;
+        const SurfaceConfiguration* config) override;
     ResultOrError<Ref<TextureBase>> CreateTextureImpl(
         const UnpackedPtr<TextureDescriptor>& descriptor) override;
     ResultOrError<Ref<TextureViewBase>> CreateTextureViewImpl(
diff --git a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
index 79c4293..aadc109 100644
--- a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
+++ b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
@@ -36,8 +36,10 @@
 #include "dawn/native/Limits.h"
 #include "dawn/native/vulkan/BackendVk.h"
 #include "dawn/native/vulkan/DeviceVk.h"
+#include "dawn/native/vulkan/SwapChainVk.h"
 #include "dawn/native/vulkan/TextureVk.h"
 #include "dawn/native/vulkan/UtilsVulkan.h"
+#include "dawn/native/vulkan/VulkanError.h"
 #include "dawn/platform/DawnPlatform.h"
 
 #if DAWN_PLATFORM_IS(ANDROID)
@@ -836,6 +838,81 @@
     return mDefaultComputeSubgroupSize;
 }
 
+ResultOrError<PhysicalDeviceSurfaceCapabilities> PhysicalDevice::GetSurfaceCapabilities(
+    const Surface* surface) const {
+    PhysicalDeviceSurfaceCapabilities capabilities;
+
+    // Formats
+
+    // This is the only supported format in native mode (see crbug.com/dawn/160).
+#if DAWN_PLATFORM_IS(ANDROID)
+    capabilities.formats.push_back(wgpu::TextureFormat::RGBA8Unorm);
+#else
+    capabilities.formats.push_back(wgpu::TextureFormat::BGRA8Unorm);
+#endif  // !DAWN_PLATFORM_IS(ANDROID)
+
+    // Present Modes
+
+    // TODO(dawn:2320) Other present modes than Mailbox do not pass tests on Intel Graphics. Once
+    // 'surface' will actually contain a vkSurface we'll be able to test
+    // vkGetPhysicalDeviceSurfaceSupportKHR to avoid this #if.
+#if DAWN_PLATFORM_IS(LINUX)
+    capabilities.presentModes = {
+        wgpu::PresentMode::Mailbox,
+    };
+#else
+    capabilities.presentModes = {
+        wgpu::PresentMode::Fifo,
+        wgpu::PresentMode::Immediate,
+        wgpu::PresentMode::Mailbox,
+    };
+#endif  // !DAWN_PLATFORM_IS(LINUX)
+
+    // Alpha Modes
+
+#if !DAWN_PLATFORM_IS(ANDROID)
+    capabilities.alphaModes.push_back(wgpu::CompositeAlphaMode::Opaque);
+#else
+    VkSurfaceKHR vkSurface;
+    DAWN_TRY_ASSIGN(vkSurface, CreateVulkanSurface(this, surface));
+
+    VkPhysicalDevice vkPhysicalDevice = GetVkPhysicalDevice();
+    const VulkanFunctions& fn = GetVulkanInstance()->GetFunctions();
+    VkInstance vkInstance = GetVulkanInstance()->GetVkInstance();
+    const VulkanFunctions& vkFunctions = GetVulkanInstance()->GetFunctions();
+
+    // Get the surface capabilities
+    VkSurfaceCapabilitiesKHR vkCapabilities;
+    DAWN_TRY_WITH_CLEANUP(CheckVkSuccess(vkFunctions.GetPhysicalDeviceSurfaceCapabilitiesKHR(
+                                             vkPhysicalDevice, vkSurface, &vkCapabilities),
+                                         "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"),
+                          { fn.DestroySurfaceKHR(vkInstance, vkSurface, nullptr); });
+
+    fn.DestroySurfaceKHR(vkInstance, vkSurface, nullptr);
+
+    // TODO(dawn:286): investigate composite alpha for WebGPU native
+    struct AlphaModePairs {
+        VkCompositeAlphaFlagBitsKHR vkBit;
+        wgpu::CompositeAlphaMode webgpuEnum;
+    };
+    AlphaModePairs alphaModePairs[] = {
+        {VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, wgpu::CompositeAlphaMode::Opaque},
+        {VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, wgpu::CompositeAlphaMode::Premultiplied},
+        {VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR, wgpu::CompositeAlphaMode::Unpremultiplied},
+        {VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, wgpu::CompositeAlphaMode::Inherit},
+    };
+
+    for (auto mode : alphaModePairs) {
+        if (vkCapabilities.supportedCompositeAlpha & mode.vkBit) {
+            capabilities.alphaModes.push_back(mode.webgpuEnum);
+        }
+    }
+#endif  // #if !DAWN_PLATFORM_IS(ANDROID)
+    capabilities.alphaModes.push_back(wgpu::CompositeAlphaMode::Auto);
+
+    return capabilities;
+}
+
 void PhysicalDevice::PopulateBackendProperties(UnpackedPtr<AdapterProperties>& properties) const {
     if (auto* memoryHeapProperties = properties.Get<AdapterPropertiesMemoryHeaps>()) {
         size_t count = mDeviceInfo.memoryHeaps.size();
diff --git a/src/dawn/native/vulkan/PhysicalDeviceVk.h b/src/dawn/native/vulkan/PhysicalDeviceVk.h
index bbb66c5..6d6df0b 100644
--- a/src/dawn/native/vulkan/PhysicalDeviceVk.h
+++ b/src/dawn/native/vulkan/PhysicalDeviceVk.h
@@ -61,6 +61,9 @@
 
     uint32_t GetDefaultComputeSubgroupSize() const;
 
+    ResultOrError<PhysicalDeviceSurfaceCapabilities> GetSurfaceCapabilities(
+        const Surface* surface) const override;
+
   private:
     MaybeError InitializeImpl() override;
     void InitializeSupportedFeaturesImpl() override;
diff --git a/src/dawn/native/vulkan/SwapChainVk.cpp b/src/dawn/native/vulkan/SwapChainVk.cpp
index 05191a5..bf0ea02 100644
--- a/src/dawn/native/vulkan/SwapChainVk.cpp
+++ b/src/dawn/native/vulkan/SwapChainVk.cpp
@@ -51,147 +51,6 @@
 
 namespace {
 
-ResultOrError<VkSurfaceKHR> CreateVulkanSurface(const PhysicalDevice* physicalDevice,
-                                                const Surface* surface) {
-    // May not be used in the platform-specific switches below.
-    [[maybe_unused]] const VulkanGlobalInfo& info =
-        physicalDevice->GetVulkanInstance()->GetGlobalInfo();
-    [[maybe_unused]] const VulkanFunctions& fn =
-        physicalDevice->GetVulkanInstance()->GetFunctions();
-    [[maybe_unused]] VkInstance instance = physicalDevice->GetVulkanInstance()->GetVkInstance();
-
-    switch (surface->GetType()) {
-#if defined(DAWN_ENABLE_BACKEND_METAL)
-        case Surface::Type::MetalLayer:
-            if (info.HasExt(InstanceExt::MetalSurface)) {
-                VkMetalSurfaceCreateInfoEXT createInfo;
-                createInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
-                createInfo.pNext = nullptr;
-                createInfo.flags = 0;
-                createInfo.pLayer = surface->GetMetalLayer();
-
-                VkSurfaceKHR vkSurface = VK_NULL_HANDLE;
-                DAWN_TRY(CheckVkSuccess(
-                    fn.CreateMetalSurfaceEXT(instance, &createInfo, nullptr, &*vkSurface),
-                    "CreateMetalSurface"));
-                return vkSurface;
-            }
-            break;
-#endif  // defined(DAWN_ENABLE_BACKEND_METAL)
-
-#if DAWN_PLATFORM_IS(WINDOWS)
-        case Surface::Type::WindowsHWND:
-            if (info.HasExt(InstanceExt::Win32Surface)) {
-                VkWin32SurfaceCreateInfoKHR createInfo;
-                createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
-                createInfo.pNext = nullptr;
-                createInfo.flags = 0;
-                createInfo.hinstance = static_cast<HINSTANCE>(surface->GetHInstance());
-                createInfo.hwnd = static_cast<HWND>(surface->GetHWND());
-
-                VkSurfaceKHR vkSurface = VK_NULL_HANDLE;
-                DAWN_TRY(CheckVkSuccess(
-                    fn.CreateWin32SurfaceKHR(instance, &createInfo, nullptr, &*vkSurface),
-                    "CreateWin32Surface"));
-                return vkSurface;
-            }
-            break;
-#endif  // DAWN_PLATFORM_IS(WINDOWS)
-
-#if DAWN_PLATFORM_IS(ANDROID)
-        case Surface::Type::AndroidWindow: {
-            if (info.HasExt(InstanceExt::AndroidSurface)) {
-                DAWN_ASSERT(surface->GetAndroidNativeWindow() != nullptr);
-
-                VkAndroidSurfaceCreateInfoKHR createInfo;
-                createInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
-                createInfo.pNext = nullptr;
-                createInfo.flags = 0;
-                createInfo.window =
-                    static_cast<struct ANativeWindow*>(surface->GetAndroidNativeWindow());
-
-                VkSurfaceKHR vkSurface = VK_NULL_HANDLE;
-                DAWN_TRY(CheckVkSuccess(
-                    fn.CreateAndroidSurfaceKHR(instance, &createInfo, nullptr, &*vkSurface),
-                    "CreateAndroidSurfaceKHR"));
-                return vkSurface;
-            }
-
-            break;
-        }
-
-#endif  // DAWN_PLATFORM_IS(ANDROID)
-
-#if defined(DAWN_USE_WAYLAND)
-        case Surface::Type::WaylandSurface: {
-            if (info.HasExt(InstanceExt::XlibSurface)) {
-                VkWaylandSurfaceCreateInfoKHR createInfo;
-                createInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
-                createInfo.pNext = nullptr;
-                createInfo.flags = 0;
-                createInfo.display = static_cast<struct wl_display*>(surface->GetWaylandDisplay());
-                createInfo.surface = static_cast<struct wl_surface*>(surface->GetWaylandSurface());
-
-                VkSurfaceKHR vkSurface = VK_NULL_HANDLE;
-                DAWN_TRY(CheckVkSuccess(
-                    fn.CreateWaylandSurfaceKHR(instance, &createInfo, nullptr, &*vkSurface),
-                    "CreateWaylandSurface"));
-                return vkSurface;
-            }
-            break;
-        }
-#endif  // defined(DAWN_USE_WAYLAND)
-
-#if defined(DAWN_USE_X11)
-        case Surface::Type::XlibWindow: {
-            if (info.HasExt(InstanceExt::XlibSurface)) {
-                VkXlibSurfaceCreateInfoKHR createInfo;
-                createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR;
-                createInfo.pNext = nullptr;
-                createInfo.flags = 0;
-                createInfo.dpy = static_cast<Display*>(surface->GetXDisplay());
-                createInfo.window = surface->GetXWindow();
-
-                VkSurfaceKHR vkSurface = VK_NULL_HANDLE;
-                DAWN_TRY(CheckVkSuccess(
-                    fn.CreateXlibSurfaceKHR(instance, &createInfo, nullptr, &*vkSurface),
-                    "CreateXlibSurface"));
-                return vkSurface;
-            }
-
-            // Fall back to using XCB surfaces if the Xlib extension isn't available.
-            // See https://xcb.freedesktop.org/MixingCalls/ for more information about
-            // interoperability between Xlib and XCB
-            const X11Functions* x11 = physicalDevice->GetInstance()->GetOrLoadX11Functions();
-            DAWN_ASSERT(x11 != nullptr);
-
-            if (info.HasExt(InstanceExt::XcbSurface) && x11->IsX11XcbLoaded()) {
-                VkXcbSurfaceCreateInfoKHR createInfo;
-                createInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;
-                createInfo.pNext = nullptr;
-                createInfo.flags = 0;
-                // The XCB connection lives as long as the X11 display.
-                createInfo.connection =
-                    x11->xGetXCBConnection(static_cast<Display*>(surface->GetXDisplay()));
-                createInfo.window = surface->GetXWindow();
-
-                VkSurfaceKHR vkSurface = VK_NULL_HANDLE;
-                DAWN_TRY(CheckVkSuccess(
-                    fn.CreateXcbSurfaceKHR(instance, &createInfo, nullptr, &*vkSurface),
-                    "CreateXcbSurfaceKHR"));
-                return vkSurface;
-            }
-            break;
-        }
-#endif  // defined(DAWN_USE_X11)
-
-        default:
-            break;
-    }
-
-    return DAWN_VALIDATION_ERROR("Unsupported surface type (%s) for Vulkan.", surface->GetType());
-}
-
 VkPresentModeKHR ToVulkanPresentMode(wgpu::PresentMode mode) {
     switch (mode) {
         case wgpu::PresentMode::Fifo:
@@ -258,8 +117,8 @@
 ResultOrError<Ref<SwapChain>> SwapChain::Create(Device* device,
                                                 Surface* surface,
                                                 SwapChainBase* previousSwapChain,
-                                                const SwapChainDescriptor* descriptor) {
-    Ref<SwapChain> swapchain = AcquireRef(new SwapChain(device, surface, descriptor));
+                                                const SurfaceConfiguration* config) {
+    Ref<SwapChain> swapchain = AcquireRef(new SwapChain(device, surface, config));
     DAWN_TRY(swapchain->Initialize(previousSwapChain));
     return swapchain;
 }
@@ -635,12 +494,13 @@
     }
 }
 
-ResultOrError<Ref<TextureBase>> SwapChain::GetCurrentTextureImpl() {
+ResultOrError<SwapChainTextureInfo> SwapChain::GetCurrentTextureImpl() {
     return GetCurrentTextureInternal();
 }
 
-ResultOrError<Ref<TextureBase>> SwapChain::GetCurrentTextureInternal(bool isReentrant) {
+ResultOrError<SwapChainTextureInfo> SwapChain::GetCurrentTextureInternal(bool isReentrant) {
     Device* device = ToBackend(GetDevice());
+    SwapChainTextureInfo swapChainTextureInfo;
 
     // Transiently create a semaphore that will be signaled when the presentation engine is done
     // with the swapchain image. Further operations on the image will wait for this semaphore.
@@ -670,14 +530,17 @@
         ToBackend(GetDevice())->GetFencedDeleter()->DeleteWhenUnused(semaphore);
     }
 
+    swapChainTextureInfo.suboptimal = false;
     switch (result) {
-        // TODO(crbug.com/dawn/269): Introduce a mechanism to notify the application that
-        // the swapchain is in a suboptimal state?
         case VK_SUBOPTIMAL_KHR:
+            swapChainTextureInfo.suboptimal = true;
+            ABSL_FALLTHROUGH_INTENDED;
         case VK_SUCCESS:
+            swapChainTextureInfo.status = wgpu::SurfaceGetCurrentTextureStatus::Success;
             break;
 
         case VK_ERROR_OUT_OF_DATE_KHR: {
+            swapChainTextureInfo.status = wgpu::SurfaceGetCurrentTextureStatus::Outdated;
             // Prevent infinite recursive calls to GetCurrentTextureViewInternal when the
             // swapchains always return that they are out of date.
             if (isReentrant) {
@@ -693,6 +556,9 @@
 
         // TODO(crbug.com/dawn/269): Allow losing the surface at Dawn's API level?
         case VK_ERROR_SURFACE_LOST_KHR:
+            swapChainTextureInfo.status = wgpu::SurfaceGetCurrentTextureStatus::Lost;
+            break;
+
         default:
             DAWN_TRY(CheckVkSuccess(::VkResult(result), "AcquireNextImage"));
     }
@@ -708,7 +574,8 @@
 
     // In the happy path we can use the swapchain image directly.
     if (!mConfig.needsBlit) {
-        return mTexture;
+        swapChainTextureInfo.texture = mTexture;
+        return swapChainTextureInfo;
     }
 
     // The blit texture always perfectly matches what the user requested for the swapchain.
@@ -716,7 +583,8 @@
     TextureDescriptor desc = GetSwapChainBaseTextureDescriptor(this);
     DAWN_TRY_ASSIGN(mBlitTexture,
                     Texture::Create(device, Unpack(&desc), VK_IMAGE_USAGE_TRANSFER_SRC_BIT));
-    return mBlitTexture;
+    swapChainTextureInfo.texture = mBlitTexture;
+    return swapChainTextureInfo;
 }
 
 void SwapChain::DetachFromSurfaceImpl() {
@@ -748,4 +616,145 @@
     }
 }
 
+ResultOrError<VkSurfaceKHR> CreateVulkanSurface(const PhysicalDevice* physicalDevice,
+                                                const Surface* surface) {
+    // May not be used in the platform-specific switches below.
+    [[maybe_unused]] const VulkanGlobalInfo& info =
+        physicalDevice->GetVulkanInstance()->GetGlobalInfo();
+    [[maybe_unused]] const VulkanFunctions& fn =
+        physicalDevice->GetVulkanInstance()->GetFunctions();
+    [[maybe_unused]] VkInstance instance = physicalDevice->GetVulkanInstance()->GetVkInstance();
+
+    switch (surface->GetType()) {
+#if defined(DAWN_ENABLE_BACKEND_METAL)
+        case Surface::Type::MetalLayer:
+            if (info.HasExt(InstanceExt::MetalSurface)) {
+                VkMetalSurfaceCreateInfoEXT createInfo;
+                createInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
+                createInfo.pNext = nullptr;
+                createInfo.flags = 0;
+                createInfo.pLayer = surface->GetMetalLayer();
+
+                VkSurfaceKHR vkSurface = VK_NULL_HANDLE;
+                DAWN_TRY(CheckVkSuccess(
+                    fn.CreateMetalSurfaceEXT(instance, &createInfo, nullptr, &*vkSurface),
+                    "CreateMetalSurface"));
+                return vkSurface;
+            }
+            break;
+#endif  // defined(DAWN_ENABLE_BACKEND_METAL)
+
+#if DAWN_PLATFORM_IS(WINDOWS)
+        case Surface::Type::WindowsHWND:
+            if (info.HasExt(InstanceExt::Win32Surface)) {
+                VkWin32SurfaceCreateInfoKHR createInfo;
+                createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
+                createInfo.pNext = nullptr;
+                createInfo.flags = 0;
+                createInfo.hinstance = static_cast<HINSTANCE>(surface->GetHInstance());
+                createInfo.hwnd = static_cast<HWND>(surface->GetHWND());
+
+                VkSurfaceKHR vkSurface = VK_NULL_HANDLE;
+                DAWN_TRY(CheckVkSuccess(
+                    fn.CreateWin32SurfaceKHR(instance, &createInfo, nullptr, &*vkSurface),
+                    "CreateWin32Surface"));
+                return vkSurface;
+            }
+            break;
+#endif  // DAWN_PLATFORM_IS(WINDOWS)
+
+#if DAWN_PLATFORM_IS(ANDROID)
+        case Surface::Type::AndroidWindow: {
+            if (info.HasExt(InstanceExt::AndroidSurface)) {
+                DAWN_ASSERT(surface->GetAndroidNativeWindow() != nullptr);
+
+                VkAndroidSurfaceCreateInfoKHR createInfo;
+                createInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
+                createInfo.pNext = nullptr;
+                createInfo.flags = 0;
+                createInfo.window =
+                    static_cast<struct ANativeWindow*>(surface->GetAndroidNativeWindow());
+
+                VkSurfaceKHR vkSurface = VK_NULL_HANDLE;
+                DAWN_TRY(CheckVkSuccess(
+                    fn.CreateAndroidSurfaceKHR(instance, &createInfo, nullptr, &*vkSurface),
+                    "CreateAndroidSurfaceKHR"));
+                return vkSurface;
+            }
+
+            break;
+        }
+
+#endif  // DAWN_PLATFORM_IS(ANDROID)
+
+#if defined(DAWN_USE_WAYLAND)
+        case Surface::Type::WaylandSurface: {
+            if (info.HasExt(InstanceExt::XlibSurface)) {
+                VkWaylandSurfaceCreateInfoKHR createInfo;
+                createInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
+                createInfo.pNext = nullptr;
+                createInfo.flags = 0;
+                createInfo.display = static_cast<struct wl_display*>(surface->GetWaylandDisplay());
+                createInfo.surface = static_cast<struct wl_surface*>(surface->GetWaylandSurface());
+
+                VkSurfaceKHR vkSurface = VK_NULL_HANDLE;
+                DAWN_TRY(CheckVkSuccess(
+                    fn.CreateWaylandSurfaceKHR(instance, &createInfo, nullptr, &*vkSurface),
+                    "CreateWaylandSurface"));
+                return vkSurface;
+            }
+            break;
+        }
+#endif  // defined(DAWN_USE_WAYLAND)
+
+#if defined(DAWN_USE_X11)
+        case Surface::Type::XlibWindow: {
+            if (info.HasExt(InstanceExt::XlibSurface)) {
+                VkXlibSurfaceCreateInfoKHR createInfo;
+                createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR;
+                createInfo.pNext = nullptr;
+                createInfo.flags = 0;
+                createInfo.dpy = static_cast<Display*>(surface->GetXDisplay());
+                createInfo.window = surface->GetXWindow();
+
+                VkSurfaceKHR vkSurface = VK_NULL_HANDLE;
+                DAWN_TRY(CheckVkSuccess(
+                    fn.CreateXlibSurfaceKHR(instance, &createInfo, nullptr, &*vkSurface),
+                    "CreateXlibSurface"));
+                return vkSurface;
+            }
+
+            // Fall back to using XCB surfaces if the Xlib extension isn't available.
+            // See https://xcb.freedesktop.org/MixingCalls/ for more information about
+            // interoperability between Xlib and XCB
+            const X11Functions* x11 = physicalDevice->GetInstance()->GetOrLoadX11Functions();
+            DAWN_ASSERT(x11 != nullptr);
+
+            if (info.HasExt(InstanceExt::XcbSurface) && x11->IsX11XcbLoaded()) {
+                VkXcbSurfaceCreateInfoKHR createInfo;
+                createInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;
+                createInfo.pNext = nullptr;
+                createInfo.flags = 0;
+                // The XCB connection lives as long as the X11 display.
+                createInfo.connection =
+                    x11->xGetXCBConnection(static_cast<Display*>(surface->GetXDisplay()));
+                createInfo.window = surface->GetXWindow();
+
+                VkSurfaceKHR vkSurface = VK_NULL_HANDLE;
+                DAWN_TRY(CheckVkSuccess(
+                    fn.CreateXcbSurfaceKHR(instance, &createInfo, nullptr, &*vkSurface),
+                    "CreateXcbSurfaceKHR"));
+                return vkSurface;
+            }
+            break;
+        }
+#endif  // defined(DAWN_USE_X11)
+
+        default:
+            break;
+    }
+
+    return DAWN_VALIDATION_ERROR("Unsupported surface type (%s) for Vulkan.", surface->GetType());
+}
+
 }  // namespace dawn::native::vulkan
diff --git a/src/dawn/native/vulkan/SwapChainVk.h b/src/dawn/native/vulkan/SwapChainVk.h
index b0d322d..da1f7ea 100644
--- a/src/dawn/native/vulkan/SwapChainVk.h
+++ b/src/dawn/native/vulkan/SwapChainVk.h
@@ -38,6 +38,7 @@
 
 class Device;
 class Texture;
+class PhysicalDevice;
 struct VulkanSurfaceInfo;
 
 class SwapChain : public SwapChainBase {
@@ -45,7 +46,7 @@
     static ResultOrError<Ref<SwapChain>> Create(Device* device,
                                                 Surface* surface,
                                                 SwapChainBase* previousSwapChain,
-                                                const SwapChainDescriptor* descriptor);
+                                                const SurfaceConfiguration* config);
 
     static ResultOrError<wgpu::TextureUsage> GetSupportedSurfaceUsage(const Device* device,
                                                                       const Surface* surface);
@@ -77,11 +78,11 @@
         bool needsBlit = false;
     };
     ResultOrError<Config> ChooseConfig(const VulkanSurfaceInfo& surfaceInfo) const;
-    ResultOrError<Ref<TextureBase>> GetCurrentTextureInternal(bool isReentrant = false);
+    ResultOrError<SwapChainTextureInfo> GetCurrentTextureInternal(bool isReentrant = false);
 
     // SwapChainBase implementation
     MaybeError PresentImpl() override;
-    ResultOrError<Ref<TextureBase>> GetCurrentTextureImpl() override;
+    ResultOrError<SwapChainTextureInfo> GetCurrentTextureImpl() override;
     void DetachFromSurfaceImpl() override;
 
     Config mConfig;
@@ -96,6 +97,9 @@
     Ref<Texture> mTexture;
 };
 
+ResultOrError<VkSurfaceKHR> CreateVulkanSurface(const PhysicalDevice* physicalDevice,
+                                                const Surface* surface);
+
 }  // namespace dawn::native::vulkan
 
 #endif  // SRC_DAWN_NATIVE_VULKAN_SWAPCHAINVK_H_
diff --git a/src/dawn/native/webgpu_absl_format.cpp b/src/dawn/native/webgpu_absl_format.cpp
index 95e2cc3..e41b2f7 100644
--- a/src/dawn/native/webgpu_absl_format.cpp
+++ b/src/dawn/native/webgpu_absl_format.cpp
@@ -31,6 +31,7 @@
 #include <vector>
 
 #include "dawn/common/MatchVariant.h"
+#include "dawn/native/Adapter.h"
 #include "dawn/native/AttachmentState.h"
 #include "dawn/native/BindingInfo.h"
 #include "dawn/native/Device.h"
@@ -185,6 +186,23 @@
 //
 
 absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert(
+    const AdapterBase* value,
+    const absl::FormatConversionSpec& spec,
+    absl::FormatSink* s) {
+    if (value == nullptr) {
+        s->Append("[null]");
+        return {true};
+    }
+    s->Append("[Adapter");
+    const std::string& name = value->GetName();
+    if (!name.empty()) {
+        s->Append(absl::StrFormat(" \"%s\"", name));
+    }
+    s->Append("]");
+    return {true};
+}
+
+absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert(
     const DeviceBase* value,
     const absl::FormatConversionSpec& spec,
     absl::FormatSink* s) {
@@ -281,6 +299,23 @@
     return {true};
 }
 
+absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert(
+    const Surface* value,
+    const absl::FormatConversionSpec& spec,
+    absl::FormatSink* s) {
+    if (value == nullptr) {
+        s->Append("[null]");
+        return {true};
+    }
+    s->Append("[Surface");
+    const std::string& label = value->GetLabel();
+    if (!label.empty()) {
+        s->Append(absl::StrFormat(" \"%s\"", label));
+    }
+    s->Append("]");
+    return {true};
+}
+
 //
 // Enums
 //
diff --git a/src/dawn/native/webgpu_absl_format.h b/src/dawn/native/webgpu_absl_format.h
index e8f9da8..dceb3bc 100644
--- a/src/dawn/native/webgpu_absl_format.h
+++ b/src/dawn/native/webgpu_absl_format.h
@@ -106,6 +106,12 @@
 
 class DeviceBase;
 absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert(
+    const AdapterBase* value,
+    const absl::FormatConversionSpec& spec,
+    absl::FormatSink* s);
+
+class DeviceBase;
+absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert(
     const DeviceBase* value,
     const absl::FormatConversionSpec& spec,
     absl::FormatSink* s);
@@ -122,6 +128,12 @@
     const absl::FormatConversionSpec& spec,
     absl::FormatSink* s);
 
+class Surface;
+absl::FormatConvertResult<absl::FormatConversionCharSet::kString> AbslFormatConvert(
+    const Surface* value,
+    const absl::FormatConversionSpec& spec,
+    absl::FormatSink* s);
+
 //
 // Enums
 //
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index 977f5b3..23a2c99 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -710,6 +710,8 @@
 
   if (dawn_supports_glfw_for_windowing) {
     sources += [
+      "end2end/SurfaceConfigurationValidationTests.cpp",
+      "end2end/SurfaceTests.cpp",
       "end2end/SwapChainTests.cpp",
       "end2end/SwapChainValidationTests.cpp",
       "end2end/WindowSurfaceTests.cpp",
diff --git a/src/dawn/tests/end2end/SurfaceConfigurationValidationTests.cpp b/src/dawn/tests/end2end/SurfaceConfigurationValidationTests.cpp
new file mode 100644
index 0000000..c1042da
--- /dev/null
+++ b/src/dawn/tests/end2end/SurfaceConfigurationValidationTests.cpp
@@ -0,0 +1,303 @@
+// Copyright 2024 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 <cmath>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "dawn/common/Constants.h"
+#include "dawn/tests/DawnTest.h"
+#include "dawn/utils/ComboRenderBundleEncoderDescriptor.h"
+#include "dawn/utils/ComboRenderPipelineDescriptor.h"
+#include "dawn/utils/WGPUHelpers.h"
+#include "webgpu/webgpu_glfw.h"
+
+#include "GLFW/glfw3.h"
+
+namespace dawn::utils {
+static constexpr std::array<wgpu::CompositeAlphaMode, 5> kAllAlphaModes = {
+    wgpu::CompositeAlphaMode::Auto,          wgpu::CompositeAlphaMode::Opaque,
+    wgpu::CompositeAlphaMode::Premultiplied, wgpu::CompositeAlphaMode::Unpremultiplied,
+    wgpu::CompositeAlphaMode::Inherit,
+};
+static constexpr std::array<wgpu::PresentMode, 3> kAllPresentModes = {
+    wgpu::PresentMode::Fifo,
+    wgpu::PresentMode::Immediate,
+    wgpu::PresentMode::Mailbox,
+};
+}  // namespace dawn::utils
+
+namespace dawn {
+namespace {
+
+struct GLFWindowDestroyer {
+    void operator()(GLFWwindow* ptr) { glfwDestroyWindow(ptr); }
+};
+
+class SurfaceConfigurationValidationTests : public DawnTest {
+  public:
+    void SetUp() override {
+        DawnTest::SetUp();
+        DAWN_TEST_UNSUPPORTED_IF(UsesWire());
+        DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("skip_validation"));
+
+        glfwSetErrorCallback([](int code, const char* message) {
+            ErrorLog() << "GLFW error " << code << " " << message;
+        });
+
+        // GLFW can fail to start in headless environments, in which SwapChainTests are
+        // inapplicable. Skip this cases without producing a test failure.
+        if (glfwInit() == GLFW_FALSE) {
+            GTEST_SKIP();
+        }
+
+        // Set GLFW_NO_API to avoid GLFW bringing up a GL context that we won't use.
+        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+        window.reset(glfwCreateWindow(500, 400, "SurfaceConfigurationValidationTests window",
+                                      nullptr, nullptr));
+
+        int width;
+        int height;
+        glfwGetFramebufferSize(window.get(), &width, &height);
+
+        baseConfig.device = device;
+        baseConfig.width = width;
+        baseConfig.height = height;
+        baseConfig.usage = wgpu::TextureUsage::RenderAttachment;
+        baseConfig.viewFormatCount = 0;
+        baseConfig.viewFormats = nullptr;
+    }
+
+    wgpu::Surface CreateTestSurface() {
+        return wgpu::glfw::CreateSurfaceForWindow(GetInstance(), window.get());
+    }
+
+    wgpu::SurfaceConfiguration GetPreferredConfiguration(wgpu::Surface surface) {
+        wgpu::SurfaceCapabilities capabilities;
+        surface.GetCapabilities(adapter, &capabilities);
+
+        wgpu::TextureFormat preferredFormat = surface.GetPreferredFormat(adapter);
+
+        wgpu::SurfaceConfiguration config = baseConfig;
+        config.format = preferredFormat;
+        config.alphaMode = capabilities.alphaModes[0];
+        config.presentMode = capabilities.presentModes[0];
+        return config;
+    }
+
+    bool SupportsFormat(const wgpu::SurfaceCapabilities& capabilities, wgpu::TextureFormat format) {
+        for (size_t i = 0; i < capabilities.formatCount; ++i) {
+            if (capabilities.formats[i] == format) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    bool SupportsAlphaMode(const wgpu::SurfaceCapabilities& capabilities,
+                           wgpu::CompositeAlphaMode mode) {
+        for (size_t i = 0; i < capabilities.alphaModeCount; ++i) {
+            if (capabilities.alphaModes[i] == mode) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    bool SupportsPresentMode(const wgpu::SurfaceCapabilities& capabilities,
+                             wgpu::PresentMode mode) {
+        for (size_t i = 0; i < capabilities.presentModeCount; ++i) {
+            if (capabilities.presentModes[i] == mode) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+  protected:
+    std::unique_ptr<GLFWwindow, GLFWindowDestroyer> window = nullptr;
+    wgpu::SurfaceConfiguration baseConfig;
+};
+
+// Using undefined format is not valid
+TEST_P(SurfaceConfigurationValidationTests, UndefinedFormat) {
+    wgpu::SurfaceConfiguration config;
+    config.device = device;
+    config.format = wgpu::TextureFormat::Undefined;
+    ASSERT_DEVICE_ERROR(CreateTestSurface().Configure(&config));
+}
+
+// Supports at least one configuration
+TEST_P(SurfaceConfigurationValidationTests, AtLeastOneSupportedConfiguration) {
+    wgpu::Surface surface = CreateTestSurface();
+
+    wgpu::SurfaceCapabilities capabilities;
+    surface.GetCapabilities(adapter, &capabilities);
+
+    ASSERT_GT(capabilities.formatCount, 0u);
+    ASSERT_GT(capabilities.alphaModeCount, 0u);
+    ASSERT_GT(capabilities.presentModeCount, 0u);
+}
+
+// Using any combination of the reported capability is ok for configuring the surface.
+TEST_P(SurfaceConfigurationValidationTests, AnyCombinationOfCapabilities) {
+    // TODO(dawn:2320) Fails with "internal drawable creation failed" on the Windows NVIDIA CQ
+    // builders but not locally. This is a similar limitation to SwapChainTests.SwitchPresentMode.
+    DAWN_SUPPRESS_TEST_IF(IsWindows() && IsVulkan() && IsNvidia());
+
+    wgpu::Surface surface = CreateTestSurface();
+
+    wgpu::SurfaceConfiguration config = baseConfig;
+
+    wgpu::SurfaceCapabilities capabilities;
+    surface.GetCapabilities(adapter, &capabilities);
+
+    for (wgpu::TextureFormat format : dawn::utils::kAllTextureFormats) {
+        for (wgpu::CompositeAlphaMode alphaMode : dawn::utils::kAllAlphaModes) {
+            for (wgpu::PresentMode presentMode : dawn::utils::kAllPresentModes) {
+                config.format = format;
+                config.alphaMode = alphaMode;
+                config.presentMode = presentMode;
+
+                if (!SupportsFormat(capabilities, config.format) ||
+                    !SupportsAlphaMode(capabilities, config.alphaMode) ||
+                    !SupportsPresentMode(capabilities, config.presentMode)) {
+                    ASSERT_DEVICE_ERROR(surface.Configure(&config));
+                } else {
+                    surface.Configure(&config);
+
+                    // Check that we can present
+                    wgpu::SurfaceTexture surfaceTexture;
+                    surface.GetCurrentTexture(&surfaceTexture);
+                    surface.Present();
+                }
+                device.Tick();
+            }
+        }
+    }
+}
+
+// Preferred format is always valid.
+TEST_P(SurfaceConfigurationValidationTests, PreferredFormatIsValid) {
+    wgpu::Surface surface = CreateTestSurface();
+
+    wgpu::SurfaceCapabilities capabilities;
+    surface.GetCapabilities(adapter, &capabilities);
+
+    wgpu::TextureFormat preferredFormat = surface.GetPreferredFormat(adapter);
+    bool found = false;
+    for (size_t i = 0; i < capabilities.formatCount; ++i) {
+        found = found || capabilities.formats[i] == preferredFormat;
+    }
+    ASSERT_TRUE(found);
+}
+
+// Invalid view format fails
+TEST_P(SurfaceConfigurationValidationTests, InvalidViewFormat) {
+    wgpu::Surface surface = CreateTestSurface();
+    auto invalid = wgpu::TextureFormat::R32Uint;
+
+    wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
+    config.viewFormatCount = 1;
+    config.viewFormats = &invalid;
+    ASSERT_DEVICE_ERROR(surface.Configure(&config));
+}
+
+// View format is valid when it matches the config format
+TEST_P(SurfaceConfigurationValidationTests, ValidViewFormat) {
+    wgpu::Surface surface = CreateTestSurface();
+
+    wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
+    config.viewFormatCount = 1;
+    config.viewFormats = &config.format;
+    surface.Configure(&config);
+
+    // TODO(dawn:2320) Also test the equivalent (non-)sRGB view format
+}
+
+// A width of 0 fails
+TEST_P(SurfaceConfigurationValidationTests, ZeroWidth) {
+    wgpu::Surface surface = CreateTestSurface();
+
+    wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
+    config.width = 0;
+    ASSERT_DEVICE_ERROR(surface.Configure(&config));
+}
+
+// A height of 0 fails
+TEST_P(SurfaceConfigurationValidationTests, ZeroHeight) {
+    wgpu::Surface surface = CreateTestSurface();
+
+    wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
+    config.height = 0;
+    ASSERT_DEVICE_ERROR(surface.Configure(&config));
+}
+
+// A width that exceeds the maximum texture size fails
+TEST_P(SurfaceConfigurationValidationTests, ExcessiveWidth) {
+    wgpu::Surface surface = CreateTestSurface();
+    wgpu::SupportedLimits supported;
+    device.GetLimits(&supported);
+
+    wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
+    config.width = supported.limits.maxTextureDimension1D + 1;
+    ASSERT_DEVICE_ERROR(surface.Configure(&config));
+}
+
+// A height that exceeds the maximum texture size fails
+TEST_P(SurfaceConfigurationValidationTests, ExcessiveHeight) {
+    wgpu::Surface surface = CreateTestSurface();
+    wgpu::SupportedLimits supported;
+    device.GetLimits(&supported);
+
+    wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
+    config.height = supported.limits.maxTextureDimension2D + 1;
+    ASSERT_DEVICE_ERROR(surface.Configure(&config));
+}
+
+// A surface that was not configured must not be unconfigured
+TEST_P(SurfaceConfigurationValidationTests, UnconfigureNonConfiguredSurfaceFails) {
+    // TODO(dawn:2320): With SwiftShader, this throws a device error anyways (maybe because
+    // mInstance->ConsumedError calls the device error callback?). We should have a
+    // ASSERT_INSTANCE_ERROR to fully fix this test case.
+    DAWN_SUPPRESS_TEST_IF(IsSwiftshader());
+
+    // TODO(dawn:2320) This cannot throw a device error since the surface is
+    // not aware of the device at this stage.
+    /*ASSERT_DEVICE_ERROR(*/ CreateTestSurface().Unconfigure() /*)*/;
+}
+
+DAWN_INSTANTIATE_TEST(SurfaceConfigurationValidationTests,
+                      D3D11Backend(),
+                      D3D12Backend(),
+                      MetalBackend(),
+                      NullBackend(),
+                      VulkanBackend());
+
+}  // anonymous namespace
+}  // namespace dawn
diff --git a/src/dawn/tests/end2end/SurfaceTests.cpp b/src/dawn/tests/end2end/SurfaceTests.cpp
new file mode 100644
index 0000000..a0ff7b4
--- /dev/null
+++ b/src/dawn/tests/end2end/SurfaceTests.cpp
@@ -0,0 +1,433 @@
+// Copyright 2024 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 <memory>
+#include <string>
+#include <vector>
+
+#include "dawn/common/Constants.h"
+#include "dawn/tests/DawnTest.h"
+#include "dawn/utils/ComboRenderBundleEncoderDescriptor.h"
+#include "dawn/utils/ComboRenderPipelineDescriptor.h"
+#include "dawn/utils/WGPUHelpers.h"
+#include "webgpu/webgpu_glfw.h"
+
+#include "GLFW/glfw3.h"
+
+namespace dawn {
+namespace {
+
+struct GLFWindowDestroyer {
+    void operator()(GLFWwindow* ptr) { glfwDestroyWindow(ptr); }
+};
+
+class SurfaceTests : public DawnTest {
+  public:
+    void SetUp() override {
+        DawnTest::SetUp();
+        DAWN_TEST_UNSUPPORTED_IF(UsesWire());
+
+        glfwSetErrorCallback([](int code, const char* message) {
+            ErrorLog() << "GLFW error " << code << " " << message;
+        });
+
+        // GLFW can fail to start in headless environments, in which SurfaceTests are
+        // inapplicable. Skip this cases without producing a test failure.
+        if (glfwInit() == GLFW_FALSE) {
+            GTEST_SKIP();
+        }
+
+        // Set GLFW_NO_API to avoid GLFW bringing up a GL context that we won't use.
+        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+        window.reset(glfwCreateWindow(400, 500, "SurfaceTests window", nullptr, nullptr));
+
+        int width;
+        int height;
+        glfwGetFramebufferSize(window.get(), &width, &height);
+
+        baseConfig.device = device;
+        baseConfig.width = width;
+        baseConfig.height = height;
+        baseConfig.usage = wgpu::TextureUsage::RenderAttachment;
+        baseConfig.viewFormatCount = 0;
+        baseConfig.viewFormats = nullptr;
+    }
+
+    void TearDown() override {
+        // Destroy the surface before the window as required by webgpu-native.
+        window.reset();
+        DawnTest::TearDown();
+    }
+
+    wgpu::Surface CreateTestSurface() {
+        return wgpu::glfw::CreateSurfaceForWindow(GetInstance(), window.get());
+    }
+
+    wgpu::SurfaceConfiguration GetPreferredConfiguration(wgpu::Surface surface) {
+        wgpu::SurfaceCapabilities capabilities;
+        surface.GetCapabilities(adapter, &capabilities);
+
+        wgpu::TextureFormat preferredFormat = surface.GetPreferredFormat(adapter);
+
+        wgpu::SurfaceConfiguration config = baseConfig;
+        config.format = preferredFormat;
+        config.alphaMode = capabilities.alphaModes[0];
+        config.presentMode = capabilities.presentModes[0];
+        return config;
+    }
+
+    void ClearTexture(wgpu::Texture texture,
+                      wgpu::Color color,
+                      wgpu::Device preferredDevice = nullptr) {
+        if (preferredDevice == nullptr) {
+            preferredDevice = device;
+        }
+
+        utils::ComboRenderPassDescriptor desc({texture.CreateView()});
+        desc.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear;
+        desc.cColorAttachments[0].clearValue = color;
+
+        wgpu::CommandEncoder encoder = preferredDevice.CreateCommandEncoder();
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&desc);
+        pass.End();
+
+        wgpu::CommandBuffer commands = encoder.Finish();
+        preferredDevice.GetQueue().Submit(1, &commands);
+    }
+
+    bool SupportsPresentMode(const wgpu::SurfaceCapabilities& capabilities,
+                             wgpu::PresentMode mode) {
+        for (size_t i = 0; i < capabilities.presentModeCount; ++i) {
+            if (capabilities.presentModes[i] == mode) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+  protected:
+    std::unique_ptr<GLFWwindow, GLFWindowDestroyer> window = nullptr;
+    wgpu::SurfaceConfiguration baseConfig;
+};
+
+// Basic test for creating a swapchain and presenting one frame.
+TEST_P(SurfaceTests, Basic) {
+    wgpu::Surface surface = CreateTestSurface();
+
+    // Configure
+    wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
+    surface.Configure(&config);
+
+    // Get texture
+    wgpu::SurfaceTexture surfaceTexture;
+    surface.GetCurrentTexture(&surfaceTexture);
+    ASSERT_EQ(surfaceTexture.status, wgpu::SurfaceGetCurrentTextureStatus::Success);
+    ClearTexture(surfaceTexture.texture, {1.0, 0.0, 0.0, 1.0});
+
+    // Present
+    surface.Present();
+}
+
+// Test reconfiguring the surface
+TEST_P(SurfaceTests, ReconfigureBasic) {
+    wgpu::Surface surface = CreateTestSurface();
+    wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
+
+    surface.Configure(&config);
+
+    surface.Configure(&config);
+}
+
+// Test replacing the swapchain after GetCurrentTexture
+TEST_P(SurfaceTests, ReconfigureAfterGetCurrentTexture) {
+    wgpu::Surface surface = CreateTestSurface();
+    wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
+    wgpu::SurfaceTexture surfaceTexture;
+
+    surface.Configure(&config);
+    surface.GetCurrentTexture(&surfaceTexture);
+    ClearTexture(surfaceTexture.texture, {1.0, 0.0, 0.0, 1.0});
+
+    surface.Configure(&config);
+    surface.GetCurrentTexture(&surfaceTexture);
+    ClearTexture(surfaceTexture.texture, {0.0, 1.0, 0.0, 1.0});
+    surface.Present();
+}
+
+// Test inconfiguring then reconfiguring the surface
+TEST_P(SurfaceTests, ReconfigureAfterUnconfigure) {
+    wgpu::Surface surface = CreateTestSurface();
+    wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
+    wgpu::SurfaceTexture surfaceTexture;
+
+    surface.Configure(&config);
+    surface.GetCurrentTexture(&surfaceTexture);
+    ClearTexture(surfaceTexture.texture, {1.0, 0.0, 0.0, 1.0});
+    surface.Present();
+
+    surface.Unconfigure();
+
+    surface.Configure(&config);
+    surface.GetCurrentTexture(&surfaceTexture);
+    ClearTexture(surfaceTexture.texture, {0.0, 1.0, 0.0, 1.0});
+    surface.Present();
+}
+
+// Test destroying the swapchain after GetCurrentTexture
+TEST_P(SurfaceTests, UnconfigureAfterGet) {
+    wgpu::Surface surface = CreateTestSurface();
+    wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
+    wgpu::SurfaceTexture surfaceTexture;
+
+    surface.Configure(&config);
+    surface.GetCurrentTexture(&surfaceTexture);
+    ClearTexture(surfaceTexture.texture, {1.0, 0.0, 0.0, 1.0});
+
+    surface.Unconfigure();
+}
+// Test switching between surfaces that have different present modes.
+TEST_P(SurfaceTests, SwitchPresentMode) {
+    // Fails with "internal drawable creation failed" on the Windows NVIDIA CQ builders but not
+    // locally.
+    DAWN_SUPPRESS_TEST_IF(IsWindows() && IsVulkan() && IsNvidia());
+
+    // TODO(jiawei.shao@intel.com): find out why this test sometimes hangs on the latest Linux Intel
+    // Vulkan drivers.
+    DAWN_SUPPRESS_TEST_IF(IsLinux() && IsVulkan() && IsIntel());
+
+    constexpr wgpu::PresentMode kAllPresentModes[] = {
+        wgpu::PresentMode::Immediate,
+        wgpu::PresentMode::Fifo,
+        wgpu::PresentMode::Mailbox,
+    };
+
+    wgpu::Surface surface1 = CreateTestSurface();
+    wgpu::Surface surface2 = CreateTestSurface();
+    wgpu::SurfaceTexture surfaceTexture;
+
+    wgpu::SurfaceCapabilities capabilities;
+    surface1.GetCapabilities(adapter, &capabilities);
+
+    for (wgpu::PresentMode mode1 : kAllPresentModes) {
+        if (!SupportsPresentMode(capabilities, mode1)) {
+            continue;
+        }
+        for (wgpu::PresentMode mode2 : kAllPresentModes) {
+            if (!SupportsPresentMode(capabilities, mode2)) {
+                continue;
+            }
+
+            wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface1);
+
+            config.presentMode = mode1;
+            surface1.Configure(&config);
+            surface1.GetCurrentTexture(&surfaceTexture);
+            ClearTexture(surfaceTexture.texture, {0.0, 0.0, 0.0, 1.0});
+            surface1.Present();
+            surface1.Unconfigure();
+
+            config.presentMode = mode2;
+            surface2.Configure(&config);
+            surface2.GetCurrentTexture(&surfaceTexture);
+            ClearTexture(surfaceTexture.texture, {0.0, 0.0, 0.0, 1.0});
+            surface2.Present();
+            surface2.Unconfigure();
+        }
+    }
+}
+
+// Test resizing the surface and without resizing the window.
+TEST_P(SurfaceTests, ResizingSurfaceOnly) {
+    wgpu::Surface surface = CreateTestSurface();
+    wgpu::SurfaceTexture surfaceTexture;
+
+    for (int i = 0; i < 10; i++) {
+        wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
+        config.width += i * 10;
+        config.height -= i * 10;
+
+        surface.Configure(&config);
+        surface.GetCurrentTexture(&surfaceTexture);
+        ClearTexture(surfaceTexture.texture, {0.05f * i, 0.0, 0.0, 1.0});
+        surface.Present();
+    }
+}
+
+// Test resizing the window but not the surface.
+TEST_P(SurfaceTests, ResizingWindowOnly) {
+    // TODO(crbug.com/1503912): Failing new ValidateImageAcquireWait in Vulkan Validation Layer.
+    DAWN_SUPPRESS_TEST_IF(IsBackendValidationEnabled() && IsWindows() && IsVulkan() && IsIntel());
+
+    wgpu::Surface surface = CreateTestSurface();
+    wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
+    wgpu::SurfaceTexture surfaceTexture;
+
+    surface.Configure(&config);
+
+    for (int i = 0; i < 10; i++) {
+        glfwSetWindowSize(window.get(), 400 - 10 * i, 400 + 10 * i);
+        glfwPollEvents();
+
+        surface.GetCurrentTexture(&surfaceTexture);
+        ClearTexture(surfaceTexture.texture, {0.05f * i, 0.0, 0.0, 1.0});
+        surface.Present();
+    }
+}
+
+// Test resizing both the window and the surface at the same time.
+TEST_P(SurfaceTests, ResizingWindowAndSurface) {
+    // TODO(crbug.com/dawn/1205) Currently failing on new NVIDIA GTX 1660s on Linux/Vulkan.
+    DAWN_SUPPRESS_TEST_IF(IsLinux() && IsVulkan() && IsNvidia());
+
+    wgpu::Surface surface = CreateTestSurface();
+    wgpu::SurfaceTexture surfaceTexture;
+
+    for (int i = 0; i < 10; i++) {
+        glfwSetWindowSize(window.get(), 400 - 10 * i, 400 + 10 * i);
+        glfwPollEvents();
+
+        int width;
+        int height;
+        glfwGetFramebufferSize(window.get(), &width, &height);
+
+        wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
+        config.width = width;
+        config.height = height;
+        surface.Configure(&config);
+
+        surface.GetCurrentTexture(&surfaceTexture);
+        ClearTexture(surfaceTexture.texture, {0.05f * i, 0.0, 0.0, 1.0});
+        surface.Present();
+    }
+}
+
+// Test switching devices on the same adapter.
+TEST_P(SurfaceTests, SwitchingDevice) {
+    // TODO(https://crbug.com/dawn/2116): Disabled due to new Validation Layer failures.
+    DAWN_SUPPRESS_TEST_IF(IsVulkan());
+
+    wgpu::Device device2 = CreateDevice();
+
+    wgpu::Surface surface = CreateTestSurface();
+    wgpu::SurfaceTexture surfaceTexture;
+
+    wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
+
+    for (int i = 0; i < 3; i++) {
+        wgpu::Device deviceToUse;
+        if (i % 2 == 0) {
+            deviceToUse = device;
+        } else {
+            deviceToUse = device2;
+        }
+
+        config.device = deviceToUse;
+        surface.Configure(&config);
+        surface.GetCurrentTexture(&surfaceTexture);
+        ClearTexture(surfaceTexture.texture, {0.0, 1.0, 0.0, 1.0}, deviceToUse);
+        surface.Present();
+    }
+}
+
+// Test that configuring with TextureBinding usage without enabling SurfaceCapabilities
+// feature should fail.
+TEST_P(SurfaceTests, ErrorCreateWithTextureBindingUsage) {
+    DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("skip_validation"));
+    EXPECT_FALSE(device.HasFeature(wgpu::FeatureName::SurfaceCapabilities));
+
+    wgpu::Surface surface = CreateTestSurface();
+    wgpu::SurfaceTexture surfaceTexture;
+
+    wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
+    config.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment;
+
+    ASSERT_DEVICE_ERROR_MSG(
+        { surface.Configure(&config); },
+        testing::HasSubstr("require enabling FeatureName::SurfaceCapabilities"));
+}
+
+// Getting current texture without configuring returns an invalid surface texture
+// It cannot raise a device error at this stage since it has never been configured with a device
+TEST_P(SurfaceTests, GetWithoutConfigure) {
+    wgpu::Surface surface = CreateTestSurface();
+    wgpu::SurfaceTexture surfaceTexture;
+    surface.GetCurrentTexture(&surfaceTexture);
+    EXPECT_NE(surfaceTexture.status, wgpu::SurfaceGetCurrentTextureStatus::Success);
+}
+
+// Getting current texture after unconfiguring fails
+TEST_P(SurfaceTests, GetAfterUnconfigure) {
+    wgpu::Surface surface = CreateTestSurface();
+    wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
+    wgpu::SurfaceTexture surfaceTexture;
+
+    surface.Configure(&config);
+
+    surface.Unconfigure();
+
+    ASSERT_DEVICE_ERROR(surface.GetCurrentTexture(&surfaceTexture));
+}
+
+// Presenting without configuring fails
+TEST_P(SurfaceTests, PresentWithoutConfigure) {
+    wgpu::Surface surface = CreateTestSurface();
+    // TODO(dawn:2320) This cannot throw a device error since the surface is
+    // not aware of the device at this stage.
+    /*ASSERT_DEVICE_ERROR(*/ surface.Present() /*)*/;
+}
+
+// Presenting after unconfiguring fails
+TEST_P(SurfaceTests, PresentAfterUnconfigure) {
+    wgpu::Surface surface = CreateTestSurface();
+    wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
+
+    surface.Configure(&config);
+
+    surface.Unconfigure();
+
+    ASSERT_DEVICE_ERROR(surface.Present());
+}
+
+// Presenting without getting current texture first fails
+TEST_P(SurfaceTests, PresentWithoutGet) {
+    wgpu::Surface surface = CreateTestSurface();
+    wgpu::SurfaceConfiguration config = GetPreferredConfiguration(surface);
+
+    surface.Configure(&config);
+    ASSERT_DEVICE_ERROR(surface.Present());
+}
+
+// TODO(dawn:2320) Enable D3D tests (though they are not enabled in SwapChainTests neither)
+DAWN_INSTANTIATE_TEST(SurfaceTests,
+                      // D3D11Backend(),
+                      // D3D12Backend(),
+                      MetalBackend(),
+                      NullBackend(),
+                      VulkanBackend());
+
+}  // anonymous namespace
+}  // namespace dawn
diff --git a/src/dawn/tests/end2end/SwapChainTests.cpp b/src/dawn/tests/end2end/SwapChainTests.cpp
index f007ae3..950d246 100644
--- a/src/dawn/tests/end2end/SwapChainTests.cpp
+++ b/src/dawn/tests/end2end/SwapChainTests.cpp
@@ -86,6 +86,13 @@
         DawnTest::TearDown();
     }
 
+    wgpu::SwapChain CreateSwapChain(wgpu::Surface const& otherSurface,
+                                    wgpu::SwapChainDescriptor const* descriptor) {
+        wgpu::SwapChain swapchain;
+        EXPECT_DEPRECATION_WARNING(swapchain = device.CreateSwapChain(otherSurface, descriptor));
+        return swapchain;
+    }
+
     void ClearTexture(wgpu::Texture texture, wgpu::Color color) {
         utils::ComboRenderPassDescriptor desc({texture.CreateView()});
         desc.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear;
@@ -108,47 +115,47 @@
 
 // Basic test for creating a swapchain and presenting one frame.
 TEST_P(SwapChainTests, Basic) {
-    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &baseDescriptor);
+    wgpu::SwapChain swapchain = CreateSwapChain(surface, &baseDescriptor);
     ClearTexture(swapchain.GetCurrentTexture(), {1.0, 0.0, 0.0, 1.0});
     swapchain.Present();
 }
 
 // Test replacing the swapchain
 TEST_P(SwapChainTests, ReplaceBasic) {
-    wgpu::SwapChain swapchain1 = device.CreateSwapChain(surface, &baseDescriptor);
+    wgpu::SwapChain swapchain1 = CreateSwapChain(surface, &baseDescriptor);
     ClearTexture(swapchain1.GetCurrentTexture(), {1.0, 0.0, 0.0, 1.0});
     swapchain1.Present();
 
-    wgpu::SwapChain swapchain2 = device.CreateSwapChain(surface, &baseDescriptor);
+    wgpu::SwapChain swapchain2 = CreateSwapChain(surface, &baseDescriptor);
     ClearTexture(swapchain2.GetCurrentTexture(), {0.0, 1.0, 0.0, 1.0});
     swapchain2.Present();
 }
 
 // Test replacing the swapchain after GetCurrentTexture
 TEST_P(SwapChainTests, ReplaceAfterGet) {
-    wgpu::SwapChain swapchain1 = device.CreateSwapChain(surface, &baseDescriptor);
+    wgpu::SwapChain swapchain1 = CreateSwapChain(surface, &baseDescriptor);
     ClearTexture(swapchain1.GetCurrentTexture(), {1.0, 0.0, 0.0, 1.0});
 
-    wgpu::SwapChain swapchain2 = device.CreateSwapChain(surface, &baseDescriptor);
+    wgpu::SwapChain swapchain2 = CreateSwapChain(surface, &baseDescriptor);
     ClearTexture(swapchain2.GetCurrentTexture(), {0.0, 1.0, 0.0, 1.0});
     swapchain2.Present();
 }
 
 // Test destroying the swapchain after GetCurrentTexture
 TEST_P(SwapChainTests, DestroyAfterGet) {
-    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &baseDescriptor);
+    wgpu::SwapChain swapchain = CreateSwapChain(surface, &baseDescriptor);
     ClearTexture(swapchain.GetCurrentTexture(), {1.0, 0.0, 0.0, 1.0});
 }
 
 // Test destroying the surface before the swapchain
 TEST_P(SwapChainTests, DestroySurface) {
-    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &baseDescriptor);
+    wgpu::SwapChain swapchain = CreateSwapChain(surface, &baseDescriptor);
     surface = nullptr;
 }
 
 // Test destroying the surface before the swapchain but after GetCurrentTexture
 TEST_P(SwapChainTests, DestroySurfaceAfterGet) {
-    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &baseDescriptor);
+    wgpu::SwapChain swapchain = CreateSwapChain(surface, &baseDescriptor);
     ClearTexture(swapchain.GetCurrentTexture(), {1.0, 0.0, 0.0, 1.0});
     surface = nullptr;
 }
@@ -174,12 +181,12 @@
             wgpu::SwapChainDescriptor desc = baseDescriptor;
 
             desc.presentMode = mode1;
-            wgpu::SwapChain swapchain1 = device.CreateSwapChain(surface, &desc);
+            wgpu::SwapChain swapchain1 = CreateSwapChain(surface, &desc);
             ClearTexture(swapchain1.GetCurrentTexture(), {0.0, 0.0, 0.0, 1.0});
             swapchain1.Present();
 
             desc.presentMode = mode2;
-            wgpu::SwapChain swapchain2 = device.CreateSwapChain(surface, &desc);
+            wgpu::SwapChain swapchain2 = CreateSwapChain(surface, &desc);
             ClearTexture(swapchain2.GetCurrentTexture(), {0.0, 0.0, 0.0, 1.0});
             swapchain2.Present();
         }
@@ -193,7 +200,7 @@
         desc.width += i * 10;
         desc.height -= i * 10;
 
-        wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &desc);
+        wgpu::SwapChain swapchain = CreateSwapChain(surface, &desc);
         ClearTexture(swapchain.GetCurrentTexture(), {0.05f * i, 0.0f, 0.0f, 1.0f});
         swapchain.Present();
     }
@@ -204,7 +211,7 @@
     // TODO(crbug.com/1503912): Failing new ValidateImageAcquireWait in Vulkan Validation Layer.
     DAWN_SUPPRESS_TEST_IF(IsBackendValidationEnabled() && IsWindows() && IsVulkan() && IsIntel());
 
-    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &baseDescriptor);
+    wgpu::SwapChain swapchain = CreateSwapChain(surface, &baseDescriptor);
 
     for (int i = 0; i < 10; i++) {
         glfwSetWindowSize(window.get(), 400 - 10 * i, 400 + 10 * i);
@@ -231,7 +238,7 @@
         desc.width = width;
         desc.height = height;
 
-        wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &desc);
+        wgpu::SwapChain swapchain = CreateSwapChain(surface, &desc);
         ClearTexture(swapchain.GetCurrentTexture(), {0.05f * i, 0.0f, 0.0f, 1.0f});
         swapchain.Present();
     }
@@ -242,12 +249,13 @@
     // TODO(https://crbug.com/dawn/2116): Disabled due to new Validation Layer failures.
     DAWN_SUPPRESS_TEST_IF(IsVulkan());
 
+    wgpu::Device device1 = CreateDevice();
     wgpu::Device device2 = CreateDevice();
 
     for (int i = 0; i < 3; i++) {
         wgpu::Device deviceToUse;
         if (i % 2 == 0) {
-            deviceToUse = device;
+            deviceToUse = device1;
         } else {
             deviceToUse = device2;
         }
@@ -282,7 +290,7 @@
     desc.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment;
 
     ASSERT_DEVICE_ERROR_MSG(
-        { auto swapchain = device.CreateSwapChain(surface, &desc); },
+        { auto swapchain = CreateSwapChain(surface, &desc); },
         testing::HasSubstr("require enabling FeatureName::SurfaceCapabilities"));
 }
 
@@ -392,7 +400,7 @@
     auto desc = baseDescriptor;
     desc.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment;
 
-    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &desc);
+    wgpu::SwapChain swapchain = CreateSwapChain(surface, &desc);
     ClearTexture(swapchain.GetCurrentTexture(), {1.0, 0.0, 0.0, 1.0});
 
     SampleTexture(swapchain.GetCurrentTexture(), utils::RGBA8::kRed);
@@ -412,7 +420,7 @@
     auto desc = baseDescriptor;
     desc.usage = supportedUsage | wgpu::TextureUsage::StorageBinding;
 
-    ASSERT_DEVICE_ERROR_MSG({ auto swapchain = device.CreateSwapChain(surface, &desc); },
+    ASSERT_DEVICE_ERROR_MSG({ auto swapchain = CreateSwapChain(surface, &desc); },
                             testing::HasSubstr("is not supported"));
 }
 
@@ -429,7 +437,7 @@
     desc.width = 1;
     desc.height = 1;
 
-    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &desc);
+    wgpu::SwapChain swapchain = CreateSwapChain(surface, &desc);
     wgpu::Texture texture = swapchain.GetCurrentTexture();
     WriteTexture(texture, utils::RGBA8::kRed);
 
@@ -450,7 +458,7 @@
     wgpu::SwapChainDescriptor desc = baseDescriptor;
     desc.usage |= wgpu::TextureUsage::CopySrc;
 
-    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &desc);
+    wgpu::SwapChain swapchain = CreateSwapChain(surface, &desc);
     wgpu::Texture texture = swapchain.GetCurrentTexture();
 
     ClearTexture(swapchain.GetCurrentTexture(), {1.0, 0.0, 0.0, 1.0});
diff --git a/src/dawn/tests/end2end/SwapChainValidationTests.cpp b/src/dawn/tests/end2end/SwapChainValidationTests.cpp
index a81cd26..a902e25 100644
--- a/src/dawn/tests/end2end/SwapChainValidationTests.cpp
+++ b/src/dawn/tests/end2end/SwapChainValidationTests.cpp
@@ -79,6 +79,13 @@
         DawnTest::TearDown();
     }
 
+    wgpu::SwapChain CreateSwapChain(wgpu::Surface const& otherSurface,
+                                    wgpu::SwapChainDescriptor const* descriptor) {
+        wgpu::SwapChain swapchain;
+        EXPECT_DEPRECATION_WARNING(swapchain = device.CreateSwapChain(otherSurface, descriptor));
+        return swapchain;
+    }
+
   protected:
     std::unique_ptr<GLFWwindow, GLFWindowDestroyer> window = nullptr;
     wgpu::Surface surface;
@@ -136,7 +143,7 @@
 
 // Control case for a successful swapchain creation and presenting.
 TEST_P(SwapChainValidationTests, CreationSuccess) {
-    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
+    wgpu::SwapChain swapchain = CreateSwapChain(surface, &goodDescriptor);
     swapchain.GetCurrentTexture();
     swapchain.Present();
 }
@@ -146,7 +153,7 @@
     wgpu::SurfaceDescriptor surface_desc = {};
     wgpu::Surface surface = GetInstance().CreateSurface(&surface_desc);
 
-    ASSERT_DEVICE_ERROR_MSG(device.CreateSwapChain(surface, &goodDescriptor),
+    ASSERT_DEVICE_ERROR_MSG(CreateSwapChain(surface, &goodDescriptor),
                             testing::HasSubstr("[Surface] is invalid"));
 }
 
@@ -157,33 +164,33 @@
     {
         wgpu::SwapChainDescriptor desc = goodDescriptor;
         desc.width = 0;
-        ASSERT_DEVICE_ERROR(device.CreateSwapChain(surface, &desc));
+        ASSERT_DEVICE_ERROR(CreateSwapChain(surface, &desc));
     }
     // A height of 0 is invalid.
     {
         wgpu::SwapChainDescriptor desc = goodDescriptor;
         desc.height = 0;
-        ASSERT_DEVICE_ERROR(device.CreateSwapChain(surface, &desc));
+        ASSERT_DEVICE_ERROR(CreateSwapChain(surface, &desc));
     }
 
     // A width of maxTextureDimension2D is valid but maxTextureDimension2D + 1 isn't.
     {
         wgpu::SwapChainDescriptor desc = goodDescriptor;
         desc.width = supportedLimits.maxTextureDimension2D;
-        device.CreateSwapChain(surface, &desc);
+        CreateSwapChain(surface, &desc);
 
         desc.width = supportedLimits.maxTextureDimension2D + 1;
-        ASSERT_DEVICE_ERROR(device.CreateSwapChain(surface, &desc));
+        ASSERT_DEVICE_ERROR(CreateSwapChain(surface, &desc));
     }
 
     // A height of maxTextureDimension2D is valid but maxTextureDimension2D + 1 isn't.
     {
         wgpu::SwapChainDescriptor desc = goodDescriptor;
         desc.height = supportedLimits.maxTextureDimension2D;
-        device.CreateSwapChain(surface, &desc);
+        CreateSwapChain(surface, &desc);
 
         desc.height = supportedLimits.maxTextureDimension2D + 1;
-        ASSERT_DEVICE_ERROR(device.CreateSwapChain(surface, &desc));
+        ASSERT_DEVICE_ERROR(CreateSwapChain(surface, &desc));
     }
 }
 
@@ -191,20 +198,20 @@
 TEST_P(SwapChainValidationTests, InvalidCreationUsage) {
     wgpu::SwapChainDescriptor desc = goodDescriptor;
     desc.usage = wgpu::TextureUsage::TextureBinding;
-    ASSERT_DEVICE_ERROR(device.CreateSwapChain(surface, &desc));
+    ASSERT_DEVICE_ERROR(CreateSwapChain(surface, &desc));
 }
 
 // Checks that the creation format must (currently) be BGRA8Unorm
 TEST_P(SwapChainValidationTests, InvalidCreationFormat) {
     wgpu::SwapChainDescriptor desc = goodDescriptor;
     desc.format = wgpu::TextureFormat::RGBA8Unorm;
-    ASSERT_DEVICE_ERROR(device.CreateSwapChain(surface, &desc));
+    ASSERT_DEVICE_ERROR(CreateSwapChain(surface, &desc));
 }
 
 // Check swapchain operations with an error swapchain are errors
 TEST_P(SwapChainValidationTests, OperationsOnErrorSwapChain) {
     wgpu::SwapChain swapchain;
-    ASSERT_DEVICE_ERROR(swapchain = device.CreateSwapChain(surface, &badDescriptor));
+    ASSERT_DEVICE_ERROR(swapchain = CreateSwapChain(surface, &badDescriptor));
 
     wgpu::Texture texture;
     ASSERT_DEVICE_ERROR(texture = swapchain.GetCurrentTexture());
@@ -215,7 +222,7 @@
 
 // Check it is invalid to call present without getting a current texture.
 TEST_P(SwapChainValidationTests, PresentWithoutCurrentTexture) {
-    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
+    wgpu::SwapChain swapchain = CreateSwapChain(surface, &goodDescriptor);
 
     // Check it is invalid if we never called GetCurrentTexture
     ASSERT_DEVICE_ERROR(swapchain.Present());
@@ -230,7 +237,7 @@
 // swapchain is kept alive by the surface. Also check after we lose all refs to the surface, the
 // texture is destroyed.
 TEST_P(SwapChainValidationTests, TextureValidAfterSwapChainRefLost) {
-    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
+    wgpu::SwapChain swapchain = CreateSwapChain(surface, &goodDescriptor);
     wgpu::Texture texture = swapchain.GetCurrentTexture();
 
     swapchain = nullptr;
@@ -242,7 +249,7 @@
 
 // Check that the current texture is the destroyed state after present.
 TEST_P(SwapChainValidationTests, TextureDestroyedAfterPresent) {
-    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
+    wgpu::SwapChain swapchain = CreateSwapChain(surface, &goodDescriptor);
     wgpu::Texture texture = swapchain.GetCurrentTexture();
     swapchain.Present();
 
@@ -286,7 +293,7 @@
     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::SwapChain swapchain = CreateSwapChain(surface, &goodDescriptor);
     wgpu::TextureView view = swapchain.GetCurrentTexture().CreateView();
 
     // Validation will also check the dimension of the view is 2D, and it's usage contains
@@ -315,7 +322,7 @@
 TEST_P(SwapChainValidationTests, ReflectionValidGetCurrentTexture) {
     // Check with the goodDescriptor.
     {
-        wgpu::SwapChain swapChain = device.CreateSwapChain(surface, &goodDescriptor);
+        wgpu::SwapChain swapChain = CreateSwapChain(surface, &goodDescriptor);
         CheckTextureMatchesDescriptor(swapChain.GetCurrentTexture(), goodDescriptor);
     }
     // Check with properties that can be changed while keeping a valid descriptor.
@@ -323,7 +330,7 @@
         wgpu::SwapChainDescriptor otherDescriptor = goodDescriptor;
         otherDescriptor.width = 2;
         otherDescriptor.height = 7;
-        wgpu::SwapChain swapChain = device.CreateSwapChain(surface, &goodDescriptor);
+        wgpu::SwapChain swapChain = CreateSwapChain(surface, &goodDescriptor);
         CheckTextureMatchesDescriptor(swapChain.GetCurrentTexture(), goodDescriptor);
     }
 }
@@ -331,7 +338,7 @@
 // Check the reflection of textures returned by GetCurrentTexture on valid swapchain.
 TEST_P(SwapChainValidationTests, ReflectionErrorGetCurrentTexture) {
     wgpu::SwapChain swapChain;
-    ASSERT_DEVICE_ERROR(swapChain = device.CreateSwapChain(surface, &badDescriptor));
+    ASSERT_DEVICE_ERROR(swapChain = CreateSwapChain(surface, &badDescriptor));
     wgpu::Texture texture;
     ASSERT_DEVICE_ERROR(texture = swapChain.GetCurrentTexture());
     CheckTextureMatchesDescriptor(texture, badDescriptor);
@@ -339,8 +346,8 @@
 
 // Check that failing to create a new swapchain doesn't replace the previous one.
 TEST_P(SwapChainValidationTests, ErrorSwapChainDoesntReplacePreviousOne) {
-    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
-    ASSERT_DEVICE_ERROR(device.CreateSwapChain(surface, &badDescriptor));
+    wgpu::SwapChain swapchain = CreateSwapChain(surface, &goodDescriptor);
+    ASSERT_DEVICE_ERROR(CreateSwapChain(surface, &badDescriptor));
 
     swapchain.GetCurrentTexture();
     swapchain.Present();
@@ -349,15 +356,15 @@
 // Check that after replacement, all swapchain operations are errors and the texture is destroyed.
 TEST_P(SwapChainValidationTests, ReplacedSwapChainIsInvalid) {
     {
-        wgpu::SwapChain replacedSwapChain = device.CreateSwapChain(surface, &goodDescriptor);
-        device.CreateSwapChain(surface, &goodDescriptor);
+        wgpu::SwapChain replacedSwapChain = CreateSwapChain(surface, &goodDescriptor);
+        CreateSwapChain(surface, &goodDescriptor);
         ASSERT_DEVICE_ERROR(replacedSwapChain.GetCurrentTexture());
     }
 
     {
-        wgpu::SwapChain replacedSwapChain = device.CreateSwapChain(surface, &goodDescriptor);
+        wgpu::SwapChain replacedSwapChain = CreateSwapChain(surface, &goodDescriptor);
         wgpu::Texture texture = replacedSwapChain.GetCurrentTexture();
-        device.CreateSwapChain(surface, &goodDescriptor);
+        CreateSwapChain(surface, &goodDescriptor);
 
         CheckTextureIsDestroyed(texture);
         ASSERT_DEVICE_ERROR(replacedSwapChain.Present());
@@ -367,12 +374,12 @@
 // Check that after surface destruction, all swapchain operations are errors and the texture is
 // destroyed. The test is split in two to reset the wgpu::Surface in the middle.
 TEST_P(SwapChainValidationTests, SwapChainIsInvalidAfterSurfaceDestruction_GetTexture) {
-    wgpu::SwapChain replacedSwapChain = device.CreateSwapChain(surface, &goodDescriptor);
+    wgpu::SwapChain replacedSwapChain = CreateSwapChain(surface, &goodDescriptor);
     surface = nullptr;
     ASSERT_DEVICE_ERROR(replacedSwapChain.GetCurrentTexture());
 }
 TEST_P(SwapChainValidationTests, SwapChainIsInvalidAfterSurfaceDestruction_AfterGetTexture) {
-    wgpu::SwapChain replacedSwapChain = device.CreateSwapChain(surface, &goodDescriptor);
+    wgpu::SwapChain replacedSwapChain = CreateSwapChain(surface, &goodDescriptor);
     wgpu::Texture texture = replacedSwapChain.GetCurrentTexture();
     surface = nullptr;
 
@@ -382,7 +389,7 @@
 
 // Test that new swap chain present after device is lost
 TEST_P(SwapChainValidationTests, SwapChainPresentAfterDeviceLost) {
-    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
+    wgpu::SwapChain swapchain = CreateSwapChain(surface, &goodDescriptor);
     swapchain.GetCurrentTexture();
 
     LoseDeviceForTesting();
@@ -391,7 +398,7 @@
 
 // Test that new swap chain get current texture fails after device is lost
 TEST_P(SwapChainValidationTests, SwapChainGetCurrentTextureFailsAfterDevLost) {
-    wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor);
+    wgpu::SwapChain swapchain = CreateSwapChain(surface, &goodDescriptor);
 
     LoseDeviceForTesting();
     EXPECT_TRUE(dawn::native::CheckIsErrorForTesting(swapchain.GetCurrentTexture().Get()));
@@ -400,8 +407,8 @@
 // Test that creation of a new swapchain fails after device is lost
 TEST_P(SwapChainValidationTests, CreateSwapChainFailsAfterDevLost) {
     LoseDeviceForTesting();
-    EXPECT_TRUE(dawn::native::CheckIsErrorForTesting(
-        device.CreateSwapChain(surface, &goodDescriptor).Get()));
+    EXPECT_TRUE(
+        dawn::native::CheckIsErrorForTesting(CreateSwapChain(surface, &goodDescriptor).Get()));
 }
 
 DAWN_INSTANTIATE_TEST(SwapChainValidationTests, MetalBackend(), NullBackend());
diff --git a/src/dawn/tests/unittests/native/mocks/DeviceMock.h b/src/dawn/tests/unittests/native/mocks/DeviceMock.h
index d49e9a8..cc1027a 100644
--- a/src/dawn/tests/unittests/native/mocks/DeviceMock.h
+++ b/src/dawn/tests/unittests/native/mocks/DeviceMock.h
@@ -121,7 +121,7 @@
                 (override));
     MOCK_METHOD(ResultOrError<Ref<SwapChainBase>>,
                 CreateSwapChainImpl,
-                (Surface*, SwapChainBase*, const SwapChainDescriptor*),
+                (Surface*, SwapChainBase*, const SurfaceConfiguration*),
                 (override));
     MOCK_METHOD(ResultOrError<Ref<TextureBase>>,
                 CreateTextureImpl,
diff --git a/src/dawn/tests/unittests/native/mocks/SwapChainMock.cpp b/src/dawn/tests/unittests/native/mocks/SwapChainMock.cpp
index d0c9bcc..6a459cc 100644
--- a/src/dawn/tests/unittests/native/mocks/SwapChainMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/SwapChainMock.cpp
@@ -31,8 +31,8 @@
 
 SwapChainMock::SwapChainMock(DeviceBase* device,
                              Surface* surface,
-                             const SwapChainDescriptor* descriptor)
-    : SwapChainBase(device, surface, descriptor) {
+                             const SurfaceConfiguration* config)
+    : SwapChainBase(device, surface, config) {
     ON_CALL(*this, DestroyImpl).WillByDefault([this] { this->SwapChainBase::DestroyImpl(); });
 }
 
diff --git a/src/dawn/tests/unittests/native/mocks/SwapChainMock.h b/src/dawn/tests/unittests/native/mocks/SwapChainMock.h
index d1068bd..4a0ed0c 100644
--- a/src/dawn/tests/unittests/native/mocks/SwapChainMock.h
+++ b/src/dawn/tests/unittests/native/mocks/SwapChainMock.h
@@ -37,12 +37,12 @@
 
 class SwapChainMock : public SwapChainBase {
   public:
-    SwapChainMock(DeviceBase* device, Surface* surface, const SwapChainDescriptor* descriptor);
+    SwapChainMock(DeviceBase* device, Surface* surface, const SurfaceConfiguration* config);
     ~SwapChainMock() override;
 
     MOCK_METHOD(void, DestroyImpl, (), (override));
 
-    MOCK_METHOD(ResultOrError<Ref<TextureBase>>, GetCurrentTextureImpl, (), (override));
+    MOCK_METHOD(ResultOrError<SwapChainTextureInfo>, GetCurrentTextureImpl, (), (override));
     MOCK_METHOD(MaybeError, PresentImpl, (), (override));
     MOCK_METHOD(void, DetachFromSurfaceImpl, (), (override));
 };
diff --git a/src/dawn/utils/CMakeLists.txt b/src/dawn/utils/CMakeLists.txt
index b437164..11f2e0d 100644
--- a/src/dawn/utils/CMakeLists.txt
+++ b/src/dawn/utils/CMakeLists.txt
@@ -60,6 +60,9 @@
             SPIRV-Tools-opt
 )
 
+# Needed by WGPUHelpers
+target_compile_definitions(dawn_utils PUBLIC -DTINT_BUILD_SPV_READER=$<BOOL:${TINT_BUILD_SPV_READER}>)
+
 if(WIN32 AND NOT WINDOWS_STORE)
     target_sources(dawn_utils PRIVATE "WindowsDebugLogger.cpp")
 else()
diff --git a/src/dawn/wire/client/Surface.cpp b/src/dawn/wire/client/Surface.cpp
index f54aaca..c24f69d 100644
--- a/src/dawn/wire/client/Surface.cpp
+++ b/src/dawn/wire/client/Surface.cpp
@@ -27,7 +27,11 @@
 
 #include "dawn/wire/client/Surface.h"
 
+#include "dawn/common/Log.h"
 #include "dawn/common/Platform.h"
+#include "dawn/wire/client/Client.h"
+#include "dawn/wire/client/Device.h"
+#include "dawn/wire/client/Texture.h"
 
 namespace dawn::wire::client {
 
@@ -39,9 +43,50 @@
     return ObjectType::Surface;
 }
 
+void Surface::Configure(WGPUSurfaceConfiguration const* config) {
+    mTextureDescriptor = {};
+    mTextureDescriptor.size = {config->width, config->height, 1};
+    mTextureDescriptor.format = config->format;
+    mTextureDescriptor.usage = config->usage;
+    mTextureDescriptor.dimension = WGPUTextureDimension_2D;
+    mTextureDescriptor.mipLevelCount = 1;
+    mTextureDescriptor.sampleCount = 1;
+
+    SurfaceConfigureCmd cmd;
+    cmd.self = ToAPI(this);
+    cmd.config = config;
+    GetClient()->SerializeCommand(cmd);
+}
+
 WGPUTextureFormat Surface::GetPreferredFormat([[maybe_unused]] WGPUAdapter adapter) const {
+    // TODO(dawn:2320) Use the result of GetCapabilities
     // This is the only supported format in native mode (see crbug.com/dawn/160).
     return WGPUTextureFormat_BGRA8Unorm;
 }
 
+void Surface::GetCapabilities(WGPUAdapter adapter, WGPUSurfaceCapabilities* capabilities) const {
+    // TODO(dawn:2320) Implement this
+    dawn::ErrorLog() << "surface.GetCapabilities not supported yet with dawn_wire.";
+}
+
+void Surface::GetCurrentTexture(WGPUSurfaceTexture* surfaceTexture) {
+    // TODO(dawn:2320) Implement this
+    dawn::ErrorLog() << "surface.GetCurrentTexture not supported yet with dawn_wire.";
+
+    Client* wireClient = GetClient();
+    Texture* texture = wireClient->Make<Texture>(&mTextureDescriptor);
+    surfaceTexture->texture = ToAPI(texture);
+
+    SurfaceGetCurrentTextureCmd cmd;
+    cmd.self = ToAPI(this);
+    cmd.selfId = GetWireId();
+    // cmd.result = texture->GetWireHandle(); // TODO(dawn:2320) Feed surfaceTexture to cmd
+    wireClient->SerializeCommand(cmd);
+}
+
+void ClientSurfaceCapabilitiesFreeMembers(WGPUSurfaceCapabilities capabilities) {
+    // TODO(dawn:2320) Implement this
+    dawn::ErrorLog() << "surfaceCapabilities.FreeMembers not supported yet with dawn_wire.";
+}
+
 }  // namespace dawn::wire::client
diff --git a/src/dawn/wire/client/Surface.h b/src/dawn/wire/client/Surface.h
index 08aae69..cb776ea 100644
--- a/src/dawn/wire/client/Surface.h
+++ b/src/dawn/wire/client/Surface.h
@@ -43,9 +43,20 @@
 
     ObjectType GetObjectType() const override;
 
+    void Configure(WGPUSurfaceConfiguration const* config);
+
     WGPUTextureFormat GetPreferredFormat(WGPUAdapter adapter) const;
+
+    void GetCapabilities(WGPUAdapter adapter, WGPUSurfaceCapabilities* capabilities) const;
+
+    void GetCurrentTexture(WGPUSurfaceTexture* surfaceTexture);
+
+  private:
+    WGPUTextureDescriptor mTextureDescriptor;
 };
 
+void ClientSurfaceCapabilitiesFreeMembers(WGPUSurfaceCapabilities capabilities);
+
 }  // namespace dawn::wire::client
 
 #endif  // SRC_DAWN_WIRE_CLIENT_SURFACE_H_