Implement WGPUTextureDescriptor.viewFormats and sampling reinterpretation

Reinterpretation for render/resolve attachments not yet implemented.

Bug: dawn:1276
Change-Id: I43d73ce5c943c4ba49df06c6865867f378f2de25
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/84280
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/dawn.json b/dawn.json
index 54d75ad..3578858 100644
--- a/dawn.json
+++ b/dawn.json
@@ -2536,8 +2536,8 @@
             {"name": "format", "type": "texture format"},
             {"name": "mip level count", "type": "uint32_t", "default": 1},
             {"name": "sample count", "type": "uint32_t", "default": 1},
-            {"name": "view format count", "type": "uint32_t", "default": 0, "tags": ["upstream"]},
-            {"name": "view formats", "type": "texture format", "annotation": "const*", "length": "view format count", "tags": ["upstream"]}
+            {"name": "view format count", "type": "uint32_t", "default": 0},
+            {"name": "view formats", "type": "texture format", "annotation": "const*", "length": "view format count"}
         ]
     },
     "texture dimension": {
diff --git a/src/dawn/common/ityp_bitset.h b/src/dawn/common/ityp_bitset.h
index d91cb19..9c27cfe 100644
--- a/src/dawn/common/ityp_bitset.h
+++ b/src/dawn/common/ityp_bitset.h
@@ -34,6 +34,8 @@
         }
 
       public:
+        using reference = typename Base::reference;
+
         constexpr bitset() noexcept : Base() {
         }
 
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index d94f909..f79ead5 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -681,7 +681,7 @@
     }
 
     ResultOrError<const Format*> DeviceBase::GetInternalFormat(wgpu::TextureFormat format) const {
-        size_t index = ComputeFormatIndex(format);
+        FormatIndex index = ComputeFormatIndex(format);
         DAWN_INVALID_IF(index >= mFormatTable.size(), "Unknown texture format %s.", format);
 
         const Format* internalFormat = &mFormatTable[index];
@@ -691,7 +691,13 @@
     }
 
     const Format& DeviceBase::GetValidInternalFormat(wgpu::TextureFormat format) const {
-        size_t index = ComputeFormatIndex(format);
+        FormatIndex index = ComputeFormatIndex(format);
+        ASSERT(index < mFormatTable.size());
+        ASSERT(mFormatTable[index].isSupported);
+        return mFormatTable[index];
+    }
+
+    const Format& DeviceBase::GetValidInternalFormat(FormatIndex index) const {
         ASSERT(index < mFormatTable.size());
         ASSERT(mFormatTable[index].isSupported);
         return mFormatTable[index];
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index 8abf86b..db13e09 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -137,6 +137,7 @@
         // valid and supported.
         // The reference returned has the same lifetime as the device.
         const Format& GetValidInternalFormat(wgpu::TextureFormat format) const;
+        const Format& GetValidInternalFormat(FormatIndex formatIndex) const;
 
         virtual ResultOrError<Ref<CommandBufferBase>> CreateCommandBuffer(
             CommandEncoder* encoder,
diff --git a/src/dawn/native/Format.cpp b/src/dawn/native/Format.cpp
index 1e4b694..c201729 100644
--- a/src/dawn/native/Format.cpp
+++ b/src/dawn/native/Format.cpp
@@ -100,6 +100,12 @@
     }
 
     bool Format::CopyCompatibleWith(const Format& format) const {
+        // TODO(crbug.com/dawn/1332): Add a Format compatibility matrix.
+        return baseFormat == format.baseFormat;
+    }
+
+    bool Format::ViewCompatibleWith(const Format& format) const {
+        // TODO(crbug.com/dawn/1332): Add a Format compatibility matrix.
         return baseFormat == format.baseFormat;
     }
 
@@ -115,31 +121,41 @@
         return aspectInfo[aspectIndex];
     }
 
-    size_t Format::GetIndex() const {
+    FormatIndex Format::GetIndex() const {
         return ComputeFormatIndex(format);
     }
 
+    // FormatSet implementation
+
+    bool FormatSet::operator[](const Format& format) const {
+        return Base::operator[](format.GetIndex());
+    }
+
+    typename std::bitset<kKnownFormatCount>::reference FormatSet::operator[](const Format& format) {
+        return Base::operator[](format.GetIndex());
+    }
+
     // Implementation details of the format table of the DeviceBase
 
     // For the enum for formats are packed but this might change when we have a broader feature
     // mechanism for webgpu.h. Formats start at 1 because 0 is the undefined format.
-    size_t ComputeFormatIndex(wgpu::TextureFormat format) {
+    FormatIndex ComputeFormatIndex(wgpu::TextureFormat format) {
         // This takes advantage of overflows to make the index of TextureFormat::Undefined outside
         // of the range of the FormatTable.
         static_assert(static_cast<uint32_t>(wgpu::TextureFormat::Undefined) - 1 >
                       kKnownFormatCount);
-        return static_cast<size_t>(static_cast<uint32_t>(format) - 1);
+        return static_cast<FormatIndex>(static_cast<uint32_t>(format) - 1);
     }
 
     FormatTable BuildFormatTable(const DeviceBase* device) {
         FormatTable table;
-        std::bitset<kKnownFormatCount> formatsSet;
+        FormatSet formatsSet;
 
         static constexpr SampleTypeBit kAnyFloat =
             SampleTypeBit::Float | SampleTypeBit::UnfilterableFloat;
 
         auto AddFormat = [&table, &formatsSet](Format format) {
-            size_t index = ComputeFormatIndex(format.format);
+            FormatIndex index = ComputeFormatIndex(format.format);
             ASSERT(index < table.size());
 
             // This checks that each format is set at most once, the first part of checking that all
@@ -211,11 +227,11 @@
                 AddFormat(internalFormat);
             };
 
-        auto AddDepthFormat = [&AddFormat](
-                                  wgpu::TextureFormat format, uint32_t byteSize, bool isSupported,
-                                  wgpu::TextureFormat baseFormat = wgpu::TextureFormat::Undefined) {
+        auto AddDepthFormat = [&AddFormat](wgpu::TextureFormat format, uint32_t byteSize,
+                                           bool isSupported) {
             Format internalFormat;
             internalFormat.format = format;
+            internalFormat.baseFormat = format;
             internalFormat.isRenderable = true;
             internalFormat.isCompressed = false;
             internalFormat.isSupported = isSupported;
@@ -225,13 +241,6 @@
             internalFormat.aspects = Aspect::Depth;
             internalFormat.componentCount = 1;
 
-            // Default baseFormat of each depth formats should be themselves.
-            if (baseFormat == wgpu::TextureFormat::Undefined) {
-                internalFormat.baseFormat = format;
-            } else {
-                internalFormat.baseFormat = baseFormat;
-            }
-
             AspectInfo* firstAspect = internalFormat.aspectInfo.data();
             firstAspect->block.byteSize = byteSize;
             firstAspect->block.width = 1;
@@ -242,11 +251,10 @@
             AddFormat(internalFormat);
         };
 
-        auto AddStencilFormat = [&AddFormat](wgpu::TextureFormat format, bool isSupported,
-                                             wgpu::TextureFormat baseFormat =
-                                                 wgpu::TextureFormat::Undefined) {
+        auto AddStencilFormat = [&AddFormat](wgpu::TextureFormat format, bool isSupported) {
             Format internalFormat;
             internalFormat.format = format;
+            internalFormat.baseFormat = format;
             internalFormat.isRenderable = true;
             internalFormat.isCompressed = false;
             internalFormat.isSupported = isSupported;
@@ -255,14 +263,6 @@
             internalFormat.supportsResolveTarget = false;
             internalFormat.aspects = Aspect::Stencil;
             internalFormat.componentCount = 1;
-            internalFormat.baseFormat = baseFormat;
-
-            // Default baseFormat of each stencil formats should be themselves.
-            if (baseFormat == wgpu::TextureFormat::Undefined) {
-                internalFormat.baseFormat = format;
-            } else {
-                internalFormat.baseFormat = baseFormat;
-            }
 
             // Duplicate the data for the stencil aspect in both the first and second aspect info.
             //  - aspectInfo[0] is used by AddMultiAspectFormat to copy the info for the whole
@@ -319,10 +319,10 @@
             [&AddFormat, &table](wgpu::TextureFormat format, Aspect aspects,
                                  wgpu::TextureFormat firstFormat, wgpu::TextureFormat secondFormat,
                                  bool isRenderable, bool isSupported, bool supportsMultisample,
-                                 uint8_t componentCount,
-                                 wgpu::TextureFormat baseFormat = wgpu::TextureFormat::Undefined) {
+                                 uint8_t componentCount) {
                 Format internalFormat;
                 internalFormat.format = format;
+                internalFormat.baseFormat = format;
                 internalFormat.isRenderable = isRenderable;
                 internalFormat.isCompressed = false;
                 internalFormat.isSupported = isSupported;
@@ -332,18 +332,11 @@
                 internalFormat.aspects = aspects;
                 internalFormat.componentCount = componentCount;
 
-                // Default baseFormat of each multi aspect formats should be themselves.
-                if (baseFormat == wgpu::TextureFormat::Undefined) {
-                    internalFormat.baseFormat = format;
-                } else {
-                    internalFormat.baseFormat = baseFormat;
-                }
-
                 // Multi aspect formats just copy information about single-aspect formats. This
                 // means that the single-plane formats must have been added before multi-aspect
                 // ones. (it is ASSERTed below).
-                const size_t firstFormatIndex = ComputeFormatIndex(firstFormat);
-                const size_t secondFormatIndex = ComputeFormatIndex(secondFormat);
+                const FormatIndex firstFormatIndex = ComputeFormatIndex(firstFormat);
+                const FormatIndex secondFormatIndex = ComputeFormatIndex(secondFormat);
 
                 ASSERT(table[firstFormatIndex].aspectInfo[0].format !=
                        wgpu::TextureFormat::Undefined);
diff --git a/src/dawn/native/Format.h b/src/dawn/native/Format.h
index 0f72d87..457c6cb 100644
--- a/src/dawn/native/Format.h
+++ b/src/dawn/native/Format.h
@@ -17,6 +17,8 @@
 
 #include "dawn/native/dawn_platform.h"
 
+#include "dawn/common/TypedInteger.h"
+#include "dawn/common/ityp_array.h"
 #include "dawn/common/ityp_bitset.h"
 #include "dawn/native/EnumClassBitmasks.h"
 #include "dawn/native/Error.h"
@@ -77,15 +79,18 @@
 
     // The number of formats Dawn knows about. Asserts in BuildFormatTable ensure that this is the
     // exact number of known format.
-    static constexpr size_t kKnownFormatCount = 96;
+    static constexpr uint32_t kKnownFormatCount = 96;
+
+    using FormatIndex = TypedInteger<struct FormatIndexT, uint32_t>;
 
     struct Format;
-    using FormatTable = std::array<Format, kKnownFormatCount>;
+    using FormatTable = ityp::array<FormatIndex, Format, kKnownFormatCount>;
 
     // A wgpu::TextureFormat along with all the information about it necessary for validation.
     struct Format {
         wgpu::TextureFormat format;
 
+        // TODO(crbug.com/dawn/1332): These members could be stored in a Format capability matrix.
         bool isRenderable;
         bool isCompressed;
         // A format can be known but not supported because it is part of a disabled extension.
@@ -111,16 +116,21 @@
 
         // The index of the format in the list of all known formats: a unique number for each format
         // in [0, kKnownFormatCount)
-        size_t GetIndex() const;
+        FormatIndex GetIndex() const;
 
         // baseFormat represents the memory layout of the format.
-        // If two formats has the same baseFormat, they could copy to each other.
+        // If two formats has the same baseFormat, they could copy to and be viewed as the other
+        // format. Currently two formats have the same baseFormat if they differ only in sRGB-ness.
         wgpu::TextureFormat baseFormat;
 
-        // CopyCompatibleWith() returns true if the input format has the same baseFormat
-        // with current format.
+        // Returns true if the formats are copy compatible.
+        // Currently means they differ only in sRGB-ness.
         bool CopyCompatibleWith(const Format& format) const;
 
+        // Returns true if the formats are texture view format compatible.
+        // Currently means they differ only in sRGB-ness.
+        bool ViewCompatibleWith(const Format& format) const;
+
       private:
         // Used to store the aspectInfo for one or more planes. For single plane "color" formats,
         // only the first aspect info or aspectInfo[0] is valid. For depth-stencil, the first aspect
@@ -131,10 +141,21 @@
         friend FormatTable BuildFormatTable(const DeviceBase* device);
     };
 
+    class FormatSet : public ityp::bitset<FormatIndex, kKnownFormatCount> {
+        using Base = ityp::bitset<FormatIndex, kKnownFormatCount>;
+
+      public:
+        using Base::Base;
+        using Base::operator[];
+
+        bool operator[](const Format& format) const;
+        typename Base::reference operator[](const Format& format);
+    };
+
     // Implementation details of the format table in the device.
 
     // Returns the index of a format in the FormatTable.
-    size_t ComputeFormatIndex(wgpu::TextureFormat format);
+    FormatIndex ComputeFormatIndex(wgpu::TextureFormat format);
     // Builds the format table with the extensions enabled on the device.
     FormatTable BuildFormatTable(const DeviceBase* device);
 
diff --git a/src/dawn/native/Texture.cpp b/src/dawn/native/Texture.cpp
index 0e64f62..51cd391 100644
--- a/src/dawn/native/Texture.cpp
+++ b/src/dawn/native/Texture.cpp
@@ -29,21 +29,68 @@
 
 namespace dawn::native {
     namespace {
-        // WebGPU currently does not have texture format reinterpretation. If it does, the
-        // code to check for it might go here.
-        MaybeError ValidateTextureViewFormatCompatibility(const TextureBase* texture,
-                                                          const TextureViewDescriptor* descriptor) {
-            if (texture->GetFormat().format != descriptor->format) {
-                if (descriptor->aspect != wgpu::TextureAspect::All &&
-                    texture->GetFormat().GetAspectInfo(descriptor->aspect).format ==
-                        descriptor->format) {
-                    return {};
-                }
 
-                return DAWN_VALIDATION_ERROR(
-                    "The format of texture view is not compatible to the original texture");
+        MaybeError ValidateTextureViewFormatCompatibility(const DeviceBase* device,
+                                                          const Format& format,
+                                                          wgpu::TextureFormat viewFormatEnum) {
+            const Format* viewFormat;
+            DAWN_TRY_ASSIGN(viewFormat, device->GetInternalFormat(viewFormatEnum));
+
+            DAWN_INVALID_IF(!format.ViewCompatibleWith(*viewFormat),
+                            "The texture view format (%s) is not texture view format compatible "
+                            "with the texture format (%s).",
+                            viewFormatEnum, format.format);
+            return {};
+        }
+
+        MaybeError ValidateCanViewTextureAs(const DeviceBase* device,
+                                            const TextureBase* texture,
+                                            const Format& viewFormat,
+                                            wgpu::TextureAspect aspect) {
+            const Format& format = texture->GetFormat();
+
+            if (format.format == viewFormat.format) {
+                return {};
             }
 
+            if (aspect != wgpu::TextureAspect::All) {
+                wgpu::TextureFormat aspectFormat = format.GetAspectInfo(aspect).format;
+                if (viewFormat.format == aspectFormat) {
+                    return {};
+                } else {
+                    return DAWN_FORMAT_VALIDATION_ERROR(
+                        "The view format (%s) is not compatible with %s of %s (%s).",
+                        viewFormat.format, aspect, format.format, aspectFormat);
+                }
+            }
+
+            const FormatSet& compatibleViewFormats = texture->GetViewFormats();
+            if (compatibleViewFormats[viewFormat]) {
+                // Validation of this list is done on texture creation, so we don't need to
+                // handle the case where a format is in the list, but not compatible.
+                return {};
+            }
+
+            // |viewFormat| is not in the list. Check compatibility to generate an error message
+            // depending on whether it could be compatible, but needs to be explicitly listed,
+            // or it could never be compatible.
+            if (!format.ViewCompatibleWith(viewFormat)) {
+                // The view format isn't compatible with the format at all. Return an error
+                // that indicates this, in addition to reporting that it's missing from the
+                // list.
+                return DAWN_FORMAT_VALIDATION_ERROR(
+                    "The texture view format (%s) is not compatible with the "
+                    "texture format (%s)."
+                    "The formats must be compatible, and the view format "
+                    "must be passed in the list of view formats on texture creation.",
+                    viewFormat.format, format.format);
+            } else {
+                // The view format is compatible, but not in the list.
+                return DAWN_FORMAT_VALIDATION_ERROR(
+                    "%s was not created with the texture view format (%s) "
+                    "in the list of compatible view formats.",
+                    texture, viewFormat.format);
+            }
             return {};
         }
 
@@ -295,6 +342,12 @@
         const Format* format;
         DAWN_TRY_ASSIGN(format, device->GetInternalFormat(descriptor->format));
 
+        for (uint32_t i = 0; i < descriptor->viewFormatCount; ++i) {
+            DAWN_TRY_CONTEXT(
+                ValidateTextureViewFormatCompatibility(device, *format, descriptor->viewFormats[i]),
+                "validating viewFormats[%u]", i);
+        }
+
         wgpu::TextureUsage usage = descriptor->usage;
         if (internalUsageDesc != nullptr) {
             usage |= internalUsageDesc->internalUsage;
@@ -357,10 +410,13 @@
         DAWN_TRY(ValidateTextureFormat(descriptor->format));
         DAWN_TRY(ValidateTextureAspect(descriptor->aspect));
 
+        const Format& format = texture->GetFormat();
+        const Format& viewFormat = device->GetValidInternalFormat(descriptor->format);
+
         DAWN_INVALID_IF(
-            SelectFormatAspects(texture->GetFormat(), descriptor->aspect) == Aspect::None,
+            SelectFormatAspects(format, descriptor->aspect) == Aspect::None,
             "Texture format (%s) does not have the texture view's selected aspect (%s).",
-            texture->GetFormat().format, descriptor->aspect);
+            format.format, descriptor->aspect);
 
         DAWN_INVALID_IF(descriptor->arrayLayerCount == 0 || descriptor->mipLevelCount == 0,
                         "The texture view's arrayLayerCount (%u) or mipLevelCount (%u) is zero.",
@@ -380,7 +436,7 @@
             "texture's mip level count (%u).",
             descriptor->baseMipLevel, descriptor->mipLevelCount, texture->GetNumMipLevels());
 
-        DAWN_TRY(ValidateTextureViewFormatCompatibility(texture, descriptor));
+        DAWN_TRY(ValidateCanViewTextureAs(device, texture, viewFormat, descriptor->aspect));
         DAWN_TRY(ValidateTextureViewDimensionCompatibility(texture, descriptor));
 
         return {};
@@ -476,6 +532,15 @@
             mMipLevelCount * GetArrayLayers() * GetAspectCount(mFormat.aspects);
         mIsSubresourceContentInitializedAtIndex = std::vector<bool>(subresourceCount, false);
 
+        for (uint32_t i = 0; i < descriptor->viewFormatCount; ++i) {
+            if (descriptor->viewFormats[i] == descriptor->format) {
+                // Skip our own format, so the backends don't allocate the texture for
+                // reinterpretation if it's not needed.
+                continue;
+            }
+            mViewFormats[device->GetValidInternalFormat(descriptor->viewFormats[i])] = true;
+        }
+
         const DawnTextureInternalUsageDescriptor* internalUsageDesc = nullptr;
         FindInChain(descriptor->nextInChain, &internalUsageDesc);
         if (internalUsageDesc != nullptr) {
@@ -517,6 +582,10 @@
         ASSERT(!IsError());
         return mFormat;
     }
+    const FormatSet& TextureBase::GetViewFormats() const {
+        ASSERT(!IsError());
+        return mViewFormats;
+    }
     const Extent3D& TextureBase::GetSize() const {
         ASSERT(!IsError());
         return mSize;
diff --git a/src/dawn/native/Texture.h b/src/dawn/native/Texture.h
index de0a91e..4024c82 100644
--- a/src/dawn/native/Texture.h
+++ b/src/dawn/native/Texture.h
@@ -18,6 +18,7 @@
 #include "dawn/common/ityp_array.h"
 #include "dawn/common/ityp_bitset.h"
 #include "dawn/native/Error.h"
+#include "dawn/native/Format.h"
 #include "dawn/native/Forward.h"
 #include "dawn/native/ObjectBase.h"
 #include "dawn/native/Subresource.h"
@@ -55,6 +56,7 @@
 
         wgpu::TextureDimension GetDimension() const;
         const Format& GetFormat() const;
+        const FormatSet& GetViewFormats() const;
         const Extent3D& GetSize() const;
         uint32_t GetWidth() const;
         uint32_t GetHeight() const;
@@ -109,6 +111,7 @@
         MaybeError ValidateDestroy() const;
         wgpu::TextureDimension mDimension;
         const Format& mFormat;
+        FormatSet mViewFormats;
         Extent3D mSize;
         uint32_t mMipLevelCount;
         uint32_t mSampleCount;
diff --git a/src/dawn/native/opengl/GLFormat.cpp b/src/dawn/native/opengl/GLFormat.cpp
index a0ae13e..dac02a6 100644
--- a/src/dawn/native/opengl/GLFormat.cpp
+++ b/src/dawn/native/opengl/GLFormat.cpp
@@ -23,7 +23,7 @@
 
         auto AddFormat = [&table](wgpu::TextureFormat dawnFormat, GLenum internalFormat,
                                   GLenum format, GLenum type, Type componentType) {
-            size_t index = ComputeFormatIndex(dawnFormat);
+            FormatIndex index = ComputeFormatIndex(dawnFormat);
             ASSERT(index < table.size());
 
             table[index].internalFormat = internalFormat;
diff --git a/src/dawn/native/opengl/GLFormat.h b/src/dawn/native/opengl/GLFormat.h
index d58f697..e3e3195 100644
--- a/src/dawn/native/opengl/GLFormat.h
+++ b/src/dawn/native/opengl/GLFormat.h
@@ -34,7 +34,7 @@
         ComponentType componentType;
     };
 
-    using GLFormatTable = std::array<GLFormat, kKnownFormatCount>;
+    using GLFormatTable = ityp::array<FormatIndex, GLFormat, kKnownFormatCount>;
     GLFormatTable BuildGLFormatTable();
 
 }  // namespace dawn::native::opengl
diff --git a/src/dawn/native/vulkan/TextureVk.cpp b/src/dawn/native/vulkan/TextureVk.cpp
index e96f8a0..8a76b89 100644
--- a/src/dawn/native/vulkan/TextureVk.cpp
+++ b/src/dawn/native/vulkan/TextureVk.cpp
@@ -655,17 +655,33 @@
         VkImageCreateInfo createInfo = {};
         FillVulkanCreateInfoSizesAndType(*this, &createInfo);
 
+        PNextChainBuilder createInfoChain(&createInfo);
+
         createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
-        createInfo.pNext = nullptr;
-        createInfo.flags = 0;
         createInfo.format = VulkanImageFormat(device, GetFormat().format);
         createInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
         createInfo.usage = VulkanImageUsage(GetInternalUsage(), GetFormat()) | extraUsages;
         createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
-        createInfo.queueFamilyIndexCount = 0;
-        createInfo.pQueueFamilyIndices = nullptr;
         createInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
 
+        VkImageFormatListCreateInfo imageFormatListInfo = {};
+        std::vector<VkFormat> viewFormats;
+        if (GetViewFormats().any()) {
+            createInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
+            if (device->GetDeviceInfo().HasExt(DeviceExt::ImageFormatList)) {
+                createInfoChain.Add(&imageFormatListInfo,
+                                    VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO);
+                viewFormats.push_back(VulkanImageFormat(device, GetFormat().format));
+                for (FormatIndex i : IterateBitSet(GetViewFormats())) {
+                    const Format& viewFormat = device->GetValidInternalFormat(i);
+                    viewFormats.push_back(VulkanImageFormat(device, viewFormat.format));
+                }
+
+                imageFormatListInfo.viewFormatCount = viewFormats.size();
+                imageFormatListInfo.pViewFormats = viewFormats.data();
+            }
+        }
+
         ASSERT(IsSampleCountSupported(device, createInfo));
 
         if (GetArrayLayers() >= 6 && GetWidth() == GetHeight()) {
@@ -707,7 +723,8 @@
     // Internally managed, but imported from external handle
     MaybeError Texture::InitializeFromExternal(const ExternalImageDescriptorVk* descriptor,
                                                external_memory::Service* externalMemoryService) {
-        VkFormat format = VulkanImageFormat(ToBackend(GetDevice()), GetFormat().format);
+        Device* device = ToBackend(GetDevice());
+        VkFormat format = VulkanImageFormat(device, GetFormat().format);
         VkImageUsageFlags usage = VulkanImageUsage(GetInternalUsage(), GetFormat());
         DAWN_INVALID_IF(!externalMemoryService->SupportsCreateImage(descriptor, format, usage,
                                                                     &mSupportsDisjointVkImage),
@@ -728,8 +745,9 @@
         VkImageCreateInfo baseCreateInfo = {};
         FillVulkanCreateInfoSizesAndType(*this, &baseCreateInfo);
 
+        PNextChainBuilder createInfoChain(&baseCreateInfo);
+
         baseCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
-        baseCreateInfo.pNext = nullptr;
         baseCreateInfo.format = format;
         baseCreateInfo.usage = usage;
         baseCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
@@ -741,6 +759,23 @@
         // also required for the implementation of robust resource initialization.
         baseCreateInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
 
+        VkImageFormatListCreateInfo imageFormatListInfo = {};
+        std::vector<VkFormat> viewFormats;
+        if (GetViewFormats().any()) {
+            baseCreateInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
+            if (device->GetDeviceInfo().HasExt(DeviceExt::ImageFormatList)) {
+                createInfoChain.Add(&imageFormatListInfo,
+                                    VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO);
+                for (FormatIndex i : IterateBitSet(GetViewFormats())) {
+                    const Format& viewFormat = device->GetValidInternalFormat(i);
+                    viewFormats.push_back(VulkanImageFormat(device, viewFormat.format));
+                }
+
+                imageFormatListInfo.viewFormatCount = viewFormats.size();
+                imageFormatListInfo.pViewFormats = viewFormats.data();
+            }
+        }
+
         DAWN_TRY_ASSIGN(mHandle, externalMemoryService->CreateImage(descriptor, baseCreateInfo));
 
         SetLabelHelper("Dawn_ExternalTexture");
diff --git a/src/dawn/native/vulkan/UtilsVulkan.h b/src/dawn/native/vulkan/UtilsVulkan.h
index b13e03c..c18a5b6 100644
--- a/src/dawn/native/vulkan/UtilsVulkan.h
+++ b/src/dawn/native/vulkan/UtilsVulkan.h
@@ -59,7 +59,9 @@
         template <typename VK_STRUCT_TYPE>
         explicit PNextChainBuilder(VK_STRUCT_TYPE* head)
             : mCurrent(reinterpret_cast<VkBaseOutStructure*>(head)) {
-            ASSERT(head->pNext == nullptr);
+            while (mCurrent->pNext != nullptr) {
+                mCurrent = mCurrent->pNext;
+            }
         }
 
         // Add one item to the chain. |vk_struct| must be a Vulkan structure
diff --git a/src/dawn/native/vulkan/external_memory/MemoryServiceDmaBuf.cpp b/src/dawn/native/vulkan/external_memory/MemoryServiceDmaBuf.cpp
index c304a92..675e782 100644
--- a/src/dawn/native/vulkan/external_memory/MemoryServiceDmaBuf.cpp
+++ b/src/dawn/native/vulkan/external_memory/MemoryServiceDmaBuf.cpp
@@ -305,8 +305,8 @@
 
         VkImageCreateInfo createInfo = baseCreateInfo;
         createInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
-        createInfo.flags = 0;
         createInfo.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT;
+
         PNextChainBuilder createInfoChain(&createInfo);
 
         VkExternalMemoryImageCreateInfo externalMemoryImageCreateInfo = {};
diff --git a/src/dawn/native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp b/src/dawn/native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp
index 24a830b..ad54617 100644
--- a/src/dawn/native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp
+++ b/src/dawn/native/vulkan/external_memory/MemoryServiceOpaqueFD.cpp
@@ -17,6 +17,7 @@
 #include "dawn/native/vulkan/BackendVk.h"
 #include "dawn/native/vulkan/DeviceVk.h"
 #include "dawn/native/vulkan/TextureVk.h"
+#include "dawn/native/vulkan/UtilsVulkan.h"
 #include "dawn/native/vulkan/VulkanError.h"
 #include "dawn/native/vulkan/external_memory/MemoryService.h"
 
@@ -133,16 +134,19 @@
 
     ResultOrError<VkImage> Service::CreateImage(const ExternalImageDescriptor* descriptor,
                                                 const VkImageCreateInfo& baseCreateInfo) {
+        VkImageCreateInfo createInfo = baseCreateInfo;
+        createInfo.flags |= VK_IMAGE_CREATE_ALIAS_BIT_KHR;
+        createInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+        createInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
         VkExternalMemoryImageCreateInfo externalMemoryImageCreateInfo;
         externalMemoryImageCreateInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
         externalMemoryImageCreateInfo.pNext = nullptr;
         externalMemoryImageCreateInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
 
-        VkImageCreateInfo createInfo = baseCreateInfo;
-        createInfo.pNext = &externalMemoryImageCreateInfo;
-        createInfo.flags = VK_IMAGE_CREATE_ALIAS_BIT_KHR;
-        createInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
-        createInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+        PNextChainBuilder createInfoChain(&createInfo);
+        createInfoChain.Add(&externalMemoryImageCreateInfo,
+                            VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO);
 
         ASSERT(IsSampleCountSupported(mDevice, createInfo));
 
diff --git a/src/dawn/native/vulkan/external_memory/MemoryServiceZirconHandle.cpp b/src/dawn/native/vulkan/external_memory/MemoryServiceZirconHandle.cpp
index b61b0c5..96c04a7 100644
--- a/src/dawn/native/vulkan/external_memory/MemoryServiceZirconHandle.cpp
+++ b/src/dawn/native/vulkan/external_memory/MemoryServiceZirconHandle.cpp
@@ -17,6 +17,7 @@
 #include "dawn/native/vulkan/BackendVk.h"
 #include "dawn/native/vulkan/DeviceVk.h"
 #include "dawn/native/vulkan/TextureVk.h"
+#include "dawn/native/vulkan/UtilsVulkan.h"
 #include "dawn/native/vulkan/VulkanError.h"
 #include "dawn/native/vulkan/external_memory/MemoryService.h"
 
@@ -134,17 +135,20 @@
 
     ResultOrError<VkImage> Service::CreateImage(const ExternalImageDescriptor* descriptor,
                                                 const VkImageCreateInfo& baseCreateInfo) {
+        VkImageCreateInfo createInfo = baseCreateInfo;
+        createInfo.flags |= VK_IMAGE_CREATE_ALIAS_BIT_KHR;
+        createInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+        createInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
         VkExternalMemoryImageCreateInfo externalMemoryImageCreateInfo;
         externalMemoryImageCreateInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
         externalMemoryImageCreateInfo.pNext = nullptr;
         externalMemoryImageCreateInfo.handleTypes =
             VK_EXTERNAL_MEMORY_HANDLE_TYPE_ZIRCON_VMO_BIT_FUCHSIA;
 
-        VkImageCreateInfo createInfo = baseCreateInfo;
-        createInfo.pNext = &externalMemoryImageCreateInfo;
-        createInfo.flags = VK_IMAGE_CREATE_ALIAS_BIT_KHR;
-        createInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
-        createInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+        PNextChainBuilder createInfoChain(&createInfo);
+        createInfoChain.Add(&externalMemoryImageCreateInfo,
+                            VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO);
 
         ASSERT(IsSampleCountSupported(mDevice, createInfo));
 
diff --git a/src/dawn/tests/end2end/TextureViewTests.cpp b/src/dawn/tests/end2end/TextureViewTests.cpp
index 2dac51d..dc20399 100644
--- a/src/dawn/tests/end2end/TextureViewTests.cpp
+++ b/src/dawn/tests/end2end/TextureViewTests.cpp
@@ -438,6 +438,98 @@
     Texture2DArrayViewTest(6, 6, 2, 4);
 }
 
+// Test that an RGBA8 texture may be interpreted as RGBA8UnormSrgb
+// More extensive color value checks and format tests are left for the CTS.
+TEST_P(TextureViewSamplingTest, SRGBReinterpretation) {
+    // TODO(crbug.com/dawn/593): This test requires glTextureView, which is unsupported on GLES.
+    DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES());
+
+    wgpu::TextureViewDescriptor viewDesc = {};
+    viewDesc.format = wgpu::TextureFormat::RGBA8UnormSrgb;
+
+    wgpu::TextureDescriptor textureDesc = {};
+    textureDesc.size = {2, 2, 1};
+    textureDesc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding;
+    textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+    textureDesc.viewFormats = &viewDesc.format;
+    textureDesc.viewFormatCount = 1;
+    wgpu::Texture texture = device.CreateTexture(&textureDesc);
+
+    wgpu::ImageCopyTexture dst = {};
+    dst.texture = texture;
+    std::array<RGBA8, 4> rgbaTextureData = {
+        RGBA8(180, 0, 0, 255),
+        RGBA8(0, 84, 0, 127),
+        RGBA8(0, 0, 62, 100),
+        RGBA8(62, 180, 84, 90),
+    };
+
+    wgpu::TextureDataLayout dataLayout = {};
+    dataLayout.bytesPerRow = textureDesc.size.width * sizeof(RGBA8);
+
+    queue.WriteTexture(&dst, rgbaTextureData.data(), rgbaTextureData.size() * sizeof(RGBA8),
+                       &dataLayout, &textureDesc.size);
+
+    wgpu::TextureView textureView = texture.CreateView(&viewDesc);
+
+    utils::ComboRenderPipelineDescriptor pipelineDesc;
+    pipelineDesc.vertex.module = utils::CreateShaderModule(device, R"(
+        @stage(vertex)
+        fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
+            var pos = array<vec2<f32>, 6>(
+                                        vec2<f32>(-1.0, -1.0),
+                                        vec2<f32>(-1.0,  1.0),
+                                        vec2<f32>( 1.0, -1.0),
+                                        vec2<f32>(-1.0,  1.0),
+                                        vec2<f32>( 1.0, -1.0),
+                                        vec2<f32>( 1.0,  1.0));
+            return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+        }
+    )");
+    pipelineDesc.cFragment.module = utils::CreateShaderModule(device, R"(
+        @group(0) @binding(0) var texture : texture_2d<f32>;
+
+        @stage(fragment)
+        fn main(@builtin(position) coord: vec4<f32>) -> @location(0) vec4<f32> {
+            return textureLoad(texture, vec2<i32>(coord.xy), 0);
+        }
+    )");
+
+    utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(
+        device, textureDesc.size.width, textureDesc.size.height, wgpu::TextureFormat::RGBA8Unorm);
+    pipelineDesc.cTargets[0].format = renderPass.colorFormat;
+
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    {
+        wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDesc);
+
+        wgpu::BindGroup bindGroup =
+            utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, textureView}});
+
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
+        pass.SetPipeline(pipeline);
+        pass.SetBindGroup(0, bindGroup);
+        pass.Draw(6);
+        pass.End();
+    }
+
+    wgpu::CommandBuffer commands = encoder.Finish();
+    queue.Submit(1, &commands);
+
+    EXPECT_PIXEL_RGBA8_BETWEEN(  //
+        RGBA8(116, 0, 0, 255),   //
+        RGBA8(117, 0, 0, 255), renderPass.color, 0, 0);
+    EXPECT_PIXEL_RGBA8_BETWEEN(  //
+        RGBA8(0, 23, 0, 127),    //
+        RGBA8(0, 24, 0, 127), renderPass.color, 1, 0);
+    EXPECT_PIXEL_RGBA8_BETWEEN(  //
+        RGBA8(0, 0, 12, 100),    //
+        RGBA8(0, 0, 13, 100), renderPass.color, 0, 1);
+    EXPECT_PIXEL_RGBA8_BETWEEN(  //
+        RGBA8(12, 116, 23, 90),  //
+        RGBA8(13, 117, 24, 90), renderPass.color, 1, 1);
+}
+
 // Test sampling from a cube map texture view that covers a whole 2D array texture.
 TEST_P(TextureViewSamplingTest, TextureCubeMapOnWholeTexture) {
     constexpr uint32_t kTotalLayers = 6;
diff --git a/src/dawn/tests/unittests/validation/TextureViewValidationTests.cpp b/src/dawn/tests/unittests/validation/TextureViewValidationTests.cpp
index b109730..ea5ab30 100644
--- a/src/dawn/tests/unittests/validation/TextureViewValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/TextureViewValidationTests.cpp
@@ -14,6 +14,8 @@
 
 #include "dawn/tests/unittests/validation/ValidationTest.h"
 
+#include <array>
+
 namespace {
 
     class TextureViewValidationTest : public ValidationTest {};
@@ -667,18 +669,147 @@
     }
 
     // Test the format compatibility rules when creating a texture view.
-    // TODO(jiawei.shao@intel.com): add more tests when the rules are fully implemented.
     TEST_F(TextureViewValidationTest, TextureViewFormatCompatibility) {
-        wgpu::Texture texture = Create2DArrayTexture(device, 1);
+        wgpu::TextureDescriptor textureDesc = {};
+        textureDesc.size.width = 4;
+        textureDesc.size.height = 4;
+        textureDesc.usage = wgpu::TextureUsage::TextureBinding;
 
-        wgpu::TextureViewDescriptor base2DTextureViewDescriptor =
-            CreateDefaultViewDescriptor(wgpu::TextureViewDimension::e2D);
+        wgpu::TextureViewDescriptor viewDesc = {};
 
-        // It is an error to create a texture view in depth-stencil format on a RGBA texture.
+        // It is an error to create an sRGB texture view from an RGB texture, without viewFormats.
         {
-            wgpu::TextureViewDescriptor descriptor = base2DTextureViewDescriptor;
-            descriptor.format = wgpu::TextureFormat::Depth24PlusStencil8;
-            ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor));
+            textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+            viewDesc.format = wgpu::TextureFormat::RGBA8UnormSrgb;
+            wgpu::Texture texture = device.CreateTexture(&textureDesc);
+            ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
+        }
+
+        // It is an error to create an RGB texture view from an sRGB texture, without viewFormats.
+        {
+            textureDesc.format = wgpu::TextureFormat::BGRA8UnormSrgb;
+            viewDesc.format = wgpu::TextureFormat::BGRA8Unorm;
+            wgpu::Texture texture = device.CreateTexture(&textureDesc);
+            ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
+        }
+
+        // It is an error to create a texture view with a depth-stencil format of an RGBA texture.
+        {
+            textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+            viewDesc.format = wgpu::TextureFormat::Depth24PlusStencil8;
+            wgpu::Texture texture = device.CreateTexture(&textureDesc);
+            ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
+        }
+
+        // It is an error to create a texture view with a depth format of a depth-stencil texture.
+        {
+            textureDesc.format = wgpu::TextureFormat::Depth24PlusStencil8;
+            viewDesc.format = wgpu::TextureFormat::Depth24Plus;
+            wgpu::Texture texture = device.CreateTexture(&textureDesc);
+            ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
+        }
+
+        // It is valid to create a texture view with a depth format of a depth-stencil texture
+        // if the depth only aspect is selected.
+        {
+            textureDesc.format = wgpu::TextureFormat::Depth24PlusStencil8;
+            viewDesc.format = wgpu::TextureFormat::Depth24Plus;
+            viewDesc.aspect = wgpu::TextureAspect::DepthOnly;
+            wgpu::Texture texture = device.CreateTexture(&textureDesc);
+            texture.CreateView(&viewDesc);
+
+            viewDesc = {};
+        }
+
+        // Prep for testing a single view format in viewFormats.
+        wgpu::TextureFormat viewFormat;
+        textureDesc.viewFormats = &viewFormat;
+        textureDesc.viewFormatCount = 1;
+
+        // An aspect format is not a valid view format of a depth-stencil texture.
+        {
+            textureDesc.format = wgpu::TextureFormat::Depth24PlusStencil8;
+            viewFormat = wgpu::TextureFormat::Depth24Plus;
+            ASSERT_DEVICE_ERROR(device.CreateTexture(&textureDesc));
+        }
+
+        // Test that a RGBA texture can be viewed as both RGBA and RGBASrgb, but not BGRA or
+        // BGRASrgb
+        {
+            textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+            viewFormat = wgpu::TextureFormat::RGBA8UnormSrgb;
+            wgpu::Texture texture = device.CreateTexture(&textureDesc);
+
+            viewDesc.format = wgpu::TextureFormat::RGBA8UnormSrgb;
+            texture.CreateView(&viewDesc);
+
+            viewDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+            texture.CreateView(&viewDesc);
+
+            viewDesc.format = wgpu::TextureFormat::BGRA8Unorm;
+            ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
+
+            viewDesc.format = wgpu::TextureFormat::BGRA8UnormSrgb;
+            ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
+        }
+
+        // Test that a BGRASrgb texture can be viewed as both BGRA and BGRASrgb, but not RGBA or
+        // RGBASrgb
+        {
+            textureDesc.format = wgpu::TextureFormat::BGRA8UnormSrgb;
+            viewFormat = wgpu::TextureFormat::BGRA8Unorm;
+            wgpu::Texture texture = device.CreateTexture(&textureDesc);
+
+            viewDesc.format = wgpu::TextureFormat::BGRA8Unorm;
+            texture.CreateView(&viewDesc);
+
+            viewDesc.format = wgpu::TextureFormat::BGRA8UnormSrgb;
+            texture.CreateView(&viewDesc);
+
+            viewDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+            ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
+
+            viewDesc.format = wgpu::TextureFormat::RGBA8UnormSrgb;
+            ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
+        }
+
+        // Test an RGBA format may be viewed as RGBA (same)
+        {
+            textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+            viewFormat = wgpu::TextureFormat::RGBA8Unorm;
+            wgpu::Texture texture = device.CreateTexture(&textureDesc);
+
+            viewDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+            texture.CreateView(&viewDesc);
+
+            viewDesc.format = wgpu::TextureFormat::RGBA8UnormSrgb;
+            ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
+        }
+
+        // Test that duplicate, and multiple view formats are allowed.
+        {
+            std::array<wgpu::TextureFormat, 5> viewFormats = {
+                wgpu::TextureFormat::RGBA8UnormSrgb, wgpu::TextureFormat::RGBA8Unorm,
+                wgpu::TextureFormat::RGBA8Unorm,     wgpu::TextureFormat::RGBA8UnormSrgb,
+                wgpu::TextureFormat::RGBA8Unorm,
+            };
+            textureDesc.viewFormats = viewFormats.data();
+            textureDesc.viewFormatCount = viewFormats.size();
+
+            textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+            wgpu::Texture texture = device.CreateTexture(&textureDesc);
+
+            viewDesc.format = wgpu::TextureFormat::RGBA8UnormSrgb;
+            texture.CreateView(&viewDesc);
+
+            viewDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+            texture.CreateView(&viewDesc);
+
+            viewDesc.format = wgpu::TextureFormat::BGRA8Unorm;
+            ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
+
+            viewDesc.format = wgpu::TextureFormat::BGRA8UnormSrgb;
+            ASSERT_DEVICE_ERROR(texture.CreateView(&viewDesc));
         }
     }
 
diff --git a/src/dawn/tests/white_box/VulkanImageWrappingTests.cpp b/src/dawn/tests/white_box/VulkanImageWrappingTests.cpp
index d3a27dd..7a087e3 100644
--- a/src/dawn/tests/white_box/VulkanImageWrappingTests.cpp
+++ b/src/dawn/tests/white_box/VulkanImageWrappingTests.cpp
@@ -18,6 +18,7 @@
 #include "dawn/native/vulkan/AdapterVk.h"
 #include "dawn/native/vulkan/DeviceVk.h"
 #include "dawn/tests/DawnTest.h"
+#include "dawn/utils/ComboRenderPipelineDescriptor.h"
 #include "dawn/utils/WGPUHelpers.h"
 
 namespace dawn::native { namespace vulkan {
@@ -780,6 +781,106 @@
         IgnoreSignalSemaphore(nextWrappedTexture);
     }
 
+    // Test that texture descriptor view formats are passed to the backend for wrapped external
+    // textures, and that contents may be reinterpreted as sRGB.
+    TEST_P(VulkanImageWrappingUsageTests, SRGBReinterpretation) {
+        wgpu::TextureViewDescriptor viewDesc = {};
+        viewDesc.format = wgpu::TextureFormat::RGBA8UnormSrgb;
+
+        wgpu::TextureDescriptor textureDesc = {};
+        textureDesc.size = {2, 2, 1};
+        textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+        textureDesc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding;
+        textureDesc.viewFormatCount = 1;
+        textureDesc.viewFormats = &viewDesc.format;
+
+        std::unique_ptr<ExternalTexture> backendTexture = mBackend->CreateTexture(
+            textureDesc.size.width, textureDesc.size.height, textureDesc.format, textureDesc.usage);
+
+        // Import the image on |device|
+        wgpu::Texture texture =
+            WrapVulkanImage(device, &textureDesc, backendTexture.get(), {},
+                            VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+        ASSERT_NE(texture.Get(), nullptr);
+
+        wgpu::ImageCopyTexture dst = {};
+        dst.texture = texture;
+        std::array<RGBA8, 4> rgbaTextureData = {
+            RGBA8(180, 0, 0, 255),
+            RGBA8(0, 84, 0, 127),
+            RGBA8(0, 0, 62, 100),
+            RGBA8(62, 180, 84, 90),
+        };
+
+        wgpu::TextureDataLayout dataLayout = {};
+        dataLayout.bytesPerRow = textureDesc.size.width * sizeof(RGBA8);
+
+        queue.WriteTexture(&dst, rgbaTextureData.data(), rgbaTextureData.size() * sizeof(RGBA8),
+                           &dataLayout, &textureDesc.size);
+
+        wgpu::TextureView textureView = texture.CreateView(&viewDesc);
+
+        utils::ComboRenderPipelineDescriptor pipelineDesc;
+        pipelineDesc.vertex.module = utils::CreateShaderModule(device, R"(
+            @stage(vertex)
+            fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
+                var pos = array<vec2<f32>, 6>(
+                                            vec2<f32>(-1.0, -1.0),
+                                            vec2<f32>(-1.0,  1.0),
+                                            vec2<f32>( 1.0, -1.0),
+                                            vec2<f32>(-1.0,  1.0),
+                                            vec2<f32>( 1.0, -1.0),
+                                            vec2<f32>( 1.0,  1.0));
+                return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+            }
+        )");
+        pipelineDesc.cFragment.module = utils::CreateShaderModule(device, R"(
+            @group(0) @binding(0) var texture : texture_2d<f32>;
+
+            @stage(fragment)
+            fn main(@builtin(position) coord: vec4<f32>) -> @location(0) vec4<f32> {
+                return textureLoad(texture, vec2<i32>(coord.xy), 0);
+            }
+        )");
+
+        utils::BasicRenderPass renderPass =
+            utils::CreateBasicRenderPass(device, textureDesc.size.width, textureDesc.size.height,
+                                         wgpu::TextureFormat::RGBA8Unorm);
+        pipelineDesc.cTargets[0].format = renderPass.colorFormat;
+
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        {
+            wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDesc);
+
+            wgpu::BindGroup bindGroup =
+                utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, textureView}});
+
+            wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
+            pass.SetPipeline(pipeline);
+            pass.SetBindGroup(0, bindGroup);
+            pass.Draw(6);
+            pass.End();
+        }
+
+        wgpu::CommandBuffer commands = encoder.Finish();
+        queue.Submit(1, &commands);
+
+        EXPECT_PIXEL_RGBA8_BETWEEN(  //
+            RGBA8(116, 0, 0, 255),   //
+            RGBA8(117, 0, 0, 255), renderPass.color, 0, 0);
+        EXPECT_PIXEL_RGBA8_BETWEEN(  //
+            RGBA8(0, 23, 0, 127),    //
+            RGBA8(0, 24, 0, 127), renderPass.color, 1, 0);
+        EXPECT_PIXEL_RGBA8_BETWEEN(  //
+            RGBA8(0, 0, 12, 100),    //
+            RGBA8(0, 0, 13, 100), renderPass.color, 0, 1);
+        EXPECT_PIXEL_RGBA8_BETWEEN(  //
+            RGBA8(12, 116, 23, 90),  //
+            RGBA8(13, 117, 24, 90), renderPass.color, 1, 1);
+
+        IgnoreSignalSemaphore(texture);
+    }
+
     DAWN_INSTANTIATE_TEST(VulkanImageWrappingValidationTests, VulkanBackend());
     DAWN_INSTANTIATE_TEST(VulkanImageWrappingUsageTests, VulkanBackend());