/* * 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. Load more
/* * 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 2 static 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 slice static 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)