// Copyright 2018 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "src/dawn/native/vulkan/BindGroupVk.h"

#include <utility>
#include <vector>

#include "src/dawn/common/Enumerator.h"
#include "src/dawn/common/MatchVariant.h"
#include "src/dawn/common/Range.h"
#include "src/dawn/common/ityp_stack_vec.h"
#include "src/dawn/native/ExternalTexture.h"
#include "src/dawn/native/vulkan/BindGroupLayoutVk.h"
#include "src/dawn/native/vulkan/BufferVk.h"
#include "src/dawn/native/vulkan/DeviceVk.h"
#include "src/dawn/native/vulkan/FencedDeleter.h"
#include "src/dawn/native/vulkan/SamplerVk.h"
#include "src/dawn/native/vulkan/TexelBufferViewVk.h"
#include "src/dawn/native/vulkan/TextureVk.h"
#include "src/dawn/native/vulkan/UtilsVulkan.h"
#include "src/dawn/native/vulkan/VulkanError.h"

namespace dawn::native::vulkan {

// static
ResultOrError<Ref<BindGroup>> BindGroup::Create(
    Device* device,
    const UnpackedPtr<BindGroupDescriptor>& descriptor) {
    Ref<BindGroup> bindGroup;
    DAWN_TRY_ASSIGN(
        bindGroup,
        ToBackend(descriptor->layout->GetInternalBindGroupLayout())->AllocateBindGroup(descriptor));
    DAWN_TRY(bindGroup->Initialize(descriptor));
    return bindGroup;
}

BindGroup::BindGroup(Device* device,
                     const UnpackedPtr<BindGroupDescriptor>& descriptor,
                     DescriptorSetAllocation descriptorSetAllocation)
    : BindGroupBase(this, device, descriptor), mDescriptorSetAllocation(descriptorSetAllocation) {}

BindGroup::~BindGroup() = default;

MaybeError BindGroup::InitializeImpl() {
    WriteDescriptorSet(GetHandle(), ToBackend(GetLayout())->GetTextureToStaticSamplerMap());

    SetLabelImpl();
    return {};
}

void BindGroup::WriteDescriptorSet(VkDescriptorSet dsSet,
                                   const TextureToStaticSamplerMap& textureToStaticSampler) const {
    const auto* layout = ToBackend(GetLayout());

    // Now do a write of a single descriptor set with all possible chained data allocated on the
    // stack if possible. We need to preallocate the vectors to avoid reallocation that would
    // invalidate the pointers chained in `writes`.
    // TODO(https://crbug.com/438554018): Use Vulkan's descriptor set update template so as to need
    // a single allocation, and one that could be reused at the layout level.
    const uint32_t bindingCount = static_cast<uint32_t>((layout->GetBindingCount()));
    ityp::stack_vec<uint32_t, VkWriteDescriptorSet, kMaxOptimalBindingsPerGroup> writes(
        bindingCount);
    ityp::stack_vec<uint32_t, VkDescriptorBufferInfo, kMaxOptimalBindingsPerGroup> writeBufferInfo(
        bindingCount);
    ityp::stack_vec<uint32_t, VkDescriptorImageInfo, kMaxOptimalBindingsPerGroup> writeImageInfo(
        bindingCount);
    ityp::stack_vec<uint32_t, VkBufferView, kMaxOptimalBindingsPerGroup> writeTexelBufferViews(
        bindingCount);

    uint32_t numWrites = 0;
    auto AddWrite = [&](BindingIndex bindingIndex, auto DoWrite) {
        const BindingInfo& bindingInfo = layout->GetBindingInfo(bindingIndex);

        // Skip over bindings that cannot be seen by any shaders as they could cause us to create
        // bindgroups with more bindings than the VkDevice's limits. However keep dynamic buffers
        // as the amount of dynamic offsets need to stay the same as WebGPU's so we can passthrough
        // the dynamic offsets.
        if (bindingInfo.visibility == wgpu::ShaderStage::None &&
            bindingIndex >= layout->GetDynamicBufferCount()) {
            return;
        }

        size_t writeIndex = numWrites;
        numWrites++;

        auto& write = writes[writeIndex];
        write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
        write.pNext = nullptr;
        write.dstSet = dsSet;
        // Arrays all have a single binding, so compute the binding index for the array, which is
        // the same as the binding index for the 0th element.
        write.dstBinding = uint32_t(bindingIndex - bindingInfo.indexInArray);
        write.dstArrayElement = uint32_t(bindingInfo.indexInArray);
        write.descriptorCount = 1;
        write.descriptorType = VulkanDescriptorType(bindingInfo);

        DoWrite(writeIndex, &write);
    };

    // Loop over bindings for each binding type. Skip over already destroyed handles as it produces
    // a VVL error. The descriptor set will have null entries, which is invalid to use, but we'll
    // never do that since the WebGPU command buffers will be errors.
    // TODO(https://crbug.com/438554019): Instead, consider replacing the handles with placeholder
    // handles, to skip over branches and later allow for the use of Vulkan descriptor update
    // templates.

    for (BindingIndex i : layout->GetBufferIndices()) {
        BufferBinding binding = GetBindingAsBufferBinding(i);

        VkBuffer handle = ToBackend(binding.buffer)->GetHandle();
        if (handle == VK_NULL_HANDLE) {
            continue;
        }

        // Round uniform buffer binding sizes up to a multiple of 16 bytes since Tint will polyfill
        // them as array<vec4u, ...>.
        auto bufferInfo = std::get<BufferBindingInfo>(layout->GetBindingInfo(i).bindingLayout);
        if (bufferInfo.type == wgpu::BufferBindingType::Uniform) {
            binding.size = Align(binding.size, 16u);
        }

        AddWrite(i, [&](size_t writeIndex, VkWriteDescriptorSet* write) {
            writeBufferInfo[writeIndex].buffer = handle;
            writeBufferInfo[writeIndex].offset = binding.offset;
            writeBufferInfo[writeIndex].range = binding.size;
            write->pBufferInfo = &writeBufferInfo[writeIndex];
        });
    }

    for (BindingIndex i : layout->GetNonStaticSamplerIndices()) {
        Sampler* sampler = ToBackend(GetBindingAsSampler(i));

        AddWrite(i, [&](size_t writeIndex, VkWriteDescriptorSet* write) {
            writeImageInfo[writeIndex].sampler = sampler->GetHandle();
            write->pImageInfo = &writeImageInfo[writeIndex];
        });
    }

    for (BindingIndex i : layout->GetSampledTextureIndices()) {
        TextureView* view = ToBackend(GetBindingAsTextureView(i));

        VkImageView handle = view->GetHandle();
        if (handle == VK_NULL_HANDLE) {
            continue;
        }

        AddWrite(i, [&](size_t writeIndex, VkWriteDescriptorSet* write) {
            // TODO(crbug.com/41488897): Add GetVkDescriptorSet{Index, Type}(BindingIndex) functions
            // to BindGroupLayoutVk that access vectors holding entries for all BGL entries and
            // eliminate this special-case code in favor of calling those functions to assign
            // `dstBinding` and `descriptorType` above.
            // TODO(https://crbug.com/438554018): Alternatively take advantage of the precomputed
            // descriptor update template to do set this up once in the layout and have it be
            // transparent in the BindGroup.
            if (auto it = textureToStaticSampler.find(i); it != textureToStaticSampler.end()) {
                // Write the info of the texture at the binding index for the sampler.
                write->dstBinding = static_cast<uint32_t>(it->second);
                write->descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
            }

            writeImageInfo[writeIndex].imageView = handle;
            writeImageInfo[writeIndex].imageLayout =
                view->VulkanImageLayout(wgpu::TextureUsage::TextureBinding);
            write->pImageInfo = &writeImageInfo[writeIndex];
        });
    }

    for (BindingIndex i : layout->GetStorageTextureIndices()) {
        TextureView* view = ToBackend(GetBindingAsTextureView(i));

        VkImageView handle = VK_NULL_HANDLE;
        if (view->GetFormat().format == wgpu::TextureFormat::BGRA8Unorm) {
            handle = view->GetHandleForBGRA8UnormStorage();
        } else {
            handle = view->GetHandle();
        }
        if (handle == VK_NULL_HANDLE) {
            continue;
        }

        AddWrite(i, [&](size_t writeIndex, VkWriteDescriptorSet* write) {
            writeImageInfo[writeIndex].imageView = handle;
            writeImageInfo[writeIndex].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
            write->pImageInfo = &writeImageInfo[writeIndex];
        });
    }

    for (BindingIndex i : layout->GetTexelBufferIndices()) {
        TexelBufferView* view = ToBackend(GetBindingAsTexelBufferView(i));
        VkBufferView handle = view->GetHandle();
        if (handle == VK_NULL_HANDLE) {
            continue;
        }

        AddWrite(i, [&](size_t writeIndex, VkWriteDescriptorSet* write) {
            writeTexelBufferViews[writeIndex] = handle;
            write->pTexelBufferView = AsVkArray(&writeTexelBufferViews[writeIndex]);
        });
    }

    for (BindingIndex i : layout->GetInputAttachmentIndices()) {
        TextureView* view = ToBackend(GetBindingAsTextureView(i));

        VkImageView handle = view->GetHandle();
        if (handle == VK_NULL_HANDLE) {
            continue;
        }

        AddWrite(i, [&](size_t writeIndex, VkWriteDescriptorSet* write) {
            writeImageInfo[writeIndex].imageView = handle;
            writeImageInfo[writeIndex].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
            write->pImageInfo = &writeImageInfo[writeIndex];
        });
    }

    Device* device = ToBackend(GetDevice());
    // TODO(https://crbug.com/42242088): Batch these updates
    device->fn.UpdateDescriptorSets(device->GetVkDevice(), numWrites, writes.data(), 0, nullptr);
}

void BindGroup::DestroyImpl(DestroyReason reason) {
    BindGroupBase::DestroyImpl(reason);
    ToBackend(GetLayout())->DeallocateDescriptorSet(&mDescriptorSetAllocation);
}

void BindGroup::DeleteThis() {
    // This function must first run the destructor and then deallocate memory. Take a reference to
    // the BindGroupLayout+SlabAllocator before running the destructor so this function can access
    // it afterwards and it's not destroyed prematurely.
    Ref<BindGroupLayout> layout = ToBackend(GetLayout());
    BindGroupBase::DeleteThis();
    layout->DeallocateBindGroup(this);
}

VkDescriptorSet BindGroup::GetHandle() const {
    return mDescriptorSetAllocation.set;
}

void BindGroup::SetLabelImpl() {
    SetDebugName(ToBackend(GetDevice()), mDescriptorSetAllocation.set, "Dawn_BindGroup",
                 GetLabel());
}

}  // namespace dawn::native::vulkan
