blob: 242fa0af13f98f20832670ef9927108902b8f7f3 [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.
// -----------------------------------------------------------------------------
//
// Tile encoding
//
// Author: Skal (pascal.massimino@gmail.com)
#include "src/enc/tile_enc.h"
#include <algorithm>
#include <cassert>
#include "src/common/color_precision.h"
#include "src/common/constants.h"
#include "src/common/header_enc_dec.h"
#include "src/dsp/dsp.h"
#include "src/enc/analysis.h"
#include "src/enc/partitioning/partitioner.h"
#include "src/enc/preview/preview_enc.h"
#include "src/enc/wp2_enc_i.h"
#include "src/utils/ans_enc.h"
#include "src/utils/ans_utils.h"
#include "src/utils/utils.h"
#include "src/wp2/base.h"
#include "src/wp2/encode.h"
#include "src/wp2/format_constants.h"
namespace WP2 {
//------------------------------------------------------------------------------
WP2Status TileEncoder::AssignNextTile() {
tile_ = nullptr;
// Get first unassigned tile, and increment the counter if valid.
WP2_CHECK_STATUS(tiles_layout_->assignment_lock.Acquire());
const uint32_t tile_index = tiles_layout_->first_unassigned_tile_index;
if (tile_index < tiles_layout_->tiles.size()) {
tile_ = &tiles_layout_->tiles[tile_index];
++tiles_layout_->first_unassigned_tile_index;
}
tiles_layout_->assignment_lock.Release();
return WP2_STATUS_OK;
}
WP2Status TileEncoder::Execute() {
while (tile_ != nullptr) {
if (tiles_layout_->gparams->type_ == GlobalParams::GP_AV1) {
WP2_CHECK_STATUS(Av1Encode(&tile_->data));
} else {
ANSEnc& enc = tile_->enc;
assert(enc.GetBitstreamSize() == 0);
if (tiles_layout_->gparams->type_ == GlobalParams::GP_BOTH) {
ANSDebugPrefix prefix(&enc, "GlobalHeader");
enc.PutBool(use_lossless_, "use_lossless");
}
if (use_lossless_) {
WP2_CHECK_STATUS(LosslessEncode(&enc));
} else {
const Rectangle padded_tile_rect = {
tile_->rect.x, tile_->rect.y, Pad(tile_->rect.width, kPredWidth),
Pad(tile_->rect.height, kPredWidth)};
VectorNoCtor<Block> partition;
WP2_CHECK_STATUS(
AddForcedBlocks(*config_, padded_tile_rect, &partition));
WP2_CHECK_STATUS(LossyEncode(partition, &enc));
}
WP2_CHECK_STATUS(enc.AssembleToBitstream(/*clear_tokens=*/true));
}
WP2_CHECK_STATUS(AssignNextTile());
}
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
namespace {
// Fills the 'tiles_layout'. Dimensions are in pixels, not padded.
WP2Status GetEncTilesLayout(uint32_t frame_width, uint32_t frame_height,
uint32_t tile_width, uint32_t tile_height,
const ProgressRange& progress,
const ArgbBuffer& frame_rgb,
const YUVPlane& frame_yuv,
const GlobalParams& gparams,
EncTilesLayout* const tiles_layout) {
tiles_layout->tile_width = tile_width;
tiles_layout->tile_height = tile_height;
const uint32_t num_tiles =
GetNumTiles(frame_width, frame_height, tile_width, tile_height,
&tiles_layout->num_tiles_x, &tiles_layout->num_tiles_y);
WP2_CHECK_ALLOC_OK(tiles_layout->tiles.resize(num_tiles));
const uint32_t num_pixels = frame_width * frame_height;
uint32_t tile_y = 0, tile_x = 0;
for (EncTile& tile : tiles_layout->tiles) {
tile.rect = {tile_x * tile_width, tile_y * tile_height,
std::min(frame_width - tile_x * tile_width, tile_width),
std::min(frame_height - tile_y * tile_height, tile_height)};
tile.progress =
ProgressRange(progress, 1. * tile.rect.GetArea() / num_pixels);
// Set views on RGB and YUV input buffers, one or both may be used.
assert(!frame_rgb.IsEmpty() || !frame_yuv.IsEmpty());
if (!frame_rgb.IsEmpty()) {
assert(frame_rgb.width() == frame_width &&
frame_rgb.height() == frame_height);
WP2_CHECK_STATUS(tile.rgb_input.SetFormat(frame_rgb.format()));
WP2_CHECK_STATUS(tile.rgb_input.SetView(frame_rgb, tile.rect));
}
if (!frame_yuv.IsEmpty()) {
assert(frame_yuv.GetWidth() == Pad(frame_width, kPredWidth) &&
frame_yuv.GetHeight() == Pad(frame_height, kPredWidth));
WP2_CHECK_STATUS(tile.yuv_input.SetView(
frame_yuv,
{tile.rect.x, tile.rect.y,
std::min(frame_yuv.GetWidth() - tile.rect.x, tile_width),
std::min(frame_yuv.GetHeight() - tile.rect.y, tile_height)}));
}
if (++tile_x == tiles_layout->num_tiles_x) {
++tile_y;
tile_x = 0;
}
}
tiles_layout->first_unassigned_tile_index = 0;
tiles_layout->gparams = &gparams;
return WP2_STATUS_OK;
}
WP2Status SetupWorkers(uint32_t width, uint32_t height,
const ArgbBuffer& rgb_buffer, const YUVPlane& yuv_buffer,
const EncoderConfig& config, const GlobalParams& gparams,
EncTilesLayout* const tiles_layout,
Vector<TileEncoder>* const workers) {
// The number of workers is limited by the number of threads and tiles.
const uint32_t num_workers = std::min((uint32_t)config.thread_level + 1u,
(uint32_t)tiles_layout->tiles.size());
WP2_CHECK_ALLOC_OK(workers->resize(num_workers));
for (TileEncoder& worker : *workers) {
worker.status_ = WP2_STATUS_OK;
worker.config_ = &config;
worker.use_lossless_ =
(!config.use_neural_compression && config.quality > kMaxLossyQuality);
worker.tiles_layout_ = tiles_layout;
WP2_CHECK_STATUS(worker.AssignNextTile());
}
return WP2_STATUS_OK;
}
WP2Status CodeTiles(EncTilesLayout* const tiles_layout,
Vector<TileEncoder>* const workers, Writer* const output) {
const bool do_mt = (workers->size() > 1);
WP2Status status = WP2_STATUS_OK;
for (TileEncoder& tile : *workers) {
status = tile.Start(do_mt);
if (status != WP2_STATUS_OK) break;
}
for (TileEncoder& tile : *workers) {
const WP2Status thread_status = tile.End();
if (thread_status != WP2_STATUS_OK) {
status = thread_status;
continue;
}
if (status == WP2_STATUS_OK) status = tile.status_;
}
WP2_CHECK_STATUS(status);
for (EncTile& tile : tiles_layout->tiles) {
const uint32_t rgb_bit_depth =
(tile.rgb_input.format() == WP2_Argb_38) ? 10 : 8;
const uint32_t max_num_bytes =
GetTileMaxNumBytes(rgb_bit_depth, *tiles_layout->gparams, tile.rect);
assert((tile.enc.GetBitstreamSize() == 0) ^ (tile.data.size == 0));
const uint32_t num_bytes =
(tile.data.size > 0) ? tile.data.size : tile.enc.GetBitstreamSize();
{
uint8_t buffer[kMaxVarIntLength];
const uint32_t size =
WriteVarInt(std::min(num_bytes, max_num_bytes), 1,
max_num_bytes, buffer);
WP2_CHECK_ALLOC_OK(output->Append(buffer, size));
}
if (num_bytes >= max_num_bytes) {
// Discard ANS encoding because it takes at least as many bytes as raw
// pixels.
WP2_CHECK_STATUS(BypassTileEnc(*tiles_layout->gparams, &tile, output));
} else {
if (tile.data.size > 0) {
WP2_CHECK_ALLOC_OK(output->Append(tile.data.bytes, tile.data.size));
} else {
// TODO(yguyon): Check if 'tile.enc.GetBitstreamSize()' can even be 0.
WP2_CHECK_STATUS(tile.enc.WriteBitstreamTo(*output));
}
}
tile.enc.Reset();
tile.data.Clear();
}
return status;
}
} // namespace
//------------------------------------------------------------------------------
WP2Status EncodeTiles(uint32_t width, uint32_t height,
const ArgbBuffer& rgb_buffer, const YUVPlane& yuv_buffer,
const CSPTransform& transf, const EncoderConfig& config,
bool image_has_alpha, const ProgressRange& progress,
Writer* const output) {
WP2EncDspInit();
GlobalParams gparams;
FeatureMap features;
gparams.features_ = &features;
const ArgbBuffer* buffer = &rgb_buffer;
ArgbBuffer pre_processed_buffer(buffer->format());
// near-lossless pre-processing
constexpr uint32_t kMinNearLosslessDimension = 16; // minimum dimension
if (yuv_buffer.IsEmpty() && buffer->width() >= kMinNearLosslessDimension &&
buffer->height() >= kMinNearLosslessDimension &&
config.quality > kMaxLossyQuality && config.quality < kMaxQuality) {
WP2_CHECK_STATUS(PreprocessNearLossless(*buffer, config, /*is_alpha=*/false,
&pre_processed_buffer));
buffer = &pre_processed_buffer;
}
WP2_CHECK_STATUS(
GlobalAnalysis(*buffer, yuv_buffer, transf, config, &gparams));
// TODO(skal): analysis and num_mtx_ decision...
WP2_CHECK_STATUS(gparams.InitRndMtxSet());
WP2_CHECK_STATUS(EncodeGLBL(config, gparams, image_has_alpha, output));
WP2_CHECK_STATUS(progress.AdvanceBy(kOnePercent)); // GlobalParams
const uint32_t tile_width = TileWidth(FinalTileShape(config), width);
const uint32_t tile_height =
TileHeight(FinalTileShape(config), /*image_width=*/width);
const ProgressRange tiles_progress(progress, kProgressTiles);
EncTilesLayout tiles_layout;
WP2_CHECK_STATUS(GetEncTilesLayout(width, height, tile_width, tile_height,
tiles_progress, *buffer, yuv_buffer,
gparams, &tiles_layout));
Vector<TileEncoder> workers;
WP2_CHECK_STATUS(SetupWorkers(width, height, *buffer, yuv_buffer, config,
gparams, &tiles_layout, &workers));
WP2_CHECK_STATUS(CodeTiles(&tiles_layout, &workers, output));
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
} // namespace WP2