/*
 * This file is part of FFmpeg.
 *
 * FFmpeg 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.
 *
 * FFmpeg 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 FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#ifndef AVCODEC_VULKAN_ENCODE_H
#define AVCODEC_VULKAN_ENCODE_H

#include "codec_id.h"
#include "internal.h"

#include "encode.h"
#include "hwconfig.h"

#include "vulkan_video.h"
#include "hw_base_encode.h"

typedef struct FFVulkanEncodeDescriptor {
    enum AVCodecID                   codec_id;
    FFVulkanExtensions               encode_extension;
    VkVideoCodecOperationFlagBitsKHR encode_op;

    VkExtensionProperties ext_props;
} FFVulkanEncodeDescriptor;

typedef struct FFVulkanEncodePicture {
    FFHWBaseEncodePicture  base;
    VkVideoPictureResourceInfoKHR dpb_res;
    VkVideoReferenceSlotInfoKHR dpb_slot;

    struct {
        VkImageView        view;
        VkImageAspectFlags aspect;
    } in;

    struct {
        VkImageView        view;
        VkImageAspectFlags aspect;
    } dpb;

    void                  *codec_layer;
    void                  *codec_rc_layer;

    FFVkExecContext       *exec;
    AVBufferRef           *pkt_buf;
    int                    slices_offset;
} FFVulkanEncodePicture;

/**
 * Callback for writing stream-level headers.
 */
typedef int (*vkenc_cb_write_stream_headers)(AVCodecContext *avctx,
                                             uint8_t *data, size_t *data_len);

/**
 * Callback for initializing codec-specific picture headers.
 */
typedef int (*vkenc_cb_init_pic_headers)(AVCodecContext *avctx,
                                         FFVulkanEncodePicture *pic);

/**
 * Callback for writing alignment data.
 * Align is the value to align offset to.
 */
typedef int (*vkenc_cb_write_filler)(AVCodecContext *avctx, uint32_t filler,
                                     uint8_t *data, size_t *data_len);

/**
 * Callback for writing any extra units requested. data_len must be set
 * to the available size, and its value will be overwritten by the #bytes written
 * to the output buffer.
 */
typedef int (*vkenc_cb_write_extra_headers)(AVCodecContext *avctx,
                                            FFVulkanEncodePicture *pic,
                                            uint8_t *data, size_t *data_len);

typedef struct FFVulkanCodec {
    /**
     * Codec feature flags.
     */
    int flags;
/* Codec output packet without timestamp delay, which means the
 * output packet has same PTS and DTS. For AV1. */
#define VK_ENC_FLAG_NO_DELAY 1 << 6

    /**
     * Size of the codec-specific picture struct.
     */
    size_t picture_priv_data_size;

    /**
     * Size of the filler header.
     */
    size_t filler_header_size;

    /**
     * Initialize codec-specific structs in a Vulkan profile.
     */
    int (*init_profile)(AVCodecContext *avctx, VkVideoProfileInfoKHR *profile,
                        void *pnext);

    /**
     * Initialize codec-specific rate control structures for a picture.
     */
    int (*init_pic_rc)(AVCodecContext *avctx, FFHWBaseEncodePicture *pic,
                       VkVideoEncodeRateControlInfoKHR *rc_info,
                       VkVideoEncodeRateControlLayerInfoKHR *rc_layer);

    /**
     * Initialize codec-specific picture parameters.
     */
    int (*init_pic_params)(AVCodecContext *avctx, FFHWBaseEncodePicture *pic,
                           VkVideoEncodeInfoKHR *encode_info);

    /**
     * Callback for writing stream headers.
     */
    int (*write_sequence_headers)(AVCodecContext *avctx,
                                  FFHWBaseEncodePicture *base_pic,
                                  uint8_t *data, size_t *data_len);

    /**
     * Callback for writing alignment data.
     */
    int (*write_filler)(AVCodecContext *avctx, uint32_t filler,
                        uint8_t *data, size_t *data_len);

    /**
     * Callback for writing any extra units requested. data_len must be set
     * to the available size, and its value will be overwritten by the #bytes written
     * to the output buffer.
     */
    int (*write_extra_headers)(AVCodecContext *avctx, FFHWBaseEncodePicture *pic,
                               uint8_t *data, size_t *data_len);
} FFVulkanCodec;

typedef struct FFVkEncodeCommonOptions {
    int qp;
    int quality;
    int profile;
    int level;
    int tier;
    int async_depth;
    VkVideoEncodeUsageFlagBitsKHR usage;
    VkVideoEncodeContentFlagBitsKHR content;
    VkVideoEncodeTuningModeKHR tune;

    VkVideoEncodeRateControlModeFlagBitsKHR rc_mode;
#define FF_VK_RC_MODE_AUTO 0xFFFFFFFF
} FFVkEncodeCommonOptions;

typedef struct FFVulkanEncodeContext {
    FFVulkanContext s;
    FFVkVideoCommon common;
    FFHWBaseEncodeContext base;
    const FFVulkanCodec *codec;

    int explicit_qp;
    int session_reset;

    /* Session parameters object, initialized by each codec independently
     * and set here. */
    VkVideoSessionParametersKHR session_params;

    AVBufferPool *buf_pool;

    VkFormat pic_format;

    FFVkEncodeCommonOptions opts;

    VkVideoProfileInfoKHR profile;
    VkVideoProfileListInfoKHR profile_list;
    VkVideoCapabilitiesKHR caps;
    VkVideoEncodeQualityLevelPropertiesKHR quality_props;
    VkVideoEncodeCapabilitiesKHR enc_caps;
    VkVideoEncodeUsageInfoKHR usage_info;

    FFVkQueueFamilyCtx qf_enc;
    FFVkExecPool enc_pool;

    FFHWBaseEncodePicture *slots[32];
} FFVulkanEncodeContext;

#define VULKAN_ENCODE_COMMON_OPTIONS \
    { "qp", "Use an explicit constant quantizer for the whole stream", OFFSET(common.opts.qp), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 255, FLAGS }, \
    { "quality", "Set encode quality (trades off against speed, higher is faster)", OFFSET(common.opts.quality), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS }, \
    { "rc_mode", "Select rate control type", OFFSET(common.opts.rc_mode), AV_OPT_TYPE_INT, { .i64 = FF_VK_RC_MODE_AUTO }, 0, FF_VK_RC_MODE_AUTO, FLAGS, "rc_mode" }, \
        { "auto",    "Choose mode automatically based on parameters", 0, AV_OPT_TYPE_CONST, { .i64 = FF_VK_RC_MODE_AUTO }, INT_MIN, INT_MAX, FLAGS, "rc_mode" }, \
        { "driver",  "Driver-specific rate control", 0, AV_OPT_TYPE_CONST, { .i64 = VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DEFAULT_KHR      }, INT_MIN, INT_MAX, FLAGS, "rc_mode" }, \
        { "cqp",     "Constant quantizer mode", 0, AV_OPT_TYPE_CONST, { .i64 = VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DISABLED_BIT_KHR }, INT_MIN, INT_MAX, FLAGS, "rc_mode" }, \
        { "cbr",     "Constant bitrate mode", 0, AV_OPT_TYPE_CONST, { .i64 = VK_VIDEO_ENCODE_RATE_CONTROL_MODE_CBR_BIT_KHR      }, INT_MIN, INT_MAX, FLAGS, "rc_mode" }, \
        { "vbr",     "Variable bitrate mode", 0, AV_OPT_TYPE_CONST, { .i64 = VK_VIDEO_ENCODE_RATE_CONTROL_MODE_VBR_BIT_KHR      }, INT_MIN, INT_MAX, FLAGS, "rc_mode" }, \
    { "tune", "Select tuning type", OFFSET(common.opts.tune), AV_OPT_TYPE_INT, { .i64 = VK_VIDEO_ENCODE_TUNING_MODE_DEFAULT_KHR }, 0, INT_MAX, FLAGS, "tune" }, \
        { "default",  "Default tuning", 0, AV_OPT_TYPE_CONST, { .i64 = VK_VIDEO_ENCODE_TUNING_MODE_DEFAULT_KHR           }, INT_MIN, INT_MAX, FLAGS, "tune" }, \
        { "hq",       "High quality tuning", 0, AV_OPT_TYPE_CONST, { .i64 = VK_VIDEO_ENCODE_TUNING_MODE_HIGH_QUALITY_KHR      }, INT_MIN, INT_MAX, FLAGS, "tune" }, \
        { "ll",       "Low-latency tuning", 0, AV_OPT_TYPE_CONST, { .i64 = VK_VIDEO_ENCODE_TUNING_MODE_LOW_LATENCY_KHR       }, INT_MIN, INT_MAX, FLAGS, "tune" }, \
        { "ull",      "Ultra low-latency tuning", 0, AV_OPT_TYPE_CONST, { .i64 = VK_VIDEO_ENCODE_TUNING_MODE_ULTRA_LOW_LATENCY_KHR }, INT_MIN, INT_MAX, FLAGS, "tune" }, \
        { "lossless", "Lossless mode tuning", 0, AV_OPT_TYPE_CONST, { .i64 = VK_VIDEO_ENCODE_TUNING_MODE_LOSSLESS_KHR          }, INT_MIN, INT_MAX, FLAGS, "tune" }, \
    { "usage", "Select usage type", OFFSET(common.opts.usage), AV_OPT_TYPE_FLAGS, { .i64 = VK_VIDEO_ENCODE_USAGE_DEFAULT_KHR }, 0, INT_MAX, FLAGS, "usage" }, \
        { "default",    "Default optimizations", 0, AV_OPT_TYPE_CONST, { .i64 = VK_VIDEO_ENCODE_USAGE_DEFAULT_KHR          }, INT_MIN, INT_MAX, FLAGS, "usage" }, \
        { "transcode",  "Optimize for transcoding", 0, AV_OPT_TYPE_CONST, { .i64 = VK_VIDEO_ENCODE_USAGE_TRANSCODING_BIT_KHR  }, INT_MIN, INT_MAX, FLAGS, "usage" }, \
        { "stream",     "Optimize for streaming", 0, AV_OPT_TYPE_CONST, { .i64 = VK_VIDEO_ENCODE_USAGE_STREAMING_BIT_KHR    }, INT_MIN, INT_MAX, FLAGS, "usage" }, \
        { "record",     "Optimize for offline recording", 0, AV_OPT_TYPE_CONST, { .i64 = VK_VIDEO_ENCODE_USAGE_RECORDING_BIT_KHR    }, INT_MIN, INT_MAX, FLAGS, "usage" }, \
        { "conference", "Optimize for teleconferencing", 0, AV_OPT_TYPE_CONST, { .i64 = VK_VIDEO_ENCODE_USAGE_CONFERENCING_BIT_KHR }, INT_MIN, INT_MAX, FLAGS, "usage" }, \
    { "content", "Select content type", OFFSET(common.opts.content), AV_OPT_TYPE_FLAGS, { .i64 = VK_VIDEO_ENCODE_CONTENT_DEFAULT_KHR }, 0, INT_MAX, FLAGS, "content" }, \
        { "default",  "Default content", 0, AV_OPT_TYPE_CONST, { .i64 = VK_VIDEO_ENCODE_CONTENT_DEFAULT_KHR      }, INT_MIN, INT_MAX, FLAGS, "content" }, \
        { "camera",   "Camera footage", 0, AV_OPT_TYPE_CONST, { .i64 = VK_VIDEO_ENCODE_CONTENT_CAMERA_BIT_KHR   }, INT_MIN, INT_MAX, FLAGS, "content" }, \
        { "desktop",  "Screen recording", 0, AV_OPT_TYPE_CONST, { .i64 = VK_VIDEO_ENCODE_CONTENT_DESKTOP_BIT_KHR  }, INT_MIN, INT_MAX, FLAGS, "content" }, \
        { "rendered", "Game or 3D content", 0, AV_OPT_TYPE_CONST, { .i64 = VK_VIDEO_ENCODE_CONTENT_RENDERED_BIT_KHR }, INT_MIN, INT_MAX, FLAGS, "content" }

/**
 * Initialize encoder.
 */
av_cold int ff_vulkan_encode_init(AVCodecContext *avctx, FFVulkanEncodeContext *ctx,
                                  const FFVulkanEncodeDescriptor *vk_desc,
                                  const FFVulkanCodec *codec,
                                  void *codec_caps, void *quality_pnext);

/**
 * Write out the extradata in case its needed.
 */
av_cold int ff_vulkan_write_global_header(AVCodecContext *avctx,
                                          FFVulkanEncodeContext *ctx);

/**
 * Encode.
 */
int ff_vulkan_encode_receive_packet(AVCodecContext *avctx, AVPacket *pkt);

/**
 * Uninitialize encoder.
 */
void ff_vulkan_encode_uninit(FFVulkanEncodeContext *ctx);

/**
 * Create session parameters.
 */
int ff_vulkan_encode_create_session_params(AVCodecContext *avctx, FFVulkanEncodeContext *ctx,
                                           void *codec_params_pnext);

/**
 * Paperwork.
 */
extern const AVCodecHWConfigInternal *const ff_vulkan_encode_hw_configs[];

#endif /* AVCODEC_VULKAN_ENCODE_H */