//
// Copyright 2020 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.
//
// copy_buffer.metal: implements compute shader that copy formatted data from buffer to texture,
// and from texture to buffer.
// NOTE(hqle): This file is a bit hard to read but there are a lot of repeated works, and it would
// be a pain to implement without the use of macros.
//

@@#include <metal_pack>

#include "common.h"
#include "format_autogen.h"

using namespace rx::mtl_shader;

constant int kCopyFormatType [[function_constant(10)]];

/* -------- copy pixel data between buffer and texture ---------*/
constant int kCopyTextureType [[function_constant(20)]];
constant bool kCopyTextureType2D      = kCopyTextureType == kTextureType2D;
constant bool kCopyTextureType2DArray = kCopyTextureType == kTextureType2DArray;
constant bool kCopyTextureType2DMS    = kCopyTextureType == kTextureType2DMultisample;
constant bool kCopyTextureTypeCube    = kCopyTextureType == kTextureTypeCube;
constant bool kCopyTextureType3D      = kCopyTextureType == kTextureType3D;

struct CopyPixelParams
{
    uint3 copySize;
    uint3 textureOffset;

    uint bufferStartOffset;
    uint pixelSize;
    uint bufferRowPitch;
    uint bufferDepthPitch;
};

struct WritePixelParams
{
    uint2 copySize;
    uint2 textureOffset;

    uint bufferStartOffset;

    uint pixelSize;
    uint bufferRowPitch;

    uint textureLevel;
    uint textureLayer;

    bool reverseTextureRowOrder;
};

// clang-format off
#define TEXTURE_PARAMS(TYPE, ACCESS, NAME_PREFIX)               \
    texture2d<TYPE, ACCESS> NAME_PREFIX##Texture2d              \
    [[texture(0), function_constant(kCopyTextureType2D)]],      \
    texture2d_array<TYPE, ACCESS> NAME_PREFIX##Texture2dArray   \
    [[texture(0), function_constant(kCopyTextureType2DArray)]], \
    texture3d<TYPE, ACCESS> NAME_PREFIX##Texture3d              \
    [[texture(0), function_constant(kCopyTextureType3D)]],      \
    texturecube<TYPE, ACCESS> NAME_PREFIX##TextureCube          \
    [[texture(0), function_constant(kCopyTextureTypeCube)]]

#define FORWARD_TEXTURE_PARAMS(NAME_PREFIX) \
    NAME_PREFIX##Texture2d,                 \
    NAME_PREFIX##Texture2dArray,            \
    NAME_PREFIX##Texture3d,                 \
    NAME_PREFIX##TextureCube

// Params for reading from buffer to texture
#define DEST_TEXTURE_PARAMS(TYPE)  TEXTURE_PARAMS(TYPE, access::write, dst)
#define FORWARD_DEST_TEXTURE_PARAMS FORWARD_TEXTURE_PARAMS(dst)

#define COMMON_READ_KERNEL_PARAMS(TEXTURE_TYPE)     \
    ushort3 gIndices [[thread_position_in_grid]],   \
    constant CopyPixelParams &options[[buffer(0)]], \
    constant uchar *buffer [[buffer(1)]],           \
    DEST_TEXTURE_PARAMS(TEXTURE_TYPE)

#define COMMON_READ_FUNC_PARAMS        \
    uint bufferOffset,                 \
    constant uchar *buffer

#define FORWARD_COMMON_READ_FUNC_PARAMS bufferOffset, buffer

// Params for writing to buffer by coping from texture.
// (NOTE: it has additional multisample source texture parameter)
#define SRC_TEXTURE_PARAMS(TYPE)                             \
    TEXTURE_PARAMS(TYPE, access::read, src),                 \
    texture2d_ms<TYPE, access::read> srcTexture2dMS          \
    [[texture(0), function_constant(kCopyTextureType2DMS)]]  \

#define FORWARD_SRC_TEXTURE_PARAMS FORWARD_TEXTURE_PARAMS(src), srcTexture2dMS

#define COMMON_WRITE_KERNEL_PARAMS(TEXTURE_TYPE)     \
    ushort2 gIndices [[thread_position_in_grid]],    \
    constant WritePixelParams &options[[buffer(0)]], \
    SRC_TEXTURE_PARAMS(TEXTURE_TYPE),                \
    device uchar *buffer [[buffer(1)]]               \

#define COMMON_WRITE_FUNC_PARAMS(TYPE) \
    ushort2 gIndices,                  \
    constant WritePixelParams &options,\
    uint bufferOffset,                 \
    vec<TYPE, 4> color,                \
    device uchar *buffer               \

#define COMMON_WRITE_FLOAT_FUNC_PARAMS COMMON_WRITE_FUNC_PARAMS(float)
#define COMMON_WRITE_SINT_FUNC_PARAMS COMMON_WRITE_FUNC_PARAMS(int)
#define COMMON_WRITE_UINT_FUNC_PARAMS COMMON_WRITE_FUNC_PARAMS(uint)

#define FORWARD_COMMON_WRITE_FUNC_PARAMS gIndices, options, bufferOffset, color, buffer

// clang-format on

// Write to texture code based on texture type:
template <typename T>
static inline void textureWrite(ushort3 gIndices,
                                constant CopyPixelParams &options,
                                vec<T, 4> color,
                                DEST_TEXTURE_PARAMS(T))
{
    uint3 writeIndices = options.textureOffset + uint3(gIndices);
    switch (kCopyTextureType)
    {
        case kTextureType2D:
            dstTexture2d.write(color, writeIndices.xy);
            break;
        case kTextureType2DArray:
            dstTexture2dArray.write(color, writeIndices.xy, writeIndices.z);
            break;
        case kTextureType3D:
            dstTexture3d.write(color, writeIndices);
            break;
        case kTextureTypeCube:
            dstTextureCube.write(color, writeIndices.xy, writeIndices.z);
            break;
    }
}

// Read from texture code based on texture type:
template <typename T>
static inline vec<T, 4> textureRead(ushort2 gIndices,
                                    constant WritePixelParams &options,
                                    SRC_TEXTURE_PARAMS(T))
{
    vec<T, 4> color;
    uint2 coords = uint2(gIndices);
    if (options.reverseTextureRowOrder)
    {
        coords.y = options.copySize.y - 1 - gIndices.y;
    }
    coords += options.textureOffset;
    switch (kCopyTextureType)
    {
        case kTextureType2D:
            color = srcTexture2d.read(coords.xy, options.textureLevel);
            break;
        case kTextureType2DArray:
            color = srcTexture2dArray.read(coords.xy, options.textureLayer, options.textureLevel);
            break;
        case kTextureType2DMultisample:
            color = resolveTextureMS(srcTexture2dMS, coords.xy);
            break;
        case kTextureType3D:
            color = srcTexture3d.read(uint3(coords, options.textureLayer), options.textureLevel);
            break;
        case kTextureTypeCube:
            color = srcTextureCube.read(coords.xy, options.textureLayer, options.textureLevel);
            break;
    }
    return color;
}

// Calculate offset into buffer:
#define CALC_BUFFER_READ_OFFSET(pixelSize)                               \
    options.bufferStartOffset + (gIndices.z * options.bufferDepthPitch + \
                                 gIndices.y * options.bufferRowPitch + gIndices.x * pixelSize)

#define CALC_BUFFER_WRITE_OFFSET(pixelSize) \
    options.bufferStartOffset + (gIndices.y * options.bufferRowPitch + gIndices.x * pixelSize)

// Per format handling code:
#define READ_FORMAT_SWITCH_CASE(format)                                      \
    case FormatID::format: {                                                 \
        auto color = read##format(FORWARD_COMMON_READ_FUNC_PARAMS);          \
        textureWrite(gIndices, options, color, FORWARD_DEST_TEXTURE_PARAMS); \
    }                                                                        \
    break;

#define WRITE_FORMAT_SWITCH_CASE(format)                                         \
    case FormatID::format: {                                                     \
        auto color = textureRead(gIndices, options, FORWARD_SRC_TEXTURE_PARAMS); \
        write##format(FORWARD_COMMON_WRITE_FUNC_PARAMS);                         \
    }                                                                            \
    break;

#define READ_KERNEL_GUARD                                                       \
    if (gIndices.x >= options.copySize.x || gIndices.y >= options.copySize.y || \
        gIndices.z >= options.copySize.z)                                       \
    {                                                                           \
        return;                                                                 \
    }

#define WRITE_KERNEL_GUARD                                                    \
    if (gIndices.x >= options.copySize.x || gIndices.y >= options.copySize.y) \
    {                                                                         \
        return;                                                               \
    }

// R5G6B5
static inline float4 readR5G6B5_UNORM(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    ushort src = bytesToShort<ushort>(buffer, bufferOffset);

    color.r = normalizedToFloat<5>(getShiftedData<5, 11>(src));
    color.g = normalizedToFloat<6>(getShiftedData<6, 5>(src));
    color.b = normalizedToFloat<5>(getShiftedData<5, 0>(src));
    color.a = 1.0;
    return color;
}
static inline void writeR5G6B5_UNORM(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    ushort dst = shiftData<5, 11>(floatToNormalized<5, ushort>(color.r)) |
                 shiftData<6, 5>(floatToNormalized<6, ushort>(color.g)) |
                 shiftData<5, 0>(floatToNormalized<5, ushort>(color.b));

    shortToBytes(dst, bufferOffset, buffer);
}

// R4G4B4A4
static inline float4 readR4G4B4A4_UNORM(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    ushort src = bytesToShort<ushort>(buffer, bufferOffset);

    color.r = normalizedToFloat<4>(getShiftedData<4, 12>(src));
    color.g = normalizedToFloat<4>(getShiftedData<4, 8>(src));
    color.b = normalizedToFloat<4>(getShiftedData<4, 4>(src));
    color.a = normalizedToFloat<4>(getShiftedData<4, 0>(src));
    return color;
}
static inline void writeR4G4B4A4_UNORM(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    ushort dst = shiftData<4, 12>(floatToNormalized<4, ushort>(color.r)) |
                 shiftData<4, 8>(floatToNormalized<4, ushort>(color.g)) |
                 shiftData<4, 4>(floatToNormalized<4, ushort>(color.b)) |
                 shiftData<4, 0>(floatToNormalized<4, ushort>(color.a));
    ;

    shortToBytes(dst, bufferOffset, buffer);
}

// R5G5B5A1
static inline float4 readR5G5B5A1_UNORM(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    ushort src = bytesToShort<ushort>(buffer, bufferOffset);

    color.r = normalizedToFloat<5>(getShiftedData<5, 11>(src));
    color.g = normalizedToFloat<5>(getShiftedData<5, 6>(src));
    color.b = normalizedToFloat<5>(getShiftedData<5, 1>(src));
    color.a = normalizedToFloat<1>(getShiftedData<1, 0>(src));
    return color;
}
static inline void writeR5G5B5A1_UNORM(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    ushort dst = shiftData<5, 11>(floatToNormalized<5, ushort>(color.r)) |
                 shiftData<5, 6>(floatToNormalized<5, ushort>(color.g)) |
                 shiftData<5, 1>(floatToNormalized<5, ushort>(color.b)) |
                 shiftData<1, 0>(floatToNormalized<1, ushort>(color.a));
    ;

    shortToBytes(dst, bufferOffset, buffer);
}

// R10G10B10A2_SINT
static inline int4 readR10G10B10A2_SINT(COMMON_READ_FUNC_PARAMS)
{
    int4 color;
    int src = bytesToInt<int>(buffer, bufferOffset);

    constexpr int3 rgbSignMask(0x200);        // 1 set at the 9 bit
    constexpr int3 negativeMask(0xfffffc00);  // All bits from 10 to 31 set to 1
    constexpr int alphaSignMask = 0x2;
    constexpr int alphaNegMask  = 0xfffffffc;

    color.r = getShiftedData<10, 0>(src);
    color.g = getShiftedData<10, 10>(src);
    color.b = getShiftedData<10, 20>(src);

    int3 isRgbNegative = (color.rgb & rgbSignMask) >> 9;
    color.rgb          = (isRgbNegative * negativeMask) | color.rgb;

    color.a             = getShiftedData<2, 30>(src);
    int isAlphaNegative = color.a & alphaSignMask >> 1;
    color.a             = (isAlphaNegative * alphaNegMask) | color.a;
    return color;
}
// R10G10B10A2_UINT
static inline uint4 readR10G10B10A2_UINT(COMMON_READ_FUNC_PARAMS)
{
    uint4 color;
    uint src = bytesToInt<uint>(buffer, bufferOffset);

    color.r = getShiftedData<10, 0>(src);
    color.g = getShiftedData<10, 10>(src);
    color.b = getShiftedData<10, 20>(src);
    color.a = getShiftedData<2, 30>(src);
    return color;
}

// R8G8B8A8 generic
static inline float4 readR8G8B8A8(COMMON_READ_FUNC_PARAMS, bool isSRGB)
{
    float4 color;
    uint src = bytesToInt<uint>(buffer, bufferOffset);

    if (isSRGB)
    {
        color = unpack_unorm4x8_srgb_to_float(src);
    }
    else
    {
        color = unpack_unorm4x8_to_float(src);
    }
    return color;
}
static inline void writeR8G8B8A8(COMMON_WRITE_FLOAT_FUNC_PARAMS, bool isSRGB)
{
    uint dst;

    if (isSRGB)
    {
        dst = pack_float_to_srgb_unorm4x8(color);
    }
    else
    {
        dst = pack_float_to_unorm4x8(color);
    }

    intToBytes(dst, bufferOffset, buffer);
}

static inline float4 readR8G8B8(COMMON_READ_FUNC_PARAMS, bool isSRGB)
{
    float4 color;
    color.r = normalizedToFloat<uchar>(buffer[bufferOffset]);
    color.g = normalizedToFloat<uchar>(buffer[bufferOffset + 1]);
    color.b = normalizedToFloat<uchar>(buffer[bufferOffset + 2]);
    color.a = 1.0;

    if (isSRGB)
    {
        color = sRGBtoLinear(color);
    }
    return color;
}
static inline void writeR8G8B8(COMMON_WRITE_FLOAT_FUNC_PARAMS, bool isSRGB)
{
    color.a = 1.0;
    uint dst;

    if (isSRGB)
    {
        dst = pack_float_to_srgb_unorm4x8(color);
    }
    else
    {
        dst = pack_float_to_unorm4x8(color);
    }
    int24bitToBytes(dst, bufferOffset, buffer);
}

// RGBA8_SNORM
static inline float4 readR8G8B8A8_SNORM(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    uint src = bytesToInt<uint>(buffer, bufferOffset);

    color = unpack_snorm4x8_to_float(src);

    return color;
}
static inline void writeR8G8B8A8_SNORM(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    uint dst = pack_float_to_snorm4x8(color);

    intToBytes(dst, bufferOffset, buffer);
}

// RGB8_SNORM
static inline float4 readR8G8B8_SNORM(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = normalizedToFloat<7, char>(buffer[bufferOffset]);
    color.g = normalizedToFloat<7, char>(buffer[bufferOffset + 1]);
    color.b = normalizedToFloat<7, char>(buffer[bufferOffset + 2]);
    color.a = 1.0;

    return color;
}
static inline void writeR8G8B8_SNORM(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    uint dst = pack_float_to_snorm4x8(color);

    int24bitToBytes(dst, bufferOffset, buffer);
}

// RGBA8
static inline float4 readR8G8B8A8_UNORM(COMMON_READ_FUNC_PARAMS)
{
    return readR8G8B8A8(FORWARD_COMMON_READ_FUNC_PARAMS, false);
}
static inline void writeR8G8B8A8_UNORM(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    return writeR8G8B8A8(FORWARD_COMMON_WRITE_FUNC_PARAMS, false);
}

static inline float4 readR8G8B8A8_UNORM_SRGB(COMMON_READ_FUNC_PARAMS)
{
    return readR8G8B8A8(FORWARD_COMMON_READ_FUNC_PARAMS, true);
}
static inline void writeR8G8B8A8_UNORM_SRGB(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    return writeR8G8B8A8(FORWARD_COMMON_WRITE_FUNC_PARAMS, true);
}

// BGRA8
static inline float4 readB8G8R8A8_UNORM(COMMON_READ_FUNC_PARAMS)
{
    return readR8G8B8A8(FORWARD_COMMON_READ_FUNC_PARAMS, false).bgra;
}
static inline void writeB8G8R8A8_UNORM(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    color.rgba = color.bgra;
    return writeR8G8B8A8(FORWARD_COMMON_WRITE_FUNC_PARAMS, false);
}

static inline float4 readB8G8R8A8_UNORM_SRGB(COMMON_READ_FUNC_PARAMS)
{
    return readR8G8B8A8(FORWARD_COMMON_READ_FUNC_PARAMS, true).bgra;
}
static inline void writeB8G8R8A8_UNORM_SRGB(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    color.rgba = color.bgra;
    return writeR8G8B8A8(FORWARD_COMMON_WRITE_FUNC_PARAMS, true);
}

// RGB8
static inline float4 readR8G8B8_UNORM(COMMON_READ_FUNC_PARAMS)
{
    return readR8G8B8(FORWARD_COMMON_READ_FUNC_PARAMS, false);
}
static inline void writeR8G8B8_UNORM(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    return writeR8G8B8(FORWARD_COMMON_WRITE_FUNC_PARAMS, false);
}

static inline float4 readR8G8B8_UNORM_SRGB(COMMON_READ_FUNC_PARAMS)
{
    return readR8G8B8(FORWARD_COMMON_READ_FUNC_PARAMS, true);
}
static inline void writeR8G8B8_UNORM_SRGB(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    return writeR8G8B8(FORWARD_COMMON_WRITE_FUNC_PARAMS, true);
}

// L8
static inline float4 readL8_UNORM(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.rgb = float3(normalizedToFloat<uchar>(buffer[bufferOffset]));
    color.a   = 1.0;
    return color;
}
static inline void writeL8_UNORM(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    buffer[bufferOffset] = floatToNormalized<uchar>(color.r);
}

// A8
static inline void writeA8_UNORM(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    buffer[bufferOffset] = floatToNormalized<uchar>(color.a);
}

// L8A8
static inline float4 readL8A8_UNORM(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.rgb = float3(normalizedToFloat<uchar>(buffer[bufferOffset]));
    color.a   = normalizedToFloat<uchar>(buffer[bufferOffset + 1]);
    return color;
}
static inline void writeL8A8_UNORM(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    buffer[bufferOffset]     = floatToNormalized<uchar>(color.r);
    buffer[bufferOffset + 1] = floatToNormalized<uchar>(color.a);
}

// R8
static inline float4 readR8_UNORM(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = normalizedToFloat<uchar>(buffer[bufferOffset]);
    color.g = color.b = 0.0;
    color.a           = 1.0;
    return color;
}
static inline void writeR8_UNORM(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    buffer[bufferOffset] = floatToNormalized<uchar>(color.r);
}

static inline float4 readR8_SNORM(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = normalizedToFloat<7, char>(buffer[bufferOffset]);
    color.g = color.b = 0.0;
    color.a           = 1.0;
    return color;
}
static inline void writeR8_SNORM(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    buffer[bufferOffset] = as_type<uchar>(floatToNormalized<7, char>(color.r));
}

// R8_SINT
static inline int4 readR8_SINT(COMMON_READ_FUNC_PARAMS)
{
    int4 color;
    color.r = as_type<char>(buffer[bufferOffset]);
    color.g = color.b = 0;
    color.a           = 1;
    return color;
}
static inline void writeR8_SINT(COMMON_WRITE_SINT_FUNC_PARAMS)
{
    buffer[bufferOffset] = static_cast<uchar>(color.r);
}

// R8_UINT
static inline uint4 readR8_UINT(COMMON_READ_FUNC_PARAMS)
{
    uint4 color;
    color.r = as_type<uchar>(buffer[bufferOffset]);
    color.g = color.b = 0;
    color.a           = 1;
    return color;
}
static inline void writeR8_UINT(COMMON_WRITE_UINT_FUNC_PARAMS)
{
    buffer[bufferOffset] = static_cast<uchar>(color.r);
}

// R8G8
static inline float4 readR8G8_UNORM(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = normalizedToFloat<uchar>(buffer[bufferOffset]);
    color.g = normalizedToFloat<uchar>(buffer[bufferOffset + 1]);
    color.b = 0.0;
    color.a = 1.0;
    return color;
}
static inline void writeR8G8_UNORM(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    buffer[bufferOffset]     = floatToNormalized<uchar>(color.r);
    buffer[bufferOffset + 1] = floatToNormalized<uchar>(color.g);
}

static inline float4 readR8G8_SNORM(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = normalizedToFloat<7, char>(buffer[bufferOffset]);
    color.g = normalizedToFloat<7, char>(buffer[bufferOffset + 1]);
    color.b = 0.0;
    color.a = 1.0;
    return color;
}
static inline void writeR8G8_SNORM(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    buffer[bufferOffset]     = as_type<uchar>(floatToNormalized<7, char>(color.r));
    buffer[bufferOffset + 1] = as_type<uchar>(floatToNormalized<7, char>(color.g));
}

// RG8_SINT
static inline int4 readR8G8_SINT(COMMON_READ_FUNC_PARAMS)
{
    int4 color;
    color.r = as_type<char>(buffer[bufferOffset]);
    color.g = as_type<char>(buffer[bufferOffset + 1]);
    color.b = 0;
    color.a = 1;
    return color;
}
static inline void writeR8G8_SINT(COMMON_WRITE_SINT_FUNC_PARAMS)
{
    buffer[bufferOffset]     = static_cast<uchar>(color.r);
    buffer[bufferOffset + 1] = static_cast<uchar>(color.g);
}

// RG8_UINT
static inline uint4 readR8G8_UINT(COMMON_READ_FUNC_PARAMS)
{
    uint4 color;
    color.r = as_type<uchar>(buffer[bufferOffset]);
    color.g = as_type<uchar>(buffer[bufferOffset + 1]);
    color.b = 0;
    color.a = 1;
    return color;
}
static inline void writeR8G8_UINT(COMMON_WRITE_UINT_FUNC_PARAMS)
{
    buffer[bufferOffset]     = static_cast<uchar>(color.r);
    buffer[bufferOffset + 1] = static_cast<uchar>(color.g);
}

// R8G8B8_SINT
static inline int4 readR8G8B8_SINT(COMMON_READ_FUNC_PARAMS)
{
    int4 color;
    color.r = as_type<char>(buffer[bufferOffset]);
    color.g = as_type<char>(buffer[bufferOffset + 1]);
    color.b = as_type<char>(buffer[bufferOffset + 2]);
    color.a = 1;
    return color;
}

// R8G8B8_UINT
static inline uint4 readR8G8B8_UINT(COMMON_READ_FUNC_PARAMS)
{
    uint4 color;
    color.r = as_type<uchar>(buffer[bufferOffset]);
    color.g = as_type<uchar>(buffer[bufferOffset + 1]);
    color.b = as_type<uchar>(buffer[bufferOffset + 2]);
    color.a = 1;
    return color;
}

// R8G8G8A8_SINT
static inline int4 readR8G8B8A8_SINT(COMMON_READ_FUNC_PARAMS)
{
    int4 color;
    color.r = as_type<char>(buffer[bufferOffset]);
    color.g = as_type<char>(buffer[bufferOffset + 1]);
    color.b = as_type<char>(buffer[bufferOffset + 2]);
    color.a = as_type<char>(buffer[bufferOffset + 3]);
    return color;
}
static inline void writeR8G8B8A8_SINT(COMMON_WRITE_SINT_FUNC_PARAMS)
{
    buffer[bufferOffset]     = static_cast<uchar>(color.r);
    buffer[bufferOffset + 1] = static_cast<uchar>(color.g);
    buffer[bufferOffset + 2] = static_cast<uchar>(color.b);
    buffer[bufferOffset + 3] = static_cast<uchar>(color.a);
}

// R8G8G8A8_UINT
static inline uint4 readR8G8B8A8_UINT(COMMON_READ_FUNC_PARAMS)
{
    uint4 color;
    color.r = as_type<uchar>(buffer[bufferOffset]);
    color.g = as_type<uchar>(buffer[bufferOffset + 1]);
    color.b = as_type<uchar>(buffer[bufferOffset + 2]);
    color.a = as_type<uchar>(buffer[bufferOffset + 3]);
    return color;
}
static inline void writeR8G8B8A8_UINT(COMMON_WRITE_UINT_FUNC_PARAMS)
{
    buffer[bufferOffset]     = static_cast<uchar>(color.r);
    buffer[bufferOffset + 1] = static_cast<uchar>(color.g);
    buffer[bufferOffset + 2] = static_cast<uchar>(color.b);
    buffer[bufferOffset + 3] = static_cast<uchar>(color.a);
}

// R16_FLOAT
static inline float4 readR16_FLOAT(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = as_type<half>(bytesToShort<ushort>(buffer, bufferOffset));
    color.g = color.b = 0.0;
    color.a           = 1.0;
    return color;
}
static inline void writeR16_FLOAT(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    shortToBytes(as_type<ushort>(static_cast<half>(color.r)), bufferOffset, buffer);
}
// R16_NORM
template <typename ShortType>
static inline float4 readR16_NORM(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = normalizedToFloat<ShortType>(bytesToShort<ShortType>(buffer, bufferOffset));
    color.g = color.b = 0.0;
    color.a           = 1.0;
    return color;
}
#define readR16_SNORM readR16_NORM<short>
#define readR16_UNORM readR16_NORM<ushort>

// R16_SINT
static inline int4 readR16_SINT(COMMON_READ_FUNC_PARAMS)
{
    int4 color;
    color.r = bytesToShort<short>(buffer, bufferOffset);
    color.g = color.b = 0;
    color.a           = 1;
    return color;
}
static inline void writeR16_SINT(COMMON_WRITE_SINT_FUNC_PARAMS)
{
    shortToBytes(static_cast<short>(color.r), bufferOffset, buffer);
}

// R16_UINT
static inline uint4 readR16_UINT(COMMON_READ_FUNC_PARAMS)
{
    uint4 color;
    color.r = bytesToShort<ushort>(buffer, bufferOffset);
    color.g = color.b = 0;
    color.a           = 1;
    return color;
}
static inline void writeR16_UINT(COMMON_WRITE_UINT_FUNC_PARAMS)
{
    shortToBytes(static_cast<ushort>(color.r), bufferOffset, buffer);
}

// A16_FLOAT
static inline float4 readA16_FLOAT(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.a   = as_type<half>(bytesToShort<ushort>(buffer, bufferOffset));
    color.rgb = 0.0;
    return color;
}
static inline void writeA16_FLOAT(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    shortToBytes(as_type<ushort>(static_cast<half>(color.a)), bufferOffset, buffer);
}

// L16_FLOAT
static inline float4 readL16_FLOAT(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.rgb = as_type<half>(bytesToShort<ushort>(buffer, bufferOffset));
    color.a   = 1.0;
    return color;
}
static inline void writeL16_FLOAT(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    shortToBytes(as_type<ushort>(static_cast<half>(color.r)), bufferOffset, buffer);
}

// L16A16_FLOAT
static inline float4 readL16A16_FLOAT(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.rgb = as_type<half>(bytesToShort<ushort>(buffer, bufferOffset));
    color.a   = as_type<half>(bytesToShort<ushort>(buffer, bufferOffset + 2));
    return color;
}
static inline void writeL16A16_FLOAT(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    shortToBytes(as_type<ushort>(static_cast<half>(color.r)), bufferOffset, buffer);
    shortToBytes(as_type<ushort>(static_cast<half>(color.a)), bufferOffset + 2, buffer);
}

// R16G16_FLOAT
static inline float4 readR16G16_FLOAT(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = as_type<half>(bytesToShort<ushort>(buffer, bufferOffset));
    color.g = as_type<half>(bytesToShort<ushort>(buffer, bufferOffset + 2));
    color.b = 0.0;
    color.a = 1.0;
    return color;
}
static inline void writeR16G16_FLOAT(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    shortToBytes(as_type<ushort>(static_cast<half>(color.r)), bufferOffset, buffer);
    shortToBytes(as_type<ushort>(static_cast<half>(color.g)), bufferOffset + 2, buffer);
}

// R16G16_NORM
template <typename ShortType>
static inline float4 readR16G16_NORM(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = normalizedToFloat<ShortType>(bytesToShort<ShortType>(buffer, bufferOffset));
    color.g = normalizedToFloat<ShortType>(bytesToShort<ShortType>(buffer, bufferOffset + 2));
    color.b = 0.0;
    color.a = 1.0;
    return color;
}
#define readR16G16_SNORM readR16G16_NORM<short>
#define readR16G16_UNORM readR16G16_NORM<ushort>

// R16G16_SINT
static inline int4 readR16G16_SINT(COMMON_READ_FUNC_PARAMS)
{
    int4 color;
    color.r = bytesToShort<short>(buffer, bufferOffset);
    color.g = bytesToShort<short>(buffer, bufferOffset + 2);
    color.b = 0;
    color.a = 1;
    return color;
}
static inline void writeR16G16_SINT(COMMON_WRITE_SINT_FUNC_PARAMS)
{
    shortToBytes(static_cast<short>(color.r), bufferOffset, buffer);
    shortToBytes(static_cast<short>(color.g), bufferOffset + 2, buffer);
}

// R16G16_UINT
static inline uint4 readR16G16_UINT(COMMON_READ_FUNC_PARAMS)
{
    uint4 color;
    color.r = bytesToShort<ushort>(buffer, bufferOffset);
    color.g = bytesToShort<ushort>(buffer, bufferOffset + 2);
    color.b = 0;
    color.a = 1;
    return color;
}
static inline void writeR16G16_UINT(COMMON_WRITE_UINT_FUNC_PARAMS)
{
    shortToBytes(static_cast<ushort>(color.r), bufferOffset, buffer);
    shortToBytes(static_cast<ushort>(color.g), bufferOffset + 2, buffer);
}

// R16G16B16_FLOAT
static inline float4 readR16G16B16_FLOAT(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = as_type<half>(bytesToShort<ushort>(buffer, bufferOffset));
    color.g = as_type<half>(bytesToShort<ushort>(buffer, bufferOffset + 2));
    color.b = as_type<half>(bytesToShort<ushort>(buffer, bufferOffset + 4));
    color.a = 1.0;
    return color;
}

// R16G16B16_NORM
template <typename ShortType>
static inline float4 readR16G16B16_NORM(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = normalizedToFloat<ShortType>(bytesToShort<ShortType>(buffer, bufferOffset));
    color.g = normalizedToFloat<ShortType>(bytesToShort<ShortType>(buffer, bufferOffset + 2));
    color.b = normalizedToFloat<ShortType>(bytesToShort<ShortType>(buffer, bufferOffset + 4));
    color.a = 1.0;
    return color;
}
#define readR16G16B16_SNORM readR16G16B16_NORM<short>
#define readR16G16B16_UNORM readR16G16B16_NORM<ushort>
// R16G16B16_SINT
static inline int4 readR16G16B16_SINT(COMMON_READ_FUNC_PARAMS)
{
    int4 color;
    color.r = bytesToShort<short>(buffer, bufferOffset);
    color.g = bytesToShort<short>(buffer, bufferOffset + 2);
    color.b = bytesToShort<short>(buffer, bufferOffset + 4);
    color.a = 1;
    return color;
}

// R16G16B16_UINT
static inline uint4 readR16G16B16_UINT(COMMON_READ_FUNC_PARAMS)
{
    uint4 color;
    color.r = bytesToShort<ushort>(buffer, bufferOffset);
    color.g = bytesToShort<ushort>(buffer, bufferOffset + 2);
    color.b = bytesToShort<ushort>(buffer, bufferOffset + 4);
    color.a = 1;
    return color;
}

// R16G16B16A16_FLOAT
static inline float4 readR16G16B16A16_FLOAT(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = as_type<half>(bytesToShort<ushort>(buffer, bufferOffset));
    color.g = as_type<half>(bytesToShort<ushort>(buffer, bufferOffset + 2));
    color.b = as_type<half>(bytesToShort<ushort>(buffer, bufferOffset + 4));
    color.a = as_type<half>(bytesToShort<ushort>(buffer, bufferOffset + 6));
    return color;
}
static inline void writeR16G16B16A16_FLOAT(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    shortToBytes(as_type<ushort>(static_cast<half>(color.r)), bufferOffset, buffer);
    shortToBytes(as_type<ushort>(static_cast<half>(color.g)), bufferOffset + 2, buffer);
    shortToBytes(as_type<ushort>(static_cast<half>(color.b)), bufferOffset + 4, buffer);
    shortToBytes(as_type<ushort>(static_cast<half>(color.a)), bufferOffset + 6, buffer);
}

// R16G16B16A16_NORM
template <typename ShortType>
static inline float4 readR16G16B16A16_NORM(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = normalizedToFloat<ShortType>(bytesToShort<ShortType>(buffer, bufferOffset));
    color.g = normalizedToFloat<ShortType>(bytesToShort<ShortType>(buffer, bufferOffset + 2));
    color.b = normalizedToFloat<ShortType>(bytesToShort<ShortType>(buffer, bufferOffset + 4));
    color.a = normalizedToFloat<ShortType>(bytesToShort<ShortType>(buffer, bufferOffset + 6));
    return color;
}
#define readR16G16B16A16_SNORM readR16G16B16A16_NORM<short>
#define readR16G16B16A16_UNORM readR16G16B16A16_NORM<ushort>

// R16G16B16A16_SINT
static inline int4 readR16G16B16A16_SINT(COMMON_READ_FUNC_PARAMS)
{
    int4 color;
    color.r = bytesToShort<short>(buffer, bufferOffset);
    color.g = bytesToShort<short>(buffer, bufferOffset + 2);
    color.b = bytesToShort<short>(buffer, bufferOffset + 4);
    color.a = bytesToShort<short>(buffer, bufferOffset + 6);
    return color;
}
static inline void writeR16G16B16A16_SINT(COMMON_WRITE_SINT_FUNC_PARAMS)
{
    shortToBytes(static_cast<short>(color.r), bufferOffset, buffer);
    shortToBytes(static_cast<short>(color.g), bufferOffset + 2, buffer);
    shortToBytes(static_cast<short>(color.b), bufferOffset + 4, buffer);
    shortToBytes(static_cast<short>(color.a), bufferOffset + 6, buffer);
}

// R16G16B16A16_UINT
static inline uint4 readR16G16B16A16_UINT(COMMON_READ_FUNC_PARAMS)
{
    uint4 color;
    color.r = bytesToShort<ushort>(buffer, bufferOffset);
    color.g = bytesToShort<ushort>(buffer, bufferOffset + 2);
    color.b = bytesToShort<ushort>(buffer, bufferOffset + 4);
    color.a = bytesToShort<ushort>(buffer, bufferOffset + 6);
    return color;
}
static inline void writeR16G16B16A16_UINT(COMMON_WRITE_UINT_FUNC_PARAMS)
{
    shortToBytes(static_cast<ushort>(color.r), bufferOffset, buffer);
    shortToBytes(static_cast<ushort>(color.g), bufferOffset + 2, buffer);
    shortToBytes(static_cast<ushort>(color.b), bufferOffset + 4, buffer);
    shortToBytes(static_cast<ushort>(color.a), bufferOffset + 6, buffer);
}

// R32_FLOAT
static inline float4 readR32_FLOAT(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = as_type<float>(bytesToInt<uint>(buffer, bufferOffset));
    color.g = color.b = 0.0;
    color.a           = 1.0;
    return color;
}
static inline void writeR32_FLOAT(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    intToBytes(as_type<uint>(color.r), bufferOffset, buffer);
}

// R32_NORM
template <typename IntType>
static inline float4 readR32_NORM(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = normalizedToFloat<IntType>(bytesToInt<IntType>(buffer, bufferOffset));
    color.g = color.b = 0.0;
    color.a           = 1.0;
    return color;
}
#define readR32_SNORM readR32_NORM<int>
#define readR32_UNORM readR32_NORM<uint>

// A32_FLOAT
static inline float4 readA32_FLOAT(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.a   = as_type<float>(bytesToInt<uint>(buffer, bufferOffset));
    color.rgb = 0.0;
    return color;
}
static inline void writeA32_FLOAT(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    intToBytes(as_type<uint>(color.a), bufferOffset, buffer);
}

// L32_FLOAT
static inline float4 readL32_FLOAT(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.rgb = as_type<float>(bytesToInt<uint>(buffer, bufferOffset));
    color.a   = 1.0;
    return color;
}
static inline void writeL32_FLOAT(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    intToBytes(as_type<uint>(color.r), bufferOffset, buffer);
}

// R32_SINT
static inline int4 readR32_SINT(COMMON_READ_FUNC_PARAMS)
{
    int4 color;
    color.r = bytesToInt<int>(buffer, bufferOffset);
    color.g = color.b = 0;
    color.a           = 1;
    return color;
}
static inline void writeR32_SINT(COMMON_WRITE_SINT_FUNC_PARAMS)
{
    intToBytes(color.r, bufferOffset, buffer);
}

// R32_FIXED
static inline float4 readR32_FIXED(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    constexpr float kDivisor = 1.0f / (1 << 16);
    color.r                  = bytesToInt<int>(buffer, bufferOffset) * kDivisor;
    color.g = color.b = 0.0;
    color.a           = 1.0;
    return color;
}

// R32_UINT
static inline uint4 readR32_UINT(COMMON_READ_FUNC_PARAMS)
{
    uint4 color;
    color.r = bytesToInt<uint>(buffer, bufferOffset);
    color.g = color.b = 0;
    color.a           = 1;
    return color;
}
static inline void writeR32_UINT(COMMON_WRITE_UINT_FUNC_PARAMS)
{
    intToBytes(color.r, bufferOffset, buffer);
}

// L32A32_FLOAT
static inline float4 readL32A32_FLOAT(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.rgb = as_type<float>(bytesToInt<uint>(buffer, bufferOffset));
    color.a   = as_type<float>(bytesToInt<uint>(buffer, bufferOffset + 4));
    return color;
}
static inline void writeL32A32_FLOAT(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    intToBytes(as_type<uint>(color.r), bufferOffset, buffer);
    intToBytes(as_type<uint>(color.a), bufferOffset + 4, buffer);
}

// R32G32_FLOAT
static inline float4 readR32G32_FLOAT(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = as_type<float>(bytesToInt<uint>(buffer, bufferOffset));
    color.g = as_type<float>(bytesToInt<uint>(buffer, bufferOffset + 4));
    color.b = 0.0;
    color.a = 1.0;
    return color;
}
static inline void writeR32G32_FLOAT(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    intToBytes(as_type<uint>(color.r), bufferOffset, buffer);
    intToBytes(as_type<uint>(color.g), bufferOffset + 4, buffer);
}

// R32G32_NORM
template <typename IntType>
static inline float4 readR32G32_NORM(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = normalizedToFloat<IntType>(bytesToInt<IntType>(buffer, bufferOffset));
    color.g = normalizedToFloat<IntType>(bytesToInt<IntType>(buffer, bufferOffset + 4));
    color.b = 0.0;
    color.a = 1.0;
    return color;
}
#define readR32G32_SNORM readR32G32_NORM<int>
#define readR32G32_UNORM readR32G32_NORM<uint>

// R32G32_SINT
static inline int4 readR32G32_SINT(COMMON_READ_FUNC_PARAMS)
{
    int4 color;
    color.r = bytesToInt<int>(buffer, bufferOffset);
    color.g = bytesToInt<int>(buffer, bufferOffset + 4);
    color.b = 0;
    color.a = 1;
    return color;
}
static inline void writeR32G32_SINT(COMMON_WRITE_SINT_FUNC_PARAMS)
{
    intToBytes(color.r, bufferOffset, buffer);
    intToBytes(color.g, bufferOffset + 4, buffer);
}

// R32G32_FIXED
static inline float4 readR32G32_FIXED(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    constexpr float kDivisor = 1.0f / (1 << 16);
    color.r                  = bytesToInt<int>(buffer, bufferOffset) * kDivisor;
    color.g                  = bytesToInt<int>(buffer, bufferOffset + 4) * kDivisor;
    color.b                  = 0.0;
    color.a                  = 1.0;
    return color;
}

// R32G32_UINT
static inline uint4 readR32G32_UINT(COMMON_READ_FUNC_PARAMS)
{
    uint4 color;
    color.r = bytesToInt<uint>(buffer, bufferOffset);
    color.g = bytesToInt<uint>(buffer, bufferOffset + 4);
    color.b = 0;
    color.a = 1;
    return color;
}
static inline void writeR32G32_UINT(COMMON_WRITE_UINT_FUNC_PARAMS)
{
    intToBytes(color.r, bufferOffset, buffer);
    intToBytes(color.g, bufferOffset + 4, buffer);
}

// R32G32B32_FLOAT
static inline float4 readR32G32B32_FLOAT(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = as_type<float>(bytesToInt<uint>(buffer, bufferOffset));
    color.g = as_type<float>(bytesToInt<uint>(buffer, bufferOffset + 4));
    color.b = as_type<float>(bytesToInt<uint>(buffer, bufferOffset + 8));
    color.a = 1.0;
    return color;
}

// R32G32B32_NORM
template <typename IntType>
static inline float4 readR32G32B32_NORM(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = normalizedToFloat<IntType>(bytesToInt<IntType>(buffer, bufferOffset));
    color.g = normalizedToFloat<IntType>(bytesToInt<IntType>(buffer, bufferOffset + 4));
    color.b = normalizedToFloat<IntType>(bytesToInt<IntType>(buffer, bufferOffset + 8));
    color.a = 1.0;
    return color;
}
#define readR32G32B32_SNORM readR32G32B32_NORM<int>
#define readR32G32B32_UNORM readR32G32B32_NORM<uint>

// R32G32B32_SINT
static inline int4 readR32G32B32_SINT(COMMON_READ_FUNC_PARAMS)
{
    int4 color;
    color.r = bytesToInt<int>(buffer, bufferOffset);
    color.g = bytesToInt<int>(buffer, bufferOffset + 4);
    color.b = bytesToInt<int>(buffer, bufferOffset + 8);
    color.a = 1;
    return color;
}

// R32G32B32_FIXED
static inline float4 readR32G32B32_FIXED(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    constexpr float kDivisor = 1.0f / (1 << 16);
    color.r                  = bytesToInt<int>(buffer, bufferOffset) * kDivisor;
    color.g                  = bytesToInt<int>(buffer, bufferOffset + 4) * kDivisor;
    color.b                  = bytesToInt<int>(buffer, bufferOffset + 8) * kDivisor;
    color.a                  = 1.0;
    return color;
}

// R32G32B32_UINT
static inline uint4 readR32G32B32_UINT(COMMON_READ_FUNC_PARAMS)
{
    uint4 color;
    color.r = bytesToInt<uint>(buffer, bufferOffset);
    color.g = bytesToInt<uint>(buffer, bufferOffset + 4);
    color.b = bytesToInt<uint>(buffer, bufferOffset + 8);
    color.a = 1;
    return color;
}

// R32G32B32A32_FLOAT
static inline float4 readR32G32B32A32_FLOAT(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = as_type<float>(bytesToInt<uint>(buffer, bufferOffset));
    color.g = as_type<float>(bytesToInt<uint>(buffer, bufferOffset + 4));
    color.b = as_type<float>(bytesToInt<uint>(buffer, bufferOffset + 8));
    color.a = as_type<float>(bytesToInt<uint>(buffer, bufferOffset + 12));
    return color;
}
static inline void writeR32G32B32A32_FLOAT(COMMON_WRITE_FLOAT_FUNC_PARAMS)
{
    intToBytes(as_type<uint>(color.r), bufferOffset, buffer);
    intToBytes(as_type<uint>(color.g), bufferOffset + 4, buffer);
    intToBytes(as_type<uint>(color.b), bufferOffset + 8, buffer);
    intToBytes(as_type<uint>(color.a), bufferOffset + 12, buffer);
}

// R32G32B32A32_NORM
template <typename IntType>
static inline float4 readR32G32B32A32_NORM(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    color.r = normalizedToFloat<IntType>(bytesToInt<IntType>(buffer, bufferOffset));
    color.g = normalizedToFloat<IntType>(bytesToInt<IntType>(buffer, bufferOffset + 4));
    color.b = normalizedToFloat<IntType>(bytesToInt<IntType>(buffer, bufferOffset + 8));
    color.a = normalizedToFloat<IntType>(bytesToInt<IntType>(buffer, bufferOffset + 12));
    return color;
}
#define readR32G32B32A32_SNORM readR32G32B32A32_NORM<int>
#define readR32G32B32A32_UNORM readR32G32B32A32_NORM<uint>

// R32G32B32A32_SINT
static inline int4 readR32G32B32A32_SINT(COMMON_READ_FUNC_PARAMS)
{
    int4 color;
    color.r = bytesToInt<int>(buffer, bufferOffset);
    color.g = bytesToInt<int>(buffer, bufferOffset + 4);
    color.b = bytesToInt<int>(buffer, bufferOffset + 8);
    color.a = bytesToInt<int>(buffer, bufferOffset + 12);
    return color;
}
static inline void writeR32G32B32A32_SINT(COMMON_WRITE_SINT_FUNC_PARAMS)
{
    intToBytes(color.r, bufferOffset, buffer);
    intToBytes(color.g, bufferOffset + 4, buffer);
    intToBytes(color.b, bufferOffset + 8, buffer);
    intToBytes(color.a, bufferOffset + 12, buffer);
}
// R32G32B32A32_FIXED
static inline float4 readR32G32B32A32_FIXED(COMMON_READ_FUNC_PARAMS)
{
    float4 color;
    constexpr float kDivisor = 1.0f / (1 << 16);
    color.r                  = bytesToInt<int>(buffer, bufferOffset) * kDivisor;
    color.g                  = bytesToInt<int>(buffer, bufferOffset + 4) * kDivisor;
    color.b                  = bytesToInt<int>(buffer, bufferOffset + 8) * kDivisor;
    color.a                  = bytesToInt<int>(buffer, bufferOffset + 12) * kDivisor;
    return color;
}

// R32G32B32A32_UINT
static inline uint4 readR32G32B32A32_UINT(COMMON_READ_FUNC_PARAMS)
{
    uint4 color;
    color.r = bytesToInt<uint>(buffer, bufferOffset);
    color.g = bytesToInt<uint>(buffer, bufferOffset + 4);
    color.b = bytesToInt<uint>(buffer, bufferOffset + 8);
    color.a = bytesToInt<uint>(buffer, bufferOffset + 12);
    return color;
}
static inline void writeR32G32B32A32_UINT(COMMON_WRITE_UINT_FUNC_PARAMS)
{
    intToBytes(color.r, bufferOffset, buffer);
    intToBytes(color.g, bufferOffset + 4, buffer);
    intToBytes(color.b, bufferOffset + 8, buffer);
    intToBytes(color.a, bufferOffset + 12, buffer);
}

#define ALIAS_READ_SINT_FUNC(FORMAT)                                   \
    static inline int4 read##FORMAT##_SSCALED(COMMON_READ_FUNC_PARAMS) \
    {                                                                  \
        return read##FORMAT##_SINT(FORWARD_COMMON_READ_FUNC_PARAMS);   \
    }

#define ALIAS_READ_UINT_FUNC(FORMAT)                                    \
    static inline uint4 read##FORMAT##_USCALED(COMMON_READ_FUNC_PARAMS) \
    {                                                                   \
        return read##FORMAT##_UINT(FORWARD_COMMON_READ_FUNC_PARAMS);    \
    }

#define ALIAS_READ_INT_FUNC(FORMAT) \
    ALIAS_READ_SINT_FUNC(FORMAT)    \
    ALIAS_READ_UINT_FUNC(FORMAT)

#define ALIAS_READ_INT_FUNCS(BITS)                 \
    ALIAS_READ_INT_FUNC(R##BITS)                   \
    ALIAS_READ_INT_FUNC(R##BITS##G##BITS)          \
    ALIAS_READ_INT_FUNC(R##BITS##G##BITS##B##BITS) \
    ALIAS_READ_INT_FUNC(R##BITS##G##BITS##B##BITS##A##BITS)

ALIAS_READ_INT_FUNCS(8)
ALIAS_READ_INT_FUNCS(16)
ALIAS_READ_INT_FUNCS(32)

ALIAS_READ_INT_FUNC(R10G10B10A2)

// Copy pixels from buffer to texture
kernel void readFromBufferToFloatTexture(COMMON_READ_KERNEL_PARAMS(float))
{
    READ_KERNEL_GUARD

#define SUPPORTED_FORMATS(PROC) \
    PROC(R5G6B5_UNORM)          \
    PROC(R8G8B8A8_UNORM)        \
    PROC(R8G8B8A8_UNORM_SRGB)   \
    PROC(R8G8B8A8_SNORM)        \
    PROC(B8G8R8A8_UNORM)        \
    PROC(B8G8R8A8_UNORM_SRGB)   \
    PROC(R8G8B8_UNORM)          \
    PROC(R8G8B8_UNORM_SRGB)     \
    PROC(R8G8B8_SNORM)          \
    PROC(L8_UNORM)              \
    PROC(L8A8_UNORM)            \
    PROC(R5G5B5A1_UNORM)        \
    PROC(R4G4B4A4_UNORM)        \
    PROC(R8_UNORM)              \
    PROC(R8_SNORM)              \
    PROC(R8G8_UNORM)            \
    PROC(R8G8_SNORM)            \
    PROC(R16_FLOAT)             \
    PROC(A16_FLOAT)             \
    PROC(L16_FLOAT)             \
    PROC(L16A16_FLOAT)          \
    PROC(R16G16_FLOAT)          \
    PROC(R16G16B16_FLOAT)       \
    PROC(R16G16B16A16_FLOAT)    \
    PROC(R32_FLOAT)             \
    PROC(A32_FLOAT)             \
    PROC(L32_FLOAT)             \
    PROC(L32A32_FLOAT)          \
    PROC(R32G32_FLOAT)          \
    PROC(R32G32B32_FLOAT)       \
    PROC(R32G32B32A32_FLOAT)

    uint bufferOffset = CALC_BUFFER_READ_OFFSET(options.pixelSize);

    switch (kCopyFormatType)
    {
        SUPPORTED_FORMATS(READ_FORMAT_SWITCH_CASE)
    }

#undef SUPPORTED_FORMATS
}

kernel void readFromBufferToIntTexture(COMMON_READ_KERNEL_PARAMS(int))
{
    READ_KERNEL_GUARD

#define SUPPORTED_FORMATS(PROC) \
    PROC(R8_SINT)               \
    PROC(R8G8_SINT)             \
    PROC(R8G8B8_SINT)           \
    PROC(R8G8B8A8_SINT)         \
    PROC(R16_SINT)              \
    PROC(R16G16_SINT)           \
    PROC(R16G16B16_SINT)        \
    PROC(R16G16B16A16_SINT)     \
    PROC(R32_SINT)              \
    PROC(R32G32_SINT)           \
    PROC(R32G32B32_SINT)        \
    PROC(R32G32B32A32_SINT)

    uint bufferOffset = CALC_BUFFER_READ_OFFSET(options.pixelSize);

    switch (kCopyFormatType)
    {
        SUPPORTED_FORMATS(READ_FORMAT_SWITCH_CASE)
    }

#undef SUPPORTED_FORMATS
}

kernel void readFromBufferToUIntTexture(COMMON_READ_KERNEL_PARAMS(uint))
{
    READ_KERNEL_GUARD

#define SUPPORTED_FORMATS(PROC) \
    PROC(R8_UINT)               \
    PROC(R8G8_UINT)             \
    PROC(R8G8B8_UINT)           \
    PROC(R8G8B8A8_UINT)         \
    PROC(R16_UINT)              \
    PROC(R16G16_UINT)           \
    PROC(R16G16B16_UINT)        \
    PROC(R16G16B16A16_UINT)     \
    PROC(R32_UINT)              \
    PROC(R32G32_UINT)           \
    PROC(R32G32B32_UINT)        \
    PROC(R32G32B32A32_UINT)

    uint bufferOffset = CALC_BUFFER_READ_OFFSET(options.pixelSize);

    switch (kCopyFormatType)
    {
        SUPPORTED_FORMATS(READ_FORMAT_SWITCH_CASE)
    }

#undef SUPPORTED_FORMATS
}

// Copy pixels from texture to buffer
kernel void writeFromFloatTextureToBuffer(COMMON_WRITE_KERNEL_PARAMS(float))
{
    WRITE_KERNEL_GUARD

#define SUPPORTED_FORMATS(PROC) \
    PROC(R5G6B5_UNORM)          \
    PROC(R8G8B8A8_UNORM)        \
    PROC(R8G8B8A8_UNORM_SRGB)   \
    PROC(R8G8B8A8_SNORM)        \
    PROC(B8G8R8A8_UNORM)        \
    PROC(B8G8R8A8_UNORM_SRGB)   \
    PROC(R8G8B8_UNORM)          \
    PROC(R8G8B8_UNORM_SRGB)     \
    PROC(R8G8B8_SNORM)          \
    PROC(L8_UNORM)              \
    PROC(A8_UNORM)              \
    PROC(L8A8_UNORM)            \
    PROC(R5G5B5A1_UNORM)        \
    PROC(R4G4B4A4_UNORM)        \
    PROC(R8_UNORM)              \
    PROC(R8_SNORM)              \
    PROC(R8G8_UNORM)            \
    PROC(R8G8_SNORM)            \
    PROC(R16_FLOAT)             \
    PROC(A16_FLOAT)             \
    PROC(L16_FLOAT)             \
    PROC(L16A16_FLOAT)          \
    PROC(R16G16_FLOAT)          \
    PROC(R16G16B16A16_FLOAT)    \
    PROC(R32_FLOAT)             \
    PROC(A32_FLOAT)             \
    PROC(L32_FLOAT)             \
    PROC(L32A32_FLOAT)          \
    PROC(R32G32_FLOAT)          \
    PROC(R32G32B32A32_FLOAT)

    uint bufferOffset = CALC_BUFFER_WRITE_OFFSET(options.pixelSize);

    switch (kCopyFormatType)
    {
        SUPPORTED_FORMATS(WRITE_FORMAT_SWITCH_CASE)
    }

#undef SUPPORTED_FORMATS
}

kernel void writeFromIntTextureToBuffer(COMMON_WRITE_KERNEL_PARAMS(int))
{
    WRITE_KERNEL_GUARD

#define SUPPORTED_FORMATS(PROC) \
    PROC(R8_SINT)               \
    PROC(R8G8_SINT)             \
    PROC(R8G8B8A8_SINT)         \
    PROC(R16_SINT)              \
    PROC(R16G16_SINT)           \
    PROC(R16G16B16A16_SINT)     \
    PROC(R32_SINT)              \
    PROC(R32G32_SINT)           \
    PROC(R32G32B32A32_SINT)

    uint bufferOffset = CALC_BUFFER_WRITE_OFFSET(options.pixelSize);

    switch (kCopyFormatType)
    {
        SUPPORTED_FORMATS(WRITE_FORMAT_SWITCH_CASE)
    }

#undef SUPPORTED_FORMATS
}

kernel void writeFromUIntTextureToBuffer(COMMON_WRITE_KERNEL_PARAMS(uint))
{
    WRITE_KERNEL_GUARD

#define SUPPORTED_FORMATS(PROC) \
    PROC(R8_UINT)               \
    PROC(R8G8_UINT)             \
    PROC(R8G8B8A8_UINT)         \
    PROC(R16_UINT)              \
    PROC(R16G16_UINT)           \
    PROC(R16G16B16A16_UINT)     \
    PROC(R32_UINT)              \
    PROC(R32G32_UINT)           \
    PROC(R32G32B32A32_UINT)

    uint bufferOffset = CALC_BUFFER_WRITE_OFFSET(options.pixelSize);

    switch (kCopyFormatType)
    {
        SUPPORTED_FORMATS(WRITE_FORMAT_SWITCH_CASE)
    }

#undef SUPPORTED_FORMATS
}

/** -----  vertex format conversion --------*/
struct CopyVertexParams
{
    uint srcBufferStartOffset;
    uint srcStride;
    uint srcComponentBytes;  // unused when convert to float
    uint srcComponents;      // unused when convert to float
    // Default source alpha when expanding the number of components.
    // if source has less than 32 bits per component, only those bits are usable in
    // srcDefaultAlpha
    uchar4 srcDefaultAlphaData;  // unused when convert to float

    uint dstBufferStartOffset;
    uint dstStride;
    uint dstComponents;

    uint vertexCount;
};

#define INT_FORMAT_PROC(FORMAT, PROC) \
    PROC(FORMAT##_UNORM)              \
    PROC(FORMAT##_SNORM)              \
    PROC(FORMAT##_UINT)               \
    PROC(FORMAT##_SINT)               \
    PROC(FORMAT##_USCALED)            \
    PROC(FORMAT##_SSCALED)

#define PURE_INT_FORMAT_PROC(FORMAT, PROC) \
    PROC(FORMAT##_UINT)                    \
    PROC(FORMAT##_SINT)

#define FLOAT_FORMAT_PROC(FORMAT, PROC) PROC(FORMAT##_FLOAT)
#define FIXED_FORMAT_PROC(FORMAT, PROC) PROC(FORMAT##_FIXED)

#define FORMAT_BITS_PROC(BITS, PROC1, PROC2) \
    PROC1(R##BITS, PROC2)                    \
    PROC1(R##BITS##G##BITS, PROC2)           \
    PROC1(R##BITS##G##BITS##B##BITS, PROC2)  \
    PROC1(R##BITS##G##BITS##B##BITS##A##BITS, PROC2)

template <typename IntType>
static inline void writeFloatVertex(constant CopyVertexParams &options,
                                    uint idx,
                                    vec<IntType, 4> data,
                                    device uchar *dst)
{
    uint dstOffset = idx * options.dstStride + options.dstBufferStartOffset;

    for (uint component = 0; component < options.dstComponents; ++component, dstOffset += 4)
    {
        floatToBytes(static_cast<float>(data[component]), dstOffset, dst);
    }
}

template <>
inline void writeFloatVertex(constant CopyVertexParams &options,
                             uint idx,
                             vec<float, 4> data,
                             device uchar *dst)
{
    uint dstOffset = idx * options.dstStride + options.dstBufferStartOffset;

    for (uint component = 0; component < options.dstComponents; ++component, dstOffset += 4)
    {
        floatToBytes(data[component], dstOffset, dst);
    }
}

// Function to convert from any vertex format to float vertex format
static inline void convertToFloatVertexFormat(uint index,
                                              constant CopyVertexParams &options,
                                              constant uchar *srcBuffer,
                                              device uchar *dstBuffer)
{
#define SUPPORTED_FORMATS(PROC)                   \
    FORMAT_BITS_PROC(8, INT_FORMAT_PROC, PROC)    \
    FORMAT_BITS_PROC(16, INT_FORMAT_PROC, PROC)   \
    FORMAT_BITS_PROC(32, INT_FORMAT_PROC, PROC)   \
    FORMAT_BITS_PROC(16, FLOAT_FORMAT_PROC, PROC) \
    FORMAT_BITS_PROC(32, FLOAT_FORMAT_PROC, PROC) \
    FORMAT_BITS_PROC(32, FIXED_FORMAT_PROC, PROC) \
    PROC(R10G10B10A2_SINT)                        \
    PROC(R10G10B10A2_UINT)                        \
    PROC(R10G10B10A2_SSCALED)                     \
    PROC(R10G10B10A2_USCALED)

    uint bufferOffset = options.srcBufferStartOffset + options.srcStride * index;

#define COMVERT_FLOAT_VERTEX_SWITCH_CASE(FORMAT)           \
    case FormatID::FORMAT: {                               \
        auto data = read##FORMAT(bufferOffset, srcBuffer); \
        writeFloatVertex(options, index, data, dstBuffer); \
    }                                                      \
    break;

    switch (kCopyFormatType)
    {
        SUPPORTED_FORMATS(COMVERT_FLOAT_VERTEX_SWITCH_CASE)
    }

#undef SUPPORTED_FORMATS
}

// Kernel to convert from any vertex format to float vertex format
kernel void convertToFloatVertexFormatCS(uint index [[thread_position_in_grid]],
                                         constant CopyVertexParams &options [[buffer(0)]],
                                         constant uchar *srcBuffer [[buffer(1)]],
                                         device uchar *dstBuffer [[buffer(2)]])
{
    ANGLE_KERNEL_GUARD(index, options.vertexCount);
    convertToFloatVertexFormat(index, options, srcBuffer, dstBuffer);
}

// Vertex shader to convert from any vertex format to float vertex format
vertex void convertToFloatVertexFormatVS(uint index [[vertex_id]],
                                         constant CopyVertexParams &options [[buffer(0)]],
                                         constant uchar *srcBuffer [[buffer(1)]],
                                         device uchar *dstBuffer [[buffer(2)]])
{
    convertToFloatVertexFormat(index, options, srcBuffer, dstBuffer);
}

// Function to expand (or just simply copy) the components of the vertex
static inline void expandVertexFormatComponents(uint index,
                                                constant CopyVertexParams &options,
                                                constant uchar *srcBuffer,
                                                device uchar *dstBuffer)
{
    uint srcOffset = options.srcBufferStartOffset + options.srcStride * index;
    uint dstOffset = options.dstBufferStartOffset + options.dstStride * index;

    uint dstComponentsBeforeAlpha = min(options.dstComponents, 3u);
    uint component;
    for (component = 0; component < options.srcComponents; ++component,
        srcOffset += options.srcComponentBytes, dstOffset += options.srcComponentBytes)
    {
        for (uint byte = 0; byte < options.srcComponentBytes; ++byte)
        {
            dstBuffer[dstOffset + byte] = srcBuffer[srcOffset + byte];
        }
    }

    for (; component < dstComponentsBeforeAlpha;
         ++component, dstOffset += options.srcComponentBytes)
    {
        for (uint byte = 0; byte < options.srcComponentBytes; ++byte)
        {
            dstBuffer[dstOffset + byte] = 0;
        }
    }

    if (component < options.dstComponents)
    {
        // Last alpha component
        for (uint byte = 0; byte < options.srcComponentBytes; ++byte)
        {
            dstBuffer[dstOffset + byte] = options.srcDefaultAlphaData[byte];
        }
    }
}

// Kernel to expand (or just simply copy) the components of the vertex
kernel void expandVertexFormatComponentsCS(uint index [[thread_position_in_grid]],
                                           constant CopyVertexParams &options [[buffer(0)]],
                                           constant uchar *srcBuffer [[buffer(1)]],
                                           device uchar *dstBuffer [[buffer(2)]])
{
    ANGLE_KERNEL_GUARD(index, options.vertexCount);

    expandVertexFormatComponents(index, options, srcBuffer, dstBuffer);
}

// Vertex shader to expand (or just simply copy) the components of the vertex
vertex void expandVertexFormatComponentsVS(uint index [[vertex_id]],
                                           constant CopyVertexParams &options [[buffer(0)]],
                                           constant uchar *srcBuffer [[buffer(1)]],
                                           device uchar *dstBuffer [[buffer(2)]])
{
    expandVertexFormatComponents(index, options, srcBuffer, dstBuffer);
}
