//
// Copyright 2019 The ANGLE Project. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

#include "common.h"

using namespace rx::mtl_shader;

// function_constant(0) is already used by common.h
constant bool kSourceBufferAligned[[function_constant(100)]];
constant bool kSourceIndexIsU8[[function_constant(200)]];
constant bool kSourceIndexIsU16[[function_constant(300)]];
constant bool kSourceIndexIsU32[[function_constant(400)]];
constant bool kSourceBufferUnaligned = !kSourceBufferAligned;
constant bool kUseSourceBufferU8     = kSourceIndexIsU8 || kSourceBufferUnaligned;
constant bool kUseSourceBufferU16    = kSourceIndexIsU16 && kSourceBufferAligned;
constant bool kUseSourceBufferU32    = kSourceIndexIsU32 && kSourceBufferAligned;

struct IndexConversionParams
{
    uint32_t srcOffset;  // offset in bytes
    uint32_t indexCount;
    bool primitiveRestartEnabled;
};

#define ANGLE_IDX_CONVERSION_GUARD(IDX, OPTS) ANGLE_KERNEL_GUARD(IDX, OPTS.indexCount)

inline ushort getIndexAligned(const device ushort *inputAligned, uint offset, uint idx)
{
    return inputAligned[offset / 2 + idx];
}
inline uint getIndexAligned(const device uint *inputAligned, uint offset, uint idx)
{
    return inputAligned[offset / 4 + idx];
}
inline uchar getIndexAligned(const device uchar *input, uint offset, uint idx)
{
    return input[offset + idx];
}
inline ushort getIndexUnalignedU16(const device uchar *input, uint offset, uint idx)
{
    ushort inputLo = input[offset + 2 * idx];
    ushort inputHi = input[offset + 2 * idx + 1];
    // Little endian conversion:
    return inputLo | (inputHi << 8);
}
inline uint getIndexUnalignedU32(const device uchar *input, uint offset, uint idx)
{
    uint input0 = input[offset + 4 * idx];
    uint input1 = input[offset + 4 * idx + 1];
    uint input2 = input[offset + 4 * idx + 2];
    uint input3 = input[offset + 4 * idx + 3];
    // Little endian conversion:
    return input0 | (input1 << 8) | (input2 << 16) | (input3 << 24);
}

kernel void convertIndexU8ToU16(uint idx [[thread_position_in_grid]],
                                constant IndexConversionParams &options [[buffer(0)]],
                                const device uchar *input [[buffer(1)]],
                                device ushort *output [[buffer(2)]])
{
    ANGLE_IDX_CONVERSION_GUARD(idx, options);

    uchar value = getIndexAligned(input, options.srcOffset, idx);

    if (options.primitiveRestartEnabled && value == 0xff)
    {
        output[idx] = 0xffff;
    }
    else
    {
        output[idx] = value;
    }
}

kernel void convertIndexU16(uint idx [[thread_position_in_grid]],
                            constant IndexConversionParams &options [[buffer(0)]],
                            const device uchar *input
                            [[buffer(1), function_constant(kSourceBufferUnaligned)]],
                            const device ushort *inputAligned
                            [[buffer(1), function_constant(kSourceBufferAligned)]],
                            device ushort *output [[buffer(2)]])
{
    ANGLE_IDX_CONVERSION_GUARD(idx, options);

    ushort value;
    if (kSourceBufferAligned)
    {
        value = getIndexAligned(inputAligned, options.srcOffset, idx);
    }
    else
    {
        value = getIndexUnalignedU16(input, options.srcOffset, idx);
    }
    output[idx] = value;
}

kernel void convertIndexU32(uint idx [[thread_position_in_grid]],
                            constant IndexConversionParams &options [[buffer(0)]],
                            const device uchar *input
                            [[buffer(1), function_constant(kSourceBufferUnaligned)]],
                            const device uint *inputAligned
                            [[buffer(1), function_constant(kSourceBufferAligned)]],
                            device uint *output [[buffer(2)]])
{
    ANGLE_IDX_CONVERSION_GUARD(idx, options);

    uint value;
    if (kSourceBufferAligned)
    {
        value = getIndexAligned(inputAligned, options.srcOffset, idx);
    }
    else
    {
        value = getIndexUnalignedU32(input, options.srcOffset, idx);
    }
    output[idx] = value;
}

struct IndexFromArrayParams
{
    uint firstVertex;
    // For triangle fan: vertex count excluding the 1st & 2nd vertices.
    uint vertexCount;
};

// Generate triangle fan indices for glDrawArray()
kernel void genTriFanIndicesFromArray(uint idx [[thread_position_in_grid]],
                                      constant IndexFromArrayParams &options [[buffer(0)]],
                                      device uint *output [[buffer(2)]])
{
    ANGLE_KERNEL_GUARD(idx, options.vertexCount);

    uint vertexIdx = options.firstVertex + 2 + idx;

    // Triangle fan provoking vertex by default is i+1, not pivot
    // vertex.
    output[3 * idx ]    = vertexIdx - 1;
    output[3 * idx + 1] = vertexIdx;
    output[3 * idx + 2] = options.firstVertex;
}

inline uint getIndexU32(uint offset,
                        uint idx,
                        const device uchar *inputU8 [[function_constant(kUseSourceBufferU8)]],
                        const device ushort *inputU16 [[function_constant(kUseSourceBufferU16)]],
                        const device uint *inputU32 [[function_constant(kUseSourceBufferU32)]])
{
    if (kUseSourceBufferU8)
    {
        if (kSourceIndexIsU16)
        {
            return getIndexUnalignedU16(inputU8, offset, idx);
        }
        else if (kSourceIndexIsU32)
        {
            return getIndexUnalignedU32(inputU8, offset, idx);
        }
        return getIndexAligned(inputU8, offset, idx);
    }
    else if (kUseSourceBufferU16)
    {
        return getIndexAligned(inputU16, offset, idx);
    }
    else if (kUseSourceBufferU32)
    {
        return getIndexAligned(inputU32, offset, idx);
    }
    return 0;
}

// NOTE(hqle): triangle fan indices generation doesn't support primitive restart.
// Generate triangle fan indices from an indices buffer. indexCount options indicates number
// of indices starting from the 3rd.
kernel void genTriFanIndicesFromElements(uint idx [[thread_position_in_grid]],
                                         constant IndexConversionParams &options [[buffer(0)]],
                                         const device uchar *inputU8
                                         [[buffer(1), function_constant(kUseSourceBufferU8)]],
                                         const device ushort *inputU16
                                         [[buffer(1), function_constant(kUseSourceBufferU16)]],
                                         const device uint *inputU32
                                         [[buffer(1), function_constant(kUseSourceBufferU32)]],
                                         device uint *output [[buffer(2)]])
{
    ANGLE_IDX_CONVERSION_GUARD(idx, options);

    uint elemIdx = 2 + idx;

    output[3 * idx]     = getIndexU32(options.srcOffset, 0, inputU8, inputU16, inputU32);
    output[3 * idx + 1] = getIndexU32(options.srcOffset, elemIdx - 1, inputU8, inputU16, inputU32);
    output[3 * idx + 2] = getIndexU32(options.srcOffset, elemIdx, inputU8, inputU16, inputU32);
}

// Generate line loop indices for glDrawArray()
kernel void genLineLoopIndicesFromArray(uint idx [[thread_position_in_grid]],
                                        constant IndexFromArrayParams &options [[buffer(0)]],
                                        device uint *output [[buffer(2)]])
{
    uint totalIndices = options.vertexCount + 1;
    ANGLE_KERNEL_GUARD(idx, totalIndices);

    output[idx] = options.firstVertex + idx % options.vertexCount;
}

// NOTE(hqle): lineloop indices generation doesn't support primitive restart.
// Generate line loop indices for glDrawElements()
kernel void genLineLoopIndicesFromElements(uint idx [[thread_position_in_grid]],
                                           constant IndexConversionParams &options [[buffer(0)]],
                                           const device uchar *inputU8
                                           [[buffer(1), function_constant(kUseSourceBufferU8)]],
                                           const device ushort *inputU16
                                           [[buffer(1), function_constant(kUseSourceBufferU16)]],
                                           const device uint *inputU32
                                           [[buffer(1), function_constant(kUseSourceBufferU32)]],
                                           device uint *output [[buffer(2)]])
{
    uint totalTargetIndices = options.indexCount + 1;
    ANGLE_KERNEL_GUARD(idx, totalTargetIndices);

    output[idx] =
        getIndexU32(options.srcOffset, idx % options.indexCount, inputU8, inputU16, inputU32);
}