// Copyright 2020 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/BindingInfo.h"

#include <algorithm>

#include "src/dawn/common/MatchVariant.h"
#include "src/dawn/native/Adapter.h"
#include "src/dawn/native/ChainUtils.h"
#include "src/dawn/native/Limits.h"
#include "src/dawn/native/Sampler.h"

namespace dawn::native {

BindingInfoType GetBindingInfoType(const BindingInfo& info) {
    return MatchVariant(
        info.bindingLayout,
        [](const BufferBindingInfo&) -> BindingInfoType { return BindingInfoType::Buffer; },
        [](const SamplerBindingInfo&) -> BindingInfoType { return BindingInfoType::Sampler; },
        [](const TextureBindingInfo&) -> BindingInfoType { return BindingInfoType::Texture; },
        [](const StorageTextureBindingInfo&) -> BindingInfoType {
            return BindingInfoType::StorageTexture;
        },
        [](const TexelBufferBindingInfo&) -> BindingInfoType {
            return BindingInfoType::TexelBuffer;
        },
        [](const StaticSamplerBindingInfo&) -> BindingInfoType {
            return BindingInfoType::StaticSampler;
        },
        [](const InputAttachmentBindingInfo&) -> BindingInfoType {
            return BindingInfoType::InputAttachment;
        },
        [](const ExternalTextureBindingInfo&) -> BindingInfoType {
            return BindingInfoType::ExternalTexture;
        });
}

BindingInfoType GetBindingInfoType(const BindGroupLayoutEntry* entry) {
    if (entry->buffer.type != wgpu::BufferBindingType::BindingNotUsed) {
        return BindingInfoType::Buffer;
    } else if (entry->texture.sampleType != wgpu::TextureSampleType::BindingNotUsed) {
        return BindingInfoType::Texture;
    } else if (entry->storageTexture.access != wgpu::StorageTextureAccess::BindingNotUsed) {
        return BindingInfoType::StorageTexture;
    } else if (entry->sampler.type != wgpu::SamplerBindingType::BindingNotUsed) {
        return BindingInfoType::Sampler;
    }

    UnpackedPtr<BindGroupLayoutEntry> unpacked = Unpack(entry);
    if (unpacked.Has<TexelBufferBindingLayout>()) {
        return BindingInfoType::TexelBuffer;
    } else if (unpacked.Has<ExternalTextureBindingLayout>()) {
        return BindingInfoType::ExternalTexture;
    } else if (unpacked.Has<StaticSamplerBindingLayout>()) {
        return BindingInfoType::StaticSampler;
    }

    DAWN_UNREACHABLE();
}

void IncrementBindingCounts(BindingCounts* bindingCounts,
                            const UnpackedPtr<BindGroupLayoutEntry>& entry) {
    uint32_t arraySize = std::max(1u, entry->bindingArraySize);

    bindingCounts->totalCount += arraySize;

    uint32_t PerStageBindingCounts::* perStageBindingCountMember = nullptr;

    if (entry->buffer.type != wgpu::BufferBindingType::BindingNotUsed) {
        bindingCounts->bufferCount += arraySize;
        const BufferBindingLayout& buffer = entry->buffer;

        if (buffer.minBindingSize == 0) {
            bindingCounts->unverifiedBufferCount += arraySize;
        }

        switch (buffer.type) {
            case wgpu::BufferBindingType::Uniform:
                if (buffer.hasDynamicOffset) {
                    bindingCounts->dynamicUniformBufferCount += arraySize;
                }
                perStageBindingCountMember = &PerStageBindingCounts::uniformBufferCount;
                break;

            case wgpu::BufferBindingType::Storage:
            case kInternalStorageBufferBinding:
            case kInternalReadOnlyStorageBufferBinding:
            case wgpu::BufferBindingType::ReadOnlyStorage:
                if (buffer.hasDynamicOffset) {
                    bindingCounts->dynamicStorageBufferCount += arraySize;
                }
                perStageBindingCountMember = &PerStageBindingCounts::storageBufferCount;
                break;

            case wgpu::BufferBindingType::BindingNotUsed:
                // Can't get here due to the enclosing if statement.
            case wgpu::BufferBindingType::Undefined:
                DAWN_UNREACHABLE();
                break;
        }
    } else if (entry->sampler.type != wgpu::SamplerBindingType::BindingNotUsed) {
        perStageBindingCountMember = &PerStageBindingCounts::samplerCount;
    } else if (entry->texture.sampleType != wgpu::TextureSampleType::BindingNotUsed) {
        if (entry->texture.viewDimension == kInternalInputAttachmentDim) {
            // Internal use only.
            return;
        } else {
            perStageBindingCountMember = &PerStageBindingCounts::sampledTextureCount;
        }
    } else if (entry->storageTexture.access != wgpu::StorageTextureAccess::BindingNotUsed) {
        perStageBindingCountMember = &PerStageBindingCounts::storageTextureCount;
    } else if (entry.Has<TexelBufferBindingLayout>()) {
        perStageBindingCountMember = &PerStageBindingCounts::texelBufferCount;
    } else if (entry.Has<ExternalTextureBindingLayout>()) {
        perStageBindingCountMember = &PerStageBindingCounts::externalTextureCount;
    } else if (entry.Has<StaticSamplerBindingLayout>()) {
        ++bindingCounts->staticSamplerCount;
        perStageBindingCountMember = &PerStageBindingCounts::staticSamplerCount;
    }

    DAWN_ASSERT(perStageBindingCountMember != nullptr);
    for (SingleShaderStage stage : IterateStages(entry->visibility)) {
        (bindingCounts->perStage[stage].*perStageBindingCountMember) += arraySize;
    }
}

void AccumulateBindingCounts(BindingCounts* bindingCounts, const BindingCounts& rhs) {
    bindingCounts->totalCount += rhs.totalCount;
    bindingCounts->bufferCount += rhs.bufferCount;
    bindingCounts->unverifiedBufferCount += rhs.unverifiedBufferCount;
    bindingCounts->dynamicUniformBufferCount += rhs.dynamicUniformBufferCount;
    bindingCounts->dynamicStorageBufferCount += rhs.dynamicStorageBufferCount;

    for (SingleShaderStage stage : IterateStages(kAllStages)) {
        bindingCounts->perStage[stage].sampledTextureCount +=
            rhs.perStage[stage].sampledTextureCount;
        bindingCounts->perStage[stage].samplerCount += rhs.perStage[stage].samplerCount;
        bindingCounts->perStage[stage].storageBufferCount += rhs.perStage[stage].storageBufferCount;
        bindingCounts->perStage[stage].storageTextureCount +=
            rhs.perStage[stage].storageTextureCount;
        bindingCounts->perStage[stage].texelBufferCount += rhs.perStage[stage].texelBufferCount;
        bindingCounts->perStage[stage].uniformBufferCount += rhs.perStage[stage].uniformBufferCount;
        bindingCounts->perStage[stage].externalTextureCount +=
            rhs.perStage[stage].externalTextureCount;
        bindingCounts->perStage[stage].staticSamplerCount += rhs.perStage[stage].staticSamplerCount;
    }
}

MaybeError ValidateBindingCounts(const CombinedLimits& limits,
                                 const BindingCounts& bindingCounts,
                                 const AdapterBase* adapter) {
    // Prevent combinations that are not supported (static sampler + external texture).
    for (SingleShaderStage stage : IterateStages(kAllStages)) {
        DAWN_INVALID_IF(bindingCounts.staticSamplerCount != 0 &&
                            bindingCounts.perStage[stage].externalTextureCount != 0,
                        "Static samplers and external textures are used in the same pipeline.");
    }

    // Validation against the device limits.
    uint32_t maxDynamicUniformBuffersPerPipelineLayout =
        limits.v1.maxDynamicUniformBuffersPerPipelineLayout;
    DAWN_INVALID_IF(
        bindingCounts.dynamicUniformBufferCount > maxDynamicUniformBuffersPerPipelineLayout,
        "The number of dynamic uniform buffers (%u) exceeds the maximum per-pipeline-layout "
        "limit (%u).%s",
        bindingCounts.dynamicUniformBufferCount, maxDynamicUniformBuffersPerPipelineLayout,
        DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().v1,
                                    maxDynamicUniformBuffersPerPipelineLayout,
                                    bindingCounts.dynamicUniformBufferCount));

    uint32_t maxDynamicStorageBuffersPerPipelineLayout =
        limits.v1.maxDynamicStorageBuffersPerPipelineLayout;
    DAWN_INVALID_IF(
        bindingCounts.dynamicStorageBufferCount > maxDynamicStorageBuffersPerPipelineLayout,
        "The number of dynamic storage buffers (%u) exceeds the maximum per-pipeline-layout "
        "limit (%u).%s",
        bindingCounts.dynamicStorageBufferCount, maxDynamicStorageBuffersPerPipelineLayout,
        DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().v1,
                                    maxDynamicStorageBuffersPerPipelineLayout,
                                    bindingCounts.dynamicStorageBufferCount));

    uint32_t maxSampledTexturesPerShaderStage = limits.v1.maxSampledTexturesPerShaderStage;
    uint32_t maxSamplersPerShaderStage = limits.v1.maxSamplersPerShaderStage;
    uint32_t maxStorageBuffersInFragmentStage = limits.compat.maxStorageBuffersInFragmentStage;
    uint32_t maxStorageBuffersInVertexStage = limits.compat.maxStorageBuffersInVertexStage;
    uint32_t maxStorageBuffersPerShaderStage = limits.v1.maxStorageBuffersPerShaderStage;
    uint32_t maxUniformBuffersPerShaderStage = limits.v1.maxUniformBuffersPerShaderStage;
    uint32_t maxStorageTexturesInFragmentStage = limits.compat.maxStorageTexturesInFragmentStage;
    uint32_t maxStorageTexturesInVertexStage = limits.compat.maxStorageTexturesInVertexStage;
    uint32_t maxStorageTexturesPerShaderStage = limits.v1.maxStorageTexturesPerShaderStage;
    for (SingleShaderStage stage : IterateStages(kAllStages)) {
        uint32_t sampledTextureCount = bindingCounts.perStage[stage].sampledTextureCount;
        DAWN_INVALID_IF(
            sampledTextureCount > maxSampledTexturesPerShaderStage,
            "The number of sampled textures (%u) in the %s stage exceeds the maximum "
            "per-stage limit (%u).%s",
            sampledTextureCount, stage, maxSampledTexturesPerShaderStage,
            DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().v1, maxSampledTexturesPerShaderStage,
                                        sampledTextureCount));

        uint32_t externalTextureCount = bindingCounts.perStage[stage].externalTextureCount;
        uint32_t sampledTextureAndExternalTextureCount =
            sampledTextureCount + (externalTextureCount * kSampledTexturesPerExternalTexture);
        DAWN_INVALID_IF(
            sampledTextureAndExternalTextureCount > maxSampledTexturesPerShaderStage,
            "The combination of sampled textures (%u) and external textures (%u × %u) in the %s "
            "stage exceeds the maximum per-stage limit (%u).%s",
            sampledTextureCount, externalTextureCount, kSampledTexturesPerExternalTexture, stage,
            maxSampledTexturesPerShaderStage,
            DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().v1, maxSampledTexturesPerShaderStage,
                                        sampledTextureCount));

        uint32_t samplerCount = bindingCounts.perStage[stage].samplerCount;
        // TODO(crbug.com/dawn/2463): Account for static samplers here.
        DAWN_INVALID_IF(
            samplerCount > maxSamplersPerShaderStage,
            "The number of samplers (%u) in the %s stage exceeds the maximum per-stage limit "
            "(%u).%s",
            samplerCount, stage, maxSamplersPerShaderStage,
            DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().v1, maxSamplersPerShaderStage,
                                        samplerCount));

        uint32_t samplerAndExternalTextureCount =
            samplerCount + (externalTextureCount * kSamplersPerExternalTexture);
        // TODO(crbug.com/dawn/2463): Account for static samplers here.
        DAWN_INVALID_IF(
            samplerAndExternalTextureCount > maxSamplersPerShaderStage,
            "The combination of samplers (%u) and external textures (%u × %u) in the %s stage "
            "exceeds the maximum per-stage limit (%u).%s",
            samplerCount, externalTextureCount, kSamplersPerExternalTexture, stage,
            maxSamplersPerShaderStage,
            DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().v1, maxSamplersPerShaderStage,
                                        samplerAndExternalTextureCount));

        uint32_t storageBufferCount = bindingCounts.perStage[stage].storageBufferCount;
        DAWN_INVALID_IF(
            storageBufferCount > maxStorageBuffersPerShaderStage,
            "The number of storage buffers (%u) in the %s stage exceeds the maximum per-stage "
            "limit (%u).%s",
            storageBufferCount, stage, maxStorageBuffersPerShaderStage,
            DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().v1, maxStorageBuffersPerShaderStage,
                                        storageBufferCount));

        uint32_t storageTextureCount = bindingCounts.perStage[stage].storageTextureCount;
        DAWN_INVALID_IF(
            storageTextureCount > maxStorageTexturesPerShaderStage,
            "The number of storage textures (%u) in the %s stage exceeds the maximum per-stage "
            "limit (%u).%s",
            storageTextureCount, stage, maxStorageTexturesPerShaderStage,
            DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().v1, maxStorageTexturesPerShaderStage,
                                        storageTextureCount));

        uint32_t uniformBufferCount = bindingCounts.perStage[stage].uniformBufferCount;
        DAWN_INVALID_IF(
            uniformBufferCount > maxUniformBuffersPerShaderStage,
            "The number of uniform buffers (%u) in the %s stage exceeds the maximum per-stage "
            "limit (%u).%s",
            uniformBufferCount, stage, maxUniformBuffersPerShaderStage,
            DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().v1, maxUniformBuffersPerShaderStage,
                                        uniformBufferCount));

        uint32_t uniformBuffersAndExternalTextureCount =
            uniformBufferCount + (externalTextureCount * kUniformsPerExternalTexture);
        DAWN_INVALID_IF(
            uniformBuffersAndExternalTextureCount > maxUniformBuffersPerShaderStage,
            "The combination of uniform buffers (%u) and external textures (%u × %u) in the %s "
            "stage exceeds the maximum per-stage limit (%u).%s",
            uniformBufferCount, externalTextureCount, kUniformsPerExternalTexture, stage,
            maxUniformBuffersPerShaderStage,
            DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().v1, maxUniformBuffersPerShaderStage,
                                        uniformBuffersAndExternalTextureCount));

        switch (stage) {
            case SingleShaderStage::Fragment:
                DAWN_INVALID_IF(storageBufferCount > maxStorageBuffersInFragmentStage,
                                "number of storage buffers used in fragment stage (%u) exceeds "
                                "maxStorageBuffersInFragmentStage (%u).%s",
                                storageBufferCount, maxStorageBuffersInFragmentStage,
                                DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().compat,
                                                            maxStorageBuffersInFragmentStage,
                                                            storageBufferCount));
                DAWN_INVALID_IF(storageTextureCount > maxStorageTexturesInFragmentStage,
                                "number of storage textures used in fragment stage (%u) exceeds "
                                "maxStorageTexturesInFragmentStage (%u).%s",
                                storageTextureCount, maxStorageTexturesInFragmentStage,
                                DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().compat,
                                                            maxStorageTexturesInFragmentStage,
                                                            storageTextureCount));
                break;
            case SingleShaderStage::Vertex:
                DAWN_INVALID_IF(storageBufferCount > maxStorageBuffersInVertexStage,
                                "number of storage buffers used in vertex stage (%u) exceeds "
                                "maxStorageBuffersInVertexStage (%u).%s",
                                storageBufferCount, maxStorageBuffersInVertexStage,
                                DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().compat,
                                                            maxStorageBuffersInVertexStage,
                                                            storageBufferCount));
                DAWN_INVALID_IF(storageTextureCount > maxStorageTexturesInVertexStage,
                                "number of storage textures used in vertex stage (%u) exceeds "
                                "maxStorageTexturesInVertexStage (%u).%s",
                                storageTextureCount, maxStorageTexturesInVertexStage,
                                DAWN_INCREASE_LIMIT_MESSAGE(adapter->GetLimits().compat,
                                                            maxStorageTexturesInVertexStage,
                                                            storageTextureCount));
                break;
            default:
                break;
        }
    }

    return {};
}

// BufferBindingInfo

// static
BufferBindingInfo BufferBindingInfo::From(const BufferBindingLayout& layout) {
    BufferBindingLayout defaultedLayout = layout.WithTrivialFrontendDefaults();
    return {{
        .type = defaultedLayout.type,
        .minBindingSize = defaultedLayout.minBindingSize,
        .hasDynamicOffset = defaultedLayout.hasDynamicOffset,
    }};
}

// TextureBindingInfo

// static
TextureBindingInfo TextureBindingInfo::From(const TextureBindingLayout& layout) {
    TextureBindingLayout defaultedLayout = layout.WithTrivialFrontendDefaults();
    return {{
        .sampleType = defaultedLayout.sampleType,
        .viewDimension = defaultedLayout.viewDimension,
        .multisampled = defaultedLayout.multisampled,
    }};
}

// TexelBufferBindingInfo

// static
TexelBufferBindingInfo TexelBufferBindingInfo::From(const TexelBufferBindingLayout& layout) {
    TexelBufferBindingLayout defaultedLayout = layout.WithTrivialFrontendDefaults();
    return {{
        .format = defaultedLayout.format,
        .access = defaultedLayout.access,
    }};
}

// StorageTextureBindingInfo

// static
StorageTextureBindingInfo StorageTextureBindingInfo::From(
    const StorageTextureBindingLayout& layout) {
    StorageTextureBindingLayout defaultedLayout = layout.WithTrivialFrontendDefaults();
    return {{
        .format = defaultedLayout.format,
        .viewDimension = defaultedLayout.viewDimension,
        .access = defaultedLayout.access,
    }};
}

// SamplerBindingInfo

// static
SamplerBindingInfo SamplerBindingInfo::From(const SamplerBindingLayout& layout) {
    SamplerBindingLayout defaultedLayout = layout.WithTrivialFrontendDefaults();
    return {{
        .type = defaultedLayout.type,
    }};
}

// SamplerBindingInfo

// static
StaticSamplerBindingInfo StaticSamplerBindingInfo::From(const StaticSamplerBindingLayout& layout) {
    return {
        .sampler = layout.sampler,
        .use = (layout.sampledTextureBinding == WGPU_LIMIT_U32_UNDEFINED)
                   ? StaticSamplerUse::Freestanding
                   : StaticSamplerUse::SingleTextureYCbCr,
    };
}

}  // namespace dawn::native
