blob: 2a7fcd82d4c0f2c075fac0d8e5b5a92ee4904911 [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.
// -----------------------------------------------------------------------------
//
// Block position/size scoring functions.
//
// Author: Yannis Guyon (yguyon@google.com)
#include "src/enc/partitioning/partition_score_func_area.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <limits>
#include <numeric>
#include "src/common/integral.h"
#include "src/dsp/dsp.h"
#include "src/dsp/math.h"
#include "src/enc/analysis.h"
#include "src/enc/partitioning/partition_score_func_multi.h"
#include "src/enc/partitioning/partitioner.h"
#include "src/enc/partitioning/partitioner_multi.h"
#include "src/enc/wp2_enc_i.h"
#include "src/wp2/format_constants.h"
namespace WP2 {
//------------------------------------------------------------------------------
AreaScoreFunc::AreaScoreFunc(uint32_t area_width, uint32_t area_height)
: area_width_(area_width),
area_height_(area_height),
area_front_mgr_(area_width, area_height),
comp_(kMaxTileSize / kMinBlockSizePix, area_width / kMinBlockSizePix,
area_height / kMinBlockSizePix) {}
WP2Status AreaScoreFunc::Init(const EncoderConfig& config,
const Rectangle& tile_rect, const YUVPlane& yuv,
const GlobalParams& gparams,
const ProgressRange& progress) {
const ProgressRange init_progress(progress, 0.1);
const ProgressRange score_func_init_progress(progress, 0.1);
const ProgressRange default_partition_progress(progress, 0.8);
WP2_CHECK_STATUS(
PartitionScoreFunc::Init(config, tile_rect, yuv, gparams, init_progress));
// This scoring function needs a strict block layout.
const BlockSize max_block_size =
GetSmallestBounds(config.partition_set, BLK_32x32);
WP2_CHECK_OK(config.partition_snapping, WP2_STATUS_INVALID_CONFIGURATION);
WP2_CHECK_OK(BlockWidthPix(max_block_size) <= area_width_ &&
BlockHeightPix(max_block_size) <= area_height_,
WP2_STATUS_INVALID_CONFIGURATION);
// Matches TileEncoder::LossyEncode() behavior.
const ChromaSubsampling chroma_subsampling =
DecideChromaSubsampling(*config_, /*more_than_one_block=*/true);
const bool use_aom_coeffs = DecideAOMCoeffs(*config_, tile_rect_);
WP2_CHECK_STATUS(DecideTransforms(config, &transforms_, &transforms_subset_));
// Retrieve the default partition (multipass) to have a reference.
{
MultiScoreFunc score_func;
MultiPassPartitioner partitioner(&score_func);
EncoderConfig cfg_no_dbg = config;
cfg_no_dbg.info = nullptr; // Do not use or output any debugging data.
WP2_CHECK_STATUS(score_func.Init(cfg_no_dbg, tile_rect_, *src_, *gparams_,
score_func_init_progress));
WP2_CHECK_STATUS(
partitioner.Init(cfg_no_dbg, *src_, tile_rect_, &score_func));
// TODO(yguyon): Add forced blocks from 'config' to 'default_partition_'
WP2_CHECK_STATUS(partitioner.GetBestPartition(default_partition_progress,
&default_partition_));
// Sort the blocks to find those in a given area faster.
std::sort(default_partition_.begin(), default_partition_.end(), comp_);
}
// Initialize the instances recording only final blocks.
WP2_CHECK_STATUS(syntax_writer_.Init(
&dicts_, *config_, *gparams_, yuv, chroma_subsampling, tile_rect,
num_block_cols_ * num_block_rows_, use_aom_coeffs, ProgressRange()));
WP2_CHECK_STATUS(syntax_writer_.SetInitialSegmentIds());
WP2_CHECK_STATUS(syntax_writer_.InitPass());
WP2_CHECK_STATUS(context_.Init(use_aom_coeffs));
if (DCDiffusionMap::GetDiffusion(config_->error_diffusion) > 0) {
WP2_CHECK_STATUS(dc_error_u_.Init(tile_rect_.width));
WP2_CHECK_STATUS(dc_error_v_.Init(tile_rect_.width));
}
// Store the reconstructed pixels of the current area partitioning and of
// the final selected blocks.
WP2_CHECK_STATUS(
buffer_.Resize(src_->Y.w_, src_->Y.h_, /*pad=*/1, with_alpha_));
// Setup the first area as the top-left corner.
WP2_CHECK_STATUS(area_front_mgr_.Init(config_->partition_set,
config_->partition_snapping,
tile_rect_.width, tile_rect_.height));
WP2_CHECK_STATUS(BeginArea(/*area_x=*/0, /*area_y=*/0)); // x,y within tile
return WP2_STATUS_OK;
}
// Returns a score in [0:1] representing how much a 'disto/rate' pair is good
// compared to a reference, where 0 is discarded, 1 is way better than 'ref' and
// 1/3 is equivalent to 'ref'.
static float GetRelativeScore(const EncoderConfig& config, float ref_disto,
float ref_rate, float disto, float rate) {
if (disto > ref_disto || rate > ref_rate) return 0.f; // No mercy.
if (ref_disto > 0.f) {
disto /= ref_disto; // Normalize.
} else {
if (disto > 0.f) return 0.f; // Discard, no way of assessing 'disto'/'ref'.
disto = 1.f; // Both values are 0 so set to 1 to signal it is equivalent.
}
if (ref_rate > 0.f) {
rate /= ref_rate; // Normalize.
} else {
if (rate > 0.f) return 0.f; // Discard, no way of assessing 'rate'/'ref'.
rate = 1.f; // Both values are 0 so set to 1 to signal it is equivalent.
}
const float delta = Clamp(config.quality / kMaxLossyQuality, 0.01f, 0.99f);
return 1.f / (1.f + delta * disto + (1.f - delta) * rate);
}
WP2Status AreaScoreFunc::ComputeScore(const Block&, const ProgressRange&,
float* const) {
return WP2_STATUS_UNSUPPORTED_FEATURE; // Unused.
}
WP2Status AreaScoreFunc::GetAreaDefaultScore(VectorNoCtor<Block>* const blocks,
float* const score) {
Vector<CodedBlock> partition;
WP2_CHECK_STATUS(GetAreaDefaultPartition(&partition));
assert(!partition.empty());
WP2_CHECK_STATUS(GetDistoRate(&partition, &default_disto_, &default_rate_));
// Compute the score with itself as reference, to be easily comparable.
*score = GetRelativeScore(*config_, default_disto_, default_rate_,
default_disto_, default_rate_);
for (const CodedBlock& cb : partition) {
WP2_CHECK_ALLOC_OK(blocks->push_back(cb.blk()));
}
WP2_CHECK_REDUCED_STATUS(
RegisterScoreForVDebug(BLK_LAST, partition, *score, default_disto_,
default_rate_));
return WP2_STATUS_OK;
}
WP2Status AreaScoreFunc::GetAreaGridScore(BlockSize block_size,
VectorNoCtor<Block>* const blocks,
float* const score) {
Vector<CodedBlock> partition;
// Fill 'partition' with as many 'block_size' as possible.
// Fill the remaining space with blocks as big as possible.
FrontMgrArea front_mgr(area_width_, area_height_);
WP2_CHECK_STATUS(front_mgr.CopyFrom(area_front_mgr_));
uint32_t num_block_units = 0;
while (!front_mgr.Done()) {
Block block;
if (!front_mgr.TryGetNextBlock(block_size, &block)) {
block = front_mgr.GetMaxFittingBlock();
}
if (!area_.Contains(block.x_pix(), block.y_pix())) break;
WP2_CHECK_ALLOC_OK(front_mgr.UseSize(block.dim(), 0, nullptr));
front_mgr.Use(block);
WP2_CHECK_ALLOC_OK(partition.resize(partition.size() + 1));
partition.back().SetDimDefault(block);
WP2_CHECK_ALLOC_OK(blocks->push_back(block));
num_block_units += block.rect().GetArea();
}
assert(num_block_units == SizeBlocks(area_.width) * SizeBlocks(area_.height));
assert(default_disto_ >= 0.f && default_rate_ >= 0.f);
float disto = 0.f, rate = 0.f;
WP2_CHECK_STATUS(GetDistoRate(&partition, &disto, &rate));
*score =
GetRelativeScore(*config_, default_disto_, default_rate_, disto, rate);
WP2_CHECK_REDUCED_STATUS(
RegisterScoreForVDebug(block_size, partition, *score, disto, rate));
return WP2_STATUS_OK;
}
WP2Status AreaScoreFunc::GetDistoRate(Vector<CodedBlock>* const area_blocks,
float* const disto,
float* const rate) const {
*rate = *disto = 0.f;
ANSDictionaries dicts;
WP2_CHECK_STATUS(dicts.CopyFrom(dicts_));
SyntaxWriter syntax_writer;
WP2_CHECK_STATUS(syntax_writer.CopyFrom(syntax_writer_, &dicts));
DCDiffusionMap dc_error_u, dc_error_v;
if (DCDiffusionMap::GetDiffusion(config_->error_diffusion) > 0) {
WP2_CHECK_STATUS(dc_error_u.CopyFrom(dc_error_u_));
WP2_CHECK_STATUS(dc_error_v.CopyFrom(dc_error_v_));
}
// Encode all 'area_blocks' using previously finished areas and blocks in this
// 'area_' as prediction context (stored in 'buffer_').
{
FrontMgrArea front_mgr(area_width_, area_height_);
WP2_CHECK_STATUS(front_mgr.CopyFrom(area_front_mgr_));
for (CodedBlock& cb : *area_blocks) {
WP2_CHECK_OK(!front_mgr.Done(), WP2_STATUS_INVALID_PARAMETER);
assert(cb.x() == front_mgr.GetMaxPossibleBlock().x() &&
cb.y() == front_mgr.GetMaxPossibleBlock().y() &&
front_mgr.GetMaxPossibleBlock().rect().Contains(cb.blk().rect()));
cb.SetDim(cb.blk(), front_mgr);
WP2_CHECK_STATUS(EncodeBlock(front_mgr, &cb, &syntax_writer, &dc_error_u,
&dc_error_v, &buffer_));
WP2_CHECK_ALLOC_OK(front_mgr.UseSize(cb.dim(),
/*ind=*/0, /*block=*/nullptr));
front_mgr.Use(cb.blk());
}
}
// Now estimate the bits necessary to encode all blocks in 'area_'.
{
// Also write the headers so that symbols are correctly set up.
ANSEnc enc;
WP2_CHECK_STATUS(syntax_writer.WriteHeader(&enc));
const float header_rate = enc.GetCost(dicts);
FrontMgrArea front_mgr(area_width_, area_height_);
WP2_CHECK_STATUS(front_mgr.CopyFrom(area_front_mgr_));
for (const CodedBlock& cb : *area_blocks) {
WP2_CHECK_STATUS(WriteBlock(front_mgr, cb, &syntax_writer, &enc));
WP2_CHECK_ALLOC_OK(front_mgr.UseSize(cb.dim(),
/*ind=*/0, /*block=*/nullptr));
front_mgr.Use(cb.blk());
}
// Exclude the header to prevent early decisions from impacting later areas.
*rate = (enc.GetCost(dicts) - header_rate) / area_.GetArea();
assert(*rate >= 0.f);
}
// Measure the distortion of this tested partition of 'area_'.
{
constexpr float disto_scale[] = {0.4f, 0.2f, 0.2f, 0.2f};
for (Channel c : {kYChannel, kUChannel, kVChannel, kAChannel}) {
if (c == kAChannel && !with_alpha_) continue;
Plane16 src_area_view, buffer_area_view;
WP2_CHECK_STATUS(src_area_view.SetView(src_->GetChannel(c), area_));
WP2_CHECK_STATUS(buffer_area_view.SetView(buffer_.GetChannel(c), area_));
*disto +=
disto_scale[c] * WP2SumSquaredErrorBlock(
src_area_view.Row(0), src_area_view.Step(),
buffer_area_view.Row(0), buffer_area_view.Step(),
area_.width, area_.height);
}
*disto /= area_.GetArea();
}
return WP2_STATUS_OK;
}
WP2Status AreaScoreFunc::BeginArea(uint32_t area_x, uint32_t area_y) {
// Make sure areas are done in order.
assert((area_x == 0 && area_y == 0) ||
(area_x == area_.x + area_width_ && area_y == area_.y) ||
(area_x == 0 && area_y == area_.y + area_height_));
area_ = Rectangle(area_x, area_y, area_width_, area_height_)
.ClipWith({0, 0, tile_rect_.width, tile_rect_.height}); // no pad
// Assuming all areas are done in order row by row, top and left contexts
// outside this 'area_' are available, if any.
// Next available block should match the current 'area_'.
assert(area_front_mgr_.GetMaxFittingBlock().x_pix() == area_.x &&
area_front_mgr_.GetMaxFittingBlock().y_pix() == area_.y);
return WP2_STATUS_OK;
}
WP2Status AreaScoreFunc::Use(const Block& block) {
CodedBlock cb;
cb.SetDim(block, area_front_mgr_);
assert(cb.blk() == block);
// Write the final pixels for future context and rate computation.
WP2_CHECK_STATUS(EncodeBlock(area_front_mgr_, &cb, &syntax_writer_,
&dc_error_u_, &dc_error_v_, &buffer_));
WP2_CHECK_ALLOC_OK(area_front_mgr_.UseSize(cb.dim(), /*ind=*/0,
/*block=*/nullptr));
area_front_mgr_.Use(cb.blk());
if (area_front_mgr_.Done()) {
// All areas are complete.
area_ = {};
area_front_mgr_.Clear();
} else {
const Block max_block = area_front_mgr_.GetMaxPossibleBlock();
if (!area_.Contains(max_block.x_pix(), max_block.y_pix())) {
// This 'area_' is complete. Prepare the next one.
uint32_t area_x = area_.x + area_width_, area_y = area_.y; // Next col.
if (area_x >= tile_rect_.width) { // Next row.
area_x = 0;
area_y += area_height_;
}
assert(area_x < tile_rect_.width && area_y < tile_rect_.height);
WP2_CHECK_STATUS(BeginArea(area_x, area_y));
}
}
default_disto_ = default_rate_ = -1;
return WP2_STATUS_OK;
}
WP2Status AreaScoreFunc::GetAreaDefaultPartition(
Vector<CodedBlock>* const area_blocks) const {
// Find the blocks in 'default_partition_' belonging to the current 'area_'.
const Block area_pos(area_.x / kMinBlockSizePix, area_.y / kMinBlockSizePix,
BLK_32x32);
VectorNoCtor<Block>::const_iterator block_it = std::lower_bound(
default_partition_.begin(), default_partition_.end(), area_pos, comp_);
uint32_t num_block_units = 0;
for (; block_it != default_partition_.end(); ++block_it) {
if (!area_.Contains(block_it->x_pix(), block_it->y_pix())) break;
WP2_CHECK_ALLOC_OK(area_blocks->resize(area_blocks->size() + 1));
area_blocks->back().SetDimDefault(*block_it);
num_block_units += block_it->rect().GetArea();
}
// If snapping is not enabled or if the 'area_' dimensions do not match it,
// the default partition cannot be used as is.
WP2_CHECK_OK(
num_block_units == SizeBlocks(area_.width) * SizeBlocks(area_.height),
WP2_STATUS_INVALID_CONFIGURATION);
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
WP2Status SubAreaScoreFunc::ComputeScore(const Block& block,
const ProgressRange& progress,
float* const score) {
const ProgressRange default_partitioning_progress(progress, 0.5);
const ProgressRange remaining_blocks_progress(progress, 0.5);
if (default_block_.dim() == BLK_LAST) {
// Get the default partitioning and score of the remaining non-final blocks.
// This is done once for each block position within each area.
Vector<CodedBlock> default_partition;
WP2_CHECK_STATUS(GetAreaRemainingDefaultPartition(
default_partitioning_progress, &default_partition));
assert(!default_partition.empty() &&
default_partition.front().dim() != BLK_LAST);
// TODO(yguyon): Also include the 'area_used_blocks_' into the disto/rate
WP2_CHECK_STATUS(GetDistoRate(&default_partition, &default_block_disto_,
&default_block_rate_));
#if !defined(WP2_REDUCE_BINARY_SIZE)
const float default_score =
GetRelativeScore(*config_, default_block_disto_, default_block_rate_,
default_block_disto_, default_block_rate_);
WP2_CHECK_REDUCED_STATUS(
RegisterScoreForVDebug(default_partition.front().blk(),
default_partition, default_score,
default_block_disto_, default_block_rate_));
#endif // WP2_REDUCE_BINARY_SIZE
default_block_ = default_partition.front().blk();
} else {
WP2_CHECK_STATUS(default_partitioning_progress.AdvanceBy(1.));
}
// 'default_block_' now contains the size of the first block among the
// remaining non-final ones given by the default partitioning.
assert(default_block_.x() == block.x() && default_block_.y() == block.y());
float disto = 0.f, rate = 0.f;
if (block == default_block_) {
disto = default_block_disto_;
rate = default_block_rate_;
*score = GetRelativeScore(*config_, default_block_disto_,
default_block_rate_, disto, rate);
WP2_CHECK_STATUS(remaining_blocks_progress.AdvanceBy(1.));
} else {
// 'block' here is the currently evaluated size for a given position.
// 'area_used_blocks_' represent the final blocks previously encoded and
// recorded. 'area_remaining_blocks' exist to fill the 'area_' partition and
// compare the same surface by rate-distortion with the default partition.
Vector<CodedBlock> area_remaining_blocks;
WP2_CHECK_ALLOC_OK(area_remaining_blocks.resize(1));
area_remaining_blocks.back().SetDimDefault(block); // Force it.
WP2_CHECK_STATUS(GetAreaRemainingDefaultPartition(remaining_blocks_progress,
&area_remaining_blocks));
WP2_CHECK_STATUS(GetDistoRate(&area_remaining_blocks, &disto, &rate));
*score = GetRelativeScore(*config_, default_block_disto_,
default_block_rate_, disto, rate);
WP2_CHECK_REDUCED_STATUS(
RegisterScoreForVDebug(block, area_remaining_blocks,
*score, disto, rate));
}
return WP2_STATUS_OK;
}
WP2Status SubAreaScoreFunc::BeginArea(uint32_t area_x, uint32_t area_y) {
WP2_CHECK_STATUS(AreaScoreFunc::BeginArea(area_x, area_y));
area_used_blocks_.clear();
return WP2_STATUS_OK;
}
WP2Status SubAreaScoreFunc::Use(const Block& block) {
WP2_CHECK_ALLOC_OK(area_used_blocks_.resize(area_used_blocks_.size() + 1));
area_used_blocks_.back().SetDim(block, area_front_mgr_);
WP2_CHECK_STATUS(AreaScoreFunc::Use(block));
default_block_ = Block(); // Reset.
default_block_disto_ = default_block_rate_ = 0.f;
return WP2_STATUS_OK;
}
WP2Status SubAreaScoreFunc::GetAreaRemainingDefaultPartition(
const ProgressRange& progress,
Vector<CodedBlock>* const area_remaining_blocks) const {
const ProgressRange score_func_init_progress(progress, 0.01);
const ProgressRange partitioner_progress(progress, 0.99);
// It would be faster to reuse the 'AreaScoreFunc::default_partition_' (or
// part of it) but it is not always compatible with the already selected
// 'area_used_blocks_' so for simplicity it is always recomputed for each
// block size of each block position.
MultiScoreFunc score_func;
MultiPassPartitioner partitioner(&score_func);
EncoderConfig config = *config_;
config.info = nullptr; // Remove any debugging input/output.
WP2_CHECK_STATUS(score_func.Init(config, tile_rect_, *src_, *gparams_,
score_func_init_progress));
WP2_CHECK_STATUS(partitioner.Init(config, *src_, tile_rect_, &score_func));
// 'blocks' will first contain all irrelevant blocks that will be forced into
// the 'partitioner' to extract only the interesting ones.
VectorNoCtor<Block> blocks;
// Force all blocks external to the current 'area_' (their size and count do
// not matter).
{
FrontMgrArea front_mgr(area_width_, area_height_);
WP2_CHECK_STATUS(front_mgr.Init(config_->partition_set,
config_->partition_snapping,
tile_rect_.width, tile_rect_.height));
uint32_t surface_kept = 0;
while (!front_mgr.Done()) {
const Block max_block = front_mgr.GetMaxFittingBlock();
WP2_CHECK_ALLOC_OK(front_mgr.UseSize(max_block.dim(), 0, nullptr));
front_mgr.Use(max_block);
if (!area_.Contains(max_block.x_pix(), max_block.y_pix())) {
WP2_CHECK_ALLOC_OK(blocks.push_back(max_block));
} else {
surface_kept += max_block.rect().GetArea();
}
}
WP2_CHECK_OK(
surface_kept == SizeBlocks(area_.width) * SizeBlocks(area_.height),
WP2_STATUS_INVALID_PARAMETER);
}
// Force all already selected final blocks.
for (const CodedBlock& cb : area_used_blocks_) {
WP2_CHECK_ALLOC_OK(blocks.push_back(cb.blk()));
}
// Force the block under review, if any.
for (const CodedBlock& cb : *area_remaining_blocks) {
WP2_CHECK_ALLOC_OK(blocks.push_back(cb.blk()));
}
const uint32_t num_default_blocks_to_ignore = blocks.size();
const uint32_t num_already_added_area_blocks = area_remaining_blocks->size();
// Get the default partitioning of the remaining empty spaces into 'blocks'.
WP2_CHECK_STATUS(partitioner.GetBestPartition(partitioner_progress, &blocks));
// Sort in lexico order only the new blocks.
std::sort(blocks.begin() + num_default_blocks_to_ignore, blocks.end());
// Copy from Block struct to CodedBlock class into 'area_remaining_blocks'.
WP2_CHECK_ALLOC_OK(area_remaining_blocks->resize(
area_remaining_blocks->size() +
(blocks.size() - num_default_blocks_to_ignore)));
for (uint32_t i = num_default_blocks_to_ignore,
j = num_already_added_area_blocks;
i < blocks.size(); ++i, ++j) {
area_remaining_blocks->at(j).SetDimDefault(blocks[i]);
}
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
} // namespace WP2