mirror of
https://github.com/ultravideo/uvg266.git
synced 2024-11-24 18:34:06 +00:00
700 lines
25 KiB
C
700 lines
25 KiB
C
/*****************************************************************************
|
|
* This file is part of Kvazaar HEVC encoder.
|
|
*
|
|
* Copyright (C) 2013-2015 Tampere University of Technology and others (see
|
|
* COPYING file).
|
|
*
|
|
* Kvazaar 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.
|
|
*
|
|
* Kvazaar 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 General Public License along
|
|
* with Kvazaar. If not, see <http://www.gnu.org/licenses/>.
|
|
****************************************************************************/
|
|
|
|
#include "intra.h"
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include "image.h"
|
|
#include "kvz_math.h"
|
|
#include "strategies/strategies-intra.h"
|
|
#include "tables.h"
|
|
#include "transform.h"
|
|
#include "videoframe.h"
|
|
|
|
// Tables for looking up the number of intra reference pixels based on
|
|
// prediction units coordinate within an LCU.
|
|
// generated by "tools/generate_ref_pixel_tables.py".
|
|
static const uint8_t num_ref_pixels_top[16][16] = {
|
|
{ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 },
|
|
{ 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4 },
|
|
{ 16, 12, 8, 4, 16, 12, 8, 4, 16, 12, 8, 4, 16, 12, 8, 4 },
|
|
{ 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4 },
|
|
{ 32, 28, 24, 20, 16, 12, 8, 4, 32, 28, 24, 20, 16, 12, 8, 4 },
|
|
{ 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4 },
|
|
{ 16, 12, 8, 4, 16, 12, 8, 4, 16, 12, 8, 4, 16, 12, 8, 4 },
|
|
{ 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4 },
|
|
{ 64, 60, 56, 52, 48, 44, 40, 36, 32, 28, 24, 20, 16, 12, 8, 4 },
|
|
{ 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4 },
|
|
{ 16, 12, 8, 4, 16, 12, 8, 4, 16, 12, 8, 4, 16, 12, 8, 4 },
|
|
{ 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4 },
|
|
{ 32, 28, 24, 20, 16, 12, 8, 4, 32, 28, 24, 20, 16, 12, 8, 4 },
|
|
{ 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4 },
|
|
{ 16, 12, 8, 4, 16, 12, 8, 4, 16, 12, 8, 4, 16, 12, 8, 4 },
|
|
{ 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4 }
|
|
};
|
|
static const uint8_t num_ref_pixels_left[16][16] = {
|
|
{ 64, 4, 8, 4, 16, 4, 8, 4, 32, 4, 8, 4, 16, 4, 8, 4 },
|
|
{ 60, 4, 4, 4, 12, 4, 4, 4, 28, 4, 4, 4, 12, 4, 4, 4 },
|
|
{ 56, 4, 8, 4, 8, 4, 8, 4, 24, 4, 8, 4, 8, 4, 8, 4 },
|
|
{ 52, 4, 4, 4, 4, 4, 4, 4, 20, 4, 4, 4, 4, 4, 4, 4 },
|
|
{ 48, 4, 8, 4, 16, 4, 8, 4, 16, 4, 8, 4, 16, 4, 8, 4 },
|
|
{ 44, 4, 4, 4, 12, 4, 4, 4, 12, 4, 4, 4, 12, 4, 4, 4 },
|
|
{ 40, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4 },
|
|
{ 36, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 },
|
|
{ 32, 4, 8, 4, 16, 4, 8, 4, 32, 4, 8, 4, 16, 4, 8, 4 },
|
|
{ 28, 4, 4, 4, 12, 4, 4, 4, 28, 4, 4, 4, 12, 4, 4, 4 },
|
|
{ 24, 4, 8, 4, 8, 4, 8, 4, 24, 4, 8, 4, 8, 4, 8, 4 },
|
|
{ 20, 4, 4, 4, 4, 4, 4, 4, 20, 4, 4, 4, 4, 4, 4, 4 },
|
|
{ 16, 4, 8, 4, 16, 4, 8, 4, 16, 4, 8, 4, 16, 4, 8, 4 },
|
|
{ 12, 4, 4, 4, 12, 4, 4, 4, 12, 4, 4, 4, 12, 4, 4, 4 },
|
|
{ 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4, 8, 4 },
|
|
{ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 }
|
|
};
|
|
|
|
int8_t kvz_intra_get_dir_luma_predictor(
|
|
const uint32_t x,
|
|
const uint32_t y,
|
|
int8_t *preds,
|
|
const cu_info_t *const cur_pu,
|
|
const cu_info_t *const left_pu,
|
|
const cu_info_t *const above_pu)
|
|
{
|
|
enum {
|
|
PLANAR_IDX = 0,
|
|
DC_IDX = 1,
|
|
HOR_IDX = 18,
|
|
VER_IDX = 50,
|
|
};
|
|
|
|
int8_t number_of_candidates = 0;
|
|
|
|
// The default mode if block is not coded yet is INTRA_PLANAR.
|
|
int8_t left_intra_dir = 0;
|
|
if (left_pu && left_pu->type == CU_INTRA) {
|
|
left_intra_dir = left_pu->intra.mode;
|
|
}
|
|
|
|
int8_t above_intra_dir = 0;
|
|
if (above_pu && above_pu->type == CU_INTRA && y % LCU_WIDTH != 0) {
|
|
above_intra_dir = above_pu->intra.mode;
|
|
}
|
|
|
|
const int offset = 61;
|
|
const int mod = 64;
|
|
|
|
preds[0] = PLANAR_IDX;
|
|
preds[1] = DC_IDX;
|
|
preds[2] = VER_IDX;
|
|
preds[3] = HOR_IDX;
|
|
preds[4] = VER_IDX - 4;
|
|
preds[5] = VER_IDX + 4;
|
|
|
|
// If the predictions are the same, add new predictions
|
|
if (left_intra_dir == above_intra_dir) {
|
|
number_of_candidates = 1;
|
|
if (left_intra_dir > DC_IDX) { // angular modes
|
|
preds[0] = PLANAR_IDX;
|
|
preds[1] = left_intra_dir;
|
|
preds[2] = ((left_intra_dir + offset) % mod) + 2;
|
|
preds[3] = ((left_intra_dir - 1) % mod) + 2;
|
|
preds[4] = DC_IDX;
|
|
preds[5] = ((left_intra_dir + offset - 1) % mod) + 2;
|
|
}
|
|
} else { // If we have two distinct predictions
|
|
//preds[0] = left_intra_dir;
|
|
//preds[1] = above_intra_dir;
|
|
number_of_candidates = 2;
|
|
uint8_t max_cand_mode_idx = preds[0] > preds[1] ? 0 : 1;
|
|
|
|
if (left_intra_dir > DC_IDX && above_intra_dir > DC_IDX) {
|
|
preds[0] = PLANAR_IDX;
|
|
preds[1] = left_intra_dir;
|
|
preds[2] = above_intra_dir;
|
|
preds[3] = DC_IDX;
|
|
max_cand_mode_idx = preds[1] > preds[2] ? 1 : 2;
|
|
uint8_t min_cand_mode_idx = preds[1] > preds[2] ? 2 : 1;
|
|
|
|
if ((preds[max_cand_mode_idx] - preds[min_cand_mode_idx] < 63) && (preds[max_cand_mode_idx] - preds[min_cand_mode_idx] > 1)) {
|
|
preds[4] = ((preds[max_cand_mode_idx] + offset) % mod) + 2;
|
|
preds[5] = ((preds[max_cand_mode_idx] - 1) % mod) + 2;
|
|
} else {
|
|
preds[4] = ((preds[max_cand_mode_idx] + offset - 1) % mod) + 2;
|
|
preds[5] = ((preds[max_cand_mode_idx]) % mod) + 2;
|
|
}
|
|
} else if(left_intra_dir + above_intra_dir >= 2){ // Add DC mode if it's not present, otherwise VER_IDX.
|
|
preds[0] = PLANAR_IDX;
|
|
preds[1] = (left_intra_dir < above_intra_dir) ? above_intra_dir : left_intra_dir;
|
|
preds[2] = DC_IDX;
|
|
max_cand_mode_idx = 1;
|
|
|
|
preds[3] = ((preds[max_cand_mode_idx] + offset) % mod) + 2;
|
|
preds[4] = ((preds[max_cand_mode_idx] - 1) % mod) + 2;
|
|
preds[5] = ((preds[max_cand_mode_idx] + offset - 1) % mod) + 2;
|
|
}
|
|
}
|
|
|
|
return number_of_candidates;
|
|
}
|
|
|
|
static void intra_filter_reference(
|
|
int_fast8_t log2_width,
|
|
kvz_intra_references *refs)
|
|
{
|
|
if (refs->filtered_initialized) {
|
|
return;
|
|
} else {
|
|
refs->filtered_initialized = true;
|
|
}
|
|
|
|
const int_fast8_t ref_width = 2 * (1 << log2_width) + 1;
|
|
kvz_intra_ref *ref = &refs->ref;
|
|
kvz_intra_ref *filtered_ref = &refs->filtered_ref;
|
|
|
|
// Starting point at top left for both iterations
|
|
filtered_ref->left[0] = (ref->left[1] + 2 * ref->left[0] + ref->top[1] + 2) >> 2;
|
|
filtered_ref->top[0] = filtered_ref->left[0];
|
|
|
|
// TODO: use block height here instead of ref_width
|
|
// Top to bottom
|
|
for (int_fast8_t y = 1; y < ref_width - 1; ++y) {
|
|
kvz_pixel *p = &ref->left[y];
|
|
filtered_ref->left[y] = (p[-1] + 2 * p[0] + p[1] + 2) >> 2;
|
|
}
|
|
// Bottom left (not filtered)
|
|
filtered_ref->left[ref_width - 1] = ref->left[ref_width - 1];
|
|
|
|
// Left to right
|
|
for (int_fast8_t x = 1; x < ref_width - 1; ++x) {
|
|
kvz_pixel *p = &ref->top[x];
|
|
filtered_ref->top[x] = (p[-1] + 2 * p[0] + p[1] + 2) >> 2;
|
|
}
|
|
// Top right (not filtered)
|
|
filtered_ref->top[ref_width - 1] = ref->top[ref_width - 1];
|
|
}
|
|
|
|
|
|
/**
|
|
* \brief Generage planar prediction.
|
|
* \param log2_width Log2 of width, range 2..5.
|
|
* \param in_ref_above Pointer to -1 index of above reference, length=width*2+1.
|
|
* \param in_ref_left Pointer to -1 index of left reference, length=width*2+1.
|
|
* \param dst Buffer of size width*width.
|
|
*/
|
|
static void intra_pred_dc(
|
|
const int_fast8_t log2_width,
|
|
const kvz_pixel *const ref_top,
|
|
const kvz_pixel *const ref_left,
|
|
kvz_pixel *const out_block)
|
|
{
|
|
int_fast8_t width = 1 << log2_width;
|
|
|
|
int_fast16_t sum = 0;
|
|
for (int_fast8_t i = 0; i < width; ++i) {
|
|
sum += ref_top[i + 1];
|
|
sum += ref_left[i + 1];
|
|
}
|
|
|
|
// JVET_K0122
|
|
// TODO: take non-square blocks into account
|
|
const int denom = width << 1;
|
|
const int divShift = kvz_math_floor_log2(denom);
|
|
const int divOffset = denom >> 1;
|
|
|
|
const kvz_pixel dc_val = (sum + divOffset) >> divShift;
|
|
//const kvz_pixel dc_val = (sum + width) >> (log2_width + 1);
|
|
const int_fast16_t block_size = 1 << (log2_width * 2);
|
|
|
|
for (int_fast16_t i = 0; i < block_size; ++i) {
|
|
out_block[i] = dc_val;
|
|
}
|
|
}
|
|
|
|
void kvz_intra_predict(
|
|
kvz_intra_references *refs,
|
|
int_fast8_t log2_width,
|
|
int_fast8_t mode,
|
|
color_t color,
|
|
kvz_pixel *dst,
|
|
bool filter_boundary)
|
|
{
|
|
const int_fast8_t width = 1 << log2_width;
|
|
|
|
const kvz_intra_ref *used_ref = &refs->ref;
|
|
if (color != COLOR_Y || mode == 1 || width == 4) {
|
|
// For chroma, DC and 4x4 blocks, always use unfiltered reference.
|
|
} else if (mode == 0) {
|
|
// Otherwise, use filtered for planar.
|
|
used_ref = &refs->filtered_ref;
|
|
} else {
|
|
// Angular modes use smoothed reference pixels, unless the mode is close
|
|
// to being either vertical or horizontal.
|
|
static const int kvz_intra_hor_ver_dist_thres[10] = {20, 20, 20, 14, 2, 0, 20, 0, 0, 0 };
|
|
int filter_threshold = kvz_intra_hor_ver_dist_thres[kvz_math_floor_log2(width)];
|
|
int dist_from_vert_or_hor = MIN(abs(mode - 50), abs(mode - 18));
|
|
if (dist_from_vert_or_hor > filter_threshold) {
|
|
used_ref = &refs->filtered_ref;
|
|
}
|
|
}
|
|
|
|
if (used_ref == &refs->filtered_ref && !refs->filtered_initialized) {
|
|
intra_filter_reference(log2_width, refs);
|
|
}
|
|
|
|
if (mode == 0) {
|
|
kvz_intra_pred_planar(log2_width, used_ref->top, used_ref->left, dst);
|
|
} else if (mode == 1) {
|
|
intra_pred_dc(log2_width, used_ref->top, used_ref->left, dst);
|
|
} else {
|
|
kvz_angular_pred(log2_width, mode, color, used_ref->top, used_ref->left, dst);
|
|
}
|
|
|
|
// pdpc
|
|
bool pdpcCondition = (mode == 0 || mode == 1 || mode == 18 || mode == 50);
|
|
if (pdpcCondition)
|
|
{
|
|
// TODO: replace latter log2_width with log2_height
|
|
const int scale = ((log2_width - 2 + log2_width - 2 + 2) >> 2);
|
|
|
|
if (mode == 0) { // planar
|
|
// TODO: replace width with height
|
|
for (int y = 0; y < width; y++) {
|
|
int wT = 32 >> MIN(31, ((y << 1) >> scale));
|
|
for (int x = 0; x < width; x++) {
|
|
int wL = 32 >> MIN(31, ((x << 1) >> scale));
|
|
dst[x + y * width] = CLIP_TO_PIXEL((wL * used_ref->left[y + 1]
|
|
+ wT * used_ref->top[x + 1]
|
|
+ (64 - wL - wT) * dst[x + y * width] + 32) >> 6);
|
|
}
|
|
}
|
|
}
|
|
else if (mode == 1) { // DC
|
|
for (int y = 0; y < width; y++) {
|
|
int wT = 32 >> MIN(31, ((y << 1) >> scale));
|
|
for (int x = 0; x < width; x++) {
|
|
int wL = 32 >> MIN(31, ((x << 1) >> scale));
|
|
int wTL = (wL >> 4) + (wT >> 4);
|
|
dst[x + y * width] = CLIP_TO_PIXEL((wL * used_ref->left[y + 1]
|
|
+ wT * used_ref->top[x + 1]
|
|
- wTL * used_ref->top[0]
|
|
+ (64 - wL - wT + wTL) * dst[x + y * width] + 32) >> 6);
|
|
}
|
|
}
|
|
}
|
|
else if (mode == 18) { // horizontal
|
|
for (int y = 0; y < width; y++) {
|
|
int wT = 32 >> MIN(31, ((y << 1) >> scale));
|
|
for (int x = 0; x < width; x++) {
|
|
int wTL = wT;
|
|
dst[x + y * width] = CLIP_TO_PIXEL((wT * used_ref->top[x + 1]
|
|
- wTL * used_ref->top[0]
|
|
+ (64 - wT + wTL) * dst[x + y * width] + 32) >> 6);
|
|
}
|
|
}
|
|
}
|
|
else if (mode == 50) { // vertical
|
|
for (int y = 0; y < width; y++) {
|
|
for (int x = 0; x < width; x++) {
|
|
int wL = 32 >> MIN(31, ((x << 1) >> scale));
|
|
int wTL = wL;
|
|
dst[x + y * width] = CLIP_TO_PIXEL((wL * used_ref->left[y + 1]
|
|
- wTL * used_ref->top[0]
|
|
+ (64 - wL + wTL) * dst[x + y * width] + 32) >> 6);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void kvz_intra_build_reference_any(
|
|
const int_fast8_t log2_width,
|
|
const color_t color,
|
|
const vector2d_t *const luma_px,
|
|
const vector2d_t *const pic_px,
|
|
const lcu_t *const lcu,
|
|
kvz_intra_references *const refs)
|
|
{
|
|
assert(log2_width >= 2 && log2_width <= 5);
|
|
|
|
refs->filtered_initialized = false;
|
|
kvz_pixel *out_left_ref = &refs->ref.left[0];
|
|
kvz_pixel *out_top_ref = &refs->ref.top[0];
|
|
|
|
const kvz_pixel dc_val = 1 << (KVZ_BIT_DEPTH - 1); //TODO: add used bitdepth as a variable
|
|
const int is_chroma = color != COLOR_Y ? 1 : 0;
|
|
const int_fast8_t width = 1 << log2_width;
|
|
|
|
// Convert luma coordinates to chroma coordinates for chroma.
|
|
const vector2d_t lcu_px = {
|
|
luma_px->x % LCU_WIDTH,
|
|
luma_px->y % LCU_WIDTH
|
|
};
|
|
const vector2d_t px = {
|
|
lcu_px.x >> is_chroma,
|
|
lcu_px.y >> is_chroma,
|
|
};
|
|
|
|
// Init pointers to LCUs reconstruction buffers, such that index 0 refers to block coordinate 0.
|
|
const kvz_pixel *left_ref = !color ? &lcu->left_ref.y[1] : (color == 1) ? &lcu->left_ref.u[1] : &lcu->left_ref.v[1];
|
|
const kvz_pixel *top_ref = !color ? &lcu->top_ref.y[1] : (color == 1) ? &lcu->top_ref.u[1] : &lcu->top_ref.v[1];
|
|
const kvz_pixel *rec_ref = !color ? lcu->rec.y : (color == 1) ? lcu->rec.u : lcu->rec.v;
|
|
|
|
// Init top borders pointer to point to the correct place in the correct reference array.
|
|
const kvz_pixel *top_border;
|
|
if (px.y) {
|
|
top_border = &rec_ref[px.x + (px.y - 1) * (LCU_WIDTH >> is_chroma)];
|
|
} else {
|
|
top_border = &top_ref[px.x];
|
|
}
|
|
|
|
// Init left borders pointer to point to the correct place in the correct reference array.
|
|
const kvz_pixel *left_border;
|
|
int left_stride; // Distance between reference samples.
|
|
if (px.x) {
|
|
left_border = &rec_ref[px.x - 1 + px.y * (LCU_WIDTH >> is_chroma)];
|
|
left_stride = LCU_WIDTH >> is_chroma;
|
|
} else {
|
|
left_border = &left_ref[px.y];
|
|
left_stride = 1;
|
|
}
|
|
|
|
// Generate left reference.
|
|
if (luma_px->x > 0) {
|
|
// Get the number of reference pixels based on the PU coordinate within the LCU.
|
|
int px_available_left = num_ref_pixels_left[lcu_px.y / 4][lcu_px.x / 4] >> is_chroma;
|
|
|
|
// Limit the number of available pixels based on block size and dimensions
|
|
// of the picture.
|
|
px_available_left = MIN(px_available_left, width * 2);
|
|
px_available_left = MIN(px_available_left, (pic_px->y - luma_px->y) >> is_chroma);
|
|
|
|
// Copy pixels from coded CUs.
|
|
for (int i = 0; i < px_available_left; ++i) {
|
|
out_left_ref[i + 1] = left_border[i * left_stride];
|
|
}
|
|
// Extend the last pixel for the rest of the reference values.
|
|
kvz_pixel nearest_pixel = out_left_ref[px_available_left];
|
|
for (int i = px_available_left; i < width * 2; ++i) {
|
|
out_left_ref[i + 1] = nearest_pixel;
|
|
}
|
|
} else {
|
|
// If we are on the left edge, extend the first pixel of the top row.
|
|
kvz_pixel nearest_pixel = luma_px->y > 0 ? top_border[0] : dc_val;
|
|
for (int i = 0; i < width * 2; i++) {
|
|
out_left_ref[i + 1] = nearest_pixel;
|
|
}
|
|
}
|
|
|
|
// Generate top-left reference.
|
|
if (luma_px->x > 0 && luma_px->y > 0) {
|
|
// If the block is at an LCU border, the top-left must be copied from
|
|
// the border that points to the LCUs 1D reference buffer.
|
|
if (px.x == 0) {
|
|
out_left_ref[0] = left_border[-1 * left_stride];
|
|
out_top_ref[0] = left_border[-1 * left_stride];
|
|
} else {
|
|
out_left_ref[0] = top_border[-1];
|
|
out_top_ref[0] = top_border[-1];
|
|
}
|
|
} else {
|
|
// Copy reference clockwise.
|
|
out_left_ref[0] = out_left_ref[1];
|
|
out_top_ref[0] = out_left_ref[1];
|
|
}
|
|
|
|
// Generate top reference.
|
|
if (luma_px->y > 0) {
|
|
// Get the number of reference pixels based on the PU coordinate within the LCU.
|
|
int px_available_top = num_ref_pixels_top[lcu_px.y / 4][lcu_px.x / 4] >> is_chroma;
|
|
|
|
// Limit the number of available pixels based on block size and dimensions
|
|
// of the picture.
|
|
px_available_top = MIN(px_available_top, width * 2);
|
|
px_available_top = MIN(px_available_top, (pic_px->x - luma_px->x) >> is_chroma);
|
|
|
|
// Copy all the pixels we can.
|
|
for (int i = 0; i < px_available_top; ++i) {
|
|
out_top_ref[i + 1] = top_border[i];
|
|
}
|
|
// Extend the last pixel for the rest of the reference values.
|
|
kvz_pixel nearest_pixel = top_border[px_available_top - 1];
|
|
for (int i = px_available_top; i < width * 2; ++i) {
|
|
out_top_ref[i + 1] = nearest_pixel;
|
|
}
|
|
} else {
|
|
// Extend nearest pixel.
|
|
kvz_pixel nearest_pixel = luma_px->x > 0 ? left_border[0] : dc_val;
|
|
for (int i = 0; i < width * 2; i++) {
|
|
out_top_ref[i + 1] = nearest_pixel;
|
|
}
|
|
}
|
|
}
|
|
|
|
void kvz_intra_build_reference_inner(
|
|
const int_fast8_t log2_width,
|
|
const color_t color,
|
|
const vector2d_t *const luma_px,
|
|
const vector2d_t *const pic_px,
|
|
const lcu_t *const lcu,
|
|
kvz_intra_references *const refs)
|
|
{
|
|
assert(log2_width >= 2 && log2_width <= 5);
|
|
|
|
refs->filtered_initialized = false;
|
|
kvz_pixel * __restrict out_left_ref = &refs->ref.left[0];
|
|
kvz_pixel * __restrict out_top_ref = &refs->ref.top[0];
|
|
|
|
const int is_chroma = color != COLOR_Y ? 1 : 0;
|
|
const int_fast8_t width = 1 << log2_width;
|
|
|
|
// Convert luma coordinates to chroma coordinates for chroma.
|
|
const vector2d_t lcu_px = {
|
|
luma_px->x % LCU_WIDTH,
|
|
luma_px->y % LCU_WIDTH
|
|
};
|
|
const vector2d_t px = {
|
|
lcu_px.x >> is_chroma,
|
|
lcu_px.y >> is_chroma,
|
|
};
|
|
|
|
// Init pointers to LCUs reconstruction buffers, such that index 0 refers to block coordinate 0.
|
|
const kvz_pixel * __restrict left_ref = !color ? &lcu->left_ref.y[1] : (color == 1) ? &lcu->left_ref.u[1] : &lcu->left_ref.v[1];
|
|
const kvz_pixel * __restrict top_ref = !color ? &lcu->top_ref.y[1] : (color == 1) ? &lcu->top_ref.u[1] : &lcu->top_ref.v[1];
|
|
const kvz_pixel * __restrict rec_ref = !color ? lcu->rec.y : (color == 1) ? lcu->rec.u : lcu->rec.v;
|
|
|
|
// Init top borders pointer to point to the correct place in the correct reference array.
|
|
const kvz_pixel * __restrict top_border;
|
|
if (px.y) {
|
|
top_border = &rec_ref[px.x + (px.y - 1) * (LCU_WIDTH >> is_chroma)];
|
|
} else {
|
|
top_border = &top_ref[px.x];
|
|
|
|
}
|
|
|
|
// Init left borders pointer to point to the correct place in the correct reference array.
|
|
const kvz_pixel * __restrict left_border;
|
|
int left_stride; // Distance between reference samples.
|
|
|
|
// Generate top-left reference.
|
|
// If the block is at an LCU border, the top-left must be copied from
|
|
// the border that points to the LCUs 1D reference buffer.
|
|
if (px.x) {
|
|
left_border = &rec_ref[px.x - 1 + px.y * (LCU_WIDTH >> is_chroma)];
|
|
left_stride = LCU_WIDTH >> is_chroma;
|
|
out_left_ref[0] = top_border[-1];
|
|
out_top_ref[0] = top_border[-1];
|
|
} else {
|
|
left_border = &left_ref[px.y];
|
|
left_stride = 1;
|
|
out_left_ref[0] = left_border[-1 * left_stride];
|
|
out_top_ref[0] = left_border[-1 * left_stride];
|
|
}
|
|
|
|
// Generate left reference.
|
|
|
|
// Get the number of reference pixels based on the PU coordinate within the LCU.
|
|
int px_available_left = num_ref_pixels_left[lcu_px.y / 4][lcu_px.x / 4] >> is_chroma;
|
|
|
|
// Limit the number of available pixels based on block size and dimensions
|
|
// of the picture.
|
|
px_available_left = MIN(px_available_left, width * 2);
|
|
px_available_left = MIN(px_available_left, (pic_px->y - luma_px->y) >> is_chroma);
|
|
|
|
// Copy pixels from coded CUs.
|
|
int i = 0;
|
|
do {
|
|
out_left_ref[i + 1] = left_border[(i + 0) * left_stride];
|
|
out_left_ref[i + 2] = left_border[(i + 1) * left_stride];
|
|
out_left_ref[i + 3] = left_border[(i + 2) * left_stride];
|
|
out_left_ref[i + 4] = left_border[(i + 3) * left_stride];
|
|
i += 4;
|
|
} while (i < px_available_left);
|
|
|
|
// Extend the last pixel for the rest of the reference values.
|
|
kvz_pixel nearest_pixel = out_left_ref[i];
|
|
for (; i < width * 2; i += 4) {
|
|
out_left_ref[i + 1] = nearest_pixel;
|
|
out_left_ref[i + 2] = nearest_pixel;
|
|
out_left_ref[i + 3] = nearest_pixel;
|
|
out_left_ref[i + 4] = nearest_pixel;
|
|
}
|
|
|
|
// Generate top reference.
|
|
|
|
// Get the number of reference pixels based on the PU coordinate within the LCU.
|
|
int px_available_top = num_ref_pixels_top[lcu_px.y / 4][lcu_px.x / 4] >> is_chroma;
|
|
|
|
// Limit the number of available pixels based on block size and dimensions
|
|
// of the picture.
|
|
px_available_top = MIN(px_available_top, width * 2);
|
|
px_available_top = MIN(px_available_top, (pic_px->x - luma_px->x) >> is_chroma);
|
|
|
|
// Copy all the pixels we can.
|
|
i = 0;
|
|
do {
|
|
memcpy(out_top_ref + i + 1, top_border + i, 4 * sizeof(kvz_pixel));
|
|
i += 4;
|
|
} while (i < px_available_top);
|
|
|
|
// Extend the last pixel for the rest of the reference values.
|
|
nearest_pixel = out_top_ref[i];
|
|
for (; i < width * 2; i += 4) {
|
|
out_top_ref[i + 1] = nearest_pixel;
|
|
out_top_ref[i + 2] = nearest_pixel;
|
|
out_top_ref[i + 3] = nearest_pixel;
|
|
out_top_ref[i + 4] = nearest_pixel;
|
|
}
|
|
}
|
|
|
|
void kvz_intra_build_reference(
|
|
const int_fast8_t log2_width,
|
|
const color_t color,
|
|
const vector2d_t *const luma_px,
|
|
const vector2d_t *const pic_px,
|
|
const lcu_t *const lcu,
|
|
kvz_intra_references *const refs)
|
|
{
|
|
// Much logic can be discarded if not on the edge
|
|
if (luma_px->x > 0 && luma_px->y > 0) {
|
|
kvz_intra_build_reference_inner(log2_width, color, luma_px, pic_px, lcu, refs);
|
|
} else {
|
|
kvz_intra_build_reference_any(log2_width, color, luma_px, pic_px, lcu, refs);
|
|
}
|
|
}
|
|
|
|
static void intra_recon_tb_leaf(
|
|
encoder_state_t *const state,
|
|
int x,
|
|
int y,
|
|
int depth,
|
|
int8_t intra_mode,
|
|
lcu_t *lcu,
|
|
color_t color)
|
|
{
|
|
const kvz_config *cfg = &state->encoder_control->cfg;
|
|
const int shift = color == COLOR_Y ? 0 : 1;
|
|
|
|
int log2width = LOG2_LCU_WIDTH - depth;
|
|
if (color != COLOR_Y && depth < MAX_PU_DEPTH) {
|
|
// Chroma width is half of luma width, when not at maximum depth.
|
|
log2width -= 1;
|
|
}
|
|
const int width = 1 << log2width;
|
|
const int lcu_width = LCU_WIDTH >> shift;
|
|
|
|
const vector2d_t luma_px = { x, y };
|
|
const vector2d_t pic_px = {
|
|
state->tile->frame->width,
|
|
state->tile->frame->height,
|
|
};
|
|
const vector2d_t lcu_px = { SUB_SCU(x) >> shift, SUB_SCU(y) >> shift};
|
|
|
|
kvz_intra_references refs;
|
|
kvz_intra_build_reference(log2width, color, &luma_px, &pic_px, lcu, &refs);
|
|
|
|
kvz_pixel pred[32 * 32];
|
|
const bool filter_boundary = color == COLOR_Y && !(cfg->lossless && cfg->implicit_rdpcm);
|
|
kvz_intra_predict(&refs, log2width, intra_mode, color, pred, filter_boundary);
|
|
|
|
const int index = lcu_px.x + lcu_px.y * lcu_width;
|
|
kvz_pixel *block = NULL;
|
|
switch (color) {
|
|
case COLOR_Y:
|
|
block = &lcu->rec.y[index];
|
|
break;
|
|
case COLOR_U:
|
|
block = &lcu->rec.u[index];
|
|
break;
|
|
case COLOR_V:
|
|
block = &lcu->rec.v[index];
|
|
break;
|
|
}
|
|
kvz_pixels_blit(pred, block , width, width, width, lcu_width);
|
|
}
|
|
|
|
/**
|
|
* \brief Reconstruct an intra CU
|
|
*
|
|
* \param state encoder state
|
|
* \param x x-coordinate of the CU in luma pixels
|
|
* \param y y-coordinate of the CU in luma pixels
|
|
* \param depth depth in the CU tree
|
|
* \param mode_luma intra mode for luma, or -1 to skip luma recon
|
|
* \param mode_chroma intra mode for chroma, or -1 to skip chroma recon
|
|
* \param cur_cu pointer to the CU, or NULL to fetch CU from LCU
|
|
* \param lcu containing LCU
|
|
*/
|
|
void kvz_intra_recon_cu(
|
|
encoder_state_t *const state,
|
|
int x,
|
|
int y,
|
|
int depth,
|
|
int8_t mode_luma,
|
|
int8_t mode_chroma,
|
|
cu_info_t *cur_cu,
|
|
lcu_t *lcu)
|
|
{
|
|
const vector2d_t lcu_px = { SUB_SCU(x), SUB_SCU(y) };
|
|
const int8_t width = LCU_WIDTH >> depth;
|
|
if (cur_cu == NULL) {
|
|
cur_cu = LCU_GET_CU_AT_PX(lcu, lcu_px.x, lcu_px.y);
|
|
}
|
|
|
|
if (depth == 0 || cur_cu->tr_depth > depth) {
|
|
const int offset = width / 2;
|
|
const int32_t x2 = x + offset;
|
|
const int32_t y2 = y + offset;
|
|
|
|
kvz_intra_recon_cu(state, x, y, depth + 1, mode_luma, mode_chroma, NULL, lcu);
|
|
kvz_intra_recon_cu(state, x2, y, depth + 1, mode_luma, mode_chroma, NULL, lcu);
|
|
kvz_intra_recon_cu(state, x, y2, depth + 1, mode_luma, mode_chroma, NULL, lcu);
|
|
kvz_intra_recon_cu(state, x2, y2, depth + 1, mode_luma, mode_chroma, NULL, lcu);
|
|
|
|
// Propagate coded block flags from child CUs to parent CU.
|
|
uint16_t child_cbfs[3] = {
|
|
LCU_GET_CU_AT_PX(lcu, lcu_px.x + offset, lcu_px.y )->cbf,
|
|
LCU_GET_CU_AT_PX(lcu, lcu_px.x, lcu_px.y + offset)->cbf,
|
|
LCU_GET_CU_AT_PX(lcu, lcu_px.x + offset, lcu_px.y + offset)->cbf,
|
|
};
|
|
|
|
if (mode_luma != -1 && depth < MAX_DEPTH) {
|
|
cbf_set_conditionally(&cur_cu->cbf, child_cbfs, depth, COLOR_Y);
|
|
}
|
|
if (mode_chroma != -1 && depth <= MAX_DEPTH) {
|
|
cbf_set_conditionally(&cur_cu->cbf, child_cbfs, depth, COLOR_U);
|
|
cbf_set_conditionally(&cur_cu->cbf, child_cbfs, depth, COLOR_V);
|
|
}
|
|
} else {
|
|
const bool has_luma = mode_luma != -1;
|
|
const bool has_chroma = mode_chroma != -1 && x % 8 == 0 && y % 8 == 0;
|
|
// Process a leaf TU.
|
|
if (has_luma) {
|
|
intra_recon_tb_leaf(state, x, y, depth, mode_luma, lcu, COLOR_Y);
|
|
}
|
|
if (has_chroma) {
|
|
intra_recon_tb_leaf(state, x, y, depth, mode_chroma, lcu, COLOR_U);
|
|
intra_recon_tb_leaf(state, x, y, depth, mode_chroma, lcu, COLOR_V);
|
|
}
|
|
|
|
kvz_quantize_lcu_residual(state, has_luma, has_chroma, x, y, depth, cur_cu, lcu);
|
|
}
|
|
}
|