// Copyright 2017 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/d3d12/TextureD3D12.h"

#include <algorithm>
#include <bit>
#include <iterator>
#include <utility>

#include "src/dawn/common/Constants.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/IntegerTypes.h"
#include "src/dawn/native/ResourceMemoryAllocation.h"
#include "src/dawn/native/ToBackend.h"
#include "src/dawn/native/d3d/D3DError.h"
#include "src/dawn/native/d3d/KeyedMutex.h"
#include "src/dawn/native/d3d12/BufferD3D12.h"
#include "src/dawn/native/d3d12/CommandRecordingContext.h"
#include "src/dawn/native/d3d12/DeviceD3D12.h"
#include "src/dawn/native/d3d12/Forward.h"
#include "src/dawn/native/d3d12/HeapD3D12.h"
#include "src/dawn/native/d3d12/QueueD3D12.h"
#include "src/dawn/native/d3d12/ResourceAllocatorManagerD3D12.h"
#include "src/dawn/native/d3d12/SharedFenceD3D12.h"
#include "src/dawn/native/d3d12/SharedTextureMemoryD3D12.h"
#include "src/dawn/native/d3d12/StagingDescriptorAllocatorD3D12.h"
#include "src/dawn/native/d3d12/TextureCopySplitter.h"
#include "src/dawn/native/d3d12/UtilsD3D12.h"
#include "src/dawn/native/utils/RenderDoc.h"
#include "src/utils/compiler.h"

namespace dawn::native::d3d12 {

namespace {

D3D12_RESOURCE_STATES D3D12TextureUsage(wgpu::TextureUsage usage, const Format& format) {
    D3D12_RESOURCE_STATES resourceState = D3D12_RESOURCE_STATE_COMMON;

    // D3D12 doesn't need special acquire operations for presentable textures.
    DAWN_ASSERT(!(usage & kPresentAcquireTextureUsage));
    if (usage & kPresentReleaseTextureUsage) {
        // The present usage is only used internally by the swapchain and is never used in
        // combination with other usages.
        DAWN_ASSERT(usage == kPresentReleaseTextureUsage);
        return D3D12_RESOURCE_STATE_PRESENT;
    }

    if (usage & wgpu::TextureUsage::CopySrc) {
        resourceState |= D3D12_RESOURCE_STATE_COPY_SOURCE;
    }
    if (usage & wgpu::TextureUsage::CopyDst) {
        resourceState |= D3D12_RESOURCE_STATE_COPY_DEST;
    }
    if (usage & (wgpu::TextureUsage::TextureBinding | kReadOnlyStorageTexture)) {
        resourceState |= D3D12_RESOURCE_STATE_ALL_SHADER_RESOURCE;
    }
    if (usage & (wgpu::TextureUsage::StorageBinding | kWriteOnlyStorageTexture)) {
        resourceState |= D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
    }
    if (usage & wgpu::TextureUsage::RenderAttachment) {
        if (usage & kResolveAttachmentLoadingUsage) {
            // Special case for MSAA resolve operations: the resolve target needs to be accessible
            // as a shader resource during expanding step. Resolve target is also not a render
            // target in D3D12 term so we can skip assigning the render target stage. Note: this is
            // important because if we assigned both shader resource and render target state, D3D12
            // would complain that it's an invalid combination of states.
            resourceState |= D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
        } else if (format.HasDepthOrStencil()) {
            resourceState |= D3D12_RESOURCE_STATE_DEPTH_WRITE;
        } else {
            resourceState |= D3D12_RESOURCE_STATE_RENDER_TARGET;
        }
    }

    if (usage & kReadOnlyRenderAttachment) {
        // There is no STENCIL_READ state. Readonly for stencil is bundled with DEPTH_READ.
        resourceState |= D3D12_RESOURCE_STATE_DEPTH_READ;
    }

    return resourceState;
}

D3D12_RESOURCE_FLAGS D3D12ResourceFlags(wgpu::TextureUsage usage, const Format& format) {
    D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE;

    // Always set the depth stencil flags on depth stencil format textures since some formats like
    // D24_UNORM_S8_UINT don't support copying for lazy clear. Also, setting depth stencil flags
    // precludes setting other resource flags like render target or unordered access.
    if (format.HasDepthOrStencil()) {
        flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
    } else {
        if (usage & wgpu::TextureUsage::RenderAttachment) {
            flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
        }
        if (usage & wgpu::TextureUsage::StorageBinding) {
            flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
        }
    }

    DAWN_ASSERT(!(flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) ||
                flags == D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL);
    return flags;
}

D3D12_RESOURCE_DIMENSION D3D12TextureDimension(wgpu::TextureDimension dimension) {
    switch (dimension) {
        case wgpu::TextureDimension::e1D:
            return D3D12_RESOURCE_DIMENSION_TEXTURE1D;
        case wgpu::TextureDimension::e2D:
            return D3D12_RESOURCE_DIMENSION_TEXTURE2D;
        case wgpu::TextureDimension::e3D:
            return D3D12_RESOURCE_DIMENSION_TEXTURE3D;
        case wgpu::TextureDimension::Undefined:
        default:
            DAWN_UNREACHABLE();
    }
}

ResourceHeapKind GetResourceHeapKind(D3D12_RESOURCE_FLAGS flags, uint32_t resourceHeapTier) {
    if (resourceHeapTier >= 2u) {
        return ResourceHeapKind::Default_AllBuffersAndTextures;
    }

    if ((flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) ||
        (flags & D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET)) {
        return ResourceHeapKind::Default_OnlyRenderableOrDepthTextures;
    }
    return ResourceHeapKind::Default_OnlyNonRenderableOrDepthTextures;
}

D3D12_SHADER_COMPONENT_MAPPING D3D12ComponentSwizzle(wgpu::ComponentSwizzle swizzle) {
    switch (swizzle) {
        case wgpu::ComponentSwizzle::Zero:
            return D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_0;
        case wgpu::ComponentSwizzle::One:
            return D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_1;
        case wgpu::ComponentSwizzle::R:
            return D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_0;
        case wgpu::ComponentSwizzle::G:
            return D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_1;
        case wgpu::ComponentSwizzle::B:
            return D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_2;
        case wgpu::ComponentSwizzle::A:
            return D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_3;

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

}  // namespace

// static
ResultOrError<Ref<Texture>> Texture::Create(Device* device,
                                            const UnpackedPtr<TextureDescriptor>& descriptor) {
    Ref<Texture> dawnTexture = AcquireRef(new Texture(device, descriptor));

    DAWN_INVALID_IF(dawnTexture->GetFormat().IsMultiPlanar(),
                    "Cannot create a multi-planar formatted texture directly");

    DAWN_TRY(dawnTexture->InitializeAsInternalTexture());
    return std::move(dawnTexture);
}

// static
ResultOrError<Ref<Texture>> Texture::CreateForSwapChain(
    Device* device,
    const UnpackedPtr<TextureDescriptor>& descriptor,
    ComPtr<ID3D12Resource> d3d12Texture,
    D3D12_RESOURCE_STATES state) {
    Ref<Texture> dawnTexture = AcquireRef(new Texture(device, descriptor));
    DAWN_TRY(dawnTexture->InitializeAsSwapChainTexture(std::move(d3d12Texture), state));
    return std::move(dawnTexture);
}

// static
ResultOrError<Ref<Texture>> Texture::CreateFromSharedTextureMemory(
    SharedTextureMemory* memory,
    const UnpackedPtr<TextureDescriptor>& descriptor) {
    Device* device = ToBackend(memory->GetDevice());
    Ref<Texture> texture = AcquireRef(new Texture(device, descriptor));
    DAWN_TRY(texture->InitializeAsExternalTexture(memory->GetD3DResource(), memory->GetKeyedMutex(),
                                                  {}, false));
    texture->mSharedResourceMemoryContents = memory->GetContents();
    return texture;
}

MaybeError Texture::InitializeAsExternalTexture(ComPtr<IUnknown> d3dTexture,
                                                Ref<d3d::KeyedMutex> keyedMutex,
                                                std::vector<FenceAndSignalValue> waitFences,
                                                bool isSwapChainTexture) {
    ComPtr<ID3D12Resource> d3d12Texture;
    DAWN_TRY(CheckHRESULT(d3dTexture.As(&d3d12Texture), "texture is not a valid ID3D12Resource"));

    D3D12_RESOURCE_DESC desc = d3d12Texture->GetDesc();
    mD3D12ResourceFlags = desc.Flags;
    DAWN_ASSERT(mD3D12ResourceFlags & D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS);

    AllocationInfo info;
    info.mMethod = AllocationMethod::kExternal;
    // When creating the ResourceHeapAllocation, the resource heap is set to nullptr because the
    // texture is owned externally. The texture's owning entity must remain responsible for
    // memory management.
    mResourceAllocation = {info, 0, std::move(d3d12Texture), nullptr,
                           ResourceHeapKind::InvalidEnum};
    mKeyedMutex = std::move(keyedMutex);
    mWaitFences = std::move(waitFences);
    mIsExternalSwapChainTexture = isSwapChainTexture;

    SetLabelHelper("Dawn_ExternalTexture");

    return {};
}

MaybeError Texture::InitializeAsInternalTexture() {
    D3D12_RESOURCE_DESC resourceDescriptor;
    resourceDescriptor.Dimension = D3D12TextureDimension(GetDimension());
    resourceDescriptor.Alignment = 0;

    const Extent3D& size = GetBaseSize();
    resourceDescriptor.Width = size.width;
    resourceDescriptor.Height = size.height;
    resourceDescriptor.DepthOrArraySize = size.depthOrArrayLayers;

    Device* device = ToBackend(GetDevice());
    // When the depth stencil texture is created on a not-zeroed heap, its first usage will also be
    // copy destination when it is initialized with a non-zero value, which also triggered the issue
    // about copying data into a placed depth stencil texture with a dirty memory, so in this
    // situation the workaround should also be enabled.
    bool applyForceClearCopyableDepthStencilTextureOnCreationToggle =
        device->IsToggleEnabled(Toggle::D3D12ForceClearCopyableDepthStencilTextureOnCreation) &&
        GetFormat().HasDepthOrStencil() &&
        ((GetInternalUsage() & wgpu::TextureUsage::CopyDst) ||
         (device->IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting) &&
          device->IsToggleEnabled(Toggle::D3D12CreateNotZeroedHeap)));
    if (applyForceClearCopyableDepthStencilTextureOnCreationToggle) {
        AddInternalUsage(wgpu::TextureUsage::RenderAttachment);
    }

    // This will need to be much more nuanced when WebGPU has
    // texture view compatibility rules.
    const bool needsTypelessFormat =
        (GetDevice()->IsToggleEnabled(Toggle::D3D12AlwaysUseTypelessFormatsForCastableTexture) &&
         GetViewFormats().any()) ||
        (GetFormat().HasDepthOrStencil() &&
         (GetInternalUsage() & wgpu::TextureUsage::TextureBinding) != 0);

    DXGI_FORMAT dxgiFormat = needsTypelessFormat
                                 ? d3d::DXGITypelessTextureFormat(device, GetFormat().format)
                                 : d3d::DXGITextureFormat(device, GetFormat().format);

    resourceDescriptor.MipLevels = static_cast<UINT16>(GetNumMipLevels());
    resourceDescriptor.Format = dxgiFormat;
    resourceDescriptor.SampleDesc.Count = GetSampleCount();
    resourceDescriptor.SampleDesc.Quality = 0;
    resourceDescriptor.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
    resourceDescriptor.Flags = D3D12ResourceFlags(GetInternalUsage(), GetFormat());
    mD3D12ResourceFlags = resourceDescriptor.Flags;

    uint32_t bytesPerBlock = 0;
    if (GetFormat().IsColor()) {
        bytesPerBlock = GetFormat().GetAspectInfo(wgpu::TextureAspect::All).block.byteSize;
    }
    bool forceAllocateAsCommittedResource =
        (device->IsToggleEnabled(
            Toggle::DisableSubAllocationFor2DTextureWithCopyDstOrRenderAttachment)) &&
        GetDimension() == wgpu::TextureDimension::e2D &&
        (GetInternalUsage() & (wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment));

    ResourceHeapKind resourceHeapKind =
        GetResourceHeapKind(mD3D12ResourceFlags, ToBackend(GetDevice())->GetResourceHeapTier());
    DAWN_TRY_ASSIGN(
        mResourceAllocation,
        device->AllocateMemory(resourceHeapKind, resourceDescriptor, D3D12_RESOURCE_STATE_COMMON,
                               bytesPerBlock, forceAllocateAsCommittedResource));

    SetLabelImpl();

    if (applyForceClearCopyableDepthStencilTextureOnCreationToggle ||
        device->IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting)) {
        CommandRecordingContext* commandContext =
            ToBackend(device->GetQueue())->GetPendingCommandContext();
        ClearValue clearValue =
            device->IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting)
                ? ClearValue::NonZero
                : ClearValue::Zero;
        DAWN_TRY(ClearTexture(commandContext, GetAllSubresources(), clearValue));
    }

    return {};
}

MaybeError Texture::InitializeAsSwapChainTexture(ComPtr<ID3D12Resource> d3d12Texture,
                                                 D3D12_RESOURCE_STATES state) {
    AllocationInfo info;
    info.mMethod = AllocationMethod::kExternal;

    D3D12_RESOURCE_DESC desc = d3d12Texture->GetDesc();
    mD3D12ResourceFlags = desc.Flags;

    // Replace the default state of COMMON with what's passed in this constructor.
    mSubresourceStateAndDecay.Fill({state, kMaxExecutionSerial, false});

    // When creating the ResourceHeapAllocation, the resource heap is set to nullptr because the
    // texture is owned externally. The texture's owning entity must remain responsible for
    // memory management.
    mResourceAllocation = {info, 0, std::move(d3d12Texture), nullptr,
                           ResourceHeapKind::InvalidEnum};

    SetLabelHelper("Dawn_SwapChainTexture");

    return {};
}

Texture::Texture(Device* device, const UnpackedPtr<TextureDescriptor>& descriptor)
    : Base(device, descriptor), mSubresourceStateAndDecay(InitialSubresourceStateAndDecay()) {}

Texture::~Texture() = default;

void Texture::DestroyImpl(DestroyReason reason) {
    TextureBase::DestroyImpl(reason);
    ToBackend(GetDevice())->DeallocateMemory(mResourceAllocation);
    // Set mIsExternalSwapChainTexture to false to prevent ever calling
    // ID3D12SharingContract::Present again.
    mIsExternalSwapChainTexture = false;
}

MaybeError Texture::PinImpl(wgpu::TextureUsage usage) {
    DAWN_ASSERT(!HasPinnedUsage());
    SubresourceRange pinnedSubresources = GetAllSubresources();

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

    // TODO(crbug.com/482008255): Handle residency of pinned resources
    TrackUsageAndTransitionNow(commandContext, usage, 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?
}

DXGI_FORMAT Texture::GetD3D12Format() const {
    return d3d::DXGITextureFormat(GetDevice(), GetFormat().format);
}

ID3D12Resource* Texture::GetD3D12Resource() const {
    return mResourceAllocation.GetD3D12Resource();
}

D3D12_RESOURCE_FLAGS Texture::GetD3D12ResourceFlags() const {
    return mD3D12ResourceFlags;
}

DXGI_FORMAT Texture::GetD3D12CopyableSubresourceFormat(Aspect aspect) const {
    DAWN_ASSERT(GetFormat().aspects & aspect);

    wgpu::TextureFormat format = GetFormat().format;
    switch (format) {
        case wgpu::TextureFormat::Depth24PlusStencil8:
        case wgpu::TextureFormat::Depth32FloatStencil8:
        case wgpu::TextureFormat::Stencil8:
            switch (aspect) {
                case Aspect::Depth:
                    // The depth24 part of a D24_UNORM_S8_UINT texture cannot be copied with D3D and
                    // is also not supported by WebGPU.
                    // See https://gpuweb.github.io/gpuweb/#depth-formats
                    DAWN_ASSERT(format == wgpu::TextureFormat::Depth32FloatStencil8);
                    return DXGI_FORMAT_R32_FLOAT;
                case Aspect::Stencil:
                    return DXGI_FORMAT_R8_UINT;
                default:
                    DAWN_UNREACHABLE();
            }
        default:
            DAWN_ASSERT(HasOneBit(GetFormat().aspects));
            return GetD3D12Format();
    }
}

MaybeError Texture::SynchronizeTextureBeforeUse(CommandRecordingContext* commandContext) {
    Device* device = ToBackend(GetDevice());
    Queue* queue = ToBackend(device->GetQueue());

    // Perform the wait only on the first call.
    std::vector<FenceAndSignalValue> waitFences = std::move(mWaitFences);

    if (SharedResourceMemoryContents* contents = GetSharedResourceMemoryContents()) {
        SharedTextureMemoryBase::PendingFenceList fences;
        contents->AcquirePendingFences(&fences);
        waitFences.insert(waitFences.end(), std::make_move_iterator(fences.begin()),
                          std::make_move_iterator(fences.end()));
    }

    ID3D12CommandQueue* commandQueue = queue->GetCommandQueue();
    const auto& queueFence = ToBackend(device->GetQueue())->GetSharedFence();
    const ExecutionSerial queueSubmittedSerial = queue->GetLastSubmittedCommandSerial();
    for (const auto& fence : waitFences) {
        auto d3dFence = ToBackend(fence.object);
        if (d3dFence.Get() == queueFence.Get()) {
            // We don't need to wait on the fence that we signaled (self-wait).
            DAWN_CHECK(ExecutionSerial(fence.signaledValue) <= queueSubmittedSerial);
            continue;
        }

        DAWN_TRY(CheckHRESULT(commandQueue->Wait(d3dFence->GetD3DFence(), fence.signaledValue),
                              "D3D12 fence wait"););
        // Keep D3D12 fence alive until commands complete.
        device->ReferenceUntilUnused(d3dFence->GetD3DFence());
    }
    if (mKeyedMutex != nullptr) {
        DAWN_TRY(commandContext->AcquireKeyedMutex(mKeyedMutex));
    }
    mLastSharedTextureMemoryUsageSerial = queue->GetPendingCommandSerial();
    return {};
}

void Texture::NotifySwapChainPresent() {
    // When using an external swap chain texture, there's no way to determine frame boundaries since
    // Dawn isn't managing the DXGI swap chains. In this mode, external tools like PIX and RenderDoc
    // will wait forever for a present that never happens in D3D12. 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;
    }

    Device* device = ToBackend(GetDevice());

    // For PIX, call ID3D12SharingContract::Present
    ID3D12SharingContract* d3dSharingContract = ToBackend(device->GetQueue())->GetSharingContract();
    if (d3dSharingContract != nullptr) {
        d3dSharingContract->Present(mResourceAllocation.GetD3D12Resource(), 0, 0);
    }

#if defined(DAWN_ENABLE_RENDERDOC)
    // 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)) {
        // 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(device->GetD3D12Device(), NULL);
        renderDocApi->StartFrameCapture(device->GetD3D12Device(), NULL);
    }
#endif
}

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

void Texture::TrackUsageAndTransitionNow(CommandRecordingContext* commandContext,
                                         wgpu::TextureUsage usage,
                                         const SubresourceRange& range) {
    TrackUsageAndTransitionNow(commandContext, D3D12TextureUsage(usage, GetFormat()), range);
}

void Texture::TrackUsageAndTransitionNow(CommandRecordingContext* commandContext,
                                         D3D12_RESOURCE_STATES newState,
                                         const SubresourceRange& range) {
    if (mResourceAllocation.GetInfo().mMethod != AllocationMethod::kExternal) {
        // Track the underlying heap to ensure residency.
        Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap());
        commandContext->TrackHeapUsage(heap, GetDevice()->GetQueue()->GetPendingCommandSerial());
    }

    std::vector<D3D12_RESOURCE_BARRIER> barriers;

    int32_t aspectCount = std::popcount(static_cast<uint8_t>(range.aspects));
    barriers.reserve(range.levelCount * range.layerCount * aspectCount);

    TransitionUsageAndGetResourceBarrier(commandContext, &barriers, newState, range);
    if (barriers.size()) {
        commandContext->GetCommandList()->ResourceBarrier(static_cast<uint32_t>(barriers.size()),
                                                          barriers.data());
    }
}

void Texture::TransitionSubresourceRange(std::vector<D3D12_RESOURCE_BARRIER>* barriers,
                                         const SubresourceRange& range,
                                         StateAndDecay* state,
                                         D3D12_RESOURCE_STATES newState,
                                         ExecutionSerial pendingCommandSerial) const {
    D3D12_RESOURCE_STATES lastState = state->lastState;

    // If the transition is from-UAV-to-UAV, then a UAV barrier is needed.
    // If one of the usages isn't UAV, then other barriers are used.
    bool needsUAVBarrier = lastState == D3D12_RESOURCE_STATE_UNORDERED_ACCESS &&
                           newState == D3D12_RESOURCE_STATE_UNORDERED_ACCESS;

    if (needsUAVBarrier) {
        D3D12_RESOURCE_BARRIER barrier;
        barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
        barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
        barrier.UAV.pResource = GetD3D12Resource();
        barriers->push_back(barrier);
        return;
    }

    // Expand resource state transition to include COPY_SOURCE if the toggle is enabled. Needed to
    // workaround issues on Nvidia where transition to ALL_SHADER_RESOURCE doesn't seem to include
    // all the necessary cache flushes or layout transitions and causes rendering corruption.
    if (GetDevice()->IsToggleEnabled(
            Toggle::D3D12ExpandShaderResourceStateTransitionsToCopySource) &&
        (newState & D3D12_RESOURCE_STATE_ALL_SHADER_RESOURCE)) {
        newState |= D3D12_RESOURCE_STATE_COPY_SOURCE;
    }

    // Reuse the subresource(s) directly and avoid transition when it isn't needed.
    if (lastState == newState) {
        return;
    }

    // Update the tracked state.
    state->lastState = newState;

    // The COMMON state represents a state where no write operations can be pending, and
    // where all pixels are uncompressed. This makes it possible to transition to and
    // from some states without synchronization (i.e. without an explicit
    // ResourceBarrier call). Textures can be implicitly promoted to 1) a single write
    // state, or 2) multiple read states. Textures will implicitly decay to the COMMON
    // state when all of the following are true: 1) the texture is accessed on a command
    // list, 2) the ExecuteCommandLists call that uses that command list has ended, and
    // 3) the texture was promoted implicitly to a read-only state and is still in that
    // state.
    // https://docs.microsoft.com/en-us/windows/desktop/direct3d12/using-resource-barriers-to-synchronize-resource-states-in-direct3d-12#implicit-state-transitions

    // To track implicit decays, we must record the pending serial on which that
    // transition will occur. When that texture is used again, the previously recorded
    // serial must be compared to the last completed serial to determine if the texture
    // has implicity decayed to the common state.
    if (state->isValidToDecay && pendingCommandSerial > state->lastDecaySerial) {
        lastState = D3D12_RESOURCE_STATE_COMMON;
    }

    // All simultaneous-access textures are qualified for an implicit promotion.
    // Destination states that qualify for an implicit promotion for a
    // non-simultaneous-access texture: NON_PIXEL_SHADER_RESOURCE,
    // PIXEL_SHADER_RESOURCE, COPY_SRC, COPY_DEST.
    {
        const D3D12_RESOURCE_STATES kD3D12PromotableReadOnlyStates =
            D3D12_RESOURCE_STATE_COPY_SOURCE | D3D12_RESOURCE_STATE_ALL_SHADER_RESOURCE;

        if (lastState == D3D12_RESOURCE_STATE_COMMON) {
            if (IsSubset(newState, kD3D12PromotableReadOnlyStates) ||
                mD3D12ResourceFlags & D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS) {
                // Implicit texture state decays can only occur when the texture was implicitly
                // transitioned to a read-only state. isValidToDecay is needed to differentiate
                // between resources that were implicitly or explicitly transitioned to a
                // read-only state.
                state->isValidToDecay = true;
                state->lastDecaySerial = pendingCommandSerial;
                return;
            } else if (newState == D3D12_RESOURCE_STATE_COPY_DEST) {
                state->isValidToDecay = false;
                return;
            }
        }
    }

    if (mSharedResourceMemoryContents) {
        // SharedTextureMemory supports concurrent reads of the underlying D3D12
        // texture via multiple TextureD3D12 instances created from a single
        // SharedTextureMemory instance. Concurrent read access requires that the
        // texture is compatible with (i.e., implicitly decayable to) the COMMON
        // state at all times that read accesses are happening; otherwise, the
        // texture can enter a state where it could be modified by one read access
        // (e.g., to be compressed or decrompessed) while being read by another.
        DAWN_ASSERT(state->isValidToDecay || mSharedResourceMemoryContents->HasWriteAccess() ||
                    mSharedResourceMemoryContents->HasExclusiveReadAccess());
    }

    D3D12_RESOURCE_BARRIER barrier;
    barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
    barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
    barrier.Transition.pResource = GetD3D12Resource();
    barrier.Transition.StateBefore = lastState;
    barrier.Transition.StateAfter = newState;

    // Currently Stencil8 is implemented with DXGI_FORMAT_D24_UNORM_S8_UINT, while we can only
    // choose the stencil aspect for Stencil8 textures, so actually we cannot select the full
    // subresource range on the Stencil8 textures.
    bool isFullRange =
        range.baseArrayLayer == 0 && range.baseMipLevel == 0 &&
        range.layerCount == GetArrayLayers() && range.levelCount == GetNumMipLevels() &&
        range.aspects == GetFormat().aspects && GetFormat().format != wgpu::TextureFormat::Stencil8;

    // Use a single transition for all subresources if possible.
    if (isFullRange) {
        barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
        barriers->push_back(barrier);
    } else {
        for (Aspect aspect : IterateEnumMask(range.aspects)) {
            for (uint32_t arrayLayer = 0; arrayLayer < range.layerCount; ++arrayLayer) {
                for (uint32_t mipLevel = 0; mipLevel < range.levelCount; ++mipLevel) {
                    barrier.Transition.Subresource = GetSubresourceIndex(
                        range.baseMipLevel + mipLevel, range.baseArrayLayer + arrayLayer, aspect);
                    barriers->push_back(barrier);
                }
            }
        }
    }

    state->isValidToDecay = false;
}

void Texture::HandleTransitionSpecialCases(CommandRecordingContext* commandContext) {
    // Externally allocated textures can be written from other graphics queues. Hence, they must be
    // acquired before command list submission to ensure work from the other queues has finished.
    // See CommandRecordingContext::ExecuteCommandList.
    if (mResourceAllocation.GetInfo().mMethod == AllocationMethod::kExternal) {
        commandContext->AddToSharedTextureList(this);
    }
}

void Texture::TransitionUsageAndGetResourceBarrier(CommandRecordingContext* commandContext,
                                                   std::vector<D3D12_RESOURCE_BARRIER>* barrier,
                                                   wgpu::TextureUsage usage,
                                                   const SubresourceRange& range) {
    TransitionUsageAndGetResourceBarrier(commandContext, barrier,
                                         D3D12TextureUsage(usage, GetFormat()), range);
}

void Texture::TransitionUsageAndGetResourceBarrier(CommandRecordingContext* commandContext,
                                                   std::vector<D3D12_RESOURCE_BARRIER>* barriers,
                                                   D3D12_RESOURCE_STATES newState,
                                                   const SubresourceRange& range) {
    HandleTransitionSpecialCases(commandContext);

    const ExecutionSerial pendingCommandSerial = GetDevice()->GetQueue()->GetPendingCommandSerial();

    mSubresourceStateAndDecay.Update(range, [&](const SubresourceRange& updateRange,
                                                StateAndDecay* state) {
        TransitionSubresourceRange(barriers, updateRange, state, newState, pendingCommandSerial);
    });
}

void Texture::TrackUsageAndGetResourceBarrierForPass(
    CommandRecordingContext* commandContext,
    std::vector<D3D12_RESOURCE_BARRIER>* barriers,
    const TextureSubresourceSyncInfo& textureSyncInfos) {
    if (mResourceAllocation.GetInfo().mMethod != AllocationMethod::kExternal) {
        // Track the underlying heap to ensure residency.
        Heap* heap = ToBackend(mResourceAllocation.GetResourceHeap());
        commandContext->TrackHeapUsage(heap, GetDevice()->GetQueue()->GetPendingCommandSerial());
    }

    HandleTransitionSpecialCases(commandContext);

    const ExecutionSerial pendingCommandSerial = GetDevice()->GetQueue()->GetPendingCommandSerial();

    mSubresourceStateAndDecay.Merge(
        textureSyncInfos, [&](const SubresourceRange& mergeRange, StateAndDecay* state,
                              const TextureSyncInfo& syncInfo) {
            // Skip if this subresource is not used during the current pass
            if (syncInfo.usage == wgpu::TextureUsage::None) {
                return;
            }

            D3D12_RESOURCE_STATES newState = D3D12TextureUsage(syncInfo.usage, GetFormat());
            TransitionSubresourceRange(barriers, mergeRange, state, newState, pendingCommandSerial);
        });
}

D3D12_RESOURCE_STATES Texture::GetCurrentStateForSwapChain() const {
    DAWN_ASSERT(GetFormat().aspects == Aspect::Color);
    return mSubresourceStateAndDecay.Get(Aspect::Color, 0, 0).lastState;
}

SubresourceStorage<Texture::StateAndDecay> Texture::InitialSubresourceStateAndDecay() const {
    return {GetFormat().aspects,
            GetArrayLayers(),
            GetNumMipLevels(),
            {D3D12_RESOURCE_STATES::D3D12_RESOURCE_STATE_COMMON, kMaxExecutionSerial, false}};
}

void Texture::ResetSubresourceStateAndDecayToCommon() {
    mSubresourceStateAndDecay = InitialSubresourceStateAndDecay();
}

D3D12_RENDER_TARGET_VIEW_DESC Texture::GetRTVDescriptor(const Format& format,
                                                        uint32_t mipLevel,
                                                        uint32_t baseSlice,
                                                        uint32_t sliceCount,
                                                        uint32_t planeSlice) const {
    D3D12_RENDER_TARGET_VIEW_DESC rtvDesc;
    rtvDesc.Format = d3d::DXGITextureFormat(GetDevice(), format.format);
    if (IsMultisampledTexture()) {
        DAWN_ASSERT(GetDimension() == wgpu::TextureDimension::e2D);
        DAWN_ASSERT(GetNumMipLevels() == 1);
        DAWN_ASSERT(sliceCount == 1);
        DAWN_ASSERT(baseSlice == 0);
        DAWN_ASSERT(mipLevel == 0);
        rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DMS;
        return rtvDesc;
    }
    switch (GetDimension()) {
        case wgpu::TextureDimension::e2D:
            // Currently we always use D3D12_TEX2D_ARRAY_RTV because we cannot specify base
            // array layer and layer count in D3D12_TEX2D_RTV. For 2D texture views, we treat
            // them as 1-layer 2D array textures. (Just like how we treat SRVs)
            // https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/ns-d3d12-d3d12_tex2d_rtv
            // https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/ns-d3d12-d3d12_tex2d_array
            // _rtv
            rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
            rtvDesc.Texture2DArray.FirstArraySlice = baseSlice;
            rtvDesc.Texture2DArray.ArraySize = sliceCount;
            rtvDesc.Texture2DArray.MipSlice = mipLevel;
            rtvDesc.Texture2DArray.PlaneSlice = planeSlice;
            break;
        case wgpu::TextureDimension::e3D:
            rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE3D;
            rtvDesc.Texture3D.MipSlice = mipLevel;
            rtvDesc.Texture3D.FirstWSlice = baseSlice;
            rtvDesc.Texture3D.WSize = sliceCount;
            break;
        case wgpu::TextureDimension::e1D:
        case wgpu::TextureDimension::Undefined:
            DAWN_UNREACHABLE();
            break;
    }
    return rtvDesc;
}

D3D12_DEPTH_STENCIL_VIEW_DESC Texture::GetDSVDescriptor(uint32_t mipLevel,
                                                        uint32_t baseArrayLayer,
                                                        uint32_t layerCount,
                                                        Aspect aspects,
                                                        bool depthReadOnly,
                                                        bool stencilReadOnly) const {
    D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc;
    dsvDesc.Format = GetD3D12Format();
    dsvDesc.Flags = D3D12_DSV_FLAG_NONE;
    if (depthReadOnly && aspects & Aspect::Depth) {
        dsvDesc.Flags |= D3D12_DSV_FLAG_READ_ONLY_DEPTH;
    }
    if (stencilReadOnly && aspects & Aspect::Stencil) {
        dsvDesc.Flags |= D3D12_DSV_FLAG_READ_ONLY_STENCIL;
    }

    if (IsMultisampledTexture()) {
        DAWN_ASSERT(GetNumMipLevels() == 1);
        DAWN_ASSERT(layerCount == 1);
        DAWN_ASSERT(baseArrayLayer == 0);
        DAWN_ASSERT(mipLevel == 0);
        dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DMS;
    } else {
        dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DARRAY;
        dsvDesc.Texture2DArray.FirstArraySlice = baseArrayLayer;
        dsvDesc.Texture2DArray.ArraySize = layerCount;
        dsvDesc.Texture2DArray.MipSlice = mipLevel;
    }

    return dsvDesc;
}

MaybeError Texture::ClearTexture(CommandRecordingContext* commandContext,
                                 const SubresourceRange& range,
                                 TextureBase::ClearValue clearValue) {
    ID3D12GraphicsCommandList* commandList = commandContext->GetCommandList();

    Device* device = ToBackend(GetDevice());

    uint8_t clearColor = (clearValue == TextureBase::ClearValue::Zero) ? 0 : 1;
    float fClearColor = (clearValue == TextureBase::ClearValue::Zero) ? 0.f : 1.f;

    if ((mD3D12ResourceFlags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) != 0) {
        TrackUsageAndTransitionNow(commandContext, D3D12_RESOURCE_STATE_DEPTH_WRITE, 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) {
                // Iterate the aspects individually to determine which clear flags to use.
                D3D12_CLEAR_FLAGS clearFlags = {};
                for (Aspect aspect : IterateEnumMask(range.aspects)) {
                    if (clearValue == TextureBase::ClearValue::Zero &&
                        IsSubresourceContentInitialized(
                            SubresourceRange::SingleMipAndLayer(level, layer, aspect))) {
                        // Skip lazy clears if already initialized.
                        continue;
                    }

                    switch (aspect) {
                        case Aspect::Depth:
                            clearFlags |= D3D12_CLEAR_FLAG_DEPTH;
                            break;
                        case Aspect::Stencil:
                            clearFlags |= D3D12_CLEAR_FLAG_STENCIL;
                            break;
                        default:
                            DAWN_UNREACHABLE();
                    }
                }

                if (clearFlags == 0) {
                    continue;
                }

                CPUDescriptorHeapAllocation dsvHandle;
                DAWN_TRY_ASSIGN(
                    dsvHandle,
                    device->GetDepthStencilViewAllocator()->AllocateTransientCPUDescriptors());
                const D3D12_CPU_DESCRIPTOR_HANDLE baseDescriptor = dsvHandle.GetBaseDescriptor();
                D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc =
                    GetDSVDescriptor(level, layer, 1, range.aspects, false, false);
                device->GetD3D12Device()->CreateDepthStencilView(GetD3D12Resource(), &dsvDesc,
                                                                 baseDescriptor);

                commandList->ClearDepthStencilView(baseDescriptor, clearFlags, fClearColor,
                                                   clearColor, 0, nullptr);
            }
        }
    } else if ((mD3D12ResourceFlags & D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET) != 0) {
        TrackUsageAndTransitionNow(commandContext, D3D12_RESOURCE_STATE_RENDER_TARGET, range);

        const float clearColorRGBA[4] = {fClearColor, fClearColor, fClearColor, fClearColor};

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

                CPUDescriptorHeapAllocation rtvHeap;
                DAWN_TRY_ASSIGN(
                    rtvHeap,
                    device->GetRenderTargetViewAllocator()->AllocateTransientCPUDescriptors());
                const D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = rtvHeap.GetBaseDescriptor();

                // For the subresources of 3d textures, range.baseArrayLayer must be 0 and
                // range.layerCount must be 1, the sliceCount is the depthOrArrayLayers of the
                // subresource virtual size, which must be 1 for 2d textures. When clearing RTV, we
                // can use the 'layer' as baseSlice and the 'depthOrArrayLayers' as sliceCount to
                // create RTV without checking the dimension.
                D3D12_RENDER_TARGET_VIEW_DESC rtvDesc =
                    GetRTVDescriptor(GetFormat(), level, layer,
                                     GetMipLevelSingleSubresourceVirtualSize(level, Aspect::Color)
                                         .depthOrArrayLayers,
                                     GetAspectIndex(range.aspects));
                device->GetD3D12Device()->CreateRenderTargetView(GetD3D12Resource(), &rtvDesc,
                                                                 rtvHandle);
                commandList->ClearRenderTargetView(rtvHandle, clearColorRGBA, 0, nullptr);
            }
        }
    } else {
        DAWN_ASSERT(!IsMultisampledTexture());

        // create temp buffer with clear color to copy to the texture image
        TrackUsageAndTransitionNow(commandContext, D3D12_RESOURCE_STATE_COPY_DEST, range);

        for (Aspect aspect : IterateEnumMask(range.aspects)) {
            const TypedTexelBlockInfo& blockInfo = GetFormat().GetAspectInfo(aspect).block;

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

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

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

                    for (uint32_t level = range.baseMipLevel;
                         level < range.baseMipLevel + range.levelCount; ++level) {
                        // compute d3d12 texture copy locations for texture and buffer
                        BlockExtent3D copySize = blockInfo.ToBlock(
                            GetMipLevelSingleSubresourcePhysicalSize(level, aspect));

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

                            BlockCount blocksPerRow = blockInfo.BytesToBlocks(bytesPerRow);
                            BlockCount rowsPerImage = largestMipSize.height;

                            TextureCopy textureCopy;
                            textureCopy.texture = this;
                            textureCopy.origin = {TexelCount{0}, TexelCount{0}, TexelCount{layer}};
                            textureCopy.mipLevel = level;
                            textureCopy.aspect = aspect;
                            RecordBufferTextureCopyWithBufferHandle(
                                BufferTextureCopyDirection::B2T, commandList,
                                ToBackend(reservation.buffer)->GetD3D12Resource(),
                                reservation.offsetInBuffer, blocksPerRow, rowsPerImage, textureCopy,
                                copySize);
                        }
                    }
                    return {};
                }));
        }
    }
    if (clearValue == TextureBase::ClearValue::Zero) {
        SetIsSubresourceContentInitialized(true, range);
        GetDevice()->IncrementLazyClearCountForTesting();
    }
    return {};
}

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

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

MaybeError Texture::EnsureSubresourceContentInitialized(CommandRecordingContext* commandContext,
                                                        const SubresourceRange& range) {
    if (!ToBackend(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(commandContext, range, TextureBase::ClearValue::Zero));
    }
    return {};
}

// static
Ref<TextureView> TextureView::Create(TextureBase* texture,
                                     const UnpackedPtr<TextureViewDescriptor>& descriptor) {
    return AcquireRef(new TextureView(texture, descriptor));
}

TextureView::TextureView(TextureBase* texture, const UnpackedPtr<TextureViewDescriptor>& descriptor)
    : TextureViewBase(texture, descriptor) {
    const Aspect aspects = GetAspects();
    const Format& textureFormat = texture->GetFormat();

    mSrvDesc.Format =
        d3d::D3DShaderResourceViewFormat(GetDevice(), textureFormat, GetFormat(), aspects);
    if (mSrvDesc.Format != DXGI_FORMAT_UNKNOWN) {
        auto swizzle = GetSwizzle();
        // Stencil is accessed using the .g component in the shader.
        DXGI_FORMAT textureDxgiFormat = d3d::DXGITextureFormat(GetDevice(), textureFormat.format);
        if (d3d::IsDepthStencil(textureDxgiFormat) && aspects == Aspect::Stencil) {
            wgpu::TextureComponentSwizzle stencilSwizzle;
            if (GetDevice()->IsToggleEnabled(Toggle::D3D12ForceStencilComponentReplicateSwizzle) &&
                textureDxgiFormat == DXGI_FORMAT_D24_UNORM_S8_UINT) {
                stencilSwizzle = {
                    .r = wgpu::ComponentSwizzle::G,
                    .g = wgpu::ComponentSwizzle::G,
                    .b = wgpu::ComponentSwizzle::G,
                    .a = wgpu::ComponentSwizzle::G,
                };
            } else {
                stencilSwizzle = {
                    .r = wgpu::ComponentSwizzle::G,
                    .g = wgpu::ComponentSwizzle::Zero,
                    .b = wgpu::ComponentSwizzle::Zero,
                    .a = wgpu::ComponentSwizzle::One,
                };
            }
            swizzle = ComposeSwizzle(stencilSwizzle, swizzle);
        }

        mSrvDesc.Shader4ComponentMapping = D3D12_ENCODE_SHADER_4_COMPONENT_MAPPING(
            D3D12ComponentSwizzle(swizzle.r), D3D12ComponentSwizzle(swizzle.g),
            D3D12ComponentSwizzle(swizzle.b), D3D12ComponentSwizzle(swizzle.a));

        // Currently we always use D3D12_TEX2D_ARRAY_SRV because we cannot specify base array layer
        // and layer count in D3D12_TEX2D_SRV. For 2D texture views, we treat them as 1-layer 2D
        // array textures.
        // Multisampled textures may only be one array layer, so we use
        // D3D12_SRV_DIMENSION_TEXTURE2DMS.
        // https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/ns-d3d12-d3d12_tex2d_srv
        // https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/ns-d3d12-d3d12_tex2d_array_srv
        if (GetTexture()->IsMultisampledTexture()) {
            switch (descriptor->dimension) {
                case wgpu::TextureViewDimension::e2DArray:
                    DAWN_ASSERT(texture->GetArrayLayers() == 1);
                    [[fallthrough]];
                case wgpu::TextureViewDimension::e2D:
                    DAWN_ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
                    mSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMS;
                    break;

                default:
                    DAWN_UNREACHABLE();
            }
        } else {
            switch (descriptor->dimension) {
                case wgpu::TextureViewDimension::e1D:
                    mSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1D;
                    mSrvDesc.Texture1D.MipLevels = descriptor->mipLevelCount;
                    mSrvDesc.Texture1D.MostDetailedMip = descriptor->baseMipLevel;
                    mSrvDesc.Texture1D.ResourceMinLODClamp = 0;
                    break;

                case wgpu::TextureViewDimension::e2D:
                case wgpu::TextureViewDimension::e2DArray:
                    DAWN_ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
                    mSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY;
                    mSrvDesc.Texture2DArray.ArraySize = descriptor->arrayLayerCount;
                    mSrvDesc.Texture2DArray.FirstArraySlice = descriptor->baseArrayLayer;
                    mSrvDesc.Texture2DArray.MipLevels = descriptor->mipLevelCount;
                    mSrvDesc.Texture2DArray.MostDetailedMip = descriptor->baseMipLevel;
                    mSrvDesc.Texture2DArray.PlaneSlice = GetAspectIndex(aspects);
                    mSrvDesc.Texture2DArray.ResourceMinLODClamp = 0;
                    break;
                case wgpu::TextureViewDimension::Cube:
                case wgpu::TextureViewDimension::CubeArray:
                    DAWN_ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
                    DAWN_ASSERT(descriptor->arrayLayerCount % 6 == 0);
                    mSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBEARRAY;
                    mSrvDesc.TextureCubeArray.First2DArrayFace = descriptor->baseArrayLayer;
                    mSrvDesc.TextureCubeArray.NumCubes = descriptor->arrayLayerCount / 6;
                    mSrvDesc.TextureCubeArray.MostDetailedMip = descriptor->baseMipLevel;
                    mSrvDesc.TextureCubeArray.MipLevels = descriptor->mipLevelCount;
                    mSrvDesc.TextureCubeArray.ResourceMinLODClamp = 0;
                    break;
                case wgpu::TextureViewDimension::e3D:
                    DAWN_ASSERT(texture->GetDimension() == wgpu::TextureDimension::e3D);
                    mSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE3D;
                    mSrvDesc.Texture3D.MostDetailedMip = descriptor->baseMipLevel;
                    mSrvDesc.Texture3D.MipLevels = descriptor->mipLevelCount;
                    mSrvDesc.Texture3D.ResourceMinLODClamp = 0;
                    break;

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

DXGI_FORMAT TextureView::GetD3D12Format() const {
    return d3d::DXGITextureFormat(GetDevice(), GetFormat().format);
}

const D3D12_SHADER_RESOURCE_VIEW_DESC& TextureView::GetSRVDescriptor() const {
    DAWN_ASSERT(mSrvDesc.Format != DXGI_FORMAT_UNKNOWN);
    return mSrvDesc;
}

D3D12_RENDER_TARGET_VIEW_DESC TextureView::GetRTVDescriptor(uint32_t depthSlice) const {
    DAWN_ASSERT(depthSlice < GetSingleSubresourceVirtualSize().depthOrArrayLayers);
    // We have validated that the depthSlice in render pass's colorAttachments must be undefined for
    // 2d RTVs, which value is set to 0. For 3d RTVs, the baseArrayLayer must be 0. So here we can
    // simply use baseArrayLayer + depthSlice to specify the slice in RTVs without checking the
    // view's dimension.
    return ToBackend(GetTexture())
        ->GetRTVDescriptor(GetFormat(), GetBaseMipLevel(), GetBaseArrayLayer() + depthSlice,
                           GetLayerCount(), GetAspectIndex(GetAspects()));
}

D3D12_DEPTH_STENCIL_VIEW_DESC TextureView::GetDSVDescriptor(bool depthReadOnly,
                                                            bool stencilReadOnly) const {
    DAWN_ASSERT(GetLevelCount() == 1);
    return ToBackend(GetTexture())
        ->GetDSVDescriptor(GetBaseMipLevel(), GetBaseArrayLayer(), GetLayerCount(), GetAspects(),
                           depthReadOnly, stencilReadOnly);
}

D3D12_UNORDERED_ACCESS_VIEW_DESC TextureView::GetUAVDescriptor() const {
    D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc;
    uavDesc.Format = GetD3D12Format();

    DAWN_ASSERT(!GetTexture()->IsMultisampledTexture());
    switch (GetDimension()) {
        case wgpu::TextureViewDimension::e1D:
            uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1D;
            uavDesc.Texture1D.MipSlice = GetBaseMipLevel();
            break;
        case wgpu::TextureViewDimension::e2D:
        case wgpu::TextureViewDimension::e2DArray:
            uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY;
            uavDesc.Texture2DArray.FirstArraySlice = GetBaseArrayLayer();
            uavDesc.Texture2DArray.ArraySize = GetLayerCount();
            uavDesc.Texture2DArray.MipSlice = GetBaseMipLevel();
            uavDesc.Texture2DArray.PlaneSlice = 0;
            break;
        case wgpu::TextureViewDimension::e3D:
            uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE3D;
            uavDesc.Texture3D.FirstWSlice = 0;
            uavDesc.Texture3D.WSize =
                std::max(1u, GetSingleSubresourceVirtualSize().depthOrArrayLayers);
            uavDesc.Texture3D.MipSlice = GetBaseMipLevel();
            break;
        // Cube and Cubemap can't be used as storage texture. So there is no need to create UAV
        // descriptor for them.
        case wgpu::TextureViewDimension::Cube:
        case wgpu::TextureViewDimension::CubeArray:
        case wgpu::TextureViewDimension::Undefined:
            DAWN_UNREACHABLE();
    }
    return uavDesc;
}

}  // namespace dawn::native::d3d12
