Reland "Remove SwapChain from the API"

This is a reland of commit 966c91706b9a39c30bd3fc24f9f776a02cda8aaf

Reland with:

 - Missing length fields added in dawn.json
 - A blocklisting of Surface::GetCurrentTexture for the LPM fuzzer
 - Minor changes to comments

Original change's description:
> 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>

Bug: dawn:2320
Change-Id: I02800d940d374c57237839c651ce26725b17928f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/182560
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
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 d226e01..e891705 100644
--- a/src/dawn/dawn.json
+++ b/src/dawn/dawn.json
@@ -408,15 +408,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*", "length": "format count"},
             {"name": "present mode count", "type": "size_t"},
-            {"name": "present modes", "type": "present mode", "annotation": "*"},
+            {"name": "present modes", "type": "present mode", "annotation": "const*", "length": "present mode count"},
             {"name": "alpha mode count", "type": "size_t"},
-            {"name": "alpha modes", "type": "composite alpha mode", "annotation": "*"}
+            {"name": "alpha modes", "type": "composite alpha mode", "annotation": "const*", "length": "alpha mode count"}
         ],
         "methods": [
             {
@@ -428,18 +427,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": {
@@ -1037,7 +1035,6 @@
     },
     "composite alpha mode": {
         "category": "enum",
-        "tags": ["upstream"],
         "values": [
             {"value": 0, "name": "auto"},
             {"value": 1, "name": "opaque"},
@@ -3497,7 +3494,6 @@
             {
                 "name": "configure",
                 "returns": "void",
-                "tags": ["upstream"],
                 "args": [
                     {"name": "config", "type": "surface configuration", "annotation": "const*"}
                 ]
@@ -3505,7 +3501,6 @@
             {
                 "name": "get capabilities",
                 "returns": "void",
-                "tags": ["upstream"],
                 "args": [
                     {"name": "adapter", "type": "adapter"},
                     {"name": "capabilities", "type": "surface capabilities", "annotation": "*"}
@@ -3514,7 +3509,6 @@
             {
                 "name": "get current texture",
                 "returns": "void",
-                "tags": ["upstream"],
                 "args": [
                     {"name": "surface texture", "type": "surface texture", "annotation": "*"}
                 ]
@@ -3529,14 +3523,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"}
+                ]
             }
         ]
     },
@@ -3655,7 +3655,6 @@
     },
     "surface texture": {
         "category": "structure",
-        "tags": ["upstream"],
         "members": [
             {"name": "texture", "type": "texture"},
             {"name": "suboptimal", "type": "bool"},
@@ -3803,7 +3802,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 f87a712..3f3c7d0e 100644
--- a/src/dawn/dawn_wire.json
+++ b/src/dawn/dawn_wire.json
@@ -243,6 +243,7 @@
             "QueueOnSubmittedWorkDoneF",
             "QueueWriteBuffer",
             "QueueWriteTexture",
+            "SurfaceGetCapabilities",
             "SurfaceGetPreferredFormat",
             "TextureGetWidth",
             "TextureGetHeight",
@@ -263,6 +264,8 @@
             "DeviceInjectError",
             "InstanceProcessEvents",
             "InstanceWaitAny",
+            "SurfaceConfigure",
+            "SurfaceGetCurrentTexture",
             "SwapChainGetCurrentTexture"
         ],
         "client_special_objects": [
@@ -274,6 +277,7 @@
             "Queue",
             "ShaderModule",
             "Surface",
+            "SurfaceCapabilities",
             "SwapChain",
             "Texture"
         ],
diff --git a/src/dawn/fuzzers/lpmfuzz/dawn_lpm.json b/src/dawn/fuzzers/lpmfuzz/dawn_lpm.json
index 7869115..f039498 100644
--- a/src/dawn/fuzzers/lpmfuzz/dawn_lpm.json
+++ b/src/dawn/fuzzers/lpmfuzz/dawn_lpm.json
@@ -38,7 +38,8 @@
     "blocklisted_cmds": [
         "surface descriptor from windows core window",
         "surface descriptor from windows swap chain panel",
-        "surface descriptor from canvas html selector"
+        "surface descriptor from canvas html selector",
+        "surface get current texture"
     ],
 
     "lpm_info": {
@@ -58,4 +59,4 @@
 
         "invalid object id": 2147483647
     }
-}
\ No newline at end of file
+}
diff --git a/src/dawn/native/Adapter.cpp b/src/dawn/native/Adapter.cpp
index 6441adc..5ae7d47 100644
--- a/src/dawn/native/Adapter.cpp
+++ b/src/dawn/native/Adapter.cpp
@@ -75,6 +75,10 @@
     return mPhysicalDevice.Get();
 }
 
+const PhysicalDeviceBase* AdapterBase::GetPhysicalDevice() const {
+    return mPhysicalDevice.Get();
+}
+
 InstanceBase* AdapterBase::APIGetInstance() const {
     InstanceBase* instance = mPhysicalDevice->GetInstance();
     DAWN_ASSERT(instance != nullptr);
@@ -379,6 +383,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 038de4b..ed2797e 100644
--- a/src/dawn/native/Adapter.h
+++ b/src/dawn/native/Adapter.h
@@ -28,11 +28,13 @@
 #ifndef SRC_DAWN_NATIVE_ADAPTER_H_
 #define SRC_DAWN_NATIVE_ADAPTER_H_
 
+#include <string>
 #include <utility>
 #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/Device.h"
 #include "dawn/native/PhysicalDevice.h"
@@ -44,7 +46,7 @@
 class TogglesState;
 struct SupportedLimits;
 
-class AdapterBase : public RefCounted {
+class AdapterBase : public RefCounted, public WeakRefSupport<AdapterBase> {
   public:
     AdapterBase(Ref<PhysicalDeviceBase> physicalDevice,
                 FeatureLevel featureLevel,
@@ -72,12 +74,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:
     std::pair<Ref<DeviceBase::DeviceLostEvent>, ResultOrError<Ref<DeviceBase>>> CreateDevice(
         const DeviceDescriptor* rawDescriptor);
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index 46957d0..bf98503 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -1510,7 +1510,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));
 }
@@ -2205,15 +2216,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();
@@ -2227,6 +2254,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 715767e..36334ed 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -228,8 +228,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);
@@ -268,6 +273,7 @@
     ShaderModuleBase* APICreateShaderModule(const ShaderModuleDescriptor* descriptor);
     ShaderModuleBase* APICreateErrorShaderModule(const ShaderModuleDescriptor* descriptor,
                                                  const char* errorMessage);
+    // TODO(crbug.com/dawn/2320): Remove after deprecation.
     SwapChainBase* APICreateSwapChain(Surface* surface, const SwapChainDescriptor* descriptor);
     TextureBase* APICreateTexture(const TextureDescriptor* descriptor);
 
@@ -480,7 +486,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 2c1861f..68494b7 100644
--- a/src/dawn/native/PhysicalDevice.h
+++ b/src/dawn/native/PhysicalDevice.h
@@ -49,6 +49,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();
@@ -118,6 +125,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..b1ea52c 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()) {
+        [[maybe_unused]] bool error = 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()) {
+        [[maybe_unused]] bool error = 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()) {
+        [[maybe_unused]] bool error = mInstance->ConsumedError(std::move(maybeError));
+    } else {
+        [[maybe_unused]] bool error = GetCurrentDevice()->ConsumedError(std::move(maybeError));
+    }
+}
+
+void Surface::APIUnconfigure() {
+    MaybeError maybeError = Unconfigure();
+    if (!GetCurrentDevice()) {
+        [[maybe_unused]] bool error = 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..f31d908 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..86460d1 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 c673c0d..8262ca0 100644
--- a/src/dawn/native/d3d11/DeviceD3D11.cpp
+++ b/src/dawn/native/d3d11/DeviceD3D11.cpp
@@ -214,11 +214,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 b9bc161..a74a559 100644
--- a/src/dawn/native/d3d11/DeviceD3D11.h
+++ b/src/dawn/native/d3d11/DeviceD3D11.h
@@ -129,7 +129,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..6d2d621 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 5aea624..90d6ab7 100644
--- a/src/dawn/native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn/native/d3d12/DeviceD3D12.cpp
@@ -400,11 +400,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 c34751eb..6024775 100644
--- a/src/dawn/native/d3d12/DeviceD3D12.h
+++ b/src/dawn/native/d3d12/DeviceD3D12.h
@@ -209,7 +209,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..1e98730 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 672d705..de9968a 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,
diff --git a/src/dawn/native/metal/DeviceMTL.h b/src/dawn/native/metal/DeviceMTL.h
index 2c8df01..a47ea80 100644
--- a/src/dawn/native/metal/DeviceMTL.h
+++ b/src/dawn/native/metal/DeviceMTL.h
@@ -115,7 +115,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 10e9fbb..0a555bf 100644
--- a/src/dawn/native/metal/DeviceMTL.mm
+++ b/src/dawn/native/metal/DeviceMTL.mm
@@ -227,11 +227,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..478f726 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 adfc7be..db967bb 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 {};
 }
@@ -226,11 +235,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) {
@@ -510,8 +518,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;
 }
@@ -536,10 +544,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 694e049..b497460 100644
--- a/src/dawn/native/null/DeviceNull.h
+++ b/src/dawn/native/null/DeviceNull.h
@@ -165,7 +165,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(
@@ -195,6 +195,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;
 
@@ -324,7 +327,7 @@
     static ResultOrError<Ref<SwapChain>> Create(Device* device,
                                                 Surface* surface,
                                                 SwapChainBase* previousSwapChain,
-                                                const SwapChainDescriptor* descriptor);
+                                                const SurfaceConfiguration* config);
     ~SwapChain() override;
 
   private:
@@ -334,7 +337,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 7293d47..691b9e4 100644
--- a/src/dawn/native/opengl/DeviceGL.cpp
+++ b/src/dawn/native/opengl/DeviceGL.cpp
@@ -261,10 +261,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 1302ef7..0e312ca 100644
--- a/src/dawn/native/opengl/DeviceGL.h
+++ b/src/dawn/native/opengl/DeviceGL.h
@@ -132,7 +132,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 b9a14a7..147c1a7 100644
--- a/src/dawn/native/opengl/PhysicalDeviceGL.cpp
+++ b/src/dawn/native/opengl/PhysicalDeviceGL.cpp
@@ -452,6 +452,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 2dda6e9..ac0fa7a 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 265185c..c6c96ea 100644
--- a/src/dawn/native/vulkan/DeviceVk.cpp
+++ b/src/dawn/native/vulkan/DeviceVk.cpp
@@ -215,11 +215,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 ab716ff..57d4e76 100644
--- a/src/dawn/native/vulkan/DeviceVk.h
+++ b/src/dawn/native/vulkan/DeviceVk.h
@@ -148,7 +148,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 ce58ea0..7966619 100644
--- a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
+++ b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
@@ -37,8 +37,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)
@@ -858,6 +860,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 b40f0b1..d4fe78a 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 c8f67e5..1bc3138 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"
@@ -250,6 +251,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) {
@@ -346,6 +364,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 d98e088..b23f969 100644
--- a/src/dawn/native/webgpu_absl_format.h
+++ b/src/dawn/native/webgpu_absl_format.h
@@ -148,6 +148,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);
@@ -164,6 +170,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 8333fc3..4fa91b7 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -714,6 +714,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..9fb92ae
--- /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..ef1fc3c
--- /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 e1087c0..77bfe41 100644
--- a/src/dawn/tests/end2end/SwapChainTests.cpp
+++ b/src/dawn/tests/end2end/SwapChainTests.cpp
@@ -90,6 +90,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;
@@ -112,47 +119,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;
 }
@@ -178,12 +185,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();
         }
@@ -197,7 +204,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();
     }
@@ -208,7 +215,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);
@@ -235,7 +242,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();
     }
@@ -246,12 +253,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;
         }
@@ -286,7 +294,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"));
 }
 
@@ -396,7 +404,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);
@@ -416,7 +424,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"));
 }
 
@@ -433,7 +441,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);
 
@@ -454,7 +462,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 64b405b..6a6a50c 100644
--- a/src/dawn/tests/unittests/native/mocks/DeviceMock.h
+++ b/src/dawn/tests/unittests/native/mocks/DeviceMock.h
@@ -123,7 +123,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..24d947e 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_