/*
 * This file is part of libplacebo.
 *
 * libplacebo is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * libplacebo is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with libplacebo.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "formats.h"

#define FMT(_name, num, size, ftype, bits, idx) \
    (struct pl_fmt_t) {                         \
        .name = _name,                          \
        .type = PL_FMT_##ftype,                 \
        .num_components  = num,                 \
        .component_depth = bits,                \
        .internal_size   = size,                \
        .opaque          = false,               \
        .texel_size      = size,                \
        .texel_align     = size,                \
        .host_bits       = bits,                \
        .sample_order    = idx,                 \
    }

#define IDX(...)  {__VA_ARGS__}
#define BITS(...) {__VA_ARGS__}

#define REGFMT(name, num, bits, type)           \
    FMT(name, num, (num) * (bits) / 8, type,    \
        BITS(bits, bits, bits, bits),           \
        IDX(0, 1, 2, 3))

#define EMUFMT(_name, in, en, ib, eb, ftype)    \
    (struct pl_fmt_t) {                         \
        .name = _name,                          \
        .type = PL_FMT_##ftype,                 \
        .num_components  = en,                  \
        .component_depth = BITS(ib, ib, ib, ib),\
        .internal_size   = (in) * (ib) / 8,     \
        .opaque          = false,               \
        .emulated        = true,                \
        .texel_size      = (en) * (eb) / 8,     \
        .texel_align     = (eb) / 8,            \
        .host_bits       = BITS(eb, eb, eb, eb),\
        .sample_order    = IDX(0, 1, 2, 3),     \
    }

#define PACKED16FMT(_name, num, b)              \
    (struct pl_fmt_t) {                         \
        .name            = _name,               \
        .type            = PL_FMT_UNORM,        \
        .num_components  = num,                 \
        .component_depth = BITS(b, b, b, b),    \
        .internal_size   = (num) * 2,           \
        .texel_size      = (num) * 2,           \
        .texel_align     = (num) * 2,           \
        .host_bits       = BITS(16, 16, 16, 16),\
        .sample_order    = IDX(0, 1, 2, 3),     \
    }

#define PLANARFMT(_name, planes, size, bits)    \
    (struct pl_fmt_t) {                         \
        .name            = _name,               \
        .type            = PL_FMT_UNORM,        \
        .num_planes      = planes,              \
        .num_components  = 3,                   \
        .component_depth = {bits, bits, bits},  \
        .internal_size   = size,                \
        .opaque          = true,                \
    }

static const struct vk_format rgb8e = {
    .tfmt   = VK_FORMAT_R8G8B8A8_UNORM,
    .bfmt   = VK_FORMAT_R8G8B8_UNORM,
    .icomps = 4,
    .fmt    = EMUFMT("rgb8", 4, 3, 8, 8, UNORM),
};

static const struct vk_format rgb16e = {
    .tfmt   = VK_FORMAT_R16G16B16A16_UNORM,
    .bfmt   = VK_FORMAT_R16G16B16_UNORM,
    .icomps = 4,
    .fmt    = EMUFMT("rgb16", 4, 3, 16, 16, UNORM),
};

static const struct vk_format vk_formats[] = {
    // Regular, byte-aligned integer formats
    {VK_FORMAT_R8_UNORM,              REGFMT("r8",       1,  8, UNORM)},
    {VK_FORMAT_R8G8_UNORM,            REGFMT("rg8",      2,  8, UNORM)},
    {VK_FORMAT_R8G8B8_UNORM,          REGFMT("rgb8",     3,  8, UNORM), .emufmt = &rgb8e},
    {VK_FORMAT_R8G8B8A8_UNORM,        REGFMT("rgba8",    4,  8, UNORM)},
    {VK_FORMAT_R16_UNORM,             REGFMT("r16",      1, 16, UNORM)},
    {VK_FORMAT_R16G16_UNORM,          REGFMT("rg16",     2, 16, UNORM)},
    {VK_FORMAT_R16G16B16_UNORM,       REGFMT("rgb16",    3, 16, UNORM), .emufmt = &rgb16e},
    {VK_FORMAT_R16G16B16A16_UNORM,    REGFMT("rgba16",   4, 16, UNORM)},

    {VK_FORMAT_R8_SNORM,              REGFMT("r8s",      1,  8, SNORM)},
    {VK_FORMAT_R8G8_SNORM,            REGFMT("rg8s",     2,  8, SNORM)},
    {VK_FORMAT_R8G8B8_SNORM,          REGFMT("rgb8s",    3,  8, SNORM)},
    {VK_FORMAT_R8G8B8A8_SNORM,        REGFMT("rgba8s",   4,  8, SNORM)},
    {VK_FORMAT_R16_SNORM,             REGFMT("r16s",     1, 16, SNORM)},
    {VK_FORMAT_R16G16_SNORM,          REGFMT("rg16s",    2, 16, SNORM)},
    {VK_FORMAT_R16G16B16_SNORM,       REGFMT("rgb16s",   3, 16, SNORM)},
    {VK_FORMAT_R16G16B16A16_SNORM,    REGFMT("rgba16s",  4, 16, SNORM)},

    // Float formats (native formats: hf = half float, df = double float)
    {VK_FORMAT_R16_SFLOAT,            REGFMT("r16hf",    1, 16, FLOAT)},
    {VK_FORMAT_R16G16_SFLOAT,         REGFMT("rg16hf",   2, 16, FLOAT)},
    {VK_FORMAT_R16G16B16_SFLOAT,      REGFMT("rgb16hf",  3, 16, FLOAT)},
    {VK_FORMAT_R16G16B16A16_SFLOAT,   REGFMT("rgba16hf", 4, 16, FLOAT)},
    {VK_FORMAT_R32_SFLOAT,            REGFMT("r32f",     1, 32, FLOAT)},
    {VK_FORMAT_R32G32_SFLOAT,         REGFMT("rg32f",    2, 32, FLOAT)},
    {VK_FORMAT_R32G32B32_SFLOAT,      REGFMT("rgb32f",   3, 32, FLOAT)},
    {VK_FORMAT_R32G32B32A32_SFLOAT,   REGFMT("rgba32f",  4, 32, FLOAT)},

    // Float formats (emulated upload/download)
    {VK_FORMAT_R16_SFLOAT,            EMUFMT("r16f",     1, 1, 16, 32, FLOAT)},
    {VK_FORMAT_R16G16_SFLOAT,         EMUFMT("rg16f",    2, 2, 16, 32, FLOAT)},
    {VK_FORMAT_R16G16B16_SFLOAT,      EMUFMT("rgb16f",   3, 3, 16, 32, FLOAT)},
    {VK_FORMAT_R16G16B16A16_SFLOAT,   EMUFMT("rgba16f",  4, 4, 16, 32, FLOAT)},

    // Integer-sampled formats
    {VK_FORMAT_R8_UINT,               REGFMT("r8u",      1,  8, UINT)},
    {VK_FORMAT_R8G8_UINT,             REGFMT("rg8u",     2,  8, UINT)},
    {VK_FORMAT_R8G8B8_UINT,           REGFMT("rgb8u",    3,  8, UINT)},
    {VK_FORMAT_R8G8B8A8_UINT,         REGFMT("rgba8u",   4,  8, UINT)},
    {VK_FORMAT_R16_UINT,              REGFMT("r16u",     1, 16, UINT)},
    {VK_FORMAT_R16G16_UINT,           REGFMT("rg16u",    2, 16, UINT)},
    {VK_FORMAT_R16G16B16_UINT,        REGFMT("rgb16u",   3, 16, UINT)},
    {VK_FORMAT_R16G16B16A16_UINT,     REGFMT("rgba16u",  4, 16, UINT)},
    {VK_FORMAT_R32_UINT,              REGFMT("r32u",     1, 32, UINT)},
    {VK_FORMAT_R32G32_UINT,           REGFMT("rg32u",    2, 32, UINT)},
    {VK_FORMAT_R32G32B32_UINT,        REGFMT("rgb32u",   3, 32, UINT)},
    {VK_FORMAT_R32G32B32A32_UINT,     REGFMT("rgba32u",  4, 32, UINT)},

    {VK_FORMAT_R8_SINT,               REGFMT("r8i",      1,  8, SINT)},
    {VK_FORMAT_R8G8_SINT,             REGFMT("rg8i",     2,  8, SINT)},
    {VK_FORMAT_R8G8B8_SINT,           REGFMT("rgb8i",    3,  8, SINT)},
    {VK_FORMAT_R8G8B8A8_SINT,         REGFMT("rgba8i",   4,  8, SINT)},
    {VK_FORMAT_R16_SINT,              REGFMT("r16i",     1, 16, SINT)},
    {VK_FORMAT_R16G16_SINT,           REGFMT("rg16i",    2, 16, SINT)},
    {VK_FORMAT_R16G16B16_SINT,        REGFMT("rgb16i",   3, 16, SINT)},
    {VK_FORMAT_R16G16B16A16_SINT,     REGFMT("rgba16i",  4, 16, SINT)},
    {VK_FORMAT_R32_SINT,              REGFMT("r32i",     1, 32, SINT)},
    {VK_FORMAT_R32G32_SINT,           REGFMT("rg32i",    2, 32, SINT)},
    {VK_FORMAT_R32G32B32_SINT,        REGFMT("rgb32i",   3, 32, SINT)},
    {VK_FORMAT_R32G32B32A32_SINT,     REGFMT("rgba32i",  4, 32, SINT)},

    // "Swapped" component order formats
    {VK_FORMAT_B8G8R8_UNORM,             FMT("bgr8",     3,  3, UNORM, BITS(8,  8,  8),     IDX(2, 1, 0))},
    {VK_FORMAT_B8G8R8A8_UNORM,           FMT("bgra8",    4,  4, UNORM, BITS(8,  8,  8,  8), IDX(2, 1, 0, 3))},

    {VK_FORMAT_B8G8R8_UINT,              FMT("bgr8u",    3,  3, UINT,  BITS(8,  8,  8),     IDX(2, 1, 0))},
    {VK_FORMAT_B8G8R8A8_UINT,            FMT("bgra8u",   4,  4, UINT,  BITS(8,  8,  8,  8), IDX(2, 1, 0, 3))},

    {VK_FORMAT_B8G8R8_SINT,              FMT("bgr8i",    3,  3, SINT,  BITS(8,  8,  8),     IDX(2, 1, 0))},
    {VK_FORMAT_B8G8R8A8_SINT,            FMT("bgra8i",   4,  4, SINT,  BITS(8,  8,  8,  8), IDX(2, 1, 0, 3))},

    // "Packed" integer formats
    //
    // Note: These have the component order reversed from what the vulkan name
    // implies, because we order our IDX from LSB to MSB (consistent with the
    // usual ordering from lowest byte to highest byte, on little endian
    // platforms), but Vulkan names them from MSB to LSB.
    {VK_FORMAT_R4G4_UNORM_PACK8,         FMT("gr4",      2,  1, UNORM, BITS(4,  4),         IDX(1, 0))},
    {VK_FORMAT_B4G4R4A4_UNORM_PACK16,    FMT("argb4",    4,  2, UNORM, BITS(4,  4,  4,  4), IDX(3, 0, 1, 2))},
    {VK_FORMAT_R4G4B4A4_UNORM_PACK16,    FMT("abgr4",    4,  2, UNORM, BITS(4,  4,  4,  4), IDX(3, 2, 1, 0))},

    {VK_FORMAT_R5G6B5_UNORM_PACK16,      FMT("bgr565",   3,  2, UNORM, BITS(5,  6,  5),     IDX(2, 1, 0))},
    {VK_FORMAT_B5G6R5_UNORM_PACK16,      FMT("rgb565",   3,  2, UNORM, BITS(5,  6,  5),     IDX(0, 1, 2))},

    {VK_FORMAT_R5G5B5A1_UNORM_PACK16,    FMT("a1bgr5",   4,  2, UNORM, BITS(1,  5,  5,  5), IDX(3, 2, 1, 0))},
    {VK_FORMAT_B5G5R5A1_UNORM_PACK16,    FMT("a1rgb5",   4,  2, UNORM, BITS(1,  5,  5,  5), IDX(3, 0, 1, 2))},
    {VK_FORMAT_A1R5G5B5_UNORM_PACK16,    FMT("bgr5a1",   4,  2, UNORM, BITS(5,  5,  5,  1), IDX(2, 1, 0, 3))},

    {VK_FORMAT_A2B10G10R10_UNORM_PACK32, FMT("rgb10a2",  4,  4, UNORM, BITS(10, 10, 10, 2), IDX(0, 1, 2, 3))},
    {VK_FORMAT_A2R10G10B10_UNORM_PACK32, FMT("bgr10a2",  4,  4, UNORM, BITS(10, 10, 10, 2), IDX(2, 1, 0, 3))},
    {VK_FORMAT_A2B10G10R10_SNORM_PACK32, FMT("rgb10a2s", 4,  4, SNORM, BITS(10, 10, 10, 2), IDX(0, 1, 2, 3))},
    {VK_FORMAT_A2R10G10B10_SNORM_PACK32, FMT("bgr10a2s", 4,  4, SNORM, BITS(10, 10, 10, 2), IDX(2, 1, 0, 3))},
    {VK_FORMAT_A2B10G10R10_UINT_PACK32,  FMT("rgb10a2u", 4,  4, UINT,  BITS(10, 10, 10, 2), IDX(0, 1, 2, 3))},
    {VK_FORMAT_A2R10G10B10_UINT_PACK32,  FMT("bgr10a2u", 4,  4, UINT,  BITS(10, 10, 10, 2), IDX(2, 1, 0, 3))},
    {VK_FORMAT_A2B10G10R10_SINT_PACK32,  FMT("rgb10a2i", 4,  4, SINT,  BITS(10, 10, 10, 2), IDX(0, 1, 2, 3))},
    {VK_FORMAT_A2R10G10B10_SINT_PACK32,  FMT("bgr10a2i", 4,  4, SINT,  BITS(10, 10, 10, 2), IDX(2, 1, 0, 3))},


    // Packed 16 bit formats
    {VK_FORMAT_R10X6_UNORM_PACK16,                  PACKED16FMT("rx10",         1, 10)},
    {VK_FORMAT_R10X6G10X6_UNORM_2PACK16,            PACKED16FMT("rxgx10",       2, 10)},
    {VK_FORMAT_R12X4_UNORM_PACK16,                  PACKED16FMT("rx12",         1, 12)},
    {VK_FORMAT_R12X4G12X4_UNORM_2PACK16,            PACKED16FMT("rxgx12",       2, 12)},

    // FIXME: enabling these requires VK_EXT_rgba10x6_formats or equivalent
    // {VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,  PACKED16FMT("rxgxbxax10",   4, 10)},
    // {VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16,  PACKED16FMT("rxgxbxax12",   4, 12)},

    // Planar formats
    {VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM, PLANARFMT("g8_b8_r8_420", 3, 12, 8),
        .pfmt = {
            {VK_FORMAT_R8_UNORM},
            {VK_FORMAT_R8_UNORM, .sx = 1, .sy = 1},
            {VK_FORMAT_R8_UNORM, .sx = 1, .sy = 1},
        },
    },
    {VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM, PLANARFMT("g8_b8_r8_422", 3, 16, 8),
        .pfmt = {
            {VK_FORMAT_R8_UNORM},
            {VK_FORMAT_R8_UNORM, .sx = 1},
            {VK_FORMAT_R8_UNORM, .sx = 1},
        },
    },
    {VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM, PLANARFMT("g8_b8_r8_444", 3, 24, 8),
        .pfmt = {
            {VK_FORMAT_R8_UNORM},
            {VK_FORMAT_R8_UNORM},
            {VK_FORMAT_R8_UNORM},
        },
    },

    {VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM, PLANARFMT("g16_b16_r16_420", 3, 24, 16),
        .pfmt = {
            {VK_FORMAT_R16_UNORM},
            {VK_FORMAT_R16_UNORM, .sx = 1, .sy = 1},
            {VK_FORMAT_R16_UNORM, .sx = 1, .sy = 1},
        },
    },
    {VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM, PLANARFMT("g16_b16_r16_422", 3, 32, 16),
        .pfmt = {
            {VK_FORMAT_R16_UNORM},
            {VK_FORMAT_R16_UNORM, .sx = 1},
            {VK_FORMAT_R16_UNORM, .sx = 1},
        },
    },
    {VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM, PLANARFMT("g16_b16_r16_444", 3, 48, 16),
        .pfmt = {
            {VK_FORMAT_R16_UNORM},
            {VK_FORMAT_R16_UNORM},
            {VK_FORMAT_R16_UNORM},
        },
    },

    {VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16, PLANARFMT("gx10_bx10_rx10_420", 3, 24, 10),
        .pfmt = {
            {VK_FORMAT_R10X6_UNORM_PACK16},
            {VK_FORMAT_R10X6_UNORM_PACK16, .sx = 1, .sy = 1},
            {VK_FORMAT_R10X6_UNORM_PACK16, .sx = 1, .sy = 1},
        },
    },
    {VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16, PLANARFMT("gx10_bx10_rx10_422", 3, 32, 10),
        .pfmt = {
            {VK_FORMAT_R10X6_UNORM_PACK16},
            {VK_FORMAT_R10X6_UNORM_PACK16, .sx = 1},
            {VK_FORMAT_R10X6_UNORM_PACK16, .sx = 1},
        },
    },
    {VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16, PLANARFMT("gx10_bx10_rx10_444", 3, 48, 10),
        .pfmt = {
            {VK_FORMAT_R10X6_UNORM_PACK16},
            {VK_FORMAT_R10X6_UNORM_PACK16},
            {VK_FORMAT_R10X6_UNORM_PACK16},
        },
    },

    {VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16, PLANARFMT("gx12_bx12_rx12_420", 3, 24, 12),
        .pfmt = {
            {VK_FORMAT_R12X4_UNORM_PACK16},
            {VK_FORMAT_R12X4_UNORM_PACK16, .sx = 1, .sy = 1},
            {VK_FORMAT_R12X4_UNORM_PACK16, .sx = 1, .sy = 1},
        },
    },
    {VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16, PLANARFMT("gx12_bx12_rx12_422", 3, 32, 12),
        .pfmt = {
            {VK_FORMAT_R12X4_UNORM_PACK16},
            {VK_FORMAT_R12X4_UNORM_PACK16, .sx = 1},
            {VK_FORMAT_R12X4_UNORM_PACK16, .sx = 1},
        },
    },
    {VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16, PLANARFMT("gx12_bx12_rx12_444", 3, 48, 12),
        .pfmt = {
            {VK_FORMAT_R12X4_UNORM_PACK16},
            {VK_FORMAT_R12X4_UNORM_PACK16},
            {VK_FORMAT_R12X4_UNORM_PACK16},
        },
    },

    {VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, PLANARFMT("g8_br8_420", 2, 12, 8),
        .pfmt = {
            {VK_FORMAT_R8_UNORM},
            {VK_FORMAT_R8G8_UNORM, .sx = 1, .sy = 1},
        },
    },
    {VK_FORMAT_G8_B8R8_2PLANE_422_UNORM, PLANARFMT("g8_br8_422", 2, 16, 8),
        .pfmt = {
            {VK_FORMAT_R8_UNORM},
            {VK_FORMAT_R8G8_UNORM, .sx = 1},
        },
    },
    {VK_FORMAT_G8_B8R8_2PLANE_444_UNORM, PLANARFMT("g8_br8_444", 2, 24, 8),
        .min_ver = VK_API_VERSION_1_3,
        .pfmt = {
            {VK_FORMAT_R8_UNORM},
            {VK_FORMAT_R8G8_UNORM},
        },
    },

    {VK_FORMAT_G16_B16R16_2PLANE_420_UNORM, PLANARFMT("g16_br16_420", 2, 24, 16),
        .pfmt = {
            {VK_FORMAT_R16_UNORM},
            {VK_FORMAT_R16G16_UNORM, .sx = 1, .sy = 1},
        },
    },
    {VK_FORMAT_G16_B16R16_2PLANE_422_UNORM, PLANARFMT("g16_br16_422", 2, 32, 16),
        .pfmt = {
            {VK_FORMAT_R16_UNORM},
            {VK_FORMAT_R16G16_UNORM, .sx = 1},
        },
    },
    {VK_FORMAT_G16_B16R16_2PLANE_444_UNORM, PLANARFMT("g16_br16_444", 2, 48, 16),
        .min_ver = VK_API_VERSION_1_3,
        .pfmt = {
            {VK_FORMAT_R16_UNORM},
            {VK_FORMAT_R16G16_UNORM},
        },
    },

    {VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16, PLANARFMT("gx10_bxrx10_420", 2, 24, 10),
        .pfmt = {
            {VK_FORMAT_R10X6_UNORM_PACK16},
            {VK_FORMAT_R10X6G10X6_UNORM_2PACK16, .sx = 1, .sy = 1},
        },
    },
    {VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16, PLANARFMT("gx10_bxrx10_422", 2, 32, 10),
        .pfmt = {
            {VK_FORMAT_R10X6_UNORM_PACK16},
            {VK_FORMAT_R10X6G10X6_UNORM_2PACK16, .sx = 1},
        },
    },
    {VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16, PLANARFMT("gx10_bxrx10_444", 2, 48, 10),
        .min_ver = VK_API_VERSION_1_3,
        .pfmt = {
            {VK_FORMAT_R10X6_UNORM_PACK16},
            {VK_FORMAT_R10X6G10X6_UNORM_2PACK16},
        },
    },

    {VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16, PLANARFMT("gx12_bxrx12_420", 2, 24, 12),
        .pfmt = {
            {VK_FORMAT_R12X4_UNORM_PACK16},
            {VK_FORMAT_R12X4G12X4_UNORM_2PACK16, .sx = 1, .sy = 1},
        },
    },
    {VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16, PLANARFMT("gx12_bxrx12_422", 2, 32, 12),
        .pfmt = {
            {VK_FORMAT_R12X4_UNORM_PACK16},
            {VK_FORMAT_R12X4G12X4_UNORM_2PACK16, .sx = 1},
        },
    },
    {VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16, PLANARFMT("gx12_bxrx12_444", 2, 48, 12),
        .min_ver = VK_API_VERSION_1_3,
        .pfmt = {
            {VK_FORMAT_R12X4_UNORM_PACK16},
            {VK_FORMAT_R12X4G12X4_UNORM_2PACK16},
        },
    },

    {0}
};

#undef BITS
#undef IDX
#undef REGFMT
#undef FMT

/* Backwards compatibility with older versions of the drm mods ext */
static VkDrmFormatModifierPropertiesList2EXT get_drm_mods_v2(struct vk_ctx *vk, VkFormat fmt)
{
    const bool has_v2 = vk->api_ver >= VK_API_VERSION_1_3;

    VkDrmFormatModifierPropertiesList2EXT drm_props = {
        .sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_2_EXT,
    };

    VkDrmFormatModifierPropertiesListEXT drm_props_v1 = {
        .sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT,
    };

    VkFormatProperties2KHR prop = {
        .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR,
        .pNext = has_v2 ? (void *) &drm_props : (void *) &drm_props_v1,
    };

    vk->GetPhysicalDeviceFormatProperties2(vk->physd, fmt, &prop);
    if (!has_v2)
        drm_props.drmFormatModifierCount = drm_props_v1.drmFormatModifierCount;
    if (!drm_props.drmFormatModifierCount)
        return drm_props;

    drm_props.pDrmFormatModifierProperties = pl_zalloc(NULL,
        sizeof(VkDrmFormatModifierProperties2EXT) * drm_props.drmFormatModifierCount);

    if (!has_v2) {
        drm_props_v1.pDrmFormatModifierProperties = pl_zalloc(NULL,
            sizeof(VkDrmFormatModifierPropertiesEXT) * drm_props_v1.drmFormatModifierCount);
    }

    vk->GetPhysicalDeviceFormatProperties2(vk->physd, fmt, &prop);

    if (!has_v2) {
        for (int i = 0; i < drm_props_v1.drmFormatModifierCount; i++) {
            const VkDrmFormatModifierPropertiesEXT props = drm_props_v1.pDrmFormatModifierProperties[i];
            drm_props.pDrmFormatModifierProperties[i] = (VkDrmFormatModifierProperties2EXT) {
                .drmFormatModifier = props.drmFormatModifier,
                .drmFormatModifierPlaneCount = props.drmFormatModifierPlaneCount,
                .drmFormatModifierTilingFeatures = props.drmFormatModifierTilingFeatures,
            };
        }

        pl_free(drm_props_v1.pDrmFormatModifierProperties);
    }

    return drm_props;
}

void vk_setup_formats(struct pl_gpu_t *gpu)
{
    struct pl_vk *p = PL_PRIV(gpu);
    struct vk_ctx *vk = p->vk;
    PL_ARRAY(pl_fmt) formats = {0};

    // Texture format emulation requires at least support for texel buffers
    bool has_emu = gpu->glsl.compute && gpu->limits.max_buffer_texels;

    const VkPhysicalDeviceVulkan14Features *features_vk14 =
        vk_find_struct(&vk->features, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_FEATURES);

    for (const struct vk_format *pvk_fmt = vk_formats; pvk_fmt->tfmt; pvk_fmt++) {
        const struct vk_format *vk_fmt = pvk_fmt;

        // Skip formats that require a too new version of Vulkan
        if (vk_fmt->min_ver > vk->api_ver)
            continue;

        // Skip formats with innately emulated representation if unsupported
        if (vk_fmt->fmt.emulated && !has_emu)
            continue;

        // Suppress some errors/warnings spit out by the format probing code
        pl_log_level_cap(vk->log, PL_LOG_INFO);

        VkFormatProperties3KHR prop3 = {
            .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3_KHR,
        };

        VkFormatProperties2KHR prop2 = {
            .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2,
            .pNext = vk->api_ver >= VK_API_VERSION_1_3 ? &prop3 : NULL,
        };

        vk->GetPhysicalDeviceFormatProperties2(vk->physd, vk_fmt->tfmt, &prop2);

        // If wholly unsupported, try falling back to the emulation formats
        // for texture operations
        const VkFormatProperties *prop = &prop2.formatProperties;
        while (has_emu && !prop->optimalTilingFeatures && vk_fmt->emufmt) {
            vk_fmt = vk_fmt->emufmt;
            vk->GetPhysicalDeviceFormatProperties2(vk->physd, vk_fmt->tfmt, &prop2);
        }

        VkFormatFeatureFlags2 texflags = prop->optimalTilingFeatures | prop3.optimalTilingFeatures;
        VkFormatFeatureFlags2 bufflags = prop->bufferFeatures | prop3.bufferFeatures;
        if (vk_fmt->fmt.emulated) {
            // Emulated formats might have a different buffer representation
            // than their texture representation. If they don't, assume their
            // buffer representation is nonsensical (e.g. r16f)
            if (vk_fmt->bfmt) {
                vk->GetPhysicalDeviceFormatProperties2(vk->physd, vk_fmt->bfmt, &prop2);
                bufflags = prop->bufferFeatures | prop3.bufferFeatures;
            } else {
                bufflags = 0;
            }
        } else if (vk_fmt->fmt.num_planes) {
            // Planar textures cannot be used directly
            texflags = bufflags = 0;
        }

        pl_log_level_cap(vk->log, PL_LOG_NONE);

        struct pl_fmt_t *fmt = pl_alloc_obj(gpu, fmt, struct pl_fmt_vk);
        struct pl_fmt_vk *fmtp = PL_PRIV(fmt);
        *fmt = vk_fmt->fmt;
        *fmtp = (struct pl_fmt_vk) {
            .vk_fmt = vk_fmt
        };

        // Always set the signature to the actual texture format, so we can use
        // it to guarantee renderpass compatibility.
        fmt->signature = (uint64_t) vk_fmt->tfmt;

        // For sanity, clear the superfluous fields
        for (int i = fmt->num_components; i < 4; i++) {
            fmt->component_depth[i] = 0;
            fmt->sample_order[i] = 0;
            fmt->host_bits[i] = 0;
        }

        // We can set this universally
        fmt->fourcc = pl_fmt_fourcc(fmt);

        if (vk->GetImageDrmFormatModifierPropertiesEXT) {

            PL_ARRAY(uint64_t) modlist = {0};

            // Query the list of supported DRM modifiers from the driver
            VkDrmFormatModifierPropertiesList2EXT drm_props =
                get_drm_mods_v2(vk, vk_fmt->tfmt);

            for (int i = 0; i < drm_props.drmFormatModifierCount; i++) {
                const VkDrmFormatModifierProperties2EXT mod = drm_props.pDrmFormatModifierProperties[i];
                if (mod.drmFormatModifierPlaneCount > 1) {
                    PL_TRACE(gpu, "Ignoring format modifier %s of "
                             "format %s because its plane count %d > 1",
                             PRINT_DRM_MOD(mod.drmFormatModifier),
                             fmt->name, mod.drmFormatModifierPlaneCount);
                    continue;
                }

                // Only warn about texture format features relevant to us
                const VkFormatFeatureFlags2 flag_mask =
                    VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT |
                    VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT |
                    VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
                    VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT |
                    VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT |
                    VK_FORMAT_FEATURE_BLIT_SRC_BIT |
                    VK_FORMAT_FEATURE_BLIT_DST_BIT |
                    VK_FORMAT_FEATURE_2_HOST_IMAGE_TRANSFER_BIT;

                VkFormatFeatureFlags2 flags = mod.drmFormatModifierTilingFeatures;
                if ((flags & flag_mask) != (texflags & flag_mask)) {
                    PL_DEBUG(gpu, "DRM format modifier %s of format %s "
                            "supports fewer caps (0x%"PRIx64") than optimal tiling "
                            "(0x%"PRIx64"), may result in limited capability!",
                            PRINT_DRM_MOD(mod.drmFormatModifier),
                            fmt->name, flags, texflags);
                }

                PL_ARRAY_APPEND(fmt, modlist, mod.drmFormatModifier);
            }

            pl_free(drm_props.pDrmFormatModifierProperties);
            fmt->num_modifiers = modlist.num;
            fmt->modifiers = modlist.elem;

        } else if (gpu->export_caps.tex & PL_HANDLE_DMA_BUF) {

            // Hard-code a list of static mods that we're likely to support
            static const uint64_t static_mods[2] = {
                DRM_FORMAT_MOD_INVALID,
                DRM_FORMAT_MOD_LINEAR,
            };

            fmt->num_modifiers = PL_ARRAY_SIZE(static_mods);
            fmt->modifiers = static_mods;

        }

        struct { VkFormatFeatureFlags flags; enum pl_fmt_caps caps; } bufbits[] = {
            {VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT,        PL_FMT_CAP_VERTEX},
            {VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT, PL_FMT_CAP_TEXEL_UNIFORM},
            {VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT, PL_FMT_CAP_TEXEL_STORAGE},
        };

        for (int i = 0; i < PL_ARRAY_SIZE(bufbits); i++) {
            if ((bufflags & bufbits[i].flags) == bufbits[i].flags)
                fmt->caps |= bufbits[i].caps;
        }

        if (fmt->caps) {
            fmt->glsl_type = pl_var_glsl_type_name(pl_var_from_fmt(fmt, ""));
            pl_assert(fmt->glsl_type);
        }

        struct { VkFormatFeatureFlags flags; enum pl_fmt_caps caps; } bits[] = {
            {VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT,      PL_FMT_CAP_BLENDABLE},
            {VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT, PL_FMT_CAP_LINEAR},
            {VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT,               PL_FMT_CAP_SAMPLEABLE},
            {VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT,               PL_FMT_CAP_STORABLE},
            {VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT,            PL_FMT_CAP_RENDERABLE},

            // We don't distinguish between the two blit modes for pl_fmt_caps
            {VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT,
                PL_FMT_CAP_BLITTABLE},
        };

        for (int i = 0; i < PL_ARRAY_SIZE(bits); i++) {
            if ((texflags & bits[i].flags) == bits[i].flags)
                fmt->caps |= bits[i].caps;
        }

        // Internal capabilities
        if (vk->props.vendorID != VK_VENDOR_ID_NVIDIA &&
            vk->props.vendorID != VK_VENDOR_ID_APPLE) { // FIXME: remove when upstream works
            if ((texflags & VK_FORMAT_FEATURE_2_HOST_IMAGE_TRANSFER_BIT) &&
                features_vk14 && features_vk14->hostImageCopy)
                fmtp->can_host_copy = true;
        }

        // Host copy seems to fail on some Intel GPUs, particularly on our CI
        // machine, exclude for now this GPU.
        // https://gitlab.freedesktop.org/mesa/mesa/-/issues/14633
        if (vk->props.vendorID == VK_VENDOR_ID_INTEL && vk->props.deviceID == 0x3185)
            fmtp->can_host_copy = false;

        // For blit emulation via compute shaders
        if (!(fmt->caps & PL_FMT_CAP_BLITTABLE) && (fmt->caps & PL_FMT_CAP_STORABLE)) {
            fmt->caps |= PL_FMT_CAP_BLITTABLE;
            fmtp->blit_emulated = true;
        }

        // This is technically supported for all textures, but the semantics
        // of pl_gpu require it only be listed for non-opaque ones
        if (!fmt->opaque)
            fmt->caps |= PL_FMT_CAP_HOST_READABLE;

        // Vulkan requires a minimum GLSL version that supports textureGather()
        if (fmt->caps & PL_FMT_CAP_SAMPLEABLE)
            fmt->gatherable = true;

        // Disable implied capabilities where the dependencies are unavailable
        enum pl_fmt_caps storable = PL_FMT_CAP_STORABLE | PL_FMT_CAP_TEXEL_STORAGE;
        if (!(fmt->caps & PL_FMT_CAP_SAMPLEABLE))
            fmt->caps &= ~PL_FMT_CAP_LINEAR;
        if (!gpu->glsl.compute)
            fmt->caps &= ~storable;

        bool has_nofmt = vk->features.features.shaderStorageImageReadWithoutFormat &&
                         vk->features.features.shaderStorageImageWriteWithoutFormat;

        if (fmt->caps & storable) {
            int real_comps = PL_DEF(vk_fmt->icomps, fmt->num_components);
            fmt->glsl_format = pl_fmt_glsl_format(fmt, real_comps);
            if (!fmt->glsl_format && !has_nofmt) {
                PL_DEBUG(gpu, "Storable format '%s' has no matching GLSL "
                         "format qualifier but read/write without format "
                         "is not supported.. disabling", fmt->name);
                fmt->caps &= ~storable;
            }
        }

        if (fmt->caps & storable)
            fmt->caps |= PL_FMT_CAP_READWRITE;

        // Pick sub-plane formats for planar formats
        for (int n = 0; n < fmt->num_planes; n++) {
            for (int i = 0; i < formats.num; i++) {
                if (formats.elem[i]->signature == vk_fmt->pfmt[n].fmt) {
                    fmt->planes[n].format = formats.elem[i];
                    fmt->planes[n].shift_x = vk_fmt->pfmt[n].sx;
                    fmt->planes[n].shift_y = vk_fmt->pfmt[n].sy;
                    break;
                }
            }

            pl_assert(fmt->planes[n].format);
        }

        PL_ARRAY_APPEND(gpu, formats, fmt);
    }

    gpu->formats = formats.elem;
    gpu->num_formats = formats.num;
}
