blob: 41facdf2c598c01c7d8f002d98bf27ab7ab766ca [file]
// 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.
// -----------------------------------------------------------------------------
//
// Tool for finding the best block layout.
//
// Author: Yannis Guyon (yguyon@google.com)
#include "src/enc/partitioning/partitioner_multi.h"
#include <algorithm>
#include <cassert>
#include <cstdint>
#include "src/common/lossy/block_size.h"
#include "src/common/lossy/block_size_io.h"
#include "src/common/progress_watcher.h"
#include "src/dsp/math.h"
#include "src/enc/partitioning/partition_score_func_multi.h"
#include "src/utils/utils.h"
#include "src/utils/vector.h"
#include "src/wp2/base.h"
#include "src/wp2/format_constants.h"
namespace WP2 {
//------------------------------------------------------------------------------
using Pass = MultiScoreFunc::Pass;
WP2Status MultiPassPartitioner::GetBestPartition(
const ProgressRange& progress, VectorNoCtor<Block>* const blocks,
Vector_u32* const) {
std::fill(occupancy_.begin(), occupancy_.end(), false);
uint32_t max_num_blocks_left = num_block_cols_ * num_block_rows_;
WP2_CHECK_STATUS(RegisterForcedBlocks(*blocks, &max_num_blocks_left));
const uint32_t max_num_passes =
8 + GetNumBlockSizes(config_->partition_set) * 3;
const ProgressScale pass_progress(progress, 1. / max_num_passes);
// Run several passes to select matching blocks with a given size.
// GetBlocks() will skip any pass with a BlockSize not belonging to the
// current PartitionSet.
// Start with easy-to-spot flat blocks (narrow range of values in the
// original luma and alpha planes).
for (BlockSize block_size : {BLK_32x32, BLK_32x16, BLK_16x32, BLK_16x16,
BLK_16x8, BLK_8x16, BLK_8x8}) {
WP2_CHECK_STATUS(GetBlocks(Pass::LumaAlphaGradient, block_size,
Grid::Snapped, pass_progress,
&max_num_blocks_left, blocks));
}
// Pick a few not-too-small blocks that are following a luma edge.
for (const BlockSize* block_size =
GetBlockSizes(ALL_RECTS) + GetNumBlockSizes(ALL_RECTS) - 1u;
*block_size != BLK_4x4; --block_size) {
if (BlockWidth[*block_size] * BlockHeight[*block_size] < 4) continue;
WP2_CHECK_STATUS(GetBlocks(Pass::Direction, *block_size, Grid::All,
pass_progress, &max_num_blocks_left, blocks));
}
// Choose smaller and smaller blocks based on variance.
constexpr PartitionSet variance_partition_set = SMALL_RECTS;
assert(*GetBlockSizes(variance_partition_set) == BLK_4x4);
for (const BlockSize* allowed_block_size =
GetBlockSizes(variance_partition_set) +
GetNumBlockSizes(variance_partition_set) - 1u;
*allowed_block_size != BLK_4x4; --allowed_block_size) {
WP2_CHECK_STATUS(GetBlocks(Pass::NarrowStdDev, *allowed_block_size,
Grid::All, pass_progress, &max_num_blocks_left,
blocks));
}
// Merge all remaining small blocks that are following a luma edge.
for (const BlockSize* block_size =
GetBlockSizes(ALL_RECTS) + GetNumBlockSizes(ALL_RECTS) - 1u;
*block_size != BLK_4x4; --block_size) {
if (BlockWidth[*block_size] * BlockHeight[*block_size] >= 4) continue;
WP2_CHECK_STATUS(GetBlocks(Pass::Direction, *block_size, Grid::All,
pass_progress, &max_num_blocks_left, blocks));
}
// Fill the remaining empty areas.
WP2_CHECK_STATUS(GetBlocks(Pass::Any, BLK_4x4, Grid::All, pass_progress,
&max_num_blocks_left, blocks));
assert(max_num_blocks_left == 0);
assert(pass_index_ <= max_num_passes);
WP2_CHECK_STATUS(pass_progress.AdvanceBy(max_num_passes - pass_index_));
return WP2_STATUS_OK;
}
WP2Status MultiPassPartitioner::GetBlocks(Pass pass, BlockSize block_size,
Grid restrict_to,
const ProgressRange& progress,
uint32_t* const max_num_blocks_left,
VectorNoCtor<Block>* const out) {
if (GetFittingBlockSize(config_->partition_set, block_size) != block_size) {
return WP2_STATUS_OK;
}
// Only keep snapped blocks if the config forces it.
if (config_->partition_snapping) {
if (restrict_to == Grid::NonSnapped) return WP2_STATUS_OK;
restrict_to = Grid::Snapped;
}
const uint32_t num_blocks_before = out->size();
Block block(0, 0, block_size);
// Make sure the 'block_size' is valid and there is at least one remaining.
if (block.w() > num_block_cols_ || block.h() > num_block_rows_ ||
block.area() > *max_num_blocks_left) {
WP2_CHECK_REDUCED_STATUS(RegisterPassForVDebug(pass, block_size, 0));
++pass_index_;
WP2_CHECK_STATUS(progress.AdvanceBy(1.));
return WP2_STATUS_OK;
}
struct Result {
Block blk;
float score;
};
VectorNoCtor<Result> results;
multi_score_func_->SetPass(pass);
const uint32_t incr_x = (restrict_to == Grid::Snapped) ? block.w() : 1;
const uint32_t incr_y = (restrict_to == Grid::Snapped) ? block.h() : 1;
const uint32_t max_x = num_block_cols_ - block.w();
const uint32_t max_y = num_block_rows_ - block.h();
const uint32_t num_incr_x = DivCeil(max_x + 1, incr_x);
const uint32_t num_incr_y = DivCeil(max_y + 1, incr_y);
const ProgressScale incr_progress(progress, 1. / (num_incr_x * num_incr_y));
const bool* occupancy = occupancy_.data();
// Browse the grid of selectable blocks.
for (uint32_t y = 0; y <= max_y;
y += incr_y, occupancy += incr_y * num_block_cols_) {
for (uint32_t x = 0; x <= max_x; x += incr_x) {
if (!occupancy[x]) { // Quickly skip top-left-occupied blocks.
block.SetXY(x, y);
if (restrict_to == Grid::Snapped) assert(block.IsSnapped());
// Assume snapped blocks were already tried if 'restrict_to==NonSnapped'
const bool tried_block_earlier =
(restrict_to == Grid::NonSnapped && block.IsSnapped());
if (!tried_block_earlier && !IsOccupied(block)) {
float score;
WP2_CHECK_STATUS(
score_func_->ComputeScore(block, incr_progress, &score));
if (score >= MultiScoreFunc::kMinScore) {
if (restrict_to == Grid::Snapped) {
// Immediately select any passing block.
WP2_CHECK_STATUS(
SelectBlock(pass, block, max_num_blocks_left, out));
} else {
// Keep the scores of the passing blocks in memory.
WP2_CHECK_ALLOC_OK(results.push_back({block, score}));
}
}
} else {
WP2_CHECK_STATUS(incr_progress.AdvanceBy(1.));
}
} else {
WP2_CHECK_STATUS(incr_progress.AdvanceBy(1.));
}
}
}
if (restrict_to != Grid::Snapped) {
// Select blocks with best (=lowest) score first.
// stable_sort() is used for its determinism.
std::stable_sort(
results.begin(), results.end(),
[](const Result& l, const Result& r) { return l.score < r.score; });
for (const Result& result : results) {
if (IsOccupied(result.blk)) continue; // 'occupancy_' might have changed.
WP2_CHECK_STATUS(SelectBlock(pass, result.blk, max_num_blocks_left, out));
}
}
(void)num_blocks_before;
WP2_CHECK_REDUCED_STATUS(
RegisterPassForVDebug(pass, block_size, out->size() - num_blocks_before));
++pass_index_;
return WP2_STATUS_OK;
}
WP2Status MultiPassPartitioner::SelectBlock(MultiScoreFunc::Pass pass,
const Block& block,
uint32_t* const max_num_blocks_left,
VectorNoCtor<Block>* const out) {
WP2_CHECK_REDUCED_STATUS(
RegisterOrderForVDebug((uint32_t)pass, pass_index_, block, out->size(),
num_block_cols_ * num_block_rows_));
Occupy(block);
const uint32_t block_area = block.w() * block.h();
assert(*max_num_blocks_left >= block_area);
*max_num_blocks_left -= block_area;
WP2_CHECK_ALLOC_OK(out->push_back(block));
WP2_CHECK_STATUS(score_func_->Use(block));
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
} // namespace WP2