// 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/RenderPipelineD3D12.h"

#include <d3dcompiler.h>

#include <memory>
#include <utility>

#include "src/dawn/common/Assert.h"
#include "src/dawn/native/CreatePipelineAsyncEvent.h"
#include "src/dawn/native/Instance.h"
#include "src/dawn/native/d3d/BlobD3D.h"
#include "src/dawn/native/d3d/D3DError.h"
#include "src/dawn/native/d3d12/DeviceD3D12.h"
#include "src/dawn/native/d3d12/PipelineLayoutD3D12.h"
#include "src/dawn/native/d3d12/PlatformFunctionsD3D12.h"
#include "src/dawn/native/d3d12/ShaderModuleD3D12.h"
#include "src/dawn/native/d3d12/TextureD3D12.h"
#include "src/dawn/native/d3d12/UtilsD3D12.h"
#include "src/dawn/platform/metrics/HistogramMacros.h"
#include "src/utils/compiler.h"

namespace dawn::native::d3d12 {
namespace {

D3D12_INPUT_CLASSIFICATION VertexStepModeFunction(wgpu::VertexStepMode mode) {
    switch (mode) {
        case wgpu::VertexStepMode::Vertex:
            return D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA;
        case wgpu::VertexStepMode::Instance:
            return D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA;
        case wgpu::VertexStepMode::Undefined:
            break;
    }
    DAWN_UNREACHABLE();
}

D3D12_PRIMITIVE_TOPOLOGY D3D12PrimitiveTopology(wgpu::PrimitiveTopology primitiveTopology) {
    switch (primitiveTopology) {
        case wgpu::PrimitiveTopology::PointList:
            return D3D_PRIMITIVE_TOPOLOGY_POINTLIST;
        case wgpu::PrimitiveTopology::LineList:
            return D3D_PRIMITIVE_TOPOLOGY_LINELIST;
        case wgpu::PrimitiveTopology::LineStrip:
            return D3D_PRIMITIVE_TOPOLOGY_LINESTRIP;
        case wgpu::PrimitiveTopology::TriangleList:
            return D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
        case wgpu::PrimitiveTopology::TriangleStrip:
            return D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
        case wgpu::PrimitiveTopology::Undefined:
            break;
    }
    DAWN_UNREACHABLE();
}

D3D12_PRIMITIVE_TOPOLOGY_TYPE D3D12PrimitiveTopologyType(
    wgpu::PrimitiveTopology primitiveTopology) {
    switch (primitiveTopology) {
        case wgpu::PrimitiveTopology::PointList:
            return D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT;
        case wgpu::PrimitiveTopology::LineList:
        case wgpu::PrimitiveTopology::LineStrip:
            return D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE;
        case wgpu::PrimitiveTopology::TriangleList:
        case wgpu::PrimitiveTopology::TriangleStrip:
            return D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
        case wgpu::PrimitiveTopology::Undefined:
            break;
    }
    DAWN_UNREACHABLE();
}

D3D12_CULL_MODE D3D12CullMode(wgpu::CullMode mode) {
    switch (mode) {
        case wgpu::CullMode::None:
            return D3D12_CULL_MODE_NONE;
        case wgpu::CullMode::Front:
            return D3D12_CULL_MODE_FRONT;
        case wgpu::CullMode::Back:
            return D3D12_CULL_MODE_BACK;
        case wgpu::CullMode::Undefined:
            break;
    }
    DAWN_UNREACHABLE();
}

D3D12_BLEND D3D12Blend(wgpu::BlendFactor factor) {
    switch (factor) {
        case wgpu::BlendFactor::Zero:
            return D3D12_BLEND_ZERO;
        case wgpu::BlendFactor::One:
            return D3D12_BLEND_ONE;
        case wgpu::BlendFactor::Src:
            return D3D12_BLEND_SRC_COLOR;
        case wgpu::BlendFactor::OneMinusSrc:
            return D3D12_BLEND_INV_SRC_COLOR;
        case wgpu::BlendFactor::SrcAlpha:
            return D3D12_BLEND_SRC_ALPHA;
        case wgpu::BlendFactor::OneMinusSrcAlpha:
            return D3D12_BLEND_INV_SRC_ALPHA;
        case wgpu::BlendFactor::Dst:
            return D3D12_BLEND_DEST_COLOR;
        case wgpu::BlendFactor::OneMinusDst:
            return D3D12_BLEND_INV_DEST_COLOR;
        case wgpu::BlendFactor::DstAlpha:
            return D3D12_BLEND_DEST_ALPHA;
        case wgpu::BlendFactor::OneMinusDstAlpha:
            return D3D12_BLEND_INV_DEST_ALPHA;
        case wgpu::BlendFactor::SrcAlphaSaturated:
            return D3D12_BLEND_SRC_ALPHA_SAT;
        case wgpu::BlendFactor::Constant:
            return D3D12_BLEND_BLEND_FACTOR;
        case wgpu::BlendFactor::OneMinusConstant:
            return D3D12_BLEND_INV_BLEND_FACTOR;
        case wgpu::BlendFactor::Src1:
            return D3D12_BLEND_SRC1_COLOR;
        case wgpu::BlendFactor::OneMinusSrc1:
            return D3D12_BLEND_INV_SRC1_COLOR;
        case wgpu::BlendFactor::Src1Alpha:
            return D3D12_BLEND_SRC1_ALPHA;
        case wgpu::BlendFactor::OneMinusSrc1Alpha:
            return D3D12_BLEND_INV_SRC1_ALPHA;
        case wgpu::BlendFactor::Undefined:
            break;
    }
    DAWN_UNREACHABLE();
}

// When a blend factor is defined for the alpha channel, any of the factors that don't
// explicitly state that they apply to alpha should be treated as their explicitly-alpha
// equivalents. See: https://github.com/gpuweb/gpuweb/issues/65
D3D12_BLEND D3D12AlphaBlend(wgpu::BlendFactor factor) {
    switch (factor) {
        case wgpu::BlendFactor::Src:
            return D3D12_BLEND_SRC_ALPHA;
        case wgpu::BlendFactor::OneMinusSrc:
            return D3D12_BLEND_INV_SRC_ALPHA;
        case wgpu::BlendFactor::Dst:
            return D3D12_BLEND_DEST_ALPHA;
        case wgpu::BlendFactor::OneMinusDst:
            return D3D12_BLEND_INV_DEST_ALPHA;
        case wgpu::BlendFactor::Src1:
            return D3D12_BLEND_SRC1_ALPHA;
        case wgpu::BlendFactor::OneMinusSrc1:
            return D3D12_BLEND_INV_SRC1_ALPHA;

        // Other blend factors translate to the same D3D12 enum as the color blend factors.
        default:
            return D3D12Blend(factor);
    }
}

D3D12_BLEND_OP D3D12BlendOperation(wgpu::BlendOperation operation) {
    switch (operation) {
        case wgpu::BlendOperation::Add:
            return D3D12_BLEND_OP_ADD;
        case wgpu::BlendOperation::Subtract:
            return D3D12_BLEND_OP_SUBTRACT;
        case wgpu::BlendOperation::ReverseSubtract:
            return D3D12_BLEND_OP_REV_SUBTRACT;
        case wgpu::BlendOperation::Min:
            return D3D12_BLEND_OP_MIN;
        case wgpu::BlendOperation::Max:
            return D3D12_BLEND_OP_MAX;
        case wgpu::BlendOperation::Undefined:
            break;
    }
    DAWN_UNREACHABLE();
}

uint8_t D3D12RenderTargetWriteMask(wgpu::ColorWriteMask writeMask) {
    static_assert(static_cast<D3D12_COLOR_WRITE_ENABLE>(wgpu::ColorWriteMask::Red) ==
                      D3D12_COLOR_WRITE_ENABLE_RED,
                  "ColorWriteMask values must match");
    static_assert(static_cast<D3D12_COLOR_WRITE_ENABLE>(wgpu::ColorWriteMask::Green) ==
                      D3D12_COLOR_WRITE_ENABLE_GREEN,
                  "ColorWriteMask values must match");
    static_assert(static_cast<D3D12_COLOR_WRITE_ENABLE>(wgpu::ColorWriteMask::Blue) ==
                      D3D12_COLOR_WRITE_ENABLE_BLUE,
                  "ColorWriteMask values must match");
    static_assert(static_cast<D3D12_COLOR_WRITE_ENABLE>(wgpu::ColorWriteMask::Alpha) ==
                      D3D12_COLOR_WRITE_ENABLE_ALPHA,
                  "ColorWriteMask values must match");
    return static_cast<uint8_t>(writeMask);
}

D3D12_RENDER_TARGET_BLEND_DESC ComputeColorDesc(const DeviceBase* device,
                                                const ColorTargetState* state) {
    D3D12_RENDER_TARGET_BLEND_DESC blendDesc = {};
    blendDesc.BlendEnable = state->blend != nullptr;
    if (blendDesc.BlendEnable) {
        blendDesc.SrcBlend = D3D12Blend(state->blend->color.srcFactor);
        if (device->GetValidInternalFormat(state->format).componentCount < 4 &&
            blendDesc.SrcBlend == D3D12_BLEND_DEST_ALPHA) {
            // According to the D3D SPEC, the default value for missing components in an element
            // format is "0" for any component except A, which gets "1". So here
            // D3D12_BLEND_DEST_ALPHA should have same effect as D3D12_BLEND_ONE.
            // Note that this replacement can be an optimization as using D3D12_BLEND_ONE means the
            // GPU hardware no longer needs to get pixels from the destination texture. It can also
            // be served as a workaround against an Intel driver issue about alpha blending (see
            // http://crbug.com/dawn/1579 for more details).
            blendDesc.SrcBlend = D3D12_BLEND_ONE;
        }

        blendDesc.DestBlend = D3D12Blend(state->blend->color.dstFactor);
        blendDesc.BlendOp = D3D12BlendOperation(state->blend->color.operation);
        blendDesc.SrcBlendAlpha = D3D12AlphaBlend(state->blend->alpha.srcFactor);
        blendDesc.DestBlendAlpha = D3D12AlphaBlend(state->blend->alpha.dstFactor);
        blendDesc.BlendOpAlpha = D3D12BlendOperation(state->blend->alpha.operation);

        if (device->IsToggleEnabled(
                Toggle::D3D12ReplaceAddWithMinusWhenDstFactorIsZeroAndSrcFactorIsDstAlpha) &&
            blendDesc.SrcBlend == D3D12_BLEND_DEST_ALPHA &&
            blendDesc.SrcBlendAlpha == D3D12_BLEND_DEST_ALPHA &&
            blendDesc.BlendOp == D3D12_BLEND_OP_ADD &&
            blendDesc.BlendOpAlpha == D3D12_BLEND_OP_ADD &&
            blendDesc.DestBlend == D3D12_BLEND_ZERO &&
            blendDesc.DestBlendAlpha == D3D12_BLEND_ZERO) {
            blendDesc.BlendOp = D3D12_BLEND_OP_SUBTRACT;
            blendDesc.BlendOpAlpha = D3D12_BLEND_OP_SUBTRACT;
        }
    }
    blendDesc.RenderTargetWriteMask = D3D12RenderTargetWriteMask(state->writeMask);
    blendDesc.LogicOpEnable = false;
    blendDesc.LogicOp = D3D12_LOGIC_OP_NOOP;
    return blendDesc;
}

D3D12_STENCIL_OP StencilOp(wgpu::StencilOperation op) {
    switch (op) {
        case wgpu::StencilOperation::Keep:
            return D3D12_STENCIL_OP_KEEP;
        case wgpu::StencilOperation::Zero:
            return D3D12_STENCIL_OP_ZERO;
        case wgpu::StencilOperation::Replace:
            return D3D12_STENCIL_OP_REPLACE;
        case wgpu::StencilOperation::IncrementClamp:
            return D3D12_STENCIL_OP_INCR_SAT;
        case wgpu::StencilOperation::DecrementClamp:
            return D3D12_STENCIL_OP_DECR_SAT;
        case wgpu::StencilOperation::Invert:
            return D3D12_STENCIL_OP_INVERT;
        case wgpu::StencilOperation::IncrementWrap:
            return D3D12_STENCIL_OP_INCR;
        case wgpu::StencilOperation::DecrementWrap:
            return D3D12_STENCIL_OP_DECR;
        case wgpu::StencilOperation::Undefined:
            break;
    }
    DAWN_UNREACHABLE();
}

D3D12_DEPTH_STENCILOP_DESC StencilOpDesc(const StencilFaceState& descriptor) {
    D3D12_DEPTH_STENCILOP_DESC desc = {};

    desc.StencilFailOp = StencilOp(descriptor.failOp);
    desc.StencilDepthFailOp = StencilOp(descriptor.depthFailOp);
    desc.StencilPassOp = StencilOp(descriptor.passOp);
    desc.StencilFunc = ToD3D12ComparisonFunc(descriptor.compare);

    return desc;
}

D3D12_INDEX_BUFFER_STRIP_CUT_VALUE ComputeIndexBufferStripCutValue(
    wgpu::PrimitiveTopology primitiveTopology,
    wgpu::IndexFormat indexFormat) {
    if (primitiveTopology != wgpu::PrimitiveTopology::TriangleStrip &&
        primitiveTopology != wgpu::PrimitiveTopology::LineStrip) {
        return D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_DISABLED;
    }

    switch (indexFormat) {
        case wgpu::IndexFormat::Uint16:
            return D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_0xFFFF;
        case wgpu::IndexFormat::Uint32:
            return D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_0xFFFFFFFF;
        case wgpu::IndexFormat::Undefined:
            return D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_DISABLED;
        default:
            DAWN_UNREACHABLE();
    }
}

}  // anonymous namespace

Ref<RenderPipeline> RenderPipeline::CreateUninitialized(
    Device* device,
    const UnpackedPtr<RenderPipelineDescriptor>& descriptor) {
    return AcquireRef(new RenderPipeline(device, descriptor));
}

MaybeError RenderPipeline::InitializeImpl() {
    Device* device = ToBackend(GetDevice());
    uint32_t compileFlags = 0;

    if (!device->IsToggleEnabled(Toggle::UseDXC) &&
        !device->IsToggleEnabled(Toggle::FxcOptimizations)) {
        compileFlags |= D3DCOMPILE_OPTIMIZATION_LEVEL0;
    }

    if (device->IsToggleEnabled(Toggle::EmitHLSLDebugSymbols)) {
        compileFlags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
    }

    if (device->IsToggleEnabled(Toggle::D3DSkipShaderOptimizations)) {
        compileFlags |= D3DCOMPILE_SKIP_OPTIMIZATION;
    }

    if (device->IsToggleEnabled(Toggle::UseDXC) &&
        ((compileFlags & D3DCOMPILE_OPTIMIZATION_LEVEL2) == 0)) {
        // DXC's default opt level is /O3, unlike FXC's /O1. Set explicitly, otherwise there's no
        // way to tell if we want /O1 as D3DCOMPILE_OPTIMIZATION_LEVEL1 is defined to 0.
        compileFlags |= D3DCOMPILE_OPTIMIZATION_LEVEL3;
    }

    // Tint does matrix multiplication expecting row major matrices
    compileFlags |= D3DCOMPILE_PACK_MATRIX_ROW_MAJOR;

    D3D12_GRAPHICS_PIPELINE_STATE_DESC descriptorD3D12 = {};

    PerStage<D3D12_SHADER_BYTECODE*> shaders;
    shaders[SingleShaderStage::Vertex] = &descriptorD3D12.VS;
    shaders[SingleShaderStage::Fragment] = &descriptorD3D12.PS;

    PerStage<d3d::CompiledShader> compiledShader;

    std::optional<dawn::native::d3d::InterStageShaderVariablesMask> usedInterstageVariables;
    dawn::native::EntryPointMetadata fragmentEntryPoint;
    if (GetStageMask() & wgpu::ShaderStage::Fragment) {
        // Now that only fragment shader can have interstage inputs.
        const ProgrammableStage& programmableStage = GetStage(SingleShaderStage::Fragment);
        fragmentEntryPoint = programmableStage.module->GetEntryPoint(programmableStage.entryPoint);
        usedInterstageVariables = dawn::native::d3d::ToInterStageShaderVariablesMask(
            fragmentEntryPoint.usedInterStageVariables);
    }

    for (auto stage : IterateStages(GetStageMask())) {
        const ProgrammableStage& programmableStage = GetStage(stage);
        uint32_t additionalCompileFlags = 0;
        if (programmableStage.module->GetStrictMath().value_or(
                !device->IsToggleEnabled(Toggle::D3DDisableIEEEStrictness))) {
            additionalCompileFlags |= D3DCOMPILE_IEEE_STRICTNESS;
        }

        // This must be accurate in determining when Sample Shading is active.
        // It cannot be conservatively correct because the polyfill changes behavior.
        bool applySampleMaskPolyfill = (stage == SingleShaderStage::Fragment) &&
                                       UsesSampleMaskInput() && UseSampleRateShading();

        DAWN_TRY_ASSIGN(compiledShader[stage],
                        ToBackend(programmableStage.module)
                            ->Compile(programmableStage, stage, ToBackend(GetLayout()),
                                      compileFlags | additionalCompileFlags,
                                      applySampleMaskPolyfill, usedInterstageVariables));
        *shaders[stage] = {compiledShader[stage].shaderBlob.DataPtr(),
                           compiledShader[stage].shaderBlob.Size()};
    }

    mUsesVertexOrInstanceIndex = compiledShader[SingleShaderStage::Vertex].usesVertexIndex ||
                                 compiledShader[SingleShaderStage::Vertex].usesInstanceIndex;

    PipelineLayout* layout = ToBackend(GetLayout());

    descriptorD3D12.pRootSignature = layout->GetRootSignature();

    // D3D12 logs warnings if any empty input state is used
    std::array<D3D12_INPUT_ELEMENT_DESC, kMaxVertexAttributes> inputElementDescriptors;
    if (GetAttributeLocationsUsed().any()) {
        descriptorD3D12.InputLayout = ComputeInputLayout(&inputElementDescriptors);
    }

    descriptorD3D12.IBStripCutValue =
        ComputeIndexBufferStripCutValue(GetPrimitiveTopology(), GetStripIndexFormat());

    descriptorD3D12.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
    descriptorD3D12.RasterizerState.CullMode = D3D12CullMode(GetCullMode());
    descriptorD3D12.RasterizerState.FrontCounterClockwise =
        (GetFrontFace() == wgpu::FrontFace::CCW) ? TRUE : FALSE;
    descriptorD3D12.RasterizerState.DepthBias = GetDepthBias();
    descriptorD3D12.RasterizerState.DepthBiasClamp = GetDepthBiasClamp();
    descriptorD3D12.RasterizerState.SlopeScaledDepthBias = GetDepthBiasSlopeScale();
    descriptorD3D12.RasterizerState.DepthClipEnable = !HasUnclippedDepth();
    descriptorD3D12.RasterizerState.MultisampleEnable = (GetSampleCount() > 1) ? TRUE : FALSE;
    descriptorD3D12.RasterizerState.AntialiasedLineEnable = FALSE;
    descriptorD3D12.RasterizerState.ForcedSampleCount = 0;
    descriptorD3D12.RasterizerState.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;

    if (HasDepthStencilAttachment()) {
        descriptorD3D12.DSVFormat = d3d::DXGITextureFormat(device, GetDepthStencilFormat());
    }

    static_assert(kMaxColorAttachments == 8);
    auto highestColorAttachmentIndexPlusOne = GetHighestBitIndexPlusOne(GetColorAttachmentsMask());
    for (uint8_t i = 0; i < kMaxColorAttachments; i++) {
        if (i < static_cast<uint8_t>(highestColorAttachmentIndexPlusOne)) {
            DAWN_UNSAFE_TODO(descriptorD3D12.RTVFormats[i]) =
                GetNullRTVDXGIFormatForD3D12RenderPass();
        } else {
            DAWN_UNSAFE_TODO(descriptorD3D12.RTVFormats[i]) = DXGI_FORMAT_UNKNOWN;
        }
        DAWN_UNSAFE_TODO(descriptorD3D12.BlendState.RenderTarget[i]).LogicOp = D3D12_LOGIC_OP_NOOP;
    }
    for (auto i : GetColorAttachmentsMask()) {
        DAWN_UNSAFE_TODO(descriptorD3D12.RTVFormats[static_cast<uint8_t>(i)]) =
            d3d::DXGITextureFormat(device, GetColorAttachmentFormat(i));
        DAWN_UNSAFE_TODO(descriptorD3D12.BlendState.RenderTarget[static_cast<uint8_t>(i)]) =
            ComputeColorDesc(device, GetColorTargetState(i));
    }
    DAWN_ASSERT(highestColorAttachmentIndexPlusOne <= kMaxColorAttachmentsTyped);
    descriptorD3D12.NumRenderTargets = static_cast<uint8_t>(highestColorAttachmentIndexPlusOne);

    descriptorD3D12.BlendState.AlphaToCoverageEnable = IsAlphaToCoverageEnabled();
    descriptorD3D12.BlendState.IndependentBlendEnable = TRUE;

    descriptorD3D12.DepthStencilState = ComputeDepthStencilDesc();

    descriptorD3D12.SampleMask = GetSampleMask();
    descriptorD3D12.PrimitiveTopologyType = D3D12PrimitiveTopologyType(GetPrimitiveTopology());
    descriptorD3D12.SampleDesc.Count = GetSampleCount();
    descriptorD3D12.SampleDesc.Quality = 0;

    mD3d12PrimitiveTopology = D3D12PrimitiveTopology(GetPrimitiveTopology());

    StreamIn(&mCacheKey, descriptorD3D12, *layout->GetRootSignatureBlob());

    // Try to see if we have anything in the blob cache.
    Blob blob = device->LoadCachedBlob(GetCacheKey());
    bool cacheHit = !blob.Empty();
    if (cacheHit) {
        // Cache hits, attach cached blob to descriptor.
        descriptorD3D12.CachedPSO.pCachedBlob = blob.DataPtr();
        descriptorD3D12.CachedPSO.CachedBlobSizeInBytes = blob.Size();
    }

    // We don't use the scoped cache histogram counters for the cache hit here so that we can
    // condition on whether it fails appropriately.
    auto* d3d12Device = device->GetD3D12Device();
    platform::metrics::DawnHistogramTimer cacheTimer(device->GetPlatform());
    HRESULT result =
        d3d12Device->CreateGraphicsPipelineState(&descriptorD3D12, IID_PPV_ARGS(&mPipelineState));
    if (cacheHit && result == D3D12_ERROR_DRIVER_VERSION_MISMATCH) {
        // See dawn:1878 where it is possible for the PSO creation to fail with this error.
        cacheHit = false;
        descriptorD3D12.CachedPSO.pCachedBlob = nullptr;
        descriptorD3D12.CachedPSO.CachedBlobSizeInBytes = 0;
        cacheTimer.Reset();
        result = d3d12Device->CreateGraphicsPipelineState(&descriptorD3D12,
                                                          IID_PPV_ARGS(&mPipelineState));
    }
    DAWN_TRY(CheckHRESULT(result, "D3D12 create graphics pipeline state"));

    if (!cacheHit) {
        // Cache misses, need to get pipeline cached blob and store.
        cacheTimer.RecordMicroseconds("D3D12.CreateGraphicsPipelineState.CacheMiss");
        ComPtr<ID3DBlob> d3dBlob;
        if (!device->GetInstance()->ConsumedError(
                CheckHRESULT(GetPipelineState()->GetCachedBlob(&d3dBlob),
                             "D3D12 render pipeline state get cached blob"))) {
            device->StoreCachedBlob(GetCacheKey(), CreateBlob(std::move(d3dBlob)));
        }
    } else {
        cacheTimer.RecordMicroseconds("D3D12.CreateGraphicsPipelineState.CacheHit");
    }

    SetLabelImpl();

    return {};
}

RenderPipeline::~RenderPipeline() = default;

void RenderPipeline::DestroyImpl(DestroyReason reason) {
    RenderPipelineBase::DestroyImpl(reason);
    ToBackend(GetDevice())->ReferenceUntilUnused(mPipelineState);
}

D3D12_PRIMITIVE_TOPOLOGY RenderPipeline::GetD3D12PrimitiveTopology() const {
    return mD3d12PrimitiveTopology;
}

ID3D12PipelineState* RenderPipeline::GetPipelineState() const {
    return mPipelineState.Get();
}

bool RenderPipeline::UsesVertexOrInstanceIndex() const {
    return mUsesVertexOrInstanceIndex;
}

void RenderPipeline::SetLabelImpl() {
    SetDebugName(ToBackend(GetDevice()), GetPipelineState(), "Dawn_RenderPipeline", GetLabel());
}

ComPtr<ID3D12CommandSignature> RenderPipeline::GetDrawIndirectCommandSignature() {
    if (mUsesVertexOrInstanceIndex) {
        return ToBackend(GetLayout())->GetDrawIndirectCommandSignatureWithInstanceVertexOffsets();
    }

    return ToBackend(GetDevice())->GetDrawIndirectSignature();
}

ComPtr<ID3D12CommandSignature> RenderPipeline::GetDrawIndexedIndirectCommandSignature() {
    if (mUsesVertexOrInstanceIndex) {
        return ToBackend(GetLayout())
            ->GetDrawIndexedIndirectCommandSignatureWithInstanceVertexOffsets();
    }

    return ToBackend(GetDevice())->GetDrawIndexedIndirectSignature();
}

D3D12_INPUT_LAYOUT_DESC RenderPipeline::ComputeInputLayout(
    std::array<D3D12_INPUT_ELEMENT_DESC, kMaxVertexAttributes>* inputElementDescriptors) {
    unsigned int count = 0;
    for (VertexAttributeLocation loc : GetAttributeLocationsUsed()) {
        D3D12_INPUT_ELEMENT_DESC& inputElementDescriptor = (*inputElementDescriptors)[count++];

        const VertexAttributeInfo& attribute = GetAttribute(loc);

        // If the HLSL semantic is TEXCOORDN the SemanticName should be "TEXCOORD" and the
        // SemanticIndex N
        inputElementDescriptor.SemanticName = "TEXCOORD";
        inputElementDescriptor.SemanticIndex = static_cast<uint8_t>(loc);
        inputElementDescriptor.Format = d3d::DXGIVertexFormat(attribute.format);
        inputElementDescriptor.InputSlot = static_cast<uint8_t>(attribute.vertexBufferSlot);

        const VertexBufferInfo& input = GetVertexBuffer(attribute.vertexBufferSlot);

        inputElementDescriptor.AlignedByteOffset = static_cast<uint32_t>(attribute.offset);
        inputElementDescriptor.InputSlotClass = VertexStepModeFunction(input.stepMode);
        if (inputElementDescriptor.InputSlotClass == D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA) {
            inputElementDescriptor.InstanceDataStepRate = 0;
        } else {
            inputElementDescriptor.InstanceDataStepRate = 1;
        }
    }

    D3D12_INPUT_LAYOUT_DESC inputLayoutDescriptor;
    inputLayoutDescriptor.pInputElementDescs = &(*inputElementDescriptors)[0];
    inputLayoutDescriptor.NumElements = count;
    return inputLayoutDescriptor;
}

D3D12_DEPTH_STENCIL_DESC RenderPipeline::ComputeDepthStencilDesc() {
    const DepthStencilState* descriptor = GetDepthStencilState();

    D3D12_DEPTH_STENCIL_DESC depthStencilDescriptor = {};
    depthStencilDescriptor.DepthEnable =
        (descriptor->depthCompare == wgpu::CompareFunction::Always &&
         descriptor->depthWriteEnabled != wgpu::OptionalBool::True)
            ? FALSE
            : TRUE;
    depthStencilDescriptor.DepthWriteMask =
        descriptor->depthWriteEnabled == wgpu::OptionalBool::True ? D3D12_DEPTH_WRITE_MASK_ALL
                                                                  : D3D12_DEPTH_WRITE_MASK_ZERO;
    depthStencilDescriptor.DepthFunc = ToD3D12ComparisonFunc(descriptor->depthCompare);

    depthStencilDescriptor.StencilEnable = UsesStencil() ? TRUE : FALSE;
    depthStencilDescriptor.StencilReadMask = static_cast<UINT8>(descriptor->stencilReadMask);
    depthStencilDescriptor.StencilWriteMask = static_cast<UINT8>(descriptor->stencilWriteMask);

    depthStencilDescriptor.FrontFace = StencilOpDesc(descriptor->stencilFront);
    depthStencilDescriptor.BackFace = StencilOpDesc(descriptor->stencilBack);
    return depthStencilDescriptor;
}

}  // namespace dawn::native::d3d12
