blob: eca0e559010721b527c7507ea8e5b810e65cdc3d [file] [log] [blame]
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// -----------------------------------------------------------------------------
//
// Deblocking filter.
//
// Author: Yannis Guyon (yguyon@google.com)
#include "src/dec/filters/deblocking_filter.h"
#include "src/common/global_params.h"
#include "src/common/vdebug.h"
#include "src/dec/wp2_dec_i.h"
#include "src/dsp/dsp.h"
#include "src/dsp/math.h"
#include "src/utils/utils.h"
#include "src/wp2/format_constants.h"
namespace WP2 {
//------------------------------------------------------------------------------
uint32_t DeblockingFilter::GetStrengthFromMagnitude(uint32_t filter_magnitude) {
// Full filtering at low qualities, no filtering at highest quality.
return std::min(
DivRound(filter_magnitude * kDblkMaxStrength * 2, kMaxYuvFilterMagnitude),
kDblkMaxStrength);
}
uint32_t DeblockingFilter::GetStrengthFromBpp(uint32_t num_bits_per_pixel) {
assert(num_bits_per_pixel <= 255);
// Full filtering at 0bpp, no filtering at 2bpp (=255).
return std::min((255 - num_bits_per_pixel) * kDblkMaxStrength / 255,
kDblkMaxStrength);
}
uint32_t DeblockingFilter::GetSharpnessFromMagnitude(
uint32_t filter_magnitude) {
return DivRound(
(kMaxYuvFilterMagnitude - filter_magnitude) * kDblkMaxSharpness,
kMaxYuvFilterMagnitude);
}
uint32_t DeblockingFilter::GetSharpnessFromResidualDensity(uint32_t res_den) {
assert(res_den <= 255);
// Heavy filtering at 10% (=25) of non-zero coeffs, slight filtering above 30%
// (=75).
return std::min(SafeSub(res_den, 25u) * kDblkMaxSharpness / (75 - 25),
kDblkMaxSharpness);
}
//------------------------------------------------------------------------------
bool IsDeblockingFilterEnabled(const DecoderConfig& config,
const GlobalParams& gparams) {
return (config.enable_deblocking_filter &&
DeblockingFilter::GetStrengthFromMagnitude(
gparams.yuv_filter_magnitude_) > 0) ||
VDMatch(config, "deblocking-filter");
}
//------------------------------------------------------------------------------
DeblockingFilter::DeblockingFilter(const DecoderConfig& config,
const GlobalParams& gparams,
const FilterBlockMap& blocks)
: config_(config),
gparams_(gparams),
blocks_(blocks),
enabled_(IsDeblockingFilterEnabled(config, gparams)) {
assert(blocks_.bit_depth_.num_bits >= 8u); // To match AV1 algorithms.
}
WP2Status DeblockingFilter::Allocate() {
if (!enabled_) return WP2_STATUS_OK;
// 'x_to_next_up_block_y_' is filled with zeros (by ctor).
WP2_CHECK_ALLOC_OK(x_to_next_up_block_y_.resize(blocks_.max_num_blocks_x_));
// "Block position should fit in x_to_next_up_block_y_
static_assert(kMaxTileSize / kMinBlockSizePix <
(1u << (sizeof(x_to_next_up_block_y_[0]) * 8)),
"Incorrect kMaxTileSize value");
// No vertical deblocking will happen before at least 1 min block and a half.
min_num_rows_to_vdblk_ = std::min(kMinBlockSizePix + kMinBlockSizePix / 2,
blocks_.tile_rect_.height);
DblkFilterInit();
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
uint32_t DeblockingFilter::Deblock(uint32_t num_rows) {
if (!enabled_) return num_rows;
assert(num_rows <= blocks_.tile_rect_.height);
assert(num_horizontally_deblocked_rows_ <= num_rows);
assert(num_vertically_deblocked_rows_ <= num_horizontally_deblocked_rows_);
uint32_t num_deblockable_rows = num_rows;
#if !defined(WP2_REDUCE_BINARY_SIZE)
if (VDMatch(config_, "deblocking-filter")) SavePixelsForVDebug(num_rows);
#endif // WP2_REDUCE_BINARY_SIZE
// All vertical edges up to 'num_deblockable_rows' can be horizontally
// deblocked now.
const uint32_t from_y = num_horizontally_deblocked_rows_;
if (from_y >= num_deblockable_rows) return num_vertically_deblocked_rows_;
num_horizontally_deblocked_rows_ =
DeblockHorizontally(from_y, /*to_y=*/num_deblockable_rows - 1u);
// Horizontal edges before row index 'num_horizontally_deblocked_rows_ -
// half_num_filtered_pixels' can be vertically deblocked now; vertical
// deblocking must be applied only on pixels that are already horizontally
// deblocked, for gradient consistency.
num_deblockable_rows = num_horizontally_deblocked_rows_;
if (num_deblockable_rows < min_num_rows_to_vdblk_) {
// It's unsure whether at least one edge per column is ready to be deblocked
// so wait before maybe uselessly browsing 'max_num_blocks_x_'.
return num_vertically_deblocked_rows_;
}
num_vertically_deblocked_rows_ =
DeblockVertically(/*to_y=*/num_deblockable_rows - 1u);
min_num_rows_to_vdblk_ =
std::min(num_vertically_deblocked_rows_ + blocks_.max_registered_height_,
blocks_.tile_rect_.height);
#if !defined(WP2_REDUCE_BINARY_SIZE)
if (VDMatch(config_, "deblocking-filter")) ApplyVDebug(num_rows);
#endif // WP2_REDUCE_BINARY_SIZE
return num_vertically_deblocked_rows_;
}
//------------------------------------------------------------------------------
uint32_t DeblockingFilter::DeblockHorizontally(uint32_t from_y, uint32_t to_y) {
const uint32_t block_step = blocks_.max_num_blocks_x_;
uint32_t y = from_y;
uint32_t block_y = y / kMinBlockSizePix;
const FilterBlockMap::BlockFeatures* row_block_features =
&blocks_.features_[block_y * block_step];
while (y <= to_y) {
// At most 'kMinBlockSizePix' rows can be deblocked with the same pattern.
uint32_t num_pixel_rows =
std::min(kMinBlockSizePix - y % kMinBlockSizePix, to_y - y + 1u);
// In 'kMinBlockSizePix' units.
uint32_t left_block_x = 0;
uint32_t left_block_width =
BlockWidth[row_block_features[left_block_x].size];
uint32_t right_block_x = left_block_width;
// All vertical edges between adjacent pairs of blocks belonging to these
// 'num_pixel_rows' are parsed, from the left.
while (right_block_x < blocks_.max_num_blocks_x_) {
// The X coordinate of the left-most pixel from right block.
const uint32_t q0_x = right_block_x * kMinBlockSizePix;
assert(q0_x < blocks_.tile_rect_.width);
DeblockEdge(config_, /*intertile=*/false, blocks_.tile_rect_,
blocks_.pixels_->HasAlpha(), gparams_.yuv_filter_magnitude_,
blocks_.bit_depth_.num_bits, blocks_.yuv_min_,
blocks_.yuv_max_, row_block_features[left_block_x],
row_block_features[right_block_x],
/*edge_rect=*/{q0_x, y, 0, num_pixel_rows}, blocks_.pixels_);
// Advance to next pair of adjacent blocks.
left_block_x = right_block_x;
right_block_x += BlockWidth[row_block_features[right_block_x].size];
}
// Advance to next batch of at most 'kMinBlockSizePix' pixel rows.
y += num_pixel_rows;
++block_y;
row_block_features += block_step;
}
return y;
}
//------------------------------------------------------------------------------
uint32_t DeblockingFilter::DeblockVertically(uint32_t to_y) {
// Includes partial blocks.
const uint32_t to_block_y = to_y / kMinBlockSizePix;
const uint32_t block_step = blocks_.max_num_blocks_x_;
// For each column, wide of kMinBlockSizePix pixels.
for (uint32_t block_x = 0; block_x < blocks_.max_num_blocks_x_; ++block_x) {
const uint32_t x = block_x * kMinBlockSizePix; // In pixel units.
const uint32_t num_pixel_cols =
std::min(kMinBlockSizePix, blocks_.tile_rect_.width - x);
// In 'kMinBlockSizePix' units.
const uint32_t first_block_y = x_to_next_up_block_y_[block_x];
const FilterBlockMap::BlockFeatures* up_block_features =
&blocks_.features_[first_block_y * block_step + block_x];
uint32_t up_block_height = BlockHeight[up_block_features->size];
uint32_t down_block_y = first_block_y + up_block_height;
// Deblock as many edges as available starting from the top.
while (down_block_y <= to_block_y) {
const FilterBlockMap::BlockFeatures* down_block_features =
&blocks_.features_[down_block_y * block_step + block_x];
// In 'kMinBlockSizePix' units.
const uint32_t down_block_height = BlockHeight[down_block_features->size];
// The Y coordinate of the up-most pixel from the block below the edge.
const uint32_t q0_y = down_block_y * kMinBlockSizePix;
// Up block is never cropped.
const uint32_t up_block_half_pixel_height =
up_block_height * (kMinBlockSizePix / 2);
// Down block might be cropped if it's the last one of the column.
const uint32_t down_block_half_pixel_height =
std::min(blocks_.tile_rect_.height - q0_y,
down_block_height * (kMinBlockSizePix / 2));
// Only symmetrical filtering.
const uint32_t min_half =
std::min(up_block_half_pixel_height, down_block_half_pixel_height);
const uint32_t min_num_deblockable_rows = q0_y + min_half;
if (min_num_deblockable_rows > to_y + 1u) {
break; // Not enough horizontally deblocked pixel rows to continue.
}
DeblockEdge(config_, /*intertile=*/false, blocks_.tile_rect_,
blocks_.pixels_->HasAlpha(), gparams_.yuv_filter_magnitude_,
blocks_.bit_depth_.num_bits, blocks_.yuv_min_,
blocks_.yuv_max_, *up_block_features, *down_block_features,
/*edge_rect=*/{x, q0_y, num_pixel_cols, 0}, blocks_.pixels_);
// Up block of next edge is down block of current edge.
x_to_next_up_block_y_[block_x] = (uint16_t)down_block_y;
// Advance to next pair of adjacent blocks.
up_block_features = down_block_features;
up_block_height = down_block_height;
down_block_y += down_block_height;
}
}
// Decoded all lines, all edges should have been deblocked.
if (to_y + 1u == blocks_.tile_rect_.height) return blocks_.tile_rect_.height;
// Return the position of the up-most not yet deblocked edge.
const uint32_t min_edge_y = *std::min_element(x_to_next_up_block_y_.begin(),
x_to_next_up_block_y_.end());
return std::min(min_edge_y * kMinBlockSizePix, blocks_.tile_rect_.height);
}
//------------------------------------------------------------------------------
void DeblockingFilter::DeblockEdge(const DecoderConfig& config, bool intertile,
const Rectangle& tile_rect, bool has_alpha,
uint32_t yuv_filter_magnitude,
uint32_t yuv_num_bits, int32_t yuv_min,
int32_t yuv_max,
const FilterBlockMap::BlockFeatures& p_block,
const FilterBlockMap::BlockFeatures& q_block,
const Rectangle& edge_rect,
YUVPlane* const pixels) {
assert((edge_rect.width == 0) ^ (edge_rect.height == 0));
const bool vertical_edge = (edge_rect.width == 0);
// The strength is determined by the block using the fewest bits per px.
const uint32_t yuv_strength = std::min(
GetStrengthFromMagnitude(yuv_filter_magnitude),
GetStrengthFromBpp(std::min(p_block.min_bpp, q_block.min_bpp)));
// TODO(maryla): this should be computed based on alpha_quality and
// alpha bpp instead!
const uint32_t alpha_strength = yuv_strength;
if ((!has_alpha || alpha_strength == 0) && yuv_strength == 0) {
return; // Early no-op exit.
}
// Left/up block is never cropped.
const uint32_t p_block_half =
(vertical_edge ? BlockWidth[p_block.size] : BlockHeight[p_block.size]) *
(kMinBlockSizePix / 2);
// Right/down block might be cropped if it's the last one of the row/column.
const uint32_t q_block_half =
vertical_edge
? std::min(tile_rect.width - edge_rect.x,
BlockWidth[q_block.size] * (kMinBlockSizePix / 2))
: std::min(tile_rect.height - edge_rect.y,
BlockHeight[q_block.size] * (kMinBlockSizePix / 2));
const uint32_t half = std::min({p_block_half, q_block_half, kDblkMaxHalf});
const uint32_t edge_length =
vertical_edge ? edge_rect.height : edge_rect.width;
assert(half <= kDblkMaxHalf && edge_length <= kMaxBlockSizePix);
const bool has_lossless_alpha =
has_alpha && (!p_block.has_lossy_alpha || !q_block.has_lossy_alpha);
const uint32_t sharpness_from_quality =
GetSharpnessFromMagnitude(yuv_filter_magnitude);
// Store whether each alpha pixel line across the edge was deblocked.
uint8_t a_halfs[kMaxBlockSizePix];
int16_t cache[4 * (2 * kDblkMaxHalf)]; // scratch buffer. TODO(skal): align?
for (Channel c : {kAChannel, kYChannel, kUChannel, kVChannel}) {
if (c == kAChannel && !has_alpha) continue;
const uint32_t strength = (c == kAChannel) ? alpha_strength : yuv_strength;
if (strength == 0) {
// If A is not deblocked, the remaining channels neither.
if (c == kAChannel) break;
continue;
}
const uint32_t num_bits =
(c == kAChannel) ? (kAlphaBits + 1) : yuv_num_bits;
const int32_t min = (c == kAChannel) ? 0 : yuv_min;
const int32_t max = (c == kAChannel) ? (int32_t)kAlphaMax : yuv_max;
const bool do_deblock = (c != kAChannel || !has_lossless_alpha);
// TODO(maryla): also filter lossy blocks next to lossless ones?
uint32_t sharpness = sharpness_from_quality;
if (do_deblock) {
// The sharpness is determined by the block with more details.
const uint32_t p_res_den = p_block.res_den[c];
const uint32_t q_res_den = q_block.res_den[c];
const uint32_t max_res_den = std::max(p_res_den, q_res_den);
sharpness =
std::max(sharpness, GetSharpnessFromResidualDensity(max_res_den));
} else {
// We can't compute sharpness from residual density since we
// don't have it for lossless alpha blocks.
}
const int32_t deblock_threshold =
DeblockThresholdFromSharpness(sharpness, num_bits);
Plane16* const plane = &pixels->GetChannel(c);
int16_t* const src0 = &plane->At(edge_rect.x, edge_rect.y);
uint32_t const src_step0 = plane->Step();
// import transposed edge in a local cache if needed
int16_t* src = src0;
uint32_t src_step = src_step0;
if (!vertical_edge) {
int16_t* const q0 = cache + kDblkMaxHalf;
FilterCopyIn(src, src_step, q0, edge_length, half);
src = q0;
src_step = kDblCacheStep;
}
uint8_t halfs[kMaxBlockSizePix];
MeasureFlatLengths(sharpness, half, src, src_step, halfs, edge_length);
#if !defined(WP2_REDUCE_BINARY_SIZE)
const bool vd_match =
VDMatch(config, intertile ? "intertile-filter" : "deblocking-filter");
if (vd_match) {
for (uint32_t pos = 0; pos < edge_length; ++pos) {
RegisterPixelsForVDebug(config,
tile_rect.x + edge_rect.x + (vertical_edge ? 0 : pos),
tile_rect.y + edge_rect.y + (vertical_edge ? pos : 0),
half, halfs[pos], vertical_edge, c, strength, sharpness,
/*before_deblocking=*/true, do_deblock, num_bits,
src + pos * src_step, /*step=*/1);
}
}
#endif // WP2_REDUCE_BINARY_SIZE
// Process the 'edge_length' rows.
uint8_t max_half = 0;
int16_t* q = src;
for (uint32_t pos = 0; pos < edge_length; ++pos, q += src_step) {
uint8_t h = halfs[pos];
if (!do_deblock) {
// just record if AChannel would be deblocked
a_halfs[pos] = (h > 0) && WouldDeblockLine(deblock_threshold, h, q);
} else {
if (has_alpha) {
if (c == kAChannel) {
// record if AChannel is deblocked, for later
a_halfs[pos] = (h > 0) && WouldDeblockLine(deblock_threshold, h, q);
} else {
// if AChannel was not deblocked, don't deblock Y/U/V either
if (a_halfs[pos] == 0) h = 0;
}
}
if (h > 0) { // Process line.
DeblockLine(strength, deblock_threshold, h, min, max, q);
max_half = std::max(max_half, h);
}
}
}
if (do_deblock && max_half > 0) {
// if we modified the pixels locally, bring them back
if (!vertical_edge) {
FilterCopyOut(src, src0, src_step0, edge_length, max_half);
}
}
#if !defined(WP2_REDUCE_BINARY_SIZE)
if (vd_match) {
for (uint32_t pos = 0; pos < edge_length; ++pos) {
RegisterPixelsForVDebug(config,
tile_rect.x + edge_rect.x + (vertical_edge ? 0 : pos),
tile_rect.y + edge_rect.y + (vertical_edge ? pos : 0),
half, halfs[pos], vertical_edge, c, strength, sharpness,
/*before_deblocking=*/false, do_deblock, num_bits,
src + pos * src_step, /*step=*/1);
}
}
#endif // WP2_REDUCE_BINARY_SIZE
}
}
//------------------------------------------------------------------------------
} // namespace WP2