846 lines · 24.8 KB · C
RawDownload
/* * VVC thread logic * * Copyright (C) 2023 Nuo Mi * * 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 */ #include <stdatomic.h> #include "libavcodec/executor.h"#include "libavutil/mem.h"#include "libavutil/thread.h" #include "thread.h"#include "ctu.h"#include "filter.h"#include "inter.h"#include "intra.h"#include "refs.h" typedef struct ProgressListener {    VVCProgressListener l;    struct VVCTask *task;    VVCContext *s;} ProgressListener; typedef enum VVCTaskStage {    VVC_TASK_STAGE_INIT,                    // for CTU(0, 0) only    VVC_TASK_STAGE_PARSE,    VVC_TASK_STAGE_DEBLOCK_BS,    VVC_TASK_STAGE_INTER,    VVC_TASK_STAGE_RECON,    VVC_TASK_STAGE_LMCS,    VVC_TASK_STAGE_DEBLOCK_V,    VVC_TASK_STAGE_DEBLOCK_H,    VVC_TASK_STAGE_SAO,    VVC_TASK_STAGE_ALF,    VVC_TASK_STAGE_LAST} VVCTaskStage; typedef struct VVCTask {    union {        struct VVCTask *next;                //for executor debug only        FFTask task;    } u;     VVCTaskStage stage;     // ctu x, y, and raster scan order    int rx, ry, rs;    VVCFrameContext *fc;     ProgressListener col_listener;    ProgressListener listener[2][VVC_MAX_REF_ENTRIES];     // for parse task only    SliceContext *sc;    EntryPoint *ep;    int ctu_idx;                    //ctu idx in the current slice     // tasks with target scores met are ready for scheduling    atomic_uchar score[VVC_TASK_STAGE_LAST];    atomic_uchar target_inter_score;} VVCTask; typedef struct VVCRowThread {    atomic_int col_progress[VVC_PROGRESS_LAST];} VVCRowThread; typedef struct VVCFrameThread {    // error return for tasks    atomic_int ret;     VVCRowThread *rows;    VVCTask *tasks;     int ctu_size;    int ctu_width;    int ctu_height;    int ctu_count;     //protected by lock    atomic_int nb_scheduled_tasks;    atomic_int nb_scheduled_listeners;     int row_progress[VVC_PROGRESS_LAST];     AVMutex lock;    AVCond  cond;} VVCFrameThread; #define PRIORITY_LOWEST 2static void add_task(VVCContext *s, VVCTask *t){    VVCFrameThread *ft     = t->fc->ft;    FFTask *task           = &t->u.task;    const int priorities[] = {        0,                  // VVC_TASK_STAGE_INIT,        0,                  // VVC_TASK_STAGE_PARSE,        1,                  // VVC_TASK_STAGE_DEBLOCK_BS        // For an 8K clip, a CTU line completed in the reference frame may trigger 64 and more inter tasks.        // We assign these tasks the lowest priority to avoid being overwhelmed with inter tasks.        PRIORITY_LOWEST,    // VVC_TASK_STAGE_INTER        1,                  // VVC_TASK_STAGE_RECON,        1,                  // VVC_TASK_STAGE_LMCS,        1,                  // VVC_TASK_STAGE_DEBLOCK_V,        1,                  // VVC_TASK_STAGE_DEBLOCK_H,        1,                  // VVC_TASK_STAGE_SAO,        1,                  // VVC_TASK_STAGE_ALF,    };     atomic_fetch_add(&ft->nb_scheduled_tasks, 1);    task->priority = priorities[t->stage];    ff_executor_execute(s->executor, task);} static void task_init(VVCTask *t, VVCTaskStage stage, VVCFrameContext *fc, const int rx, const int ry){    memset(t, 0, sizeof(*t));    t->stage = stage;    t->fc    = fc;    t->rx    = rx;    t->ry    = ry;    t->rs    = ry * fc->ft->ctu_width + rx;    for (int i = 0; i < FF_ARRAY_ELEMS(t->score); i++)        atomic_store(t->score + i, 0);    atomic_store(&t->target_inter_score, 0);} static int task_init_parse(VVCTask *t, SliceContext *sc, EntryPoint *ep, const int ctu_idx){    if (t->sc) {        // the task already inited, error bitstream        return AVERROR_INVALIDDATA;    }    t->sc      = sc;    t->ep      = ep;    t->ctu_idx = ctu_idx;     return 0;} static uint8_t task_add_score(VVCTask *t, const VVCTaskStage stage){    return atomic_fetch_add(&t->score[stage], 1) + 1;} static uint8_t task_get_score(VVCTask *t, const VVCTaskStage stage){    return atomic_load(&t->score[stage]);} //first row in tile or slicestatic int is_first_row(const VVCFrameContext *fc, const int rx, const int ry){    const VVCFrameThread *ft = fc->ft;    const VVCPPS *pps        = fc->ps.pps;     if (ry != pps->ctb_to_row_bd[ry]) {        const int rs = ry * ft->ctu_width + rx;        return fc->tab.slice_idx[rs] != fc->tab.slice_idx[rs - ft->ctu_width];    }    return 1;} static int task_has_target_score(VVCTask *t, const VVCTaskStage stage, const uint8_t score){    // l:left, r:right, t: top, b: bottom    static const uint8_t target_score[] =    {        2,          //VVC_TASK_STAGE_DEBLOCK_BS,need l + t parse        0,          //VVC_TASK_STAGE_INTER,     not used        2,          //VVC_TASK_STAGE_RECON,     need l + rt recon        3,          //VVC_TASK_STAGE_LMCS,      need r + b + rb recon        1,          //VVC_TASK_STAGE_DEBLOCK_V, need l deblock v        2,          //VVC_TASK_STAGE_DEBLOCK_H, need r deblock v + t deblock h        5,          //VVC_TASK_STAGE_SAO,       need l + r + lb + b + rb deblock h        8,          //VVC_TASK_STAGE_ALF,       need sao around the ctu    };    uint8_t target = 0;    VVCFrameContext *fc = t->fc;     if (stage == VVC_TASK_STAGE_INIT)        return 1;     if (stage == VVC_TASK_STAGE_PARSE) {        const H266RawSPS *rsps   = fc->ps.sps->r;        const int wpp            = rsps->sps_entropy_coding_sync_enabled_flag && !is_first_row(fc, t->rx, t->ry);        const int no_prev_stage  = t->rs > 0;        target = 2 + wpp - no_prev_stage;                           //left parse + colocation + wpp - no_prev_stage    } else if (stage == VVC_TASK_STAGE_INTER) {        target = atomic_load(&t->target_inter_score);    } else {        target = target_score[stage - VVC_TASK_STAGE_DEBLOCK_BS];    }     //+1 for previous stage    av_assert0(score <= target + 1);    return score == target + 1;} static void frame_thread_add_score(VVCContext *s, VVCFrameThread *ft,    const int rx, const int ry, const VVCTaskStage stage){    VVCTask *t = ft->tasks + ft->ctu_width * ry + rx;    uint8_t score;     if (rx < 0 || rx >= ft->ctu_width || ry < 0 || ry >= ft->ctu_height)        return;     score = task_add_score(t, stage);    if (task_has_target_score(t, stage, score)) {        av_assert0(s);        av_assert0(stage == t->stage);        add_task(s, t);    }} static void sheduled_done(VVCFrameThread *ft, atomic_int *scheduled){    if (atomic_fetch_sub(scheduled, 1) == 1) {        ff_mutex_lock(&ft->lock);        ff_cond_signal(&ft->cond);        ff_mutex_unlock(&ft->lock);    }} static void progress_done(VVCProgressListener *_l, const int type){    const ProgressListener *l = (ProgressListener *)_l;    const VVCTask *t          = l->task;    VVCFrameThread *ft        = t->fc->ft;     frame_thread_add_score(l->s, ft, t->rx, t->ry, type);    sheduled_done(ft, &ft->nb_scheduled_listeners);} static void pixel_done(VVCProgressListener *l){    progress_done(l, VVC_TASK_STAGE_INTER);} static void mv_done(VVCProgressListener *l){    progress_done(l, VVC_TASK_STAGE_PARSE);} static void listener_init(ProgressListener *l,  VVCTask *t, VVCContext *s, const VVCProgress vp, const int y){    const int is_inter = vp == VVC_PROGRESS_PIXEL;     l->task = t;    l->s    = s;    l->l.vp = vp;    l->l.y  = y;    l->l.progress_done = is_inter ? pixel_done : mv_done;    if (is_inter)        atomic_fetch_add(&t->target_inter_score, 1);} static void add_progress_listener(VVCFrame *ref, ProgressListener *l,    VVCTask *t, VVCContext *s, const VVCProgress vp, const int y){    VVCFrameThread *ft = t->fc->ft;     atomic_fetch_add(&ft->nb_scheduled_listeners, 1);    listener_init(l, t, s, vp, y);    ff_vvc_add_progress_listener(ref, (VVCProgressListener*)l);} static void schedule_next_parse(VVCContext *s, VVCFrameContext *fc, const SliceContext *sc, const VVCTask *t){    VVCFrameThread *ft = fc->ft;    EntryPoint *ep     = t->ep;    const VVCSPS *sps  = fc->ps.sps;     if (sps->r->sps_entropy_coding_sync_enabled_flag) {        if (t->rx == fc->ps.pps->ctb_to_col_bd[t->rx]) {            EntryPoint *next = ep + 1;            if (next < sc->eps + sc->nb_eps && !is_first_row(fc, t->rx, t->ry + 1)) {                memcpy(next->cabac_state, ep->cabac_state, sizeof(next->cabac_state));                ff_vvc_ep_init_stat_coeff(next, sps->bit_depth, sps->r->sps_persistent_rice_adaptation_enabled_flag);            }        }        if (t->ry + 1 < ft->ctu_height && !is_first_row(fc, t->rx, t->ry + 1))            frame_thread_add_score(s, ft, t->rx, t->ry + 1, VVC_TASK_STAGE_PARSE);    }     if (t->ctu_idx + 1 < t->ep->ctu_end) {        const int next_rs = sc->sh.ctb_addr_in_curr_slice[t->ctu_idx + 1];        const int next_rx = next_rs % ft->ctu_width;        const int next_ry = next_rs / ft->ctu_width;        frame_thread_add_score(s, ft, next_rx, next_ry, VVC_TASK_STAGE_PARSE);    }} static void schedule_inter(VVCContext *s, VVCFrameContext *fc, const SliceContext *sc, VVCTask *t, const int rs){    const VVCSH *sh = &sc->sh;     if (!IS_I(sh->r)) {        CTU *ctu = fc->tab.ctus + rs;        for (int lx = 0; lx < 2; lx++) {            for (int i = 0; i < sh->r->num_ref_idx_active[lx]; i++) {                int y = ctu->max_y[lx][i];                VVCRefPic *refp = sc->rpl[lx].refs + i;                VVCFrame *ref   = refp->ref;                if (ref && y >= 0) {                    if (refp->is_scaled)                        y = y * refp->scale[1] >> 14;                    add_progress_listener(ref, &t->listener[lx][i], t, s, VVC_PROGRESS_PIXEL, y + LUMA_EXTRA_AFTER);                }            }        }    }} static void parse_task_done(VVCContext *s, VVCFrameContext *fc, const int rx, const int ry){    VVCFrameThread *ft  = fc->ft;    const int rs        = ry * ft->ctu_width + rx;    const int slice_idx = fc->tab.slice_idx[rs];    VVCTask *t          = ft->tasks + rs;    const SliceContext *sc = fc->slices[slice_idx];     schedule_next_parse(s, fc, sc, t);    schedule_inter(s, fc, sc, t, rs);} static void task_stage_done(const VVCTask *t, VVCContext *s){    VVCFrameContext *fc      = t->fc;    VVCFrameThread *ft       = fc->ft;    const VVCTaskStage stage = t->stage; #define ADD(dx, dy, stage) frame_thread_add_score(s, ft, t->rx + (dx), t->ry + (dy), stage)     //this is a reserve map of ready_score, ordered by zigzag    if (stage == VVC_TASK_STAGE_PARSE) {        ADD( 0,  1, VVC_TASK_STAGE_DEBLOCK_BS);        ADD( 1,  0, VVC_TASK_STAGE_DEBLOCK_BS);        if (t->rx < 0 || t->rx >= ft->ctu_width || t->ry < 0 || t->ry >= ft->ctu_height)            return;        parse_task_done(s, fc, t->rx, t->ry);    } else if (stage == VVC_TASK_STAGE_RECON) {        ADD(-1,  1, VVC_TASK_STAGE_RECON);        ADD( 1,  0, VVC_TASK_STAGE_RECON);        ADD(-1, -1, VVC_TASK_STAGE_LMCS);        ADD( 0, -1, VVC_TASK_STAGE_LMCS);        ADD(-1,  0, VVC_TASK_STAGE_LMCS);    } else if (stage == VVC_TASK_STAGE_DEBLOCK_V) {        ADD( 1,  0,  VVC_TASK_STAGE_DEBLOCK_V);        ADD(-1,  0,  VVC_TASK_STAGE_DEBLOCK_H);    } else if (stage == VVC_TASK_STAGE_DEBLOCK_H) {        ADD( 0,  1,  VVC_TASK_STAGE_DEBLOCK_H);        ADD(-1, -1,  VVC_TASK_STAGE_SAO);        ADD( 0, -1,  VVC_TASK_STAGE_SAO);        ADD(-1,  0,  VVC_TASK_STAGE_SAO);        ADD( 1, -1,  VVC_TASK_STAGE_SAO);        ADD( 1,  0,  VVC_TASK_STAGE_SAO);    } else if (stage == VVC_TASK_STAGE_SAO) {        ADD(-1, -1,  VVC_TASK_STAGE_ALF);        ADD( 0, -1,  VVC_TASK_STAGE_ALF);        ADD(-1,  0,  VVC_TASK_STAGE_ALF);        ADD( 1, -1,  VVC_TASK_STAGE_ALF);        ADD(-1,  1,  VVC_TASK_STAGE_ALF);        ADD( 1,  0,  VVC_TASK_STAGE_ALF);        ADD( 0,  1,  VVC_TASK_STAGE_ALF);        ADD( 1,  1,  VVC_TASK_STAGE_ALF);    }} static int task_is_stage_ready(VVCTask *t, int add){    const VVCTaskStage stage = t->stage;    uint8_t score;    if (stage > VVC_TASK_STAGE_ALF)        return 0;    score = task_get_score(t, stage) + add;    return task_has_target_score(t, stage, score);} static void check_colocation(VVCContext *s, VVCTask *t){    const VVCFrameContext *fc = t->fc;     if (fc->ps.ph.r->ph_temporal_mvp_enabled_flag || fc->ps.sps->r->sps_sbtmvp_enabled_flag) {        VVCFrame *col       = fc->ref->collocated_ref;        const int first_col = t->rx == fc->ps.pps->ctb_to_col_bd[t->rx];        if (col && first_col) {            //we depend on bottom and right boundary, do not - 1 for y            const int y = (t->ry << fc->ps.sps->ctb_log2_size_y);            add_progress_listener(col, &t->col_listener, t, s, VVC_PROGRESS_MV, y);            return;        }    }    frame_thread_add_score(s, fc->ft, t->rx, t->ry, VVC_TASK_STAGE_PARSE);} static void submit_entry_point(VVCContext *s, VVCFrameThread *ft, SliceContext *sc, EntryPoint *ep){    const int rs = sc->sh.ctb_addr_in_curr_slice[ep->ctu_start];    VVCTask *t   = ft->tasks + rs;     frame_thread_add_score(s, ft, t->rx, t->ry, VVC_TASK_STAGE_PARSE);} static int run_init(VVCContext *s, VVCLocalContext *lc, VVCTask *t){    VVCFrameContext *fc = lc->fc;    VVCFrameThread *ft  = fc->ft;    const int ret       = ff_vvc_per_frame_init(fc);     if (ret < 0)        return ret;     for (int i = 0; i < fc->nb_slices; i++) {        SliceContext *sc = fc->slices[i];        for (int j = 0; j < sc->nb_eps; j++) {            EntryPoint *ep = sc->eps + j;            for (int k = ep->ctu_start; k < ep->ctu_end; k++) {                const int rs = sc->sh.ctb_addr_in_curr_slice[k];                VVCTask *t   = ft->tasks + rs;                check_colocation(s, t);            }            submit_entry_point(s, ft, sc, ep);        }    }    return 0;} static void report_frame_progress(VVCFrameContext *fc,   const int ry, const VVCProgress idx){    VVCFrameThread *ft = fc->ft;    const int ctu_size = ft->ctu_size;    int old;     if (atomic_fetch_add(&ft->rows[ry].col_progress[idx], 1) == ft->ctu_width - 1) {        int y;        ff_mutex_lock(&ft->lock);        y = old = ft->row_progress[idx];        while (y < ft->ctu_height && atomic_load(&ft->rows[y].col_progress[idx]) == ft->ctu_width)            y++;        if (old != y)            ft->row_progress[idx] = y;        // ff_vvc_report_progress will acquire other frames' locks, which could lead to a deadlock        // We need to unlock ft->lock first        ff_mutex_unlock(&ft->lock);         if (old != y) {            const int progress = y == ft->ctu_height ? INT_MAX : y * ctu_size;            ff_vvc_report_progress(fc->ref, idx, progress);        }    }} static int run_parse(VVCContext *s, VVCLocalContext *lc, VVCTask *t){    int ret;    VVCFrameContext *fc = lc->fc;    const int rs        = t->rs;    const CTU *ctu      = fc->tab.ctus + rs;     lc->ep = t->ep;     ret = ff_vvc_coding_tree_unit(lc, t->ctu_idx, rs, t->rx, t->ry);    if (ret < 0)        return ret;     if (!ctu->has_dmvr)        report_frame_progress(lc->fc, t->ry, VVC_PROGRESS_MV);     return 0;} static int run_deblock_bs(VVCContext *s, VVCLocalContext *lc, VVCTask *t){    if (!lc->sc->sh.r->sh_deblocking_filter_disabled_flag)        ff_vvc_deblock_bs(lc, t->rx, t->ry, t->rs);     return 0;} static int run_inter(VVCContext *s, VVCLocalContext *lc, VVCTask *t)

Lines 1–500 of 846.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t