/* hwaccel.c
 *
 * Copyright (c) 2003-2026 HandBrake Team
 * This file is part of the HandBrake source code.
 * Homepage: <http://handbrake.fr/>.
 * It may be used under the terms of the GNU General Public License v2.
 * For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
 */

#include "handbrake/hwaccel.h"
#include "handbrake/handbrake.h"
#include "handbrake/qsv_common.h"

static hb_buffer_t * upload(void *hw_frames_ctx, hb_buffer_t **buf_in)
{
    int ret = 0;
    AVFrame frame = {{0}};
    AVFrame *hw_frame = av_frame_alloc();

    if (hw_frame == NULL)
    {
        goto fail;
    }

    hb_video_buffer_to_avframe(&frame, buf_in);

    ret = av_hwframe_get_buffer(hw_frames_ctx, hw_frame, 0);
    if (ret < 0)
    {
        hb_log("hwaccel: failed to get hwframe buffer");
        goto fail;
    }

    av_frame_copy_props(hw_frame, &frame);
    if (ret < 0)
    {
        hb_log("hwaccel: failed to copy props");
        goto fail;
    }

    av_hwframe_transfer_data(hw_frame, &frame, 0);
    if (ret < 0)
    {
        hb_log("hwaccel: failed to transfer data");
        goto fail;
    }

    hb_buffer_t *out = hb_avframe_to_video_buffer(hw_frame, (AVRational){1,1});

    av_frame_unref(&frame);
    av_frame_unref(hw_frame);

    return out;

fail:
    av_frame_unref(&frame);
    av_frame_unref(hw_frame);
    return NULL;
}

static int can_filter(hb_list_t *filters)
{
    return 0;
}

static void * find_decoder(int codec_param)
{
    if (codec_param == AV_CODEC_ID_AV1)
    {
        return (void *)avcodec_find_decoder_by_name("av1");
    }
    else
    {
        return (void *)avcodec_find_decoder(codec_param);
    }
}

static hb_list_t *hb_list_hwaccel = NULL;

void hb_register_hwaccel(hb_hwaccel_t *hwaccel)
{
    if (hb_list_hwaccel == NULL)
    {
        hb_list_hwaccel = hb_list_init();
    }
    hb_list_add(hb_list_hwaccel, hwaccel);
}

void hb_hwaccel_common_hwaccel_init()
{
    for (int ii = 0; ii < hb_list_count(hb_list_hwaccel); ii++)
    {
        hb_hwaccel_t *hwaccel = hb_list_item(hb_list_hwaccel, ii);
        if (hwaccel->can_filter == NULL)
        {
            hwaccel->can_filter = can_filter;
        }
        if (hwaccel->find_decoder == NULL)
        {
            hwaccel->find_decoder = find_decoder;
        }
        if (hwaccel->upload == NULL)
        {
            hwaccel->upload = upload;
        }
    }
}

hb_hwaccel_t * hb_get_hwaccel(int hw_decode)
{
    hw_decode &= ~HB_DECODE_FORCE_HW;

    for (int ii = 0; ii < hb_list_count(hb_list_hwaccel); ii++)
    {
        hb_hwaccel_t *hwaccel = hb_list_item(hb_list_hwaccel, ii);

        if (hw_decode == hwaccel->id)
        {
            return hwaccel;
        }
    }

    return NULL;
}

hb_hwaccel_t * hb_get_hwaccel_from_pix_fmt(enum AVPixelFormat hw_pix_fmt)
{
    for (int ii = 0; ii < hb_list_count(hb_list_hwaccel); ii++)
    {
        hb_hwaccel_t *hwaccel = hb_list_item(hb_list_hwaccel, ii);

        if (hw_pix_fmt == hwaccel->hw_pix_fmt)
        {
            return hwaccel;
        }
    }

    return NULL;
}

static int is_encoder_supported(hb_hwaccel_t *hwaccel, int encoder)
{
    const int *encoders = hwaccel->encoders;
    while (*encoders != HB_VCODEC_INVALID)
    {
        if (*encoders == encoder)
        {
            return 1;
        }
        encoders++;
    }
    return 0;
}

static int is_rotation_supported(hb_hwaccel_t *hwaccel, int rotation)
{
    return rotation != HB_ROTATION_0 && (hwaccel->caps & HB_HWACCEL_CAP_ROTATE) == 0 ? 0 : 1;
}

static int is_color_range_supported(hb_hwaccel_t *hwaccel, int color_range)
{
    return color_range != 0 && (hwaccel->caps & HB_HWACCEL_CAP_COLOR_RANGE) == 0 ? 0 : 1;
}

int hb_hwaccel_can_use_full_hw_pipeline(hb_hwaccel_t *hwaccel, hb_list_t *list_filter, int encoder, int rotation, int color_range)
{
    return hwaccel != NULL &&
        hwaccel->can_filter(list_filter) &&
        is_rotation_supported(hwaccel, rotation) &&
        is_color_range_supported(hwaccel, color_range) &&
        is_encoder_supported(hwaccel, encoder);
}

const AVCodecHWConfig *get_hw_config(const AVCodec *codec, enum AVHWDeviceType device_type)
{
    for (int i = 0;; i++)
    {
        const AVCodecHWConfig *config = avcodec_get_hw_config(codec, i);
        if (config == NULL)
        {
            return NULL;
        }
        if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&
            config->device_type == device_type)
        {
            return config;
        }
    }
    return NULL;
}

int hb_hwaccel_is_available(hb_hwaccel_t *hwaccel, int codec_id)
{
    if (hb_is_hardware_disabled() || hwaccel == NULL)
    {
        return 0;
    }

    const AVCodec *codec = hwaccel->find_decoder(codec_id);
    const AVCodecHWConfig *config = get_hw_config(codec, hwaccel->type);

    return config != NULL;
}

enum AVPixelFormat hw_hwaccel_get_hw_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts)
{
    const hb_job_t *job = ctx->opaque;
    const enum AVPixelFormat *p;

    for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++)
    {
        if (job && job->hw_pix_fmt != AV_PIX_FMT_NONE)
        {
            if (*p == job->hw_pix_fmt)
            {
                return *p;
            }
        }
        else
        {
            return *p;
        }
    }

    hb_error("hwaccel: failed to get HW surface format");
    return AV_PIX_FMT_NONE;
}

int hb_hwaccel_hw_device_ctx_init(enum AVHWDeviceType device_type, int device_index, void **hw_device_ctx)
{
    int err = 0;

    AVBufferRef *ctx;
    AVDictionary *dict = NULL;

    if (device_index > -1)
    {
        char device[32];
        snprintf(device, 32, "%u", device_index);
        av_dict_set(&dict, "child_device", device, 0);
    }

#if defined(_WIN32) || defined(__MINGW32__)
    if (device_type == AV_HWDEVICE_TYPE_QSV)
    {
        av_dict_set(&dict, "child_device_type", "d3d11va", 0);
    }
#endif

    if ((err = av_hwdevice_ctx_create(&ctx, device_type, NULL, dict, 0)) < 0)
    {
        hb_error("hwaccel: failed to create hwdevice");
    }
    else
    {
        *hw_device_ctx = ctx;
    }

    av_dict_free(&dict);

    return err;
}

void hb_hwaccel_hw_device_ctx_close(void **hw_device_ctx)
{
    if (hw_device_ctx && *hw_device_ctx)
    {
        av_buffer_unref((AVBufferRef **)hw_device_ctx);
    }
}

AVBufferRef *hb_hwaccel_init_hw_frames_ctx(AVBufferRef *hw_device_ctx,
                                           enum AVPixelFormat sw_fmt,
                                           enum AVPixelFormat hw_fmt,
                                           int width,
                                           int height,
                                           int initial_pool_size)
{
    AVBufferRef *hw_frames_ctx = av_hwframe_ctx_alloc(hw_device_ctx);
    AVHWFramesContext *frames_ctx = (AVHWFramesContext *)hw_frames_ctx->data;
    frames_ctx->format    = hw_fmt;
    frames_ctx->sw_format = sw_fmt;
    frames_ctx->width  = width;
    frames_ctx->height = height;

    if (initial_pool_size > 0)
    {
        frames_ctx->initial_pool_size = initial_pool_size;
    }
    if (av_hwframe_ctx_init(hw_frames_ctx) != 0)
    {
        hb_error("hwaccel: failed to initialize hw frames context");
        av_buffer_unref(&hw_frames_ctx);
        return NULL;
    }

    return hw_frames_ctx;
}

int hb_hwaccel_hwframes_ctx_init(AVCodecContext *ctx,
                                 enum AVPixelFormat sw_pix_fmt,
                                 enum AVPixelFormat hw_pix_fmt)
{
    if (!ctx->hw_device_ctx)
    {
        hb_error("hwaccel: failed to initialize hw frames context - no hw_device_ctx");
        return 1;
    }

    ctx->get_format = hw_hwaccel_get_hw_format;
    ctx->pix_fmt    = hw_pix_fmt;
    ctx->sw_pix_fmt = sw_pix_fmt;
    ctx->hw_frames_ctx = av_hwframe_ctx_alloc(ctx->hw_device_ctx);

    AVHWFramesContext *frames_ctx = (AVHWFramesContext *)ctx->hw_frames_ctx->data;
    frames_ctx->format    = hw_pix_fmt;
    frames_ctx->sw_format = sw_pix_fmt;
    frames_ctx->width  = ctx->width;
    frames_ctx->height = ctx->height;

#if HB_PROJECT_FEATURE_QSV
    if (hw_pix_fmt == AV_PIX_FMT_QSV)
    {
        ctx->extra_hw_frames = HB_QSV_FFMPEG_EXTRA_HW_FRAMES;
        frames_ctx->initial_pool_size = HB_QSV_FFMPEG_INITIAL_POOL_SIZE;

        AVQSVFramesContext *frames_hwctx = frames_ctx->hwctx;
        frames_hwctx->frame_type = MFX_MEMTYPE_VIDEO_MEMORY_DECODER_TARGET;
    }
#endif

    if (av_hwframe_ctx_init(ctx->hw_frames_ctx) != 0)
    {
        hb_error("hwaccel: failed to initialize hw frames context - av_hwframe_ctx_init");
        return 1;
    }

    return 0;
}
