/*****************************************************************************
 * d3d_dynamic_shader.c: Direct3D Shader APIs
 *****************************************************************************
 * Copyright (C) 2017-2021 VLC authors and VideoLAN
 *
 * Authors: Steve Lhomme <robux4@gmail.com>
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <windows.h>
#include <assert.h>

#include <vlc_common.h>

#define COBJMACROS
#include <d3dcompiler.h>

#include "d3d_shaders.h"
#include "d3d_dynamic_shader.h"

static const char globPixelShaderDefault[] = "\
#pragma warning( disable: 3571 )\n\
cbuffer PS_CONSTANT_BUFFER : register(b0)\n\
{\n\
    float4x3 Colorspace;\n\
    float Opacity;\n\
    float LuminanceScale;\n\
    float2 Boundary;\n\
};\n\
Texture2D shaderTexture[TEXTURE_RESOURCES];\n\
SamplerState normalSampler : register(s0);\n\
SamplerState borderSampler : register(s1);\n\
\n\
struct PS_INPUT\n\
{\n\
    float4 Position   : SV_POSITION;\n\
    float2 uv         : TEXCOORD;\n\
};\n\
\n\
#define TONE_MAP_HABLE       1\n\
\n\
#define SRC_TRANSFER_709     1\n\
#define SRC_TRANSFER_PQ      2\n\
#define SRC_TRANSFER_HLG     3\n\
#define SRC_TRANSFER_SRGB    4\n\
#define SRC_TRANSFER_470BG   5\n\
\n\
#define DST_TRANSFER_SRGB    1\n\
#define DST_TRANSFER_PQ      2\n\
\n\
#define FULL_RANGE           1\n\
#define STUDIO_RANGE         2\n\
\n\
#define SAMPLE_NV12_TO_YUVA          1\n\
#define SAMPLE_YUY2_TO_YUVA          2\n\
#define SAMPLE_Y210_TO_YUVA          3\n\
#define SAMPLE_Y410_TO_YUVA          4\n\
#define SAMPLE_AYUV_TO_YUVA          5\n\
#define SAMPLE_RGBA_TO_RGBA          6\n\
#define SAMPLE_TRIPLANAR_TO_YUVA     7\n\
#define SAMPLE_TRIPLANAR10_TO_YUVA   8\n\
#define SAMPLE_PLANAR_YUVA_TO_YUVA   9\n\
#define SAMPLE_BGRX_TO_RGBA         17\n\
\n\
#define SAMPLE_NV12_TO_NV_Y         10\n\
#define SAMPLE_NV12_TO_NV_UV        11\n\
#define SAMPLE_RGBA_TO_NV_R         12\n\
#define SAMPLE_RGBA_TO_NV_GB        13\n\
#define SAMPLE_PLANAR_YUVA_TO_NV_Y  14\n\
#define SAMPLE_PLANAR_YUVA_TO_NV_UV 15\n\
#define SAMPLE_NV12A_TO_YUVA        16\n\
\n\
#if (TONE_MAPPING==TONE_MAP_HABLE)\n\
/* see http://filmicworlds.com/blog/filmic-tonemapping-operators/ */\n\
inline float3 hable(float3 x) {\n\
    const float A = 0.15, B = 0.50, C = 0.10, D = 0.20, E = 0.02, F = 0.30;\n\
    return ((x * (A*x + (C*B))+(D*E))/(x * (A*x + B) + (D*F))) - E/F;\n\
}\n\
#endif\n\
\n\
#if (SRC_TO_LINEAR==SRC_TRANSFER_HLG)\n\
/* https://en.wikipedia.org/wiki/Hybrid_Log-Gamma#Technical_details */\n\
inline float inverse_HLG(float x){\n\
    const float B67_a = 0.17883277;\n\
    const float B67_b = 0.28466892;\n\
    const float B67_c = 0.55991073;\n\
    const float B67_inv_r2 = 4.0; /* 1/0.5² */\n\
    if (x <= 0.5)\n\
        x = x * x * B67_inv_r2;\n\
    else\n\
        x = exp((x - B67_c) / B67_a) + B67_b;\n\
    return x;\n\
}\n\
#endif\n\
\n\
inline float3 sourceToLinear(float3 rgb) {\n\
#if (SRC_TO_LINEAR==SRC_TRANSFER_PQ)\n\
    const float ST2084_m1 = 2610.0 / (4096.0 * 4);\n\
    const float ST2084_m2 = (2523.0 / 4096.0) * 128.0;\n\
    const float ST2084_c1 = 3424.0 / 4096.0;\n\
    const float ST2084_c2 = (2413.0 / 4096.0) * 32.0;\n\
    const float ST2084_c3 = (2392.0 / 4096.0) * 32.0;\n\
    rgb = pow(max(rgb, 0), 1.0/ST2084_m2);\n\
    rgb = max(rgb - ST2084_c1, 0.0) / (ST2084_c2 - ST2084_c3 * rgb);\n\
    rgb = pow(rgb, 1.0/ST2084_m1);\n\
    return rgb * 10000;\n\
#elif (SRC_TO_LINEAR==SRC_TRANSFER_HLG)\n\
    /* TODO: in one call */\n\
    rgb.r = inverse_HLG(rgb.r);\n\
    rgb.g = inverse_HLG(rgb.g);\n\
    rgb.b = inverse_HLG(rgb.b);\n\
    float3 ootf_2020 = float3(0.2627, 0.6780, 0.0593);\n\
    float ootf_ys = 1000 * dot(ootf_2020, rgb);\n\
    return rgb * pow(ootf_ys, 0.200);\n\
#elif (SRC_TO_LINEAR==SRC_TRANSFER_709)\n\
    return pow(rgb, 1.0 / 0.45);\n\
#elif (SRC_TO_LINEAR==SRC_TRANSFER_SRGB)\n\
    return pow(rgb, 2.2);\n\
#elif (SRC_TO_LINEAR==SRC_TRANSFER_470BG)\n\
    return pow(rgb, 2.8);\n\
#else\n\
    return rgb;\n\
#endif\n\
}\n\
\n\
inline float3 linearToDisplay(float3 rgb) {\n\
#if (LINEAR_TO_DST==DST_TRANSFER_SRGB)\n\
    return pow(rgb, 1.0 / 2.2);\n\
#elif (LINEAR_TO_DST==DST_TRANSFER_PQ)\n\
    const float ST2084_m1 = 2610.0 / (4096.0 * 4);\n\
    const float ST2084_m2 = (2523.0 / 4096.0) * 128.0;\n\
    const float ST2084_c1 = 3424.0 / 4096.0;\n\
    const float ST2084_c2 = (2413.0 / 4096.0) * 32.0;\n\
    const float ST2084_c3 = (2392.0 / 4096.0) * 32.0;\n\
    rgb = pow(rgb / 10000, ST2084_m1);\n\
    rgb = (ST2084_c1 + ST2084_c2 * rgb) / (1 + ST2084_c3 * rgb);\n\
    return pow(rgb, ST2084_m2);\n\
#else\n\
    return rgb;\n\
#endif\n\
}\n\
\n\
inline float3 toneMapping(float3 rgb) {\n\
    rgb = rgb * LuminanceScale;\n\
#if (TONE_MAPPING==TONE_MAP_HABLE)\n\
    const float3 HABLE_DIV = hable(11.2);\n\
    rgb = hable(rgb) / HABLE_DIV;\n\
#endif\n\
    return rgb;\n\
}\n\
\n\
inline float4 sampleTexture(SamplerState samplerState, float2 coords) {\n\
    float4 sample;\n\
    /* sampling routine in sample */\n\
#if (SAMPLE_TEXTURES==SAMPLE_NV12_TO_YUVA)\n\
    sample.x  = shaderTexture[0].Sample(samplerState, coords).x;\n\
    sample.yz = shaderTexture[1].Sample(samplerState, coords).xy;\n\
    sample.a  = 1;\n\
#elif (SAMPLE_TEXTURES==SAMPLE_NV12A_TO_YUVA)\n\
    sample.x  = shaderTexture[0].Sample(samplerState, coords).x;\n\
    sample.yz = shaderTexture[1].Sample(samplerState, coords).xy;\n\
    sample.a  = shaderTexture[2].Sample(samplerState, coords).x;\n\
#elif (SAMPLE_TEXTURES==SAMPLE_YUY2_TO_YUVA)\n\
    sample.x  = shaderTexture[0].Sample(samplerState, coords).x;\n\
    sample.y  = shaderTexture[0].Sample(samplerState, coords).y;\n\
    sample.z  = shaderTexture[0].Sample(samplerState, coords).a;\n\
    sample.a  = 1;\n\
#elif (SAMPLE_TEXTURES==SAMPLE_Y210_TO_YUVA)\n\
    sample.x  = shaderTexture[0].Sample(samplerState, coords).r;\n\
    sample.y  = shaderTexture[0].Sample(samplerState, coords).g;\n\
    sample.z  = shaderTexture[0].Sample(samplerState, coords).a;\n\
    sample.a  = 1;\n\
#elif (SAMPLE_TEXTURES==SAMPLE_Y410_TO_YUVA)\n\
    sample.x  = shaderTexture[0].Sample(samplerState, coords).g;\n\
    sample.y  = shaderTexture[0].Sample(samplerState, coords).r;\n\
    sample.z  = shaderTexture[0].Sample(samplerState, coords).b;\n\
    sample.a  = 1;\n\
#elif (SAMPLE_TEXTURES==SAMPLE_AYUV_TO_YUVA)\n\
    sample.x  = shaderTexture[0].Sample(samplerState, coords).z;\n\
    sample.y  = shaderTexture[0].Sample(samplerState, coords).y;\n\
    sample.z  = shaderTexture[0].Sample(samplerState, coords).x;\n\
    sample.a  = 1;\n\
#elif (SAMPLE_TEXTURES==SAMPLE_RGBA_TO_RGBA)\n\
    sample = shaderTexture[0].Sample(samplerState, coords);\n\
#elif (SAMPLE_TEXTURES==SAMPLE_BGRX_TO_RGBA)\n\
    sample = shaderTexture[0].Sample(samplerState, coords);\n\
    sample.a  = 1;\n\
#elif (SAMPLE_TEXTURES==SAMPLE_TRIPLANAR_TO_YUVA)\n\
    sample.x  = shaderTexture[0].Sample(samplerState, coords).x;\n\
    sample.y  = shaderTexture[1].Sample(samplerState, coords).x;\n\
    sample.z  = shaderTexture[2].Sample(samplerState, coords).x;\n\
    sample.a  = 1;\n\
#elif (SAMPLE_TEXTURES==SAMPLE_TRIPLANAR10_TO_YUVA)\n\
    sample.x  = shaderTexture[0].Sample(samplerState, coords).x;\n\
    sample.y  = shaderTexture[1].Sample(samplerState, coords).x;\n\
    sample.z  = shaderTexture[2].Sample(samplerState, coords).x;\n\
    sample = sample * 64;\n\
    sample.a  = 1;\n\
#elif (SAMPLE_TEXTURES==SAMPLE_PLANAR_YUVA_TO_YUVA)\n\
    sample.x  = shaderTexture[0].Sample(samplerState, coords).x;\n\
    sample.y  = shaderTexture[1].Sample(samplerState, coords).x;\n\
    sample.z  = shaderTexture[2].Sample(samplerState, coords).x;\n\
    sample.a  = shaderTexture[3].Sample(samplerState, coords).x;\n\
#elif (SAMPLE_TEXTURES==SAMPLE_NV12_TO_NV_Y)\n\
    sample.x  = shaderTexture[0].Sample(samplerState, coords).x;\n\
    sample.y = 0.0;\n\
    sample.z = 0.0;\n\
    sample.a = 1;\n\
#elif (SAMPLE_TEXTURES==SAMPLE_NV12_TO_NV_UV)\n\
    // TODO should be shaderTexture[0] ?\n\
    sample.xy  = shaderTexture[1].Sample(samplerState, coords).xy;\n\
    sample.z = 0.0;\n\
    sample.a = 1;\n\
#elif (SAMPLE_TEXTURES==SAMPLE_RGBA_TO_NV_R)\n\
    sample = shaderTexture[0].Sample(samplerState, coords);\n\
#elif (SAMPLE_TEXTURES==SAMPLE_RGBA_TO_NV_GB)\n\
    sample.x = shaderTexture[0].Sample(samplerState, coords).y;\n\
    sample.y = shaderTexture[0].Sample(samplerState, coords).z;\n\
    sample.z = 0;\n\
    sample.a = 1;\n\
#elif (SAMPLE_TEXTURES==SAMPLE_PLANAR_YUVA_TO_NV_Y)\n\
    sample.x = shaderTexture[0].Sample(samplerState, coords).x;\n\
    sample.y = 0.0;\n\
    sample.z = 0.0;\n\
    sample.a = shaderTexture[3].Sample(samplerState, coords).x;\n\
#elif (SAMPLE_TEXTURES==SAMPLE_PLANAR_YUVA_TO_NV_UV)\n\
    sample.x = shaderTexture[1].Sample(samplerState, coords).x;\n\
    sample.y = shaderTexture[2].Sample(samplerState, coords).x;\n\
    sample.z = 0.0;\n\
    sample.a = shaderTexture[3].Sample(samplerState, coords).x;\n\
#endif\n\
    return sample;\n\
}\n\
\n\
float4 main( PS_INPUT In ) : SV_TARGET\n\
{\n\
    float4 sample;\n\
    \n\
    if (In.uv.x > Boundary.x || In.uv.y > Boundary.y) \n\
        sample = sampleTexture( borderSampler, In.uv );\n\
    else\n\
        sample = sampleTexture( normalSampler, In.uv );\n\
    float srcAlpha = saturate(sample.a  * Opacity);\n\
    sample.a = 1.0;\n\
    float3 rgb = max(mul(sample, Colorspace),0);\n\
    rgb = sourceToLinear(rgb);\n\
    rgb = toneMapping(rgb);\n\
    rgb = linearToDisplay(rgb);\n\
    return float4(rgb, srcAlpha);\n\
}\n\
";

static const char globVertexShader[] = "\n\
#if HAS_PROJECTION\n\
cbuffer VS_PROJECTION_CONST : register(b0)\n\
{\n\
    float4x4 View;\n\
    float4x4 Zoom;\n\
    float4x4 Projection;\n\
};\n\
#endif\n\
struct d3d_vertex_t\n\
{\n\
    float3 Position   : POSITION;\n\
    float2 uv         : TEXCOORD;\n\
};\n\
\n\
struct PS_INPUT\n\
{\n\
    float4 Position   : SV_POSITION;\n\
    float2 uv         : TEXCOORD;\n\
};\n\
\n\
PS_INPUT main( d3d_vertex_t In )\n\
{\n\
    PS_INPUT Output;\n\
    float4 pos = float4(In.Position, 1);\n\
#if HAS_PROJECTION\n\
    pos = mul(View, pos);\n\
    pos = mul(Zoom, pos);\n\
    pos = mul(Projection, pos);\n\
#endif\n\
    Output.Position = pos;\n\
    Output.uv = In.uv;\n\
    return Output;\n\
}\n\
";

static void ReleaseID3D10Blob(d3d_shader_blob *blob)
{
    ID3D10Blob_Release( (ID3D10Blob*)blob->opaque );
}

static void ID3D10BlobtoBlob(ID3D10Blob *d3dblob, d3d_shader_blob *blob)
{
    blob->opaque = d3dblob;
    blob->pf_release = ReleaseID3D10Blob;
    blob->buf_size = ID3D10Blob_GetBufferSize(d3dblob);
    blob->buffer = ID3D10Blob_GetBufferPointer(d3dblob);
}


static HRESULT CompileShader(vlc_object_t *obj,
                             D3D_FEATURE_LEVEL feature_level,
                             const char *psz_shader, bool pixelShader,
                             const D3D_SHADER_MACRO *defines,
                             d3d_shader_blob *blob)
{
    ID3D10Blob* pShaderBlob = NULL, *pErrBlob = NULL;

    UINT compileFlags = 0;
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
# if !defined(NDEBUG)
    if (IsDebuggerPresent())
        compileFlags += D3DCOMPILE_DEBUG;
# endif
#endif
    HRESULT hr;
    {
        const char *target;
        if (pixelShader)
        {
            if (feature_level >= D3D_FEATURE_LEVEL_12_0)
                target = "ps_5_0";
            else if (likely(feature_level >= D3D_FEATURE_LEVEL_10_0))
                target = "ps_4_0";
            else if (feature_level >= D3D_FEATURE_LEVEL_9_3)
                target = "ps_4_0_level_9_3";
            else
                target = "ps_4_0_level_9_1";
        }
        else
        {
            if (feature_level >= D3D_FEATURE_LEVEL_12_0)
                target = "vs_5_0";
            else if (likely(feature_level >= D3D_FEATURE_LEVEL_10_0))
                target = "vs_4_0";
            else if (feature_level >= D3D_FEATURE_LEVEL_9_3)
                target = "vs_4_0_level_9_3";
            else
                target = "vs_4_0_level_9_1";
        }

        hr = D3DCompile(psz_shader, strlen(psz_shader),
                        NULL, defines, NULL, "main", target,
                        compileFlags, 0, &pShaderBlob, &pErrBlob);
    }

    if (FAILED(hr) || pErrBlob) {
        const char *err = pErrBlob ? ID3D10Blob_GetBufferPointer(pErrBlob) : NULL;
        if (SUCCEEDED(hr))
            msg_Dbg(obj, "%s Shader: %s", pixelShader?"Pixel":"Vertex", err );
        else
            msg_Err(obj, "invalid %s Shader (hr=0x%lX): %s", pixelShader?"Pixel":"Vertex", hr, err );
        if (pErrBlob)
            ID3D10Blob_Release(pErrBlob);
        if (FAILED(hr))
            return hr;
    }
    if (!pShaderBlob)
        return E_INVALIDARG;
    ID3D10BlobtoBlob(pShaderBlob, blob);
    return S_OK;
}

static HRESULT CompilePixelShaderBlob(vlc_object_t *o,
                                   D3D_FEATURE_LEVEL feature_level,
                                   const char *psz_sampler,
                                   const char *psz_shader_resource_views,
                                   const char *psz_src_to_linear,
                                   const char *psz_linear_to_display,
                                   const char *psz_tone_mapping,
                                   d3d_shader_blob *pPSBlob)
{
    if (var_InheritInteger(o, "verbose") >= 4)
        msg_Dbg(o, "shader %s", globPixelShaderDefault);

    D3D_SHADER_MACRO defines[] = {
         { "TEXTURE_RESOURCES", psz_shader_resource_views },
         { "TONE_MAPPING",      psz_tone_mapping },
         { "SRC_TO_LINEAR",     psz_src_to_linear },
         { "LINEAR_TO_DST",     psz_linear_to_display },
         { "SAMPLE_TEXTURES",   psz_sampler },
         { NULL, NULL },
    };
#ifndef NDEBUG
    size_t param=0;
    while (defines[param].Name)
    {
        msg_Dbg(o,"%s = %s", defines[param].Name, defines[param].Definition);
        param++;
    }
#endif

    return CompileShader(o, feature_level, globPixelShaderDefault, true,
                         defines, pPSBlob);
}

HRESULT (D3D_CompilePixelShader)(vlc_object_t *o,
                                 D3D_FEATURE_LEVEL feature_level,
                                 const display_info_t *display,
                                 video_transfer_func_t transfer,
                                 bool src_full_range,
                                 const d3d_format_t *dxgi_fmt,
                                 d3d_shader_blob pPSBlob[DXGI_MAX_RENDER_TARGET],
                                 size_t shader_views[DXGI_MAX_RENDER_TARGET])
{
    static const char *DEFAULT_NOOP = "0";
    const char *psz_sampler[DXGI_MAX_RENDER_TARGET] = {NULL, NULL};
    const char *psz_shader_resource_views[DXGI_MAX_RENDER_TARGET] = { NULL, NULL };
    const char *psz_src_to_linear     = DEFAULT_NOOP;
    const char *psz_linear_to_display = DEFAULT_NOOP;
    const char *psz_tone_mapping      = DEFAULT_NOOP;
    shader_views[1] = 0;

    if ( display->pixelFormat->formatTexture == DXGI_FORMAT_NV12 ||
         display->pixelFormat->formatTexture == DXGI_FORMAT_P010 )
    {
        /* we need 2 shaders, one for the Y target, one for the UV target */
        switch (dxgi_fmt->formatTexture)
        {
        case DXGI_FORMAT_NV12:
        case DXGI_FORMAT_P010:
            psz_sampler[0] = "SAMPLE_NV12_TO_NV_Y";
            psz_shader_resource_views[0] = "1"; shader_views[0] = 1;
            psz_sampler[1] = "SAMPLE_NV12_TO_NV_UV";
            psz_shader_resource_views[1] = "2"; shader_views[1] = 2; // TODO should be 1 ?
            break;
        case DXGI_FORMAT_R8G8B8A8_UNORM:
        case DXGI_FORMAT_B8G8R8A8_UNORM:
        case DXGI_FORMAT_B8G8R8X8_UNORM:
        case DXGI_FORMAT_R10G10B10A2_UNORM:
        case DXGI_FORMAT_R16G16B16A16_UNORM:
        case DXGI_FORMAT_B5G6R5_UNORM:
            /* Y */
            psz_sampler[0] = "SAMPLE_RGBA_TO_NV_R";
            psz_shader_resource_views[0] = "1"; shader_views[0] = 1;
            /* UV */
            psz_sampler[1] = "SAMPLE_RGBA_TO_NV_GB";
            psz_shader_resource_views[1] = "1"; shader_views[1] = 1;
            break;
        case DXGI_FORMAT_UNKNOWN:
            switch (dxgi_fmt->fourcc)
            {
            case VLC_CODEC_YUVA:
                /* Y */
                psz_sampler[0] = "SAMPLE_PLANAR_YUVA_TO_NV_Y";
                psz_shader_resource_views[0] = "4"; shader_views[0] = 4;
                /* UV */
                psz_sampler[1] = "SAMPLE_PLANAR_YUVA_TO_NV_UV";
                psz_shader_resource_views[1] = "4"; shader_views[1] = 4;
                break;
            default:
                vlc_assert_unreachable();
            }
            break;
        default:
            vlc_assert_unreachable();
        }
    }
    else
    {
        switch (dxgi_fmt->formatTexture)
        {
        case DXGI_FORMAT_NV12:
        case DXGI_FORMAT_P010:
            switch(dxgi_fmt->alphaTexture)
            {
            case DXGI_FORMAT_UNKNOWN:
                psz_sampler[0] = "SAMPLE_NV12_TO_YUVA";
                psz_shader_resource_views[0] = "2"; shader_views[0] = 2;
                break;
            case DXGI_FORMAT_NV12:
                psz_sampler[0] = "SAMPLE_NV12A_TO_YUVA";
                psz_shader_resource_views[0] = "4"; shader_views[0] = 4;
                break;
            default:
                vlc_assert_unreachable();
            }
            break;
        case DXGI_FORMAT_YUY2:
            psz_sampler[0] = "SAMPLE_YUY2_TO_YUVA";
            psz_shader_resource_views[0] = "1"; shader_views[0] = 1;
            break;
        case DXGI_FORMAT_Y210:
            psz_sampler[0] = "SAMPLE_Y210_TO_YUVA";
            psz_shader_resource_views[0] = "1"; shader_views[0] = 1;
            break;
        case DXGI_FORMAT_Y410:
            psz_sampler[0] = "SAMPLE_Y410_TO_YUVA";
            psz_shader_resource_views[0] = "1"; shader_views[0] = 1;
            break;
        case DXGI_FORMAT_AYUV:
            psz_sampler[0] = "SAMPLE_AYUV_TO_YUVA";
            psz_shader_resource_views[0] = "1"; shader_views[0] = 1;
            break;
        case DXGI_FORMAT_R8G8B8A8_UNORM:
        case DXGI_FORMAT_B8G8R8A8_UNORM:
        case DXGI_FORMAT_R10G10B10A2_UNORM:
        case DXGI_FORMAT_R16G16B16A16_UNORM:
        case DXGI_FORMAT_B5G6R5_UNORM:
            psz_sampler[0] = "SAMPLE_RGBA_TO_RGBA";
            psz_shader_resource_views[0] = "1"; shader_views[0] = 1;
            break;
        case DXGI_FORMAT_B8G8R8X8_UNORM:
            psz_sampler[0] = "SAMPLE_BGRX_TO_RGBA";
            psz_shader_resource_views[0] = "1"; shader_views[0] = 1;
            break;
        case DXGI_FORMAT_UNKNOWN:
            switch (dxgi_fmt->fourcc)
            {
            case VLC_CODEC_I420_10L:
                psz_sampler[0] = "SAMPLE_TRIPLANAR10_TO_YUVA";
                psz_shader_resource_views[0] = "3"; shader_views[0] = 3;
                break;
            case VLC_CODEC_I444_16L:
            case VLC_CODEC_I444_12L:
            case VLC_CODEC_I444_10L:
            case VLC_CODEC_I444:
            case VLC_CODEC_I422_10L:
            case VLC_CODEC_I420:
                psz_sampler[0] = "SAMPLE_TRIPLANAR_TO_YUVA";
                psz_shader_resource_views[0] = "3"; shader_views[0] = 3;
                break;
            case VLC_CODEC_YUVA:
            case VLC_CODEC_YUV420A:
                psz_sampler[0] = "SAMPLE_PLANAR_YUVA_TO_YUVA";
                psz_shader_resource_views[0] = "4"; shader_views[0] = 4;
                break;
            default:
                vlc_assert_unreachable();
            }
            break;
        default:
            vlc_assert_unreachable();
        }
    }

    video_transfer_func_t src_transfer;

    if (transfer != display->transfer)
    {
        /* we need to go in linear mode */
        switch (transfer)
        {
            case TRANSFER_FUNC_SMPTE_ST2084:
                /* ST2084 to Linear */
                psz_src_to_linear = "SRC_TRANSFER_PQ";
                src_transfer = TRANSFER_FUNC_LINEAR;
                break;
            case TRANSFER_FUNC_HLG:
                psz_src_to_linear = "SRC_TRANSFER_HLG";
                src_transfer = TRANSFER_FUNC_LINEAR;
                break;
            case TRANSFER_FUNC_BT709:
                psz_src_to_linear = "SRC_TRANSFER_709";
                src_transfer = TRANSFER_FUNC_LINEAR;
                break;
            case TRANSFER_FUNC_BT470_M:
            case TRANSFER_FUNC_SRGB:
                psz_src_to_linear = "SRC_TRANSFER_SRGB";
                src_transfer = TRANSFER_FUNC_LINEAR;
                break;
            case TRANSFER_FUNC_BT470_BG:
                psz_src_to_linear = "SRC_TRANSFER_470BG";
                src_transfer = TRANSFER_FUNC_LINEAR;
                break;
            default:
                msg_Dbg(o, "unhandled source transfer %d", transfer);
                src_transfer = transfer;
                break;
        }

        switch (display->transfer)
        {
            case TRANSFER_FUNC_SRGB:
                if (src_transfer == TRANSFER_FUNC_LINEAR)
                {
                    /* Linear to sRGB */
                    psz_linear_to_display = "DST_TRANSFER_SRGB";

                    if (transfer == TRANSFER_FUNC_SMPTE_ST2084 || transfer == TRANSFER_FUNC_HLG)
                    {
                        /* HDR tone mapping */
                        psz_tone_mapping = "TONE_MAP_HABLE";
                    }
                }
                else
                    msg_Warn(o, "don't know how to transfer from %d to sRGB", src_transfer);
                break;

            case TRANSFER_FUNC_SMPTE_ST2084:
                if (src_transfer == TRANSFER_FUNC_LINEAR)
                {
                    /* Linear to ST2084 */
                    psz_linear_to_display = "DST_TRANSFER_PQ";
                }
                else
                    msg_Warn(o, "don't know how to transfer from %d to SMPTE ST 2084", src_transfer);
                break;
            default:
                msg_Warn(o, "don't know how to transfer from %d to %d", src_transfer, display->transfer);
                break;
        }
    }

    bool dst_full_range = display->b_full_range;
    if (!DxgiIsRGBFormat(dxgi_fmt) && DxgiIsRGBFormat(display->pixelFormat))
    {
        if (src_full_range && !dst_full_range)
            msg_Warn(o, "unsupported display full range YUV on studio range RGB");
    }

    HRESULT hr;
    hr = CompilePixelShaderBlob(o, feature_level,
                                psz_sampler[0], psz_shader_resource_views[0],
                                psz_src_to_linear,
                                psz_linear_to_display,
                                psz_tone_mapping,
                                &pPSBlob[0]);
    if (SUCCEEDED(hr) && psz_sampler[1])
    {
        hr = CompilePixelShaderBlob(o, feature_level,
                                    psz_sampler[1],  psz_shader_resource_views[1],
                                    psz_src_to_linear,
                                    psz_linear_to_display,
                                    psz_tone_mapping,
                                    &pPSBlob[1]);
        if (FAILED(hr))
            D3D_ShaderBlobRelease(&pPSBlob[0]);
    }

    return hr;
}

HRESULT D3D_CompileVertexShader(vlc_object_t *obj,
                                D3D_FEATURE_LEVEL feature_level, bool flat,
                                d3d_shader_blob *blob)
{
    D3D_SHADER_MACRO defines[] = {
         { "HAS_PROJECTION", flat ? "0" : "1" },
         { NULL, NULL },
    };
    return CompileShader(obj, feature_level, globVertexShader, false, defines, blob);
}
