// Copyright 2018 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 "src/dawn/native/vulkan/TextureVk.h"

#include <iostream>
#include <utility>

#include "dawn/native/VulkanBackend.h"
#include "src/dawn/common/Assert.h"
#include "src/dawn/common/DynamicLib.h"
#include "src/dawn/common/Math.h"
#include "src/dawn/native/ChainUtils.h"
#include "src/dawn/native/DynamicUploader.h"
#include "src/dawn/native/EnumMaskIterator.h"
#include "src/dawn/native/Error.h"
#include "src/dawn/native/utils/RenderDoc.h"
#include "src/dawn/native/vulkan/BufferVk.h"
#include "src/dawn/native/vulkan/CommandBufferVk.h"
#include "src/dawn/native/vulkan/CommandRecordingContextVk.h"
#include "src/dawn/native/vulkan/DeviceVk.h"
#include "src/dawn/native/vulkan/FencedDeleter.h"
#include "src/dawn/native/vulkan/PhysicalDeviceVk.h"
#include "src/dawn/native/vulkan/QueueVk.h"
#include "src/dawn/native/vulkan/ResourceHeapVk.h"
#include "src/dawn/native/vulkan/ResourceMemoryAllocatorVk.h"
#include "src/dawn/native/vulkan/SamplerVk.h"
#include "src/dawn/native/vulkan/SharedFenceVk.h"
#include "src/dawn/native/vulkan/UtilsVulkan.h"
#include "src/dawn/native/vulkan/VulkanError.h"
#include "src/utils/compiler.h"

namespace dawn::native::vulkan {

namespace {
// Converts an Dawn texture dimension to a Vulkan image view type.
// Contrary to image types, image view types include arrayness and cubemapness
VkImageViewType VulkanImageViewType(wgpu::TextureViewDimension dimension) {
    switch (dimension) {
        case wgpu::TextureViewDimension::e1D:
            return VK_IMAGE_VIEW_TYPE_1D;
        case wgpu::TextureViewDimension::e2D:
            return VK_IMAGE_VIEW_TYPE_2D;
        case wgpu::TextureViewDimension::e2DArray:
            return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
        case wgpu::TextureViewDimension::Cube:
            return VK_IMAGE_VIEW_TYPE_CUBE;
        case wgpu::TextureViewDimension::CubeArray:
            return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
        case wgpu::TextureViewDimension::e3D:
            return VK_IMAGE_VIEW_TYPE_3D;

        case wgpu::TextureViewDimension::Undefined:
            break;
    }
    DAWN_UNREACHABLE();
}

// Reserved texture usages to represent mixed read-only/writable depth-stencil texture usages
// when combining the planes of depth-stencil textures. They can be combined with other in-pass
// readonly usages like wgpu::TextureUsage::TextureBinding.
// TODO(dawn:2172): Consider making a bespoke enum instead of hackily extending TextureUsage.
constexpr wgpu::TextureUsage kDepthReadOnlyStencilWritableAttachment =
    kReservedTextureUsage | static_cast<wgpu::TextureUsage>(1 << 30);
constexpr wgpu::TextureUsage kDepthWritableStencilReadOnlyAttachment =
    kReservedTextureUsage | static_cast<wgpu::TextureUsage>(1 << 29);

// Merge two usages for depth and stencil into a single combined usage that uses the reserved
// texture usages above. This is used to handle combining Aspect::Depth and Aspect::Stencil into a
// single Aspect::CombinedDepthStencil.
wgpu::TextureUsage MergeDepthStencilUsage(wgpu::TextureUsage depth, wgpu::TextureUsage stencil) {
    // Aspects that are RenderAttachment cannot be anything else at the same time. This lets us
    // check if we are in one of the RenderAttachment + (ReadOnlyAttachment|readonly usage) cases
    // and know only the aspect with the readonly attachment might contain extra usages like
    // TextureBinding.
    DAWN_ASSERT(depth == wgpu::TextureUsage::RenderAttachment ||
                IsSubset(depth, ~wgpu::TextureUsage::RenderAttachment));
    DAWN_ASSERT(stencil == wgpu::TextureUsage::RenderAttachment ||
                IsSubset(stencil, ~wgpu::TextureUsage::RenderAttachment));

    if (depth == wgpu::TextureUsage::RenderAttachment && stencil & kReadOnlyRenderAttachment) {
        return kDepthWritableStencilReadOnlyAttachment | (stencil & ~kReadOnlyRenderAttachment);
    } else if (depth & kReadOnlyRenderAttachment &&
               stencil == wgpu::TextureUsage::RenderAttachment) {
        return kDepthReadOnlyStencilWritableAttachment | (depth & ~kReadOnlyRenderAttachment);
    } else {
        // Not one of the reserved usage special cases, we can just combine the aspect's usage the
        // simple way!
        return depth | stencil;
    }
}

// Computes which vulkan access type could be required for the given Dawn usage.
// TODO(crbug.com/dawn/269): We shouldn't need any access usages for srcAccessMask when
// the previous usage is readonly because an execution dependency is sufficient.
VkAccessFlags VulkanAccessFlags(wgpu::TextureUsage usage, const Format& format) {
    if (usage & kReservedTextureUsage) {
        // Handle the special readonly usages for mixed depth-stencil.
        DAWN_ASSERT(IsSubset(kDepthReadOnlyStencilWritableAttachment, usage) ||
                    IsSubset(kDepthWritableStencilReadOnlyAttachment, usage));

        // Add any additional access flags for the non-attachment part of the usage.
        const wgpu::TextureUsage nonAttachmentUsages =
            usage &
            ~(kDepthReadOnlyStencilWritableAttachment | kDepthWritableStencilReadOnlyAttachment);
        return VulkanAccessFlags(nonAttachmentUsages, format) |
               VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
               VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
    }

    VkAccessFlags flags = 0;
    if (usage & wgpu::TextureUsage::CopySrc) {
        flags |= VK_ACCESS_TRANSFER_READ_BIT;
    }
    if (usage & wgpu::TextureUsage::CopyDst) {
        flags |= VK_ACCESS_TRANSFER_WRITE_BIT;
    }
    if (usage & (wgpu::TextureUsage::TextureBinding | kReadOnlyStorageTexture)) {
        flags |= VK_ACCESS_SHADER_READ_BIT;
    }
    if (usage & kWriteOnlyStorageTexture) {
        flags |= VK_ACCESS_SHADER_WRITE_BIT;
    }
    if (usage & wgpu::TextureUsage::StorageBinding) {
        flags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
    }
    if (usage & wgpu::TextureUsage::RenderAttachment) {
        if (format.HasDepthOrStencil()) {
            flags |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
                     VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
        } else {
            flags |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
        }
    }
    if (usage & kReadOnlyRenderAttachment) {
        flags |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
    }

    if (usage & kResolveAttachmentLoadingUsage) {
        // - The texture will be used as input attachment in the first subpass and loaded with
        // VK_ATTACHMENT_LOAD_OP_LOAD. This requires VK_ACCESS_COLOR_ATTACHMENT_READ_BIT access.
        // - It will also be read as subpass input in fragment shader. This requires
        // VK_ACCESS_INPUT_ATTACHMENT_READ_BIT.
        flags |= VK_ACCESS_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
    }

    if (usage & kPresentAcquireTextureUsage) {
        // The present acquire usage is only used internally by the swapchain and is never used in
        // combination with other usages.
        DAWN_ASSERT(usage == kPresentAcquireTextureUsage);
        // The Vulkan spec has the following note:
        //
        //   When the presentable image will be accessed by some stage S, the recommended idiom
        //   for ensuring correct synchronization is:
        //
        //   The VkSubmitInfo used to submit the image layout transition for execution includes
        //   vkAcquireNextImageKHR::semaphore in its pWaitSemaphores member, with the
        //   corresponding element of pWaitDstStageMask including S.
        //
        //   The synchronization command that performs any necessary image layout transition
        //   includes S in both the srcStageMask and dstStageMask.
        //
        // There is no mention of an access flag because there is no access flag associated with
        // the presentation engine, so we leave it to 0.
        flags |= 0;
    }
    if (usage & kPresentReleaseTextureUsage) {
        // The present release usage is only used internally by the swapchain and is never used in
        // combination with other usages.
        DAWN_ASSERT(usage == kPresentReleaseTextureUsage);
        // The Vulkan spec has the following note:
        //
        //   When transitioning the image to VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR or
        //   VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, there is no need to delay subsequent
        //   processing, or perform any visibility operations (as vkQueuePresentKHR performs
        //   automatic visibility operations). To achieve this, the dstAccessMask member of
        //   the VkImageMemoryBarrier should be set to 0, and the dstStageMask parameter
        //   should be set to VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT.
        //
        // So on the transition to Present we don't need an access flag. The other
        // direction doesn't matter because swapchain textures always start a new frame
        // as uninitialized.
        flags |= 0;
    }

    return flags;
}

// Computes which Vulkan pipeline stage can access a texture in the given Dawn usage
VkPipelineStageFlags VulkanPipelineStage(wgpu::TextureUsage usage,
                                         wgpu::ShaderStage shaderStage,
                                         const Format& format) {
    if (usage & kReservedTextureUsage) {
        // Handle the special readonly usages for mixed depth-stencil.
        DAWN_ASSERT(IsSubset(kDepthReadOnlyStencilWritableAttachment, usage) ||
                    IsSubset(kDepthWritableStencilReadOnlyAttachment, usage));

        // Convert all the reserved attachment usages into just RenderAttachment.
        const wgpu::TextureUsage nonAttachmentUsages =
            usage &
            ~(kDepthReadOnlyStencilWritableAttachment | kDepthWritableStencilReadOnlyAttachment);
        return VulkanPipelineStage(nonAttachmentUsages | wgpu::TextureUsage::RenderAttachment,
                                   shaderStage, format);
    }

    VkPipelineStageFlags flags = 0;

    if (usage == wgpu::TextureUsage::None) {
        // This only happens when a texture is initially created (and for srcAccessMask) in
        // which case there is no need to wait on anything to stop accessing this texture.
        return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
    }
    if (usage & (wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst)) {
        flags |= VK_PIPELINE_STAGE_TRANSFER_BIT;
    }
    if (usage & kShaderTextureUsages) {
        if (shaderStage & wgpu::ShaderStage::Vertex) {
            flags |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
        }
        if (shaderStage & wgpu::ShaderStage::Fragment) {
            flags |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
        }
        if (shaderStage & wgpu::ShaderStage::Compute) {
            flags |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
        }
    }
    if (usage & kResolveAttachmentLoadingUsage) {
        // - The texture will be used as input attachment in the first subpass and loaded with
        // VK_ATTACHMENT_LOAD_OP_LOAD. This happens at VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
        // stage.
        // - It will also be read as subpass input in fragment shader.
        flags |=
            VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    }
    if (usage & (wgpu::TextureUsage::RenderAttachment | kReadOnlyRenderAttachment)) {
        if (format.HasDepthOrStencil()) {
            flags |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
                     VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
        } else {
            flags |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
        }
    }

    if (usage & kPresentAcquireTextureUsage) {
        // The present acquire usage is only used internally by the swapchain and is never used in
        // combination with other usages.
        DAWN_ASSERT(usage == kPresentAcquireTextureUsage);
        // The vkAcquireNextImageKHR method is a read operation in Vulkan which completes
        // before the semaphore/fence out parameters are signaled. This means that future uses
        // of the texture must performs a memory barriers that synchronizes with that
        // semaphore/barrier. Dawn uses the ALL_COMMANDS_BIT stage for the semaphore, however
        // such a semaphore doesn't synchronize with a subsequent BOTTOM_OF_PIPE or NONE
        // srcStage vkPipelineBarrier because there are no common stages. Instead we also use
        // VK_PIPELINE_STAGE_ALL_COMMANDS_BIT for srcStage for presentable images, ensuring
        // correct ordering. This explains the idiom noted in the Vulkan spec:
        //
        //   When the presentable image will be accessed by some stage S, the recommended idiom
        //   for ensuring correct synchronization is:
        //
        //   The VkSubmitInfo used to submit the image layout transition for execution includes
        //   vkAcquireNextImageKHR::semaphore in its pWaitSemaphores member, with the
        //   corresponding element of pWaitDstStageMask including S.
        //
        //   The synchronization command that performs any necessary image layout transition
        //   includes S in both the srcStageMask and dstStageMask.
        flags |= VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
    }
    if (usage & kPresentReleaseTextureUsage) {
        // The present release usage is only used internally by the swapchain and is never used in
        // combination with other usages.
        DAWN_ASSERT(usage == kPresentReleaseTextureUsage);
        // The Vulkan spec has the following note:
        //
        //   When transitioning the image to VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR or
        //   VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, there is no need to delay subsequent
        //   processing, or perform any visibility operations (as vkQueuePresentKHR performs
        //   automatic visibility operations). To achieve this, the dstAccessMask member of
        //   the VkImageMemoryBarrier should be set to 0, and the dstStageMask parameter
        //   should be set to VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT.
        //
        // So on the transition to Present we use the "bottom of pipe" stage. The other
        // direction doesn't matter because swapchain textures always start a new frame
        // as uninitialized.
        flags |= VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
    }

    // A zero value isn't a valid pipeline stage mask
    DAWN_ASSERT(flags != 0);
    return flags;
}

VkImageMemoryBarrier BuildMemoryBarrier(const Texture* texture,
                                        wgpu::TextureUsage lastUsage,
                                        wgpu::TextureUsage usage,
                                        const SubresourceRange& range) {
    const Format& format = texture->GetFormat();

    VkImageMemoryBarrier barrier;
    barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    barrier.pNext = nullptr;
    barrier.srcAccessMask = VulkanAccessFlags(lastUsage, format);
    barrier.dstAccessMask = VulkanAccessFlags(usage, format);
    barrier.oldLayout = texture->VulkanImageLayout(lastUsage);
    barrier.newLayout = texture->VulkanImageLayout(usage);
    barrier.image = texture->GetHandle();
    barrier.subresourceRange.aspectMask = VulkanAspectMask(range.aspects);
    barrier.subresourceRange.baseMipLevel = range.baseMipLevel;
    barrier.subresourceRange.levelCount = range.levelCount;
    barrier.subresourceRange.baseArrayLayer = range.baseArrayLayer;
    barrier.subresourceRange.layerCount = range.layerCount;

    barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    return barrier;
}

void FillVulkanCreateInfoSizesAndType(const Texture& texture, VkImageCreateInfo* info) {
    const Extent3D& size = texture.GetBaseSize();

    info->mipLevels = texture.GetNumMipLevels();
    info->samples = VulkanSampleCount(texture.GetSampleCount());

    // Fill in the image type, and paper over differences in how the array layer count is
    // specified between WebGPU and Vulkan.
    switch (texture.GetDimension()) {
        case wgpu::TextureDimension::Undefined:
            DAWN_UNREACHABLE();

        case wgpu::TextureDimension::e1D:
            info->imageType = VK_IMAGE_TYPE_1D;
            info->extent = {size.width, 1, 1};
            info->arrayLayers = 1;
            break;

        case wgpu::TextureDimension::e2D:
            info->imageType = VK_IMAGE_TYPE_2D;
            info->extent = {size.width, size.height, 1};
            info->arrayLayers = size.depthOrArrayLayers;
            break;

        case wgpu::TextureDimension::e3D:
            info->imageType = VK_IMAGE_TYPE_3D;
            info->extent = {size.width, size.height, size.depthOrArrayLayers};
            info->arrayLayers = 1;
            break;
    }
}

Aspect ComputeCombinedAspect(Device* device, const Format& format) {
    // In early Vulkan versions it is not possible to transition depth and stencil separately so
    // textures with Depth|Stencil will be promoted to a single CombinedDepthStencil aspect
    // internally.
    if (format.aspects == (Aspect::Depth | Aspect::Stencil)) {
        return Aspect::CombinedDepthStencil;
    }
    // Same thing for Stencil8 if it is emulated with a depth-stencil format and not directly S8.
    if (format.format == wgpu::TextureFormat::Stencil8 &&
        !device->IsToggleEnabled(Toggle::VulkanUseS8)) {
        return Aspect::CombinedDepthStencil;
    }

    // Some multiplanar images cannot have planes transitioned separately and instead Vulkan
    // requires that the "Color" aspect be used for barriers, so Plane0|Plane1|Plane2 is promoted to
    // just Color. The Vulkan spec requires: "If image has a single-plane color format or is not
    // disjoint, then the aspectMask member of subresourceRange must be VK_IMAGE_ASPECT_COLOR_BIT.".
    if (format.IsMultiPlanar()) {
        return Aspect::Color;
    }

    // No need to combine aspects.
    return Aspect::None;
}

VkComponentSwizzle VulkanComponentSwizzle(wgpu::ComponentSwizzle swizzle) {
    switch (swizzle) {
        case wgpu::ComponentSwizzle::Zero:
            return VK_COMPONENT_SWIZZLE_ZERO;
        case wgpu::ComponentSwizzle::One:
            return VK_COMPONENT_SWIZZLE_ONE;
        case wgpu::ComponentSwizzle::R:
            return VK_COMPONENT_SWIZZLE_R;
        case wgpu::ComponentSwizzle::G:
            return VK_COMPONENT_SWIZZLE_G;
        case wgpu::ComponentSwizzle::B:
            return VK_COMPONENT_SWIZZLE_B;
        case wgpu::ComponentSwizzle::A:
            return VK_COMPONENT_SWIZZLE_A;

        case wgpu::ComponentSwizzle::Undefined:
            DAWN_UNREACHABLE();
    }
}

void MaybeConvertDepthStencilSwizzleOneToAlpha(bool isDepthOrStencilFormat,
                                               const VulkanDeviceInfo& deviceInfo,
                                               wgpu::TextureComponentSwizzle* swizzle) {
    // Exit early if the format isn't depth or stencil.
    if (!isDepthOrStencilFormat) {
        return;
    }

    // Exit early if the device supports VK_COMPONENT_SWIZZLE_ONE for depth/stencil formats.
    // This is enabled by the VK_KHR_maintenance5 extension.
    // https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#textures-component-swizzle
    if (deviceInfo.HasExt(DeviceExt::Maintenance5) &&
        deviceInfo.propertiesMaintenance5.depthStencilSwizzleOneSupport == VK_TRUE) {
        return;
    }

    for (auto* componentPtr : {&swizzle->r, &swizzle->g, &swizzle->b, &swizzle->a}) {
        if (*componentPtr == wgpu::ComponentSwizzle::One) {
            // Convert 'One' to 'Alpha' (which typically samples as 1.0)
            // if the underlying Vulkan implementation doesn't support 'One' directly
            // for depth/stencil formats.
            *componentPtr = wgpu::ComponentSwizzle::A;
        }
    }
}

}  // namespace

#define SIMPLE_FORMAT_MAPPING(X)                                                      \
    X(wgpu::TextureFormat::R8Unorm, VK_FORMAT_R8_UNORM)                               \
    X(wgpu::TextureFormat::R8Snorm, VK_FORMAT_R8_SNORM)                               \
    X(wgpu::TextureFormat::R8Uint, VK_FORMAT_R8_UINT)                                 \
    X(wgpu::TextureFormat::R8Sint, VK_FORMAT_R8_SINT)                                 \
                                                                                      \
    X(wgpu::TextureFormat::R16Unorm, VK_FORMAT_R16_UNORM)                             \
    X(wgpu::TextureFormat::R16Snorm, VK_FORMAT_R16_SNORM)                             \
    X(wgpu::TextureFormat::R16Uint, VK_FORMAT_R16_UINT)                               \
    X(wgpu::TextureFormat::R16Sint, VK_FORMAT_R16_SINT)                               \
    X(wgpu::TextureFormat::R16Float, VK_FORMAT_R16_SFLOAT)                            \
    X(wgpu::TextureFormat::RG8Unorm, VK_FORMAT_R8G8_UNORM)                            \
    X(wgpu::TextureFormat::RG8Snorm, VK_FORMAT_R8G8_SNORM)                            \
    X(wgpu::TextureFormat::RG8Uint, VK_FORMAT_R8G8_UINT)                              \
    X(wgpu::TextureFormat::RG8Sint, VK_FORMAT_R8G8_SINT)                              \
                                                                                      \
    X(wgpu::TextureFormat::R32Uint, VK_FORMAT_R32_UINT)                               \
    X(wgpu::TextureFormat::R32Sint, VK_FORMAT_R32_SINT)                               \
    X(wgpu::TextureFormat::R32Float, VK_FORMAT_R32_SFLOAT)                            \
    X(wgpu::TextureFormat::RG16Unorm, VK_FORMAT_R16G16_UNORM)                         \
    X(wgpu::TextureFormat::RG16Snorm, VK_FORMAT_R16G16_SNORM)                         \
    X(wgpu::TextureFormat::RG16Uint, VK_FORMAT_R16G16_UINT)                           \
    X(wgpu::TextureFormat::RG16Sint, VK_FORMAT_R16G16_SINT)                           \
    X(wgpu::TextureFormat::RG16Float, VK_FORMAT_R16G16_SFLOAT)                        \
    X(wgpu::TextureFormat::RGBA8Unorm, VK_FORMAT_R8G8B8A8_UNORM)                      \
    X(wgpu::TextureFormat::RGBA8UnormSrgb, VK_FORMAT_R8G8B8A8_SRGB)                   \
    X(wgpu::TextureFormat::RGBA8Snorm, VK_FORMAT_R8G8B8A8_SNORM)                      \
    X(wgpu::TextureFormat::RGBA8Uint, VK_FORMAT_R8G8B8A8_UINT)                        \
    X(wgpu::TextureFormat::RGBA8Sint, VK_FORMAT_R8G8B8A8_SINT)                        \
    X(wgpu::TextureFormat::BGRA8Unorm, VK_FORMAT_B8G8R8A8_UNORM)                      \
    X(wgpu::TextureFormat::BGRA8UnormSrgb, VK_FORMAT_B8G8R8A8_SRGB)                   \
    X(wgpu::TextureFormat::RGB10A2Uint, VK_FORMAT_A2B10G10R10_UINT_PACK32)            \
    X(wgpu::TextureFormat::RGB10A2Unorm, VK_FORMAT_A2B10G10R10_UNORM_PACK32)          \
    X(wgpu::TextureFormat::RG11B10Ufloat, VK_FORMAT_B10G11R11_UFLOAT_PACK32)          \
    X(wgpu::TextureFormat::RGB9E5Ufloat, VK_FORMAT_E5B9G9R9_UFLOAT_PACK32)            \
                                                                                      \
    X(wgpu::TextureFormat::RG32Uint, VK_FORMAT_R32G32_UINT)                           \
    X(wgpu::TextureFormat::RG32Sint, VK_FORMAT_R32G32_SINT)                           \
    X(wgpu::TextureFormat::RG32Float, VK_FORMAT_R32G32_SFLOAT)                        \
    X(wgpu::TextureFormat::RGBA16Unorm, VK_FORMAT_R16G16B16A16_UNORM)                 \
    X(wgpu::TextureFormat::RGBA16Snorm, VK_FORMAT_R16G16B16A16_SNORM)                 \
    X(wgpu::TextureFormat::RGBA16Uint, VK_FORMAT_R16G16B16A16_UINT)                   \
    X(wgpu::TextureFormat::RGBA16Sint, VK_FORMAT_R16G16B16A16_SINT)                   \
    X(wgpu::TextureFormat::RGBA16Float, VK_FORMAT_R16G16B16A16_SFLOAT)                \
                                                                                      \
    X(wgpu::TextureFormat::RGBA32Uint, VK_FORMAT_R32G32B32A32_UINT)                   \
    X(wgpu::TextureFormat::RGBA32Sint, VK_FORMAT_R32G32B32A32_SINT)                   \
    X(wgpu::TextureFormat::RGBA32Float, VK_FORMAT_R32G32B32A32_SFLOAT)                \
                                                                                      \
    X(wgpu::TextureFormat::Depth16Unorm, VK_FORMAT_D16_UNORM)                         \
    X(wgpu::TextureFormat::Depth32Float, VK_FORMAT_D32_SFLOAT)                        \
    X(wgpu::TextureFormat::Depth32FloatStencil8, VK_FORMAT_D32_SFLOAT_S8_UINT)        \
                                                                                      \
    X(wgpu::TextureFormat::BC1RGBAUnorm, VK_FORMAT_BC1_RGBA_UNORM_BLOCK)              \
    X(wgpu::TextureFormat::BC1RGBAUnormSrgb, VK_FORMAT_BC1_RGBA_SRGB_BLOCK)           \
    X(wgpu::TextureFormat::BC2RGBAUnorm, VK_FORMAT_BC2_UNORM_BLOCK)                   \
    X(wgpu::TextureFormat::BC2RGBAUnormSrgb, VK_FORMAT_BC2_SRGB_BLOCK)                \
    X(wgpu::TextureFormat::BC3RGBAUnorm, VK_FORMAT_BC3_UNORM_BLOCK)                   \
    X(wgpu::TextureFormat::BC3RGBAUnormSrgb, VK_FORMAT_BC3_SRGB_BLOCK)                \
    X(wgpu::TextureFormat::BC4RSnorm, VK_FORMAT_BC4_SNORM_BLOCK)                      \
    X(wgpu::TextureFormat::BC4RUnorm, VK_FORMAT_BC4_UNORM_BLOCK)                      \
    X(wgpu::TextureFormat::BC5RGSnorm, VK_FORMAT_BC5_SNORM_BLOCK)                     \
    X(wgpu::TextureFormat::BC5RGUnorm, VK_FORMAT_BC5_UNORM_BLOCK)                     \
    X(wgpu::TextureFormat::BC6HRGBFloat, VK_FORMAT_BC6H_SFLOAT_BLOCK)                 \
    X(wgpu::TextureFormat::BC6HRGBUfloat, VK_FORMAT_BC6H_UFLOAT_BLOCK)                \
    X(wgpu::TextureFormat::BC7RGBAUnorm, VK_FORMAT_BC7_UNORM_BLOCK)                   \
    X(wgpu::TextureFormat::BC7RGBAUnormSrgb, VK_FORMAT_BC7_SRGB_BLOCK)                \
                                                                                      \
    X(wgpu::TextureFormat::ETC2RGB8Unorm, VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK)          \
    X(wgpu::TextureFormat::ETC2RGB8UnormSrgb, VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK)       \
    X(wgpu::TextureFormat::ETC2RGB8A1Unorm, VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK)      \
    X(wgpu::TextureFormat::ETC2RGB8A1UnormSrgb, VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK)   \
    X(wgpu::TextureFormat::ETC2RGBA8Unorm, VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK)       \
    X(wgpu::TextureFormat::ETC2RGBA8UnormSrgb, VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK)    \
    X(wgpu::TextureFormat::EACR11Unorm, VK_FORMAT_EAC_R11_UNORM_BLOCK)                \
    X(wgpu::TextureFormat::EACR11Snorm, VK_FORMAT_EAC_R11_SNORM_BLOCK)                \
    X(wgpu::TextureFormat::EACRG11Unorm, VK_FORMAT_EAC_R11G11_UNORM_BLOCK)            \
    X(wgpu::TextureFormat::EACRG11Snorm, VK_FORMAT_EAC_R11G11_SNORM_BLOCK)            \
                                                                                      \
    X(wgpu::TextureFormat::ASTC4x4Unorm, VK_FORMAT_ASTC_4x4_UNORM_BLOCK)              \
    X(wgpu::TextureFormat::ASTC4x4UnormSrgb, VK_FORMAT_ASTC_4x4_SRGB_BLOCK)           \
    X(wgpu::TextureFormat::ASTC5x4Unorm, VK_FORMAT_ASTC_5x4_UNORM_BLOCK)              \
    X(wgpu::TextureFormat::ASTC5x4UnormSrgb, VK_FORMAT_ASTC_5x4_SRGB_BLOCK)           \
    X(wgpu::TextureFormat::ASTC5x5Unorm, VK_FORMAT_ASTC_5x5_UNORM_BLOCK)              \
    X(wgpu::TextureFormat::ASTC5x5UnormSrgb, VK_FORMAT_ASTC_5x5_SRGB_BLOCK)           \
    X(wgpu::TextureFormat::ASTC6x5Unorm, VK_FORMAT_ASTC_6x5_UNORM_BLOCK)              \
    X(wgpu::TextureFormat::ASTC6x5UnormSrgb, VK_FORMAT_ASTC_6x5_SRGB_BLOCK)           \
    X(wgpu::TextureFormat::ASTC6x6Unorm, VK_FORMAT_ASTC_6x6_UNORM_BLOCK)              \
    X(wgpu::TextureFormat::ASTC6x6UnormSrgb, VK_FORMAT_ASTC_6x6_SRGB_BLOCK)           \
    X(wgpu::TextureFormat::ASTC8x5Unorm, VK_FORMAT_ASTC_8x5_UNORM_BLOCK)              \
    X(wgpu::TextureFormat::ASTC8x5UnormSrgb, VK_FORMAT_ASTC_8x5_SRGB_BLOCK)           \
    X(wgpu::TextureFormat::ASTC8x6Unorm, VK_FORMAT_ASTC_8x6_UNORM_BLOCK)              \
    X(wgpu::TextureFormat::ASTC8x6UnormSrgb, VK_FORMAT_ASTC_8x6_SRGB_BLOCK)           \
    X(wgpu::TextureFormat::ASTC8x8Unorm, VK_FORMAT_ASTC_8x8_UNORM_BLOCK)              \
    X(wgpu::TextureFormat::ASTC8x8UnormSrgb, VK_FORMAT_ASTC_8x8_SRGB_BLOCK)           \
    X(wgpu::TextureFormat::ASTC10x5Unorm, VK_FORMAT_ASTC_10x5_UNORM_BLOCK)            \
    X(wgpu::TextureFormat::ASTC10x5UnormSrgb, VK_FORMAT_ASTC_10x5_SRGB_BLOCK)         \
    X(wgpu::TextureFormat::ASTC10x6Unorm, VK_FORMAT_ASTC_10x6_UNORM_BLOCK)            \
    X(wgpu::TextureFormat::ASTC10x6UnormSrgb, VK_FORMAT_ASTC_10x6_SRGB_BLOCK)         \
    X(wgpu::TextureFormat::ASTC10x8Unorm, VK_FORMAT_ASTC_10x8_UNORM_BLOCK)            \
    X(wgpu::TextureFormat::ASTC10x8UnormSrgb, VK_FORMAT_ASTC_10x8_SRGB_BLOCK)         \
    X(wgpu::TextureFormat::ASTC10x10Unorm, VK_FORMAT_ASTC_10x10_UNORM_BLOCK)          \
    X(wgpu::TextureFormat::ASTC10x10UnormSrgb, VK_FORMAT_ASTC_10x10_SRGB_BLOCK)       \
    X(wgpu::TextureFormat::ASTC12x10Unorm, VK_FORMAT_ASTC_12x10_UNORM_BLOCK)          \
    X(wgpu::TextureFormat::ASTC12x10UnormSrgb, VK_FORMAT_ASTC_12x10_SRGB_BLOCK)       \
    X(wgpu::TextureFormat::ASTC12x12Unorm, VK_FORMAT_ASTC_12x12_UNORM_BLOCK)          \
    X(wgpu::TextureFormat::ASTC12x12UnormSrgb, VK_FORMAT_ASTC_12x12_SRGB_BLOCK)       \
                                                                                      \
    X(wgpu::TextureFormat::R8BG8Biplanar420Unorm, VK_FORMAT_G8_B8R8_2PLANE_420_UNORM) \
    X(wgpu::TextureFormat::R8BG8Biplanar422Unorm, VK_FORMAT_G8_B8R8_2PLANE_422_UNORM) \
    X(wgpu::TextureFormat::R8BG8Biplanar444Unorm, VK_FORMAT_G8_B8R8_2PLANE_444_UNORM) \
    X(wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm,                               \
      VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16)                            \
    X(wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm,                               \
      VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16)                            \
    X(wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm,                               \
      VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16)

// Converts Dawn texture format to Vulkan formats.
VkFormat VulkanImageFormat(const Device* device, wgpu::TextureFormat format) {
    switch (format) {
#define X(wgpuFormat, vkFormat) \
    case wgpuFormat:            \
        return vkFormat;
        SIMPLE_FORMAT_MAPPING(X)
#undef X
        case wgpu::TextureFormat::Depth24PlusStencil8:
            // Depth24PlusStencil8 maps to either of these two formats because only requires
            // that one of the two be present. The VulkanUseD32S8 toggle combines the wish of
            // the environment, default to using D32S8, and availability information so we know
            // that the format is available.
            if (device->IsToggleEnabled(Toggle::VulkanUseD32S8)) {
                return VK_FORMAT_D32_SFLOAT_S8_UINT;
            } else {
                return VK_FORMAT_D24_UNORM_S8_UINT;
            }

        case wgpu::TextureFormat::Depth24Plus:
            return VK_FORMAT_D32_SFLOAT;

        case wgpu::TextureFormat::Stencil8:
            // Try to use the stencil8 format if possible, otherwise use whatever format we can
            // use that contains a stencil8 component.
            if (device->IsToggleEnabled(Toggle::VulkanUseS8)) {
                return VK_FORMAT_S8_UINT;
            } else {
                return VulkanImageFormat(device, wgpu::TextureFormat::Depth24PlusStencil8);
            }

        case wgpu::TextureFormat::OpaqueYCbCrAndroid:
            // The VkFormat is Undefined when TextureFormat::OpaqueYCbCrAndroid is passed for YCbCr
            // samplers.
            return VK_FORMAT_UNDEFINED;

        // R8BG8A8Triplanar420Unorm format is only supported on macOS.
        case wgpu::TextureFormat::R8BG8A8Triplanar420Unorm:
        case wgpu::TextureFormat::Undefined:
            break;
    }
    DAWN_UNREACHABLE();
}

// Converts Dawn texture format to Vulkan formats.
VkFormat ColorVulkanImageFormat(wgpu::TextureFormat format) {
    switch (format) {
#define X(wgpuFormat, vkFormat) \
    case wgpuFormat:            \
        return vkFormat;
        SIMPLE_FORMAT_MAPPING(X)
#undef X
        default:
            return VK_FORMAT_UNDEFINED;
    }
    DAWN_UNREACHABLE();
}

ResultOrError<wgpu::TextureFormat> FormatFromVkFormat(const Device* device, VkFormat vkFormat) {
    switch (vkFormat) {
#define X(wgpuFormat, vkFormat) \
    case vkFormat:              \
        return wgpuFormat;
        SIMPLE_FORMAT_MAPPING(X)
#undef X
        case VK_FORMAT_S8_UINT:
            if (device->IsToggleEnabled(Toggle::VulkanUseS8)) {
                return wgpu::TextureFormat::Stencil8;
            }
            break;

        case VK_FORMAT_D24_UNORM_S8_UINT:
            if (!device->IsToggleEnabled(Toggle::VulkanUseD32S8)) {
                return wgpu::TextureFormat::Depth24PlusStencil8;
            }
            break;

        default:
            break;
    }
    return DAWN_VALIDATION_ERROR("Unsupported VkFormat %x", vkFormat);
}

#undef SIMPLE_FORMAT_MAPPING

// Converts the Dawn usage flags to Vulkan usage flags. Also needs the format to choose
// between color and depth attachment usages.
VkImageUsageFlags VulkanImageUsage(const DeviceBase* device,
                                   wgpu::TextureUsage usage,
                                   const Format& format) {
    VkImageUsageFlags flags = 0;

    if (usage & wgpu::TextureUsage::CopySrc) {
        flags |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
    }
    if (usage & wgpu::TextureUsage::CopyDst) {
        flags |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
    }
    if (usage & wgpu::TextureUsage::TextureBinding) {
        flags |= VK_IMAGE_USAGE_SAMPLED_BIT;
        // If the sampled texture is a depth/stencil texture, its image layout will be set
        // to DEPTH_STENCIL_READ_ONLY_OPTIMAL in order to support readonly depth/stencil
        // attachment. That layout requires DEPTH_STENCIL_ATTACHMENT_BIT image usage.
        if (format.HasDepthOrStencil() && format.IsRenderable()) {
            flags |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
        }
    }
    if (usage & wgpu::TextureUsage::StorageBinding) {
        // Storage bit should only be included if the format actually supports a storage uage.
        DAWN_ASSERT(format.SupportsReadOnlyStorageUsage() ||
                    format.SupportsWriteOnlyStorageUsage());
        flags |= VK_IMAGE_USAGE_STORAGE_BIT;
    }
    if (usage & wgpu::TextureUsage::RenderAttachment) {
        if (format.HasDepthOrStencil()) {
            flags |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
        } else {
            flags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

            // Automatically set "input attachment" usage so that the texture can be used in
            // ExpandResolveTexture subpass or with framebuffer fetch.
            bool mayNeedToExpandTexture = !format.IsMultiPlanar() &&
                                          usage & wgpu::TextureUsage::TextureBinding &&
                                          device->HasFeature(Feature::DawnLoadResolveTexture);
            if (mayNeedToExpandTexture || device->HasFeature(Feature::FramebufferFetch)) {
                flags |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
            }
        }
    }
    if (usage & wgpu::TextureUsage::TransientAttachment) {
        flags |= VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
    }

    // Choosing Vulkan image usages should not know about kReadOnlyRenderAttachment because that's
    // a property of when the image is used, not of the creation.
    DAWN_ASSERT(!(usage & kReadOnlyRenderAttachment));

    return flags;
}

VkImageCreateFlags VulkanImageCreateFlags(const DeviceBase* device,
                                          wgpu::TextureUsage usage,
                                          const Format& format,
                                          uint32_t sampleCount) {
    VkImageCreateFlags vkCreateFlags = 0;

    // If the MSAARenderToSingleSampled feature is enabled, textures with RenderAttachment usage
    // need to have the VK_IMAGE_CREATE_MULTISAMPLED_RENDER_TO_SINGLE_SAMPLED_BIT_EXT flag set so
    // that they can be used as a render target with an implicit sample count. syoussefi@ has
    // confirmed that this is expected to be cheap or no cost on hardware with the extension.
    if (device->HasFeature(Feature::MSAARenderToSingleSampled) &&
        (usage & wgpu::TextureUsage::RenderAttachment) && sampleCount == 1 &&
        !format.HasDepthOrStencil()) {
        vkCreateFlags |= VK_IMAGE_CREATE_MULTISAMPLED_RENDER_TO_SINGLE_SAMPLED_BIT_EXT;
    }

    return vkCreateFlags;
}

// Chooses which Vulkan image layout should be used for the given Dawn usage. Note that this
// layout must match the layout given to various Vulkan operations as well as the layout given
// to descriptor set writes.
VkImageLayout VulkanImageLayout(const Format& format,
                                wgpu::TextureUsage usage,
                                wgpu::TextureUsage allowedUsage = wgpu::TextureUsage::None) {
    if (allowedUsage == wgpu::TextureUsage::None) {
        allowedUsage = usage;
    }

    // Note some of these cases have one bit, others have multiple bits.
#if DAWN_COMPILER_IS(CLANG)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wswitch"
#endif
    switch (usage) {
        case wgpu::TextureUsage::None:
            return VK_IMAGE_LAYOUT_UNDEFINED;

        case wgpu::TextureUsage::CopyDst:
            return VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;

            // The layout returned here is the one that will be used at bindgroup creation time.
        case wgpu::TextureUsage::TextureBinding:
        case kResolveAttachmentLoadingUsage:
            // The sampled image can be used as a readonly depth/stencil attachment at the same
            // time if it is a depth/stencil renderable format, so the image layout need to be
            // VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL.
            if (format.HasDepthOrStencil() && format.IsRenderable()) {
                return VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
            }
            // Sampled textures can be used simultaneously as read only storage. If storage usage is
            // allowed fall back to VK_IMAGE_LAYOUT_GENERAL.
            // TODO(crbug.com/392121643): Investigate potential optimizations.
            if (allowedUsage & (wgpu::TextureUsage::StorageBinding | kReadOnlyStorageTexture)) {
                return VK_IMAGE_LAYOUT_GENERAL;
            }
            return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;

            // Vulkan texture copy functions require the image to be in _one_  known layout.
            // Depending on whether parts of the texture have been transitioned to only CopySrc
            // or a combination with something else, the texture could be in a combination of
            // GENERAL and TRANSFER_SRC_OPTIMAL. This would be a problem, so we make CopySrc use
            // GENERAL.
            // TODO(crbug.com/dawn/851): We no longer need to transition resources all at
            // once and can instead track subresources so we should lift this limitation.
        case wgpu::TextureUsage::CopySrc:
            // Read-only and write-only storage textures must use general layout because load
            // and store operations on storage images can only be done on the images in
            // VK_IMAGE_LAYOUT_GENERAL layout.
        case wgpu::TextureUsage::StorageBinding:
        case kReadOnlyStorageTexture:
        case kReadOnlyStorageTexture | wgpu::TextureUsage::TextureBinding:
        case kWriteOnlyStorageTexture:
            return VK_IMAGE_LAYOUT_GENERAL;

        case wgpu::TextureUsage::RenderAttachment:
            if (format.HasDepthOrStencil()) {
                return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
            } else {
                return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
            }

        case kDepthReadOnlyStencilWritableAttachment:
        case kDepthReadOnlyStencilWritableAttachment | wgpu::TextureUsage::TextureBinding:
            return VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL;
        case kDepthWritableStencilReadOnlyAttachment:
        case kDepthWritableStencilReadOnlyAttachment | wgpu::TextureUsage::TextureBinding:
            return VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL;
        case kReadOnlyRenderAttachment:
        case kReadOnlyRenderAttachment | wgpu::TextureUsage::TextureBinding:
            return VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;

        case kPresentReleaseTextureUsage:
            return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
        case kPresentAcquireTextureUsage:
            // We always consider images being acquired from the swapchain as uninitialized,
            // so we can use the UNDEFINED Vulkan image layout.
            return VK_IMAGE_LAYOUT_UNDEFINED;

        case wgpu::TextureUsage::TransientAttachment:
            // Will be covered by RenderAttachment above, as specification of
            // TransientAttachment requires that RenderAttachment also be
            // specified.
            DAWN_UNREACHABLE();
            break;

        case wgpu::TextureUsage::StorageAttachment:
            // TODO(dawn:1704): Support PLS on Vulkan.
            DAWN_UNREACHABLE();
    }
#if DAWN_COMPILER_IS(CLANG)
#pragma clang diagnostic pop
#endif
    DAWN_UNREACHABLE();
}

VkImageLayout VulkanImageLayoutForDepthStencilAttachment(const Format& format,
                                                         bool depthReadOnly,
                                                         bool stencilReadOnly) {
    wgpu::TextureUsage depth = wgpu::TextureUsage::None;
    if (format.HasDepth()) {
        depth = depthReadOnly ? kReadOnlyRenderAttachment : wgpu::TextureUsage::RenderAttachment;
    }

    wgpu::TextureUsage stencil = wgpu::TextureUsage::None;
    if (format.HasStencil()) {
        stencil =
            stencilReadOnly ? kReadOnlyRenderAttachment : wgpu::TextureUsage::RenderAttachment;
    }

    return VulkanImageLayout(format, MergeDepthStencilUsage(depth, stencil));
}

VkSampleCountFlagBits VulkanSampleCount(uint32_t sampleCount) {
    switch (sampleCount) {
        case 1:
            return VK_SAMPLE_COUNT_1_BIT;
        case 4:
            return VK_SAMPLE_COUNT_4_BIT;
    }
    DAWN_UNREACHABLE();
}

MaybeError ValidateVulkanImageCanBeWrapped(const DeviceBase*,
                                           const UnpackedPtr<TextureDescriptor>& descriptor) {
    DAWN_INVALID_IF(descriptor->dimension != wgpu::TextureDimension::e2D,
                    "Texture dimension (%s) is not %s.", descriptor->dimension,
                    wgpu::TextureDimension::e2D);

    DAWN_INVALID_IF(descriptor->mipLevelCount != 1, "Mip level count (%u) is not 1.",
                    descriptor->mipLevelCount);

    DAWN_INVALID_IF(descriptor->size.depthOrArrayLayers != 1, "Array layer count (%u) is not 1.",
                    descriptor->size.depthOrArrayLayers);

    DAWN_INVALID_IF(descriptor->sampleCount != 1, "Sample count (%u) is not 1.",
                    descriptor->sampleCount);

    return {};
}

bool IsSampleCountSupported(const dawn::native::vulkan::Device* device,
                            const VkImageCreateInfo& imageCreateInfo) {
    DAWN_ASSERT(device);

    VkPhysicalDevice vkPhysicalDevice =
        ToBackend(device->GetPhysicalDevice())->GetVkPhysicalDevice();
    VkImageFormatProperties properties;
    if (device->fn.GetPhysicalDeviceImageFormatProperties(
            vkPhysicalDevice, imageCreateInfo.format, imageCreateInfo.imageType,
            imageCreateInfo.tiling, imageCreateInfo.usage, imageCreateInfo.flags,
            &properties) != VK_SUCCESS) {
        DAWN_UNREACHABLE();
    }

    return (properties.sampleCounts & imageCreateInfo.samples) != 0u;
}

Texture::Texture(Device* device, const UnpackedPtr<TextureDescriptor>& descriptor)
    : TextureBase(device, descriptor),
      mCombinedAspect(ComputeCombinedAspect(device, GetFormat())),
      // A usage of none will make sure the texture is transitioned before its first use as
      // required by the Vulkan spec.
      mSubresourceLastSyncInfos(
          mCombinedAspect != Aspect::None ? mCombinedAspect : GetFormat().aspects,
          GetArrayLayers(),
          GetNumMipLevels(),
          TextureSyncInfo{wgpu::TextureUsage::None, wgpu::ShaderStage::None}) {}

void Texture::SetLabelHelper(const char* prefix) {
    SetDebugName(ToBackend(GetDevice()), mHandle, prefix, GetLabel());
}

void Texture::NotifySwapChainPresent() {
    // When using an external swap chain texture, there's no way to determine frame boundaries since
    // Dawn isn't managing the Vulkan swap chains. In this mode, external tools like RenderDoc
    // will wait forever for a present that never happens. We handle this by using tool-specific
    // hooks to inform them of the "presented" texture so it can determine frame
    // boundaries and use its contents for the UI.
    if (!mIsExternalSwapChainTexture) {
        return;
    }

#if defined(DAWN_ENABLE_RENDERDOC)
    Device* device = ToBackend(GetDevice());

    // For RenderDoc, we expect the user to enable and use process injection to inject RenderDoc
    // into the GPU process at startup. We start capturing all frames right away. The user has
    // to kill the process or stop it from rendering (e.g. close or change tabs in Chrome).
    if (auto renderDocApi = dawn::native::utils::GetRenderDocApi(device)) {
        void* renderDocDevicePtr = RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(device->GetVkInstance());

        // We signal the end of the current frame and the start of the next.
        // This means we miss capturing the very first frame.
        renderDocApi->EndFrameCapture(renderDocDevicePtr, NULL);
        renderDocApi->StartFrameCapture(renderDocDevicePtr, NULL);
    }
#endif
}

void Texture::SetIsExternalSwapchainTexture(bool isSwapChainTexture) {
    mIsExternalSwapChainTexture = isSwapChainTexture;
}

VkImageLayout Texture::VulkanImageLayout(wgpu::TextureUsage usage) const {
    return dawn::native::vulkan::VulkanImageLayout(GetFormat(), usage, GetUsage());
}

void Texture::SetLabelImpl() {
    SetLabelHelper("Dawn_InternalTexture");
}

void Texture::DestroyImpl(DestroyReason reason) {
    // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
    // - It may be called if the texture is explicitly destroyed with APIDestroy.
    //   This case is NOT thread-safe and needs proper synchronization with other
    //   simultaneous uses of the texture.
    // - It may be called when the last ref to the texture is dropped and the texture
    //   is implicitly destroyed. This case is thread-safe because there are no
    //   other threads using the texture since there are no other live refs.
    mHandle = VK_NULL_HANDLE;
    mIsExternalSwapChainTexture = false;

    TextureBase::DestroyImpl(reason);
}

VkImage Texture::GetHandle() const {
    return mHandle;
}

bool Texture::CanReuseWithoutBarrier(wgpu::TextureUsage lastUsage,
                                     wgpu::TextureUsage usage,
                                     wgpu::ShaderStage lastShaderStage,
                                     wgpu::ShaderStage shaderStage) {
    // Reuse the texture directly and avoid encoding barriers when it isn't needed.
    bool lastReadOnly = IsSubset(lastUsage, kReadOnlyTextureUsages);
    return lastReadOnly && lastUsage == usage && IsSubset(shaderStage, lastShaderStage);
}

void Texture::TransitionUsageForPass(CommandRecordingContext* recordingContext,
                                     const TextureSubresourceSyncInfo& textureSyncInfos,
                                     std::vector<VkImageMemoryBarrier>* imageBarriers,
                                     VkPipelineStageFlags* srcStages,
                                     VkPipelineStageFlags* dstStages) {
    if (!UseCombinedAspects()) {
        TransitionUsageForPassImpl(recordingContext, textureSyncInfos, imageBarriers, srcStages,
                                   dstStages);
        return;
    }
    // We need to combine aspects for the transition, use a new subresource storage that will
    // contain the combined usages for the aspects.
    SubresourceStorage<TextureSyncInfo> combinedUsages(mCombinedAspect, GetArrayLayers(),
                                                       GetNumMipLevels());
    if (mCombinedAspect == Aspect::CombinedDepthStencil) {
        // For depth-stencil we can't just combine the aspect with an | operation because there
        // needs to be special handling for readonly aspects. Instead figure out which aspect is
        // currently being added (and which one is already present) and call the custom merging
        // function for depth-stencil.
        textureSyncInfos.Iterate([&](const SubresourceRange& range, TextureSyncInfo syncInfo) {
            SubresourceRange updateRange = range;
            updateRange.aspects = mCombinedAspect;
            Aspect aspectsToMerge = range.aspects;
            combinedUsages.Update(
                updateRange, [&](const SubresourceRange&, TextureSyncInfo* combinedInfo) {
                    if (aspectsToMerge == Aspect::Depth) {
                        combinedInfo->usage =
                            MergeDepthStencilUsage(syncInfo.usage, combinedInfo->usage);
                    } else if (aspectsToMerge == Aspect::Stencil) {
                        combinedInfo->usage =
                            MergeDepthStencilUsage(combinedInfo->usage, syncInfo.usage);
                    } else {
                        DAWN_ASSERT(aspectsToMerge == (Aspect::Depth | Aspect::Stencil));
                        combinedInfo->usage = syncInfo.usage;
                    }
                    combinedInfo->shaderStages |= syncInfo.shaderStages;
                });
        });
    } else {
        // Combine aspect's usages with the | operation.
        textureSyncInfos.Iterate([&](const SubresourceRange& range, TextureSyncInfo syncInfo) {
            SubresourceRange updateRange = range;
            updateRange.aspects = mCombinedAspect;
            combinedUsages.Update(updateRange,
                                  [&](const SubresourceRange&, TextureSyncInfo* combinedInfo) {
                                      combinedInfo->usage |= syncInfo.usage;
                                      combinedInfo->shaderStages |= syncInfo.shaderStages;
                                  });
        });
    }
    TransitionUsageForPassImpl(recordingContext, combinedUsages, imageBarriers, srcStages,
                               dstStages);
}

void Texture::TransitionUsageForPassImpl(
    CommandRecordingContext* recordingContext,
    const SubresourceStorage<TextureSyncInfo>& subresourceSyncInfos,
    std::vector<VkImageMemoryBarrier>* imageBarriers,
    VkPipelineStageFlags* srcStages,
    VkPipelineStageFlags* dstStages) {
    size_t transitionBarrierStart = imageBarriers->size();
    const Format& format = GetFormat();

    wgpu::TextureUsage allNewUsages = wgpu::TextureUsage::None;
    wgpu::TextureUsage allLastUsages = wgpu::TextureUsage::None;

    wgpu::ShaderStage allNewShaderStages = wgpu::ShaderStage::None;
    wgpu::ShaderStage allLastShaderStages = wgpu::ShaderStage::None;

    mSubresourceLastSyncInfos.Merge(subresourceSyncInfos, [&](const SubresourceRange& range,
                                                              TextureSyncInfo* lastSyncInfo,
                                                              const TextureSyncInfo& newSyncInfo) {
        wgpu::TextureUsage newUsage = newSyncInfo.usage;
        if (newSyncInfo.shaderStages == wgpu::ShaderStage::None) {
            // If the image isn't used in any shader stages, ignore shader usages. Eg. ignore a
            // texture binding that isn't actually sampled in any shader.
            newUsage &= ~kShaderTextureUsages;
        }

        if (newUsage == wgpu::TextureUsage::None ||
            CanReuseWithoutBarrier(lastSyncInfo->usage, newUsage, lastSyncInfo->shaderStages,
                                   newSyncInfo.shaderStages)) {
            return;
        }

        imageBarriers->push_back(BuildMemoryBarrier(this, lastSyncInfo->usage, newUsage, range));

        allLastUsages |= lastSyncInfo->usage;
        allNewUsages |= newUsage;

        allLastShaderStages |= lastSyncInfo->shaderStages;
        allNewShaderStages |= newSyncInfo.shaderStages;

        if (lastSyncInfo->usage == newUsage &&
            IsSubset(lastSyncInfo->usage, kReadOnlyTextureUsages)) {
            // Read only usage and no layout transition. We can keep previous shader stages so
            // future uses in those stages don't insert barriers.
            lastSyncInfo->shaderStages |= newSyncInfo.shaderStages;
        } else {
            // Image was altered by write or layout transition. We need to clear previous shader
            // stages so future uses in those stages will insert barriers.
            lastSyncInfo->shaderStages = newSyncInfo.shaderStages;
        }
        lastSyncInfo->usage = newUsage;
    });

    TweakTransition(recordingContext, imageBarriers, transitionBarrierStart);

    // Skip adding pipeline stages if no barrier was needed to avoid putting TOP_OF_PIPE in the
    // destination stages.
    if (allNewUsages != wgpu::TextureUsage::None) {
        *srcStages |= VulkanPipelineStage(allLastUsages, allLastShaderStages, format);
        *dstStages |= VulkanPipelineStage(allNewUsages, allNewShaderStages, format);
    }
}

void Texture::TransitionUsageNow(CommandRecordingContext* recordingContext,
                                 wgpu::TextureUsage usage,
                                 wgpu::ShaderStage shaderStages,
                                 const SubresourceRange& range) {
    std::vector<VkImageMemoryBarrier> barriers;

    VkPipelineStageFlags srcStages = 0;
    VkPipelineStageFlags dstStages = 0;

    TransitionUsageAndGetResourceBarrier(usage, shaderStages, range, &barriers, &srcStages,
                                         &dstStages);

    TweakTransition(recordingContext, &barriers, 0);

    if (!barriers.empty()) {
        DAWN_ASSERT(srcStages != 0 && dstStages != 0);
        ToBackend(GetDevice())
            ->fn.CmdPipelineBarrier(recordingContext->commandBuffer, srcStages, dstStages, 0, 0,
                                    nullptr, 0, nullptr, barriers.size(), barriers.data());
    }
}

void Texture::UpdateUsage(wgpu::TextureUsage usage,
                          wgpu::ShaderStage shaderStages,
                          const SubresourceRange& range) {
    std::vector<VkImageMemoryBarrier> barriers;

    VkPipelineStageFlags srcStages = 0;
    VkPipelineStageFlags dstStages = 0;

    TransitionUsageAndGetResourceBarrier(usage, shaderStages, range, &barriers, &srcStages,
                                         &dstStages);

    // barriers are ignored.
}

void Texture::TransitionUsageAndGetResourceBarrier(wgpu::TextureUsage usage,
                                                   wgpu::ShaderStage shaderStages,
                                                   const SubresourceRange& range,
                                                   std::vector<VkImageMemoryBarrier>* imageBarriers,
                                                   VkPipelineStageFlags* srcStages,
                                                   VkPipelineStageFlags* dstStages) {
    if (UseCombinedAspects()) {
        SubresourceRange updatedRange = range;
        updatedRange.aspects = mCombinedAspect;
        TransitionUsageAndGetResourceBarrierImpl(usage, shaderStages, updatedRange, imageBarriers,
                                                 srcStages, dstStages);
    } else {
        TransitionUsageAndGetResourceBarrierImpl(usage, shaderStages, range, imageBarriers,
                                                 srcStages, dstStages);
    }
}

void Texture::TransitionUsageAndGetResourceBarrierImpl(
    wgpu::TextureUsage usage,
    wgpu::ShaderStage shaderStages,
    const SubresourceRange& range,
    std::vector<VkImageMemoryBarrier>* imageBarriers,
    VkPipelineStageFlags* srcStages,
    VkPipelineStageFlags* dstStages) {
    DAWN_ASSERT(imageBarriers != nullptr);
    const Format& format = GetFormat();

    if (shaderStages == wgpu::ShaderStage::None) {
        // If the image isn't used in any shader stages, ignore shader usages. Eg. ignore a texture
        // binding that isn't actually sampled in any shader.
        usage &= ~kShaderTextureUsages;
    }

    wgpu::TextureUsage allLastUsages = wgpu::TextureUsage::None;
    wgpu::ShaderStage allLastShaderStages = wgpu::ShaderStage::None;
    mSubresourceLastSyncInfos.Update(
        range, [&](const SubresourceRange& range, TextureSyncInfo* lastSyncInfo) {
            if (CanReuseWithoutBarrier(lastSyncInfo->usage, usage, lastSyncInfo->shaderStages,
                                       shaderStages)) {
                return;
            }

            imageBarriers->push_back(BuildMemoryBarrier(this, lastSyncInfo->usage, usage, range));

            allLastUsages |= lastSyncInfo->usage;
            allLastShaderStages |= lastSyncInfo->shaderStages;

            if (lastSyncInfo->usage == usage && IsSubset(usage, kReadOnlyTextureUsages)) {
                // Read only usage and no layout transition. We can keep previous shader stages so
                // future uses in those stages don't insert barriers.
                lastSyncInfo->shaderStages |= shaderStages;
            } else {
                // Image was altered by write or layout transition. We need to clear previous shader
                // stages so future uses in those stages will insert barriers.
                lastSyncInfo->shaderStages = shaderStages;
            }
            lastSyncInfo->usage = usage;
        });

    *srcStages |= VulkanPipelineStage(allLastUsages, allLastShaderStages, format);
    *dstStages |= VulkanPipelineStage(usage, shaderStages, format);
}

MaybeError Texture::ClearTexture(CommandRecordingContext* recordingContext,
                                 const SubresourceRange& range,
                                 TextureBase::ClearValue clearValue) {
    Device* device = ToBackend(GetDevice());

    const bool isZero = clearValue == TextureBase::ClearValue::Zero;
    uint32_t uClearColor = isZero ? 0 : 1;
    float fClearColor = isZero ? 0.f : 1.f;

    VkImageSubresourceRange imageRange = {};
    imageRange.levelCount = 1;
    imageRange.layerCount = 1;

    if ((GetInternalUsage() & wgpu::TextureUsage::RenderAttachment) && GetFormat().IsColor() &&
        !GetFormat().IsMultiPlanar()) {
        TransitionUsageNow(recordingContext, wgpu::TextureUsage::RenderAttachment,
                           wgpu::ShaderStage::None, range);

        for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount;
             ++level) {
            for (uint32_t layer = range.baseArrayLayer;
                 layer < range.baseArrayLayer + range.layerCount; ++layer) {
                Aspect aspects = Aspect::None;
                for (Aspect aspect : IterateEnumMask(range.aspects)) {
                    if (clearValue == TextureBase::ClearValue::Zero &&
                        IsSubresourceContentInitialized(
                            SubresourceRange::SingleMipAndLayer(level, layer, aspect))) {
                        // Skip lazy clears if already initialized.
                        continue;
                    }
                    aspects |= aspect;
                }

                if (aspects == Aspect::None) {
                    continue;
                }

                Extent3D mipSize = GetMipLevelSingleSubresourcePhysicalSize(level, aspects);
                BeginRenderPassCmd beginCmd{};
                beginCmd.width = mipSize.width;
                beginCmd.height = mipSize.height;
                beginCmd.renderArea = {0, 0, mipSize.width, mipSize.height};

                TextureViewDescriptor viewDesc = {};
                viewDesc.label = "Dawn_ClearTexture_View";
                viewDesc.format = GetFormat().format;

                uint32_t depthSliceCount = 1;
                switch (GetDimension()) {
                    case wgpu::TextureDimension::e2D:
                        viewDesc.dimension = wgpu::TextureViewDimension::e2D;
                        break;
                    case wgpu::TextureDimension::e3D:
                        viewDesc.dimension = wgpu::TextureViewDimension::e3D;
                        depthSliceCount = mipSize.depthOrArrayLayers;
                        DAWN_ASSERT(layer == 0);
                        break;
                    case wgpu::TextureDimension::e1D:
                    case wgpu::TextureDimension::Undefined:
                        DAWN_UNREACHABLE();
                        break;
                }

                viewDesc.baseMipLevel = level;
                viewDesc.mipLevelCount = 1u;
                viewDesc.baseArrayLayer = layer;
                viewDesc.arrayLayerCount = 1u;
                // Inherit wgpu::TextureUsage::RenderAttachment, which may be an internal usage.
                viewDesc.usage = wgpu::TextureUsage::None;

                ColorAttachmentIndex ca0(uint8_t(0));
                DAWN_TRY_ASSIGN(beginCmd.colorAttachments[ca0].view,
                                device->CreateTextureView(this, &viewDesc));

                RenderPassColorAttachment colorAttachment{};
                colorAttachment.view = beginCmd.colorAttachments[ca0].view.Get();

                beginCmd.colorAttachments[ca0].clearColor = colorAttachment.clearValue = {
                    fClearColor, fClearColor, fClearColor, fClearColor};
                beginCmd.colorAttachments[ca0].loadOp = colorAttachment.loadOp =
                    wgpu::LoadOp::Clear;
                beginCmd.colorAttachments[ca0].storeOp = colorAttachment.storeOp =
                    wgpu::StoreOp::Store;

                RenderPassDescriptor passDesc{};
                passDesc.colorAttachmentCount = 1u;
                passDesc.colorAttachments = &colorAttachment;

                for (uint32_t depthSlice = 0; depthSlice < depthSliceCount; ++depthSlice) {
                    beginCmd.colorAttachments[ca0].depthSlice = colorAttachment.depthSlice =
                        depthSlice;

                    beginCmd.attachmentState =
                        device->GetOrCreateAttachmentState(Unpack(&passDesc));

                    DAWN_TRY(
                        RecordBeginRenderPass(recordingContext, ToBackend(GetDevice()), &beginCmd));
                    RecordEndRenderPass(recordingContext, ToBackend(GetDevice()));
                }
            }
        }
    } else if (GetFormat().HasDepthOrStencil()) {
        TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopyDst, wgpu::ShaderStage::None,
                           range);

        for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount;
             ++level) {
            imageRange.baseMipLevel = level;
            for (uint32_t layer = range.baseArrayLayer;
                 layer < range.baseArrayLayer + range.layerCount; ++layer) {
                Aspect aspects = Aspect::None;
                for (Aspect aspect : IterateEnumMask(range.aspects)) {
                    if (clearValue == TextureBase::ClearValue::Zero &&
                        IsSubresourceContentInitialized(
                            SubresourceRange::SingleMipAndLayer(level, layer, aspect))) {
                        // Skip lazy clears if already initialized.
                        continue;
                    }
                    aspects |= aspect;
                }

                if (aspects == Aspect::None) {
                    continue;
                }

                imageRange.aspectMask = VulkanAspectMask(aspects);
                imageRange.baseMipLevel = level;
                imageRange.baseArrayLayer = layer;

                VkClearDepthStencilValue clearDepthStencilValue[1];
                clearDepthStencilValue[0].depth = fClearColor;
                clearDepthStencilValue[0].stencil = uClearColor;
                device->fn.CmdClearDepthStencilImage(recordingContext->commandBuffer, GetHandle(),
                                                     VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                                                     clearDepthStencilValue, 1, &imageRange);
            }
        }
    } else {
        if (range.aspects == Aspect::None) {
            return {};
        }

        TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopyDst, wgpu::ShaderStage::None,
                           range);

        // need to clear the texture with a copy from buffer
        DAWN_ASSERT(range.aspects == Aspect::Color || range.aspects == Aspect::Plane0 ||
                    range.aspects == Aspect::Plane1 || range.aspects == Aspect::Plane2);
        const TypedTexelBlockInfo& blockInfo = GetFormat().GetAspectInfo(range.aspects).block;

        BlockExtent3D largestMipSize = blockInfo.ToBlock(
            GetMipLevelSingleSubresourcePhysicalSize(range.baseMipLevel, range.aspects));

        uint64_t bytesPerRow = Align(blockInfo.ToBytes(largestMipSize.width),
                                     device->GetOptimalBytesPerRowAlignment());
        BlockCount blocksPerRow = blockInfo.BytesToBlocks(bytesPerRow);
        BlockCount uploadBlocks =
            blocksPerRow * largestMipSize.height * largestMipSize.depthOrArrayLayers;
        uint64_t uploadSize = blockInfo.ToBytes(uploadBlocks);

        DAWN_UNSAFE_TODO(DAWN_TRY(device->GetDynamicUploader()->WithUploadReservation(
            uploadSize, blockInfo.byteSize, [&](UploadReservation reservation) -> MaybeError {
                memset(reservation.mappedPointer, uClearColor, uploadSize);

                std::vector<VkBufferImageCopy> regions;
                for (uint32_t level = range.baseMipLevel;
                     level < range.baseMipLevel + range.levelCount; ++level) {
                    BlockExtent3D copySize = blockInfo.ToBlock(
                        GetMipLevelSingleSubresourcePhysicalSize(level, range.aspects));
                    imageRange.baseMipLevel = level;
                    for (uint32_t layer = range.baseArrayLayer;
                         layer < range.baseArrayLayer + range.layerCount; ++layer) {
                        if (clearValue == TextureBase::ClearValue::Zero &&
                            IsSubresourceContentInitialized(
                                SubresourceRange::SingleMipAndLayer(level, layer, range.aspects))) {
                            // Skip lazy clears if already initialized.
                            continue;
                        }

                        BufferCopy bufferCopy;
                        bufferCopy.offset = reservation.offsetInBuffer;
                        bufferCopy.blocksPerRow = blocksPerRow;
                        bufferCopy.rowsPerImage = copySize.height;

                        TextureCopy textureCopy;
                        textureCopy.aspect = range.aspects;
                        textureCopy.mipLevel = level;
                        textureCopy.origin = {TexelCount{0}, TexelCount{0}, TexelCount{layer}};
                        textureCopy.texture = this;

                        regions.push_back(
                            ComputeBufferImageCopyRegion(bufferCopy, textureCopy, copySize));
                    }
                }

                device->fn.CmdCopyBufferToImage(recordingContext->commandBuffer,
                                                ToBackend(reservation.buffer)->GetHandle(),
                                                GetHandle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                                                regions.size(), regions.data());
                return {};
            })));
    }

    if (clearValue == TextureBase::ClearValue::Zero) {
        SetIsSubresourceContentInitialized(true, range);
        device->IncrementLazyClearCountForTesting();
    }
    return {};
}

MaybeError Texture::PinImpl(wgpu::TextureUsage usage) {
    // Pinning means that until unpinned, the texture will have specific usage and can be used
    // freely in shaders without any further check or memory barrier tracking. Ensure all
    // subresources are cleared and transitioned to the usage.
    DAWN_ASSERT(!HasPinnedUsage());
    SubresourceRange pinnedSubresources = GetAllSubresources();

    CommandRecordingContext* recordingContext =
        ToBackend(GetDevice()->GetQueue())->GetPendingRecordingContext(Queue::SubmitMode::Passive);
    DAWN_TRY(EnsureSubresourceContentInitialized(recordingContext, pinnedSubresources));

    TransitionUsageNow(recordingContext, usage, kAllStages, pinnedSubresources);

    // TODO(https://issues.chromium.org/473444516): Investigate what to do for imported textures.
    // Should we consider a pin/unpin pair similar to an access on a queue such that we need to
    // wait on fences or export them?
    return {};
}

void Texture::UnpinImpl() {
    DAWN_ASSERT(HasPinnedUsage());

    // TODO(https://issues.chromium.org/473444516): Investigate what to do for imported textures.
    // Should we consider a pin/unpin pair similar to an access on a queue such that we need to
    // wait on fences or export them?
}

MaybeError Texture::EnsureSubresourceContentInitialized(CommandRecordingContext* recordingContext,
                                                        const SubresourceRange& range) {
    if (!GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
        return {};
    }
    if (!IsSubresourceContentInitialized(range)) {
        // If subresource has not been initialized, clear it to black as it could contain dirty
        // bits from recycled memory
        DAWN_TRY(ClearTexture(recordingContext, range, TextureBase::ClearValue::Zero));
    }
    return {};
}

VkImageLayout Texture::GetCurrentLayout(Aspect aspect,
                                        uint32_t arrayLayer,
                                        uint32_t mipLevel) const {
    DAWN_ASSERT(GetFormat().aspects == Aspect::Color);
    return VulkanImageLayout(mSubresourceLastSyncInfos.Get(aspect, arrayLayer, mipLevel).usage);
}

bool Texture::UseCombinedAspects() const {
    return mCombinedAspect != Aspect::None;
}

Aspect Texture::GetDisjointVulkanAspects() const {
    if (UseCombinedAspects()) {
        return mCombinedAspect;
    }
    return GetFormat().aspects;
}

void Texture::TweakTransition(CommandRecordingContext* recordingContext,
                              std::vector<VkImageMemoryBarrier>* barriers,
                              size_t transitionBarrierStart) {}

MaybeError Texture::OnBeforeSubmit(CommandRecordingContext*) {
    DAWN_UNREACHABLE();
}
MaybeError Texture::OnAfterSubmit() {
    DAWN_UNREACHABLE();
}

//
// InternalTexture
//

// static
ResultOrError<Ref<InternalTexture>> InternalTexture::Create(
    Device* device,
    const UnpackedPtr<TextureDescriptor>& descriptor,
    VkImageUsageFlags extraUsages) {
    Ref<InternalTexture> texture = AcquireRef(new InternalTexture(device, descriptor));
    DAWN_TRY(texture->Initialize(extraUsages));
    return std::move(texture);
}

MaybeError InternalTexture::Initialize(VkImageUsageFlags extraUsages) {
    Device* device = ToBackend(GetDevice());

    // If this triggers, it means it's time to add tests and implement support for readonly
    // depth-stencil attachments that are also used as readonly storage bindings in the pass.
    // Have fun! :)
    DAWN_ASSERT(
        !(GetFormat().HasDepthOrStencil() && (GetUsage() & wgpu::TextureUsage::StorageBinding)));

    // Create the Vulkan image "container". We don't need to check that the format supports the
    // combination of sample, usage etc. because validation should have been done in the Dawn
    // frontend already based on the minimum supported formats in the Vulkan spec
    VkImageCreateInfo createInfo = {};
    FillVulkanCreateInfoSizesAndType(*this, &createInfo);
    createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
    createInfo.format = VulkanImageFormat(device, GetFormat().format);
    createInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
    createInfo.usage = VulkanImageUsage(device, GetInternalUsage(), GetFormat()) | extraUsages;
    createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
    createInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    createInfo.flags =
        VulkanImageCreateFlags(device, GetInternalUsage(), GetFormat(), GetSampleCount());

    std::vector<VkFormat> viewFormats;
    bool requiresViewFormatsList = GetViewFormats().any();
    // As current SPIR-V SPEC doesn't support 'bgra8' as a valid image format, to support the
    // STORAGE usage of BGRA8Unorm we have to create an RGBA8Unorm image view on the BGRA8Unorm
    // storage texture and polyfill it as RGBA8Unorm in Tint. See http://crbug.com/dawn/1641 for
    // more details.
    if (createInfo.format == VK_FORMAT_B8G8R8A8_UNORM &&
        createInfo.usage & VK_IMAGE_USAGE_STORAGE_BIT) {
        viewFormats.push_back(VK_FORMAT_R8G8B8A8_UNORM);
        requiresViewFormatsList = true;
    }
    if (GetFormat().IsMultiPlanar() || requiresViewFormatsList) {
        // Multi-planar image needs to have VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT in order to be able
        // to create per-plane view. See
        // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCreateFlagBits.html
        //
        // Note: we cannot include R8 & RG8 in the viewFormats list of
        // G8_B8R8_2PLANE_420_UNORM. The Vulkan validation layer will disallow that.
        createInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
    }

    // Add the view format list only when the usage does not have storage. Otherwise, the VVL will
    // say creation of the texture is invalid.
    // See https://github.com/gpuweb/gpuweb/issues/4426.
    VkImageFormatListCreateInfo imageFormatListInfo = {};
    PNextChainBuilder createInfoChain(&createInfo);
    if (requiresViewFormatsList && device->GetDeviceInfo().HasExt(DeviceExt::ImageFormatList) &&
        !(createInfo.usage & VK_IMAGE_USAGE_STORAGE_BIT)) {
        createInfoChain.Add(&imageFormatListInfo, VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO);
        viewFormats.push_back(VulkanImageFormat(device, GetFormat().format));
        for (FormatIndex i : GetViewFormats()) {
            const Format& viewFormat = device->GetValidInternalFormat(i);
            viewFormats.push_back(VulkanImageFormat(device, viewFormat.format));
        }

        imageFormatListInfo.viewFormatCount = viewFormats.size();
        imageFormatListInfo.pViewFormats = viewFormats.data();
    }

    DAWN_ASSERT(IsSampleCountSupported(device, createInfo));

    if (GetArrayLayers() >= 6 && GetBaseSize().width == GetBaseSize().height) {
        createInfo.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
    }

    if (createInfo.imageType == VK_IMAGE_TYPE_3D &&
        createInfo.usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) {
        createInfo.flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT;
    }

    // Zero initialization and various workarounds need to copy to the images. Always add
    // VK_IMAGE_USAGE_TRANSFER_DST_BIT to allow for these use cases, unless the image is a transient
    // attachment and incompatible with transfers.
    if (!(GetInternalUsage() & wgpu::TextureUsage::TransientAttachment)) {
        createInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
    }

    DAWN_TRY(CheckVkOOMThenSuccess(
        device->fn.CreateImage(device->GetVkDevice(), &createInfo, nullptr, &*mHandle),
        "CreateImage"));

    // Create the image memory and associate it with the container
    VkMemoryRequirements requirements;
    device->fn.GetImageMemoryRequirements(device->GetVkDevice(), mHandle, &requirements);

    bool forceDisableSubAllocation =
        (device->IsToggleEnabled(
            Toggle::DisableSubAllocationFor2DTextureWithCopyDstOrRenderAttachment)) &&
        GetDimension() == wgpu::TextureDimension::e2D &&
        (GetInternalUsage() & (wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment));
    auto memoryKind = (GetInternalUsage() & wgpu::TextureUsage::TransientAttachment)
                          ? MemoryKind::LazilyAllocated
                          : MemoryKind::DeviceLocal;
    DAWN_TRY_ASSIGN(mMemoryAllocation, device->GetResourceMemoryAllocator()->Allocate(
                                           requirements, memoryKind, forceDisableSubAllocation));

    DAWN_TRY(CheckVkSuccess(
        device->fn.BindImageMemory(device->GetVkDevice(), mHandle,
                                   ToBackend(mMemoryAllocation.GetResourceHeap())->GetMemory(),
                                   mMemoryAllocation.GetOffset()),
        "BindImageMemory"));

    // crbug.com/1361662
    // This works around an Intel Gen12 Mesa bug due to CCS ambiguates stomping on each other.
    // https://gitlab.freedesktop.org/mesa/mesa/-/issues/7301#note_1826367
    if (device->IsToggleEnabled(Toggle::VulkanClearGen12TextureWithCCSAmbiguateOnCreation)) {
        auto format = GetFormat().format;
        bool textureIsBuggy =
            format == wgpu::TextureFormat::R8Unorm || format == wgpu::TextureFormat::R8Snorm ||
            format == wgpu::TextureFormat::R8Uint || format == wgpu::TextureFormat::R8Sint ||
            // These are flaky.
            format == wgpu::TextureFormat::RG16Sint || format == wgpu::TextureFormat::RGBA16Sint ||
            format == wgpu::TextureFormat::RGBA32Float;
        textureIsBuggy &= GetNumMipLevels() > 1;
        textureIsBuggy &= GetDimension() == wgpu::TextureDimension::e2D;
        textureIsBuggy &= IsPowerOfTwo(GetBaseSize().width) && IsPowerOfTwo(GetBaseSize().height);
        if (textureIsBuggy) {
            DAWN_TRY(ClearTexture(ToBackend(GetDevice()->GetQueue())->GetPendingRecordingContext(),
                                  GetAllSubresources(), TextureBase::ClearValue::Zero));
        }
    }

    if (device->IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting)) {
        DAWN_TRY(ClearTexture(ToBackend(GetDevice()->GetQueue())->GetPendingRecordingContext(),
                              GetAllSubresources(), TextureBase::ClearValue::NonZero));
    }

    SetLabelImpl();

    return {};
}

void InternalTexture::DestroyImpl(DestroyReason reason) {
    // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
    // - It may be called if the texture is explicitly destroyed with APIDestroy.
    //   This case is NOT thread-safe and needs proper synchronization with other
    //   simultaneous uses of the texture.
    // - It may be called when the last ref to the texture is dropped and the texture
    //   is implicitly destroyed. This case is thread-safe because there are no
    //   other threads using the texture since there are no other live refs.
    Device* device = ToBackend(GetDevice());

    device->GetFencedDeleter()->DeleteWhenUnused(mHandle);
    mHandle = VK_NULL_HANDLE;

    device->GetResourceMemoryAllocator()->Deallocate(&mMemoryAllocation);
    mMemoryAllocation = ResourceMemoryAllocation();

    Texture::DestroyImpl(reason);
}

//
// SwapChainTexture
//

// static
Ref<SwapChainTexture> SwapChainTexture::Create(Device* device,
                                               const UnpackedPtr<TextureDescriptor>& descriptor,
                                               VkImage nativeImage) {
    Ref<SwapChainTexture> texture = AcquireRef(new SwapChainTexture(device, descriptor));
    texture->Initialize(nativeImage);
    return texture;
}

void SwapChainTexture::Initialize(VkImage nativeImage) {
    mHandle = nativeImage;
    mSubresourceLastSyncInfos.Fill({kPresentAcquireTextureUsage, wgpu::ShaderStage::None});
    SetLabelHelper("Dawn_SwapChainTexture");
}

//
// ImportedTextureBase
//

ImportedTextureBase::~ImportedTextureBase() {
    // The external semaphore can be queried even after device loss or destroy (so applications can
    // keep relying on them for synchronization) so only destroy it in the destructor instead of
    // DestroyImpl.
    if (mExternalSemaphoreHandle != kNullExternalSemaphoreHandle) {
        ToBackend(GetDevice())
            ->GetExternalSemaphoreService()
            ->CloseHandle(mExternalSemaphoreHandle);
        mExternalSemaphoreHandle = kNullExternalSemaphoreHandle;
    }
}

void ImportedTextureBase::DestroyImpl(DestroyReason reason) {
    // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
    // - It may be called if the texture is explicitly destroyed with APIDestroy.
    //   This case is NOT thread-safe and needs proper synchronization with other
    //   simultaneous uses of the texture.
    // - It may be called when the last ref to the texture is dropped and the texture
    //   is implicitly destroyed. This case is thread-safe because there are no
    //   other threads using the texture since there are no other live refs.
    if (mPendingSemaphore != VK_NULL_HANDLE) {
        ToBackend(GetDevice())->GetFencedDeleter()->DeleteWhenUnused(mPendingSemaphore);
        mPendingSemaphore = VK_NULL_HANDLE;
    }
    Texture::DestroyImpl(reason);
}

void ImportedTextureBase::TransitionEagerlyForExport(CommandRecordingContext* recordingContext) {
    mExternalState = ExternalState::EagerlyTransitioned;

    // Get any usage, ideally the last one to do nothing
    DAWN_ASSERT(GetNumMipLevels() == 1 && GetArrayLayers() == 1);
    const SubresourceRange range = {GetDisjointVulkanAspects(), {0, 1}, {0, 1}};
    const TextureSyncInfo syncInfo = mSubresourceLastSyncInfos.Get(range.aspects, 0, 0);

    std::vector<VkImageMemoryBarrier> barriers;
    VkPipelineStageFlags srcStages = 0;
    VkPipelineStageFlags dstStages = 0;

    // Same usage as last.
    TransitionUsageAndGetResourceBarrier(syncInfo.usage, syncInfo.shaderStages, range, &barriers,
                                         &srcStages, &dstStages);

    DAWN_ASSERT(barriers.size() == 1);
    VkImageMemoryBarrier& barrier = barriers[0];
    // The barrier must be paired with another barrier that will specify the dst access mask on the
    // importing queue.
    barrier.dstAccessMask = 0;

    if (mDesiredExportLayout != VK_IMAGE_LAYOUT_UNDEFINED) {
        barrier.newLayout = mDesiredExportLayout;
    }

    Device* device = ToBackend(GetDevice());
    barrier.srcQueueFamilyIndex = device->GetGraphicsQueueFamily();
    barrier.dstQueueFamilyIndex = mExportQueueFamilyIndex;

    // We don't know when the importing queue will need the texture, so pass
    // VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT to ensure the barrier happens-before any usage in the
    // importing queue.
    dstStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;

    device->fn.CmdPipelineBarrier(recordingContext->commandBuffer, srcStages, dstStages, 0, 0,
                                  nullptr, 0, nullptr, 1, &barrier);
}

bool ImportedTextureBase::CanReuseWithoutBarrier(wgpu::TextureUsage lastUsage,
                                                 wgpu::TextureUsage usage,
                                                 wgpu::ShaderStage lastShaderStage,
                                                 wgpu::ShaderStage shaderStage) {
    return mLastExternalState == mExternalState &&
           Texture::CanReuseWithoutBarrier(lastUsage, usage, lastShaderStage, shaderStage);
}

void ImportedTextureBase::TweakTransition(CommandRecordingContext* recordingContext,
                                          std::vector<VkImageMemoryBarrier>* barriers,
                                          size_t transitionBarrierStart) {
    DAWN_ASSERT(GetNumMipLevels() == 1 && GetArrayLayers() == 1);

    mLastSharedTextureMemoryUsageSerial = GetDevice()->GetQueue()->GetPendingCommandSerial();

    // transitionBarrierStart specify the index where barriers for current transition start in
    // the vector. barriers->size() - transitionBarrierStart is the number of barriers that we
    // have already added into the vector during current transition.
    DAWN_ASSERT(barriers->size() - transitionBarrierStart <= 1);

    if (mExternalState == ExternalState::PendingAcquire ||
        mExternalState == ExternalState::EagerlyTransitioned) {
        recordingContext->specialSyncTextures.insert(this);
        if (barriers->size() == transitionBarrierStart) {
            barriers->push_back(BuildMemoryBarrier(
                this, wgpu::TextureUsage::None, wgpu::TextureUsage::None,
                SubresourceRange::SingleMipAndLayer(0, 0, GetDisjointVulkanAspects())));
        }

        VkImageMemoryBarrier* barrier = &(*barriers)[transitionBarrierStart];
        // Transfer texture from external queue to graphics queue
        barrier->srcQueueFamilyIndex = mExportQueueFamilyIndex;
        barrier->dstQueueFamilyIndex = ToBackend(GetDevice())->GetGraphicsQueueFamily();

        // srcAccessMask means nothing when importing. Queue transfers require a barrier on
        // both the importing and exporting queues. The exporting queue should have specified
        // this.
        barrier->srcAccessMask = 0;

        // Save the desired layout. We may need to transition through an intermediate
        // |mPendingAcquireLayout| first.
        VkImageLayout desiredLayout = barrier->newLayout;

        if (mExternalState == ExternalState::PendingAcquire) {
            bool isInitialized = IsSubresourceContentInitialized(GetAllSubresources());

            // We don't care about the pending old layout if the texture is uninitialized. The
            // driver is free to discard it. Also it is invalid to transition to layout UNDEFINED or
            // PREINITIALIZED. If the embedder provided no new layout, or we don't care about the
            // previous contents, we can skip the layout transition.
            // https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-VkImageMemoryBarrier-newLayout-01198
            if (!isInitialized || mPendingAcquireNewLayout == VK_IMAGE_LAYOUT_UNDEFINED ||
                mPendingAcquireNewLayout == VK_IMAGE_LAYOUT_PREINITIALIZED) {
                barrier->oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
                barrier->newLayout = desiredLayout;
            } else {
                barrier->oldLayout = mPendingAcquireOldLayout;
                barrier->newLayout = mPendingAcquireNewLayout;
            }
        } else {
            // In case of ExternalState::EagerlyTransitioned, the layouts of the texture's queue
            // release were always same. So we exactly match that here for the queue acquire.
            // The spec text:
            // If the transfer is via an image memory barrier, and an image layout transition is
            // desired, then the values of oldLayout and newLayout in the release operation's memory
            // barrier must be equal to values of oldLayout and newLayout in the acquire operation's
            // memory barrier.
            barrier->newLayout = barrier->oldLayout;
        }

        // If these are unequal, we need an another barrier to transition the layout.
        if (barrier->newLayout != desiredLayout) {
            VkImageMemoryBarrier layoutBarrier;
            layoutBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
            layoutBarrier.pNext = nullptr;
            layoutBarrier.image = GetHandle();
            layoutBarrier.subresourceRange = barrier->subresourceRange;

            // Transition from the acquired new layout to the desired layout.
            layoutBarrier.oldLayout = barrier->newLayout;
            layoutBarrier.newLayout = desiredLayout;

            layoutBarrier.srcAccessMask = 0;
            layoutBarrier.dstAccessMask = barrier->dstAccessMask;
            layoutBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
            layoutBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;

            barriers->push_back(layoutBarrier);
        }

        mExternalState = ExternalState::Acquired;
    }

    mLastExternalState = mExternalState;
}

MaybeError ImportedTextureBase::EndAccess(ExternalSemaphoreHandle* handle,
                                          VkImageLayout* releasedOldLayout,
                                          VkImageLayout* releasedNewLayout) {
    DAWN_ASSERT(GetNumMipLevels() == 1 && GetArrayLayers() == 1);

    // Release the texture
    mExternalState = ExternalState::Released;

    // Compute the layouts for the queue transition for export. desiredLayout == UNDEFINED is a tag
    // value used to export with whatever the current layout is. However queue transitioning to the
    // UNDEFINED layout is disallowed so we handle the case where currentLayout is UNDEFINED by
    // promoting to GENERAL.
    VkImageLayout currentLayout = GetCurrentLayout(GetDisjointVulkanAspects());
    VkImageLayout targetLayout;
    if (currentLayout != VK_IMAGE_LAYOUT_UNDEFINED) {
        targetLayout = currentLayout;
    } else {
        targetLayout = VK_IMAGE_LAYOUT_GENERAL;
    }

    // We have to manually trigger a transition if the texture hasn't been actually used or if we
    // need a layout transition.
    // TODO(dawn:1509): Avoid the empty submit.
    if (mExternalSemaphoreHandle == kNullExternalSemaphoreHandle || targetLayout != currentLayout) {
        mDesiredExportLayout = targetLayout;

        Queue* queue = ToBackend(GetDevice()->GetQueue());
        CommandRecordingContext* recordingContext = queue->GetPendingRecordingContext();
        recordingContext->specialSyncTextures.insert(this);
        DAWN_TRY(queue->SubmitPendingCommands());

        currentLayout = targetLayout;
    }
    DAWN_ASSERT(mExternalSemaphoreHandle != kNullExternalSemaphoreHandle);

    // Write out the layouts and signal semaphore
    *releasedOldLayout = currentLayout;
    *releasedNewLayout = targetLayout;
    *handle = mExternalSemaphoreHandle;
    mExternalSemaphoreHandle = kNullExternalSemaphoreHandle;
    return {};
}

MaybeError ImportedTextureBase::OnBeforeSubmit(CommandRecordingContext* context) {
    // On every submit using an exportable texture, we eagerly prepare for an export. This avoids
    // the need to schedule an almost empty submit just for exporting later. To export we both need
    // to transition the resource to export it, and need an exportable semaphore for future
    // synchronization.
    TransitionEagerlyForExport(context);

    // Create the external semaphore and add it to be signaled, but only mark it pending: if
    // anything fails during the submit we still keep the previously signaled exportable semaphore.
    // Note that VUID-VkSemaphoreGetFdInfoKHR-handleType-01135 requires that only signaled
    // semaphores be exported, so the export must be done in OnAfterSubmit.
    auto semaphoreService = ToBackend(GetDevice())->GetExternalSemaphoreService();
    DAWN_TRY_ASSIGN(mPendingSemaphore, semaphoreService->CreateExportableSemaphore());
    context->signalSemaphores.push_back(mPendingSemaphore);

    return {};
}

MaybeError ImportedTextureBase::OnAfterSubmit() {
    Device* device = ToBackend(GetDevice());

    // The submit succeeded, we can replace the previous external semaphore with the pending one.
    if (mExternalSemaphoreHandle != kNullExternalSemaphoreHandle) {
        device->GetExternalSemaphoreService()->CloseHandle(mExternalSemaphoreHandle);
        // Set the handle to null after it's been closed so that we don't accidentally attempt to
        // use or close it again if the ExportSemaphore() below fails.
        mExternalSemaphoreHandle = kNullExternalSemaphoreHandle;
    }

    DAWN_TRY_ASSIGN(mExternalSemaphoreHandle,
                    device->GetExternalSemaphoreService()->ExportSemaphore(mPendingSemaphore));
    ToBackend(GetDevice())->GetFencedDeleter()->DeleteWhenUnused(mPendingSemaphore);
    mPendingSemaphore = VK_NULL_HANDLE;

    return {};
}

//
// ExternalVkImageTexture
//

// static
ResultOrError<Ref<ExternalVkImageTexture>> ExternalVkImageTexture::Create(
    Device* device,
    const ExternalImageDescriptorVk* descriptor,
    const UnpackedPtr<TextureDescriptor>& textureDescriptor,
    external_memory::Service* externalMemoryService) {
    Ref<ExternalVkImageTexture> texture =
        AcquireRef(new ExternalVkImageTexture(device, textureDescriptor));
    DAWN_TRY(texture->Initialize(descriptor, externalMemoryService));
    return texture;
}

void ExternalVkImageTexture::DestroyImpl(DestroyReason reason) {
    // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
    // - It may be called if the texture is explicitly destroyed with APIDestroy.
    //   This case is NOT thread-safe and needs proper synchronization with other
    //   simultaneous uses of the texture.
    // - It may be called when the last ref to the texture is dropped and the texture
    //   is implicitly destroyed. This case is thread-safe because there are no
    //   other threads using the texture since there are no other live refs.
    Device* device = ToBackend(GetDevice());

    device->GetFencedDeleter()->DeleteWhenUnused(mHandle);
    mHandle = VK_NULL_HANDLE;

    if (mExternalAllocation != VK_NULL_HANDLE) {
        device->GetFencedDeleter()->DeleteWhenUnused(mExternalAllocation);
        mExternalAllocation = VK_NULL_HANDLE;
    }

    ImportedTextureBase::DestroyImpl(reason);
}

MaybeError ExternalVkImageTexture::Initialize(const ExternalImageDescriptorVk* descriptor,
                                              external_memory::Service* externalMemoryService) {
    Device* device = ToBackend(GetDevice());
    VkFormat format = VulkanImageFormat(device, GetFormat().format);
    VkImageUsageFlags usage = VulkanImageUsage(device, GetInternalUsage(), GetFormat());

    [[maybe_unused]] bool supportsDisjoint;
    DAWN_INVALID_IF(
        !externalMemoryService->SupportsCreateImage(descriptor, format, usage, &supportsDisjoint),
        "Creating an image from external memory is not supported.");
    // The creation of mSubresourceLastUsage assumes that multi-planar are always disjoint and sets
    // the combined aspect without checking for disjoint support.
    // TODO(dawn:1548): Support multi-planar images with the DISJOINT feature and potentially allow
    // acting on planes individually? Always using Color is valid even for disjoint images.
    DAWN_ASSERT(!GetFormat().IsMultiPlanar() || mCombinedAspect == Aspect::Color);

    mExternalState = ExternalState::PendingAcquire;
    mLastExternalState = ExternalState::Released;
    mExportQueueFamilyIndex = externalMemoryService->GetQueueFamilyIndex(descriptor->GetType());

    mPendingAcquireOldLayout = descriptor->releasedOldLayout;
    mPendingAcquireNewLayout = descriptor->releasedNewLayout;

    VkImageCreateInfo baseCreateInfo = {};
    FillVulkanCreateInfoSizesAndType(*this, &baseCreateInfo);
    baseCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
    baseCreateInfo.format = format;
    baseCreateInfo.usage = usage;
    baseCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
    baseCreateInfo.queueFamilyIndexCount = 0;
    baseCreateInfo.pQueueFamilyIndices = nullptr;

    // Zero initialization and various workarounds need to copy to the images. Always add
    // VK_IMAGE_USAGE_TRANSFER_DST_BIT to allow for these use cases, unless the image is a transient
    // attachment and incompatible with transfers.
    if (!(GetInternalUsage() & wgpu::TextureUsage::TransientAttachment)) {
        baseCreateInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
    }

    VkImageFormatListCreateInfo imageFormatListInfo = {};
    PNextChainBuilder createInfoChain(&baseCreateInfo);
    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 : 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_ExternalVkImageTexture");

    return {};
}

MaybeError ExternalVkImageTexture::BindExternalMemory(const ExternalImageDescriptorVk* descriptor,
                                                      VkDeviceMemory externalMemoryAllocation,
                                                      std::vector<VkSemaphore> waitSemaphores) {
    Device* device = ToBackend(GetDevice());
    DAWN_TRY(CheckVkSuccess(
        device->fn.BindImageMemory(device->GetVkDevice(), mHandle, externalMemoryAllocation, 0),
        "BindImageMemory (external)"));

    // Don't clear imported texture if already initialized
    if (descriptor->isInitialized) {
        SetIsSubresourceContentInitialized(true, GetAllSubresources());
    }

    // Success, acquire all the external objects.
    mExternalAllocation = externalMemoryAllocation;
    mWaitRequirements = std::move(waitSemaphores);
    return {};
}

MaybeError ExternalVkImageTexture::ExportExternalTexture(VkImageLayout desiredLayout,
                                                         ExternalSemaphoreHandle* handle,
                                                         VkImageLayout* releasedOldLayout,
                                                         VkImageLayout* releasedNewLayout) {
    DAWN_INVALID_IF(mExternalState == ExternalState::Released,
                    "Can't export a signal semaphore from signaled texture %s.", this);

    DAWN_INVALID_IF(mExternalAllocation == VK_NULL_HANDLE,
                    "Can't export a signal semaphore from destroyed or non-external texture %s.",
                    this);

    DAWN_INVALID_IF(desiredLayout != VK_IMAGE_LAYOUT_UNDEFINED,
                    "desiredLayout (%d) was not VK_IMAGE_LAYOUT_UNDEFINED", desiredLayout);

    DAWN_TRY(EndAccess(handle, releasedOldLayout, releasedNewLayout));

    // Destroy the texture so it can't be used again
    Destroy();
    return {};
}

MaybeError ExternalVkImageTexture::OnBeforeSubmit(CommandRecordingContext* context) {
    context->waitSemaphores.insert(context->waitSemaphores.end(), mWaitRequirements.begin(),
                                   mWaitRequirements.end());
    mWaitRequirements.clear();
    return ImportedTextureBase::OnBeforeSubmit(context);
}

//
// SharedTexture
//

// static
ResultOrError<Ref<SharedTexture>> SharedTexture::Create(
    SharedTextureMemory* memory,
    const UnpackedPtr<TextureDescriptor>& textureDescriptor) {
    Ref<SharedTexture> texture =
        AcquireRef(new SharedTexture(ToBackend(memory->GetDevice()), textureDescriptor));
    texture->Initialize(memory);
    return texture;
}

void SharedTexture::DestroyImpl(DestroyReason reason) {
    // TODO(crbug.com/dawn/831): DestroyImpl is called from two places.
    // - It may be called if the texture is explicitly destroyed with APIDestroy.
    //   This case is NOT thread-safe and needs proper synchronization with other
    //   simultaneous uses of the texture.
    // - It may be called when the last ref to the texture is dropped and the texture
    //   is implicitly destroyed. This case is thread-safe because there are no
    //   other threads using the texture since there are no other live refs.
    mSharedTextureMemoryObjects = {};

    ImportedTextureBase::DestroyImpl(reason);
}

void SharedTexture::Initialize(SharedTextureMemory* memory) {
    mSharedResourceMemoryContents = memory->GetContents();
    mSharedTextureMemoryObjects = {memory->GetVkImage(), memory->GetVkDeviceMemory()};
    mHandle = mSharedTextureMemoryObjects.vkImage->Get();
    mExportQueueFamilyIndex = memory->GetQueueFamilyIndex();
}

void SharedTexture::SetPendingAcquire(VkImageLayout pendingAcquireOldLayout,
                                      VkImageLayout pendingAcquireNewLayout) {
    DAWN_ASSERT(GetSharedResourceMemoryContents() != nullptr);
    mExternalState = ExternalState::PendingAcquire;
    mLastExternalState = ExternalState::PendingAcquire;

    mPendingAcquireOldLayout = pendingAcquireOldLayout;
    mPendingAcquireNewLayout = pendingAcquireNewLayout;
}

MaybeError SharedTexture::OnBeforeSubmit(CommandRecordingContext* context) {
    Device* device = ToBackend(GetDevice());
    SharedResourceMemoryContents* contents = GetSharedResourceMemoryContents();

    SharedTextureMemoryBase::PendingFenceList fences;
    contents->AcquirePendingFences(&fences);

    for (const auto& fence : fences) {
        // All semaphores are binary semaphores.
        DAWN_ASSERT(fence.signaledValue == 1u);
        ExternalSemaphoreHandle semaphoreHandle = ToBackend(fence.object)->GetHandle().Get();

        VkSemaphore semaphore;
        DAWN_TRY_ASSIGN(semaphore,
                        device->GetExternalSemaphoreService()->ImportSemaphore(semaphoreHandle));
        context->waitSemaphores.push_back(semaphore);
    }

    return ImportedTextureBase::OnBeforeSubmit(context);
}

//
// TextureView
//

// static
ResultOrError<Ref<TextureView>> TextureView::Create(
    TextureBase* texture,
    uint64_t textureViewId,
    const UnpackedPtr<TextureViewDescriptor>& descriptor) {
    Ref<TextureView> view = AcquireRef(new TextureView(texture, textureViewId, descriptor));
    DAWN_TRY(view->Initialize(descriptor));
    return view;
}

MaybeError TextureView::Initialize(const UnpackedPtr<TextureViewDescriptor>& descriptor) {
    if ((GetInternalUsage() & ~(wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst)) == 0) {
        // If the texture view has no other usage than CopySrc and CopyDst, then it can't
        // actually be used as a render pass attachment or sampled/storage texture. The Vulkan
        // validation errors warn if you create such a vkImageView, so return early.
        return {};
    }

    // Texture could be destroyed by the time we make a view.
    if (GetTexture()->IsDestroyed()) {
        return {};
    }

    Device* device = ToBackend(GetTexture()->GetDevice());
    VkImageViewCreateInfo createInfo = GetCreateInfo(descriptor->format, descriptor->dimension);

    VkImageViewUsageCreateInfo usageInfo = {};
    usageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO;
    usageInfo.usage = VulkanImageUsage(device, GetInternalUsage(), GetFormat());
    createInfo.pNext = &usageInfo;

    // Compute and link the VkSamplerYCbCrConversion, if any is needed.
    VkSamplerYcbcrConversionInfo samplerYCbCrInfo = {};
    if (GetTexture()->GetFormat().format == wgpu::TextureFormat::OpaqueYCbCrAndroid) {
        auto stmContents = static_cast<SharedTextureMemoryContentsVk*>(
            GetTexture()->GetSharedResourceMemoryContents());
        mIsYCbCrFilterable = stmContents->IsYCbCrFilterable();

        YCbCrVkDescriptor yCbCr;
        if (auto* yCbCrVkDescriptor = descriptor.Get<YCbCrVkDescriptor>()) {
            // The YCbCr conversion can be specified by the user with the YCbCrVulkanSamplers
            // feature.
            DAWN_ASSERT(device->HasFeature(Feature::YCbCrVulkanSamplers));

            yCbCr = yCbCrVkDescriptor->WithTrivialFrontendDefaults();
            yCbCr.nextInChain = nullptr;
        } else {
            // When using OpaqueYCbCrAndroidForExternalTexture, the YCbCr conversion is the same one
            // that's going to be used for the ExternalTexture static samplers.
            // TODO(https://crbug.com/497675620): Specialize the conversion at the same time as all
            // the other state, in order to take advantage of hardware YCbCr to RGB conversion when
            // present.
            DAWN_ASSERT(device->HasFeature(Feature::OpaqueYCbCrAndroidForExternalTexture));

            yCbCr = StaticSamplerSpecialization::GetYCbCrForTextureView(
                static_cast<VkFormat>(stmContents->GetYCbCrVkDesc().vkFormat),
                stmContents->GetYCbCrVkDesc().externalFormat);
        }

        // Create the VkSamplerYCbCrConversion and link it in the createInfo.
        DAWN_TRY_ASSIGN(mSamplerYCbCrConversion, CreateSamplerYCbCrConversion(device, yCbCr));

        samplerYCbCrInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO;
        samplerYCbCrInfo.pNext = nullptr;
        samplerYCbCrInfo.conversion = mSamplerYCbCrConversion;
        createInfo.pNext = &samplerYCbCrInfo;
    } else {
        DAWN_ASSERT(!descriptor.Has<YCbCrVkDescriptor>());
    }

    DAWN_TRY(CheckVkSuccess(
        device->fn.CreateImageView(device->GetVkDevice(), &createInfo, nullptr, &*mHandle),
        "CreateImageView"));

    // We should create an image view with format RGBA8Unorm on the BGRA8Unorm texture when the
    // texture is used as storage texture. See http://crbug.com/dawn/1641 for more details.
    if (createInfo.format == VK_FORMAT_B8G8R8A8_UNORM &&
        (GetTexture()->GetInternalUsage() & wgpu::TextureUsage::StorageBinding)) {
        createInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
        DAWN_TRY(CheckVkSuccess(device->fn.CreateImageView(device->GetVkDevice(), &createInfo,
                                                           nullptr, &*mHandleForBGRA8UnormStorage),
                                "CreateImageView for BGRA8Unorm storage"));
    }

    SetLabelImpl();

    return {};
}

TextureView::TextureView(TextureBase* texture,
                         uint64_t textureViewId,
                         const UnpackedPtr<TextureViewDescriptor>& descriptor)
    : TextureViewBase(texture, descriptor), mTextureViewId(textureViewId) {}
TextureView::~TextureView() {}

void TextureView::DestroyImpl(DestroyReason reason) {
    Device* device = ToBackend(GetTexture()->GetDevice());

    if (mSamplerYCbCrConversion != VK_NULL_HANDLE) {
        device->GetFencedDeleter()->DeleteWhenUnused(mSamplerYCbCrConversion);
        mSamplerYCbCrConversion = VK_NULL_HANDLE;
    }

    if (mHandle != VK_NULL_HANDLE) {
        device->GetFencedDeleter()->DeleteWhenUnused(mHandle);
        mHandle = VK_NULL_HANDLE;
    }

    if (mHandleForBGRA8UnormStorage != VK_NULL_HANDLE) {
        device->GetFencedDeleter()->DeleteWhenUnused(mHandleForBGRA8UnormStorage);
        mHandleForBGRA8UnormStorage = VK_NULL_HANDLE;
    }

    for (auto& handle : mHandlesFor2DViewOn3D) {
        if (handle != VK_NULL_HANDLE) {
            device->GetFencedDeleter()->DeleteWhenUnused(handle);
            handle = VK_NULL_HANDLE;
        }
    }
}

VkImageView TextureView::GetHandle() const {
    return mHandle;
}

VkImageView TextureView::GetHandleForBGRA8UnormStorage() const {
    return mHandleForBGRA8UnormStorage;
}

VkImageViewCreateInfo TextureView::GetCreateInfo(wgpu::TextureFormat format,
                                                 wgpu::TextureViewDimension dimension,
                                                 uint32_t depthSlice) const {
    Device* device = ToBackend(GetTexture()->GetDevice());

    VkImageViewCreateInfo createInfo;
    createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
    createInfo.pNext = nullptr;
    createInfo.flags = 0;
    createInfo.image = ToBackend(GetTexture())->GetHandle();
    createInfo.viewType = VulkanImageViewType(dimension);

    const Format& textureFormat = GetTexture()->GetFormat();
    if (textureFormat.HasStencil() &&
        (textureFormat.HasDepth() || !device->IsToggleEnabled(Toggle::VulkanUseS8))) {
        // Unlike multi-planar formats, depth-stencil formats have multiple aspects but are not
        // created with VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT.
        // https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkImageViewCreateInfo.html#VUID-VkImageViewCreateInfo-image-01762
        // Without, VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, the view format must match the texture
        // format.
        createInfo.format = VulkanImageFormat(device, textureFormat.format);
    } else {
        createInfo.format = VulkanImageFormat(device, format);
    }

    bool isDepthOrStencilFormat = GetTexture()->GetFormat().HasDepthOrStencil();
    const wgpu::TextureComponentSwizzle kDefaultSwizzle =
        isDepthOrStencilFormat ? kR001Swizzle : kRGBASwizzle;

    auto swizzle = ComposeSwizzle(kDefaultSwizzle, GetSwizzle());
    if (swizzle == kDefaultSwizzle) {
        // We must use identity swizzle for render views.
        // https://docs.vulkan.org/spec/latest/chapters/renderpass.html#VUID-VkFramebufferCreateInfo-pAttachments-00884
        createInfo.components = VkComponentMapping{VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G,
                                                   VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A};
    } else {
        MaybeConvertDepthStencilSwizzleOneToAlpha(isDepthOrStencilFormat, device->GetDeviceInfo(),
                                                  &swizzle);
        createInfo.components = VkComponentMapping{
            VulkanComponentSwizzle(swizzle.r), VulkanComponentSwizzle(swizzle.g),
            VulkanComponentSwizzle(swizzle.b), VulkanComponentSwizzle(swizzle.a)};
    }

    const SubresourceRange& subresources = GetSubresourceRange();
    createInfo.subresourceRange.baseMipLevel = subresources.baseMipLevel;
    createInfo.subresourceRange.levelCount = subresources.levelCount;
    createInfo.subresourceRange.baseArrayLayer = subresources.baseArrayLayer + depthSlice;
    createInfo.subresourceRange.layerCount = subresources.layerCount;
    createInfo.subresourceRange.aspectMask = VulkanAspectMask(subresources.aspects);

    return createInfo;
}

ResultOrError<VkImageView> TextureView::GetOrCreate2DViewOn3D(uint32_t depthSlice) {
    DAWN_ASSERT(GetTexture()->GetDimension() == wgpu::TextureDimension::e3D);
    DAWN_ASSERT(depthSlice < GetSingleSubresourceVirtualSize().depthOrArrayLayers);

    if (mHandlesFor2DViewOn3D.empty()) {
        mHandlesFor2DViewOn3D.resize(GetSingleSubresourceVirtualSize().depthOrArrayLayers);
    }

    if (mHandlesFor2DViewOn3D[depthSlice] != VK_NULL_HANDLE) {
        return static_cast<VkImageView>(mHandlesFor2DViewOn3D[depthSlice]);
    }

    Device* device = ToBackend(GetTexture()->GetDevice());
    VkImageViewCreateInfo createInfo =
        GetCreateInfo(GetFormat().format, wgpu::TextureViewDimension::e2D, depthSlice);

    VkImageView view;
    DAWN_TRY(CheckVkSuccess(
        device->fn.CreateImageView(device->GetVkDevice(), &createInfo, nullptr, &*view),
        "CreateImageView for 2D view on 3D image"));

    mHandlesFor2DViewOn3D[depthSlice] = view;

    return view;
}

bool TextureView::IsYCbCrFilterable() const {
    DAWN_ASSERT(IsYCbCr());
    return mIsYCbCrFilterable;
}

VkImageLayout TextureView::VulkanImageLayout(wgpu::TextureUsage usage) const {
    return dawn::native::vulkan::VulkanImageLayout(GetFormat(), usage, GetUsage());
}

void TextureView::SetLabelImpl() {
    SetDebugName(ToBackend(GetDevice()), mHandle, "Dawn_TextureView", GetLabel());
}

}  // namespace dawn::native::vulkan
