mirror of
https://github.com/webmproject/libwebp.git
synced 2024-11-20 04:18:26 +01:00
Speed-up uniform-region processing.
Change-Id: I9a88d0ac97c31d19323c9505ebe21f375d2e96b8
This commit is contained in:
parent
23e29cb1e3
commit
7d58d1b7b9
@ -653,6 +653,7 @@ typedef struct {
|
|||||||
size_t cache_intervals_size_;
|
size_t cache_intervals_size_;
|
||||||
double cost_cache_[MAX_LENGTH]; // Contains the GetLengthCost(cost_model, k).
|
double cost_cache_[MAX_LENGTH]; // Contains the GetLengthCost(cost_model, k).
|
||||||
double min_cost_cache_; // The minimum value in cost_cache_[1:].
|
double min_cost_cache_; // The minimum value in cost_cache_[1:].
|
||||||
|
double max_cost_cache_; // The maximum value in cost_cache_[1:].
|
||||||
float* costs_;
|
float* costs_;
|
||||||
uint16_t* dist_array_;
|
uint16_t* dist_array_;
|
||||||
// Most of the time, we only need few intervals -> use a free-list, to avoid
|
// Most of the time, we only need few intervals -> use a free-list, to avoid
|
||||||
@ -662,6 +663,10 @@ typedef struct {
|
|||||||
// These are regularly malloc'd remains. This list can't grow larger than than
|
// These are regularly malloc'd remains. This list can't grow larger than than
|
||||||
// size COST_CACHE_INTERVAL_SIZE_MAX - COST_MANAGER_MAX_FREE_LIST, note.
|
// size COST_CACHE_INTERVAL_SIZE_MAX - COST_MANAGER_MAX_FREE_LIST, note.
|
||||||
CostInterval* recycled_intervals_;
|
CostInterval* recycled_intervals_;
|
||||||
|
// Buffer used in BackwardReferencesHashChainDistanceOnly to store the ends
|
||||||
|
// of the intervals that can have impacted the cost at a pixel.
|
||||||
|
int* interval_ends_;
|
||||||
|
int interval_ends_size_;
|
||||||
} CostManager;
|
} CostManager;
|
||||||
|
|
||||||
static int IsCostCacheIntervalWritable(int start, int end) {
|
static int IsCostCacheIntervalWritable(int start, int end) {
|
||||||
@ -710,6 +715,7 @@ static void CostManagerClear(CostManager* const manager) {
|
|||||||
|
|
||||||
WebPSafeFree(manager->costs_);
|
WebPSafeFree(manager->costs_);
|
||||||
WebPSafeFree(manager->cache_intervals_);
|
WebPSafeFree(manager->cache_intervals_);
|
||||||
|
WebPSafeFree(manager->interval_ends_);
|
||||||
|
|
||||||
// Clear the interval lists.
|
// Clear the interval lists.
|
||||||
DeleteIntervalList(manager, manager->head_);
|
DeleteIntervalList(manager, manager->head_);
|
||||||
@ -731,6 +737,9 @@ static int CostManagerInit(CostManager* const manager,
|
|||||||
// Empirically, differences between intervals is usually of more than 1.
|
// Empirically, differences between intervals is usually of more than 1.
|
||||||
const double min_cost_diff = 0.1;
|
const double min_cost_diff = 0.1;
|
||||||
|
|
||||||
|
manager->costs_ = NULL;
|
||||||
|
manager->cache_intervals_ = NULL;
|
||||||
|
manager->interval_ends_ = NULL;
|
||||||
manager->head_ = NULL;
|
manager->head_ = NULL;
|
||||||
manager->recycled_intervals_ = NULL;
|
manager->recycled_intervals_ = NULL;
|
||||||
manager->count_ = 0;
|
manager->count_ = 0;
|
||||||
@ -750,8 +759,11 @@ static int CostManagerInit(CostManager* const manager,
|
|||||||
// Compute the minimum of cost_cache_.
|
// Compute the minimum of cost_cache_.
|
||||||
if (i == 1) {
|
if (i == 1) {
|
||||||
manager->min_cost_cache_ = manager->cost_cache_[1];
|
manager->min_cost_cache_ = manager->cost_cache_[1];
|
||||||
|
manager->max_cost_cache_ = manager->cost_cache_[1];
|
||||||
} else if (manager->cost_cache_[i] < manager->min_cost_cache_) {
|
} else if (manager->cost_cache_[i] < manager->min_cost_cache_) {
|
||||||
manager->min_cost_cache_ = manager->cost_cache_[i];
|
manager->min_cost_cache_ = manager->cost_cache_[i];
|
||||||
|
} else if (manager->cost_cache_[i] > manager->max_cost_cache_) {
|
||||||
|
manager->max_cost_cache_ = manager->cost_cache_[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -826,6 +838,54 @@ static int CostManagerInit(CostManager* const manager,
|
|||||||
// Set the initial costs_ high for every pixel as we will keep the minimum.
|
// Set the initial costs_ high for every pixel as we will keep the minimum.
|
||||||
for (i = 0; i < pix_count; ++i) manager->costs_[i] = 1e38f;
|
for (i = 0; i < pix_count; ++i) manager->costs_[i] = 1e38f;
|
||||||
|
|
||||||
|
// The cost at pixel is influenced by the cost intervals from previous pixels.
|
||||||
|
// Let us take the specific case where the offset is the same (which actually
|
||||||
|
// happens a lot in case of uniform regions).
|
||||||
|
// pixel i contributes to j>i a cost of: offset cost + cost_cache_[j-i]
|
||||||
|
// pixel i+1 contributes to j>i a cost of: 2*offset cost + cost_cache_[j-i-1]
|
||||||
|
// pixel i+2 contributes to j>i a cost of: 3*offset cost + cost_cache_[j-i-2]
|
||||||
|
// and so on.
|
||||||
|
// A pixel i influences the following length(j) < MAX_LENGTH pixels. What is
|
||||||
|
// the value of j such that pixel i + j cannot influence any of those pixels?
|
||||||
|
// This value is such that:
|
||||||
|
// max of cost_cache_ < j*offset cost + min of cost_cache_
|
||||||
|
// (pixel i + j 's cost cannot beat the worst cost given by pixel i).
|
||||||
|
// This value will be used to optimize the cost computation in
|
||||||
|
// BackwardReferencesHashChainDistanceOnly.
|
||||||
|
{
|
||||||
|
// The offset cost is computed in GetDistanceCost and has a minimum value of
|
||||||
|
// the minimum in cost_model->distance_. The case where the offset cost is 0
|
||||||
|
// will be dealt with differently later so we are only interested in the
|
||||||
|
// minimum non-zero offset cost.
|
||||||
|
double offset_cost_min = 0.;
|
||||||
|
int size;
|
||||||
|
for (i = 0; i < NUM_DISTANCE_CODES; ++i) {
|
||||||
|
if (cost_model->distance_[i] != 0) {
|
||||||
|
if (offset_cost_min == 0.) {
|
||||||
|
offset_cost_min = cost_model->distance_[i];
|
||||||
|
} else if (cost_model->distance_[i] < offset_cost_min) {
|
||||||
|
offset_cost_min = cost_model->distance_[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// In case all the cost_model->distance_ is 0, the next non-zero cost we
|
||||||
|
// can have is from the extra bit in GetDistanceCost, hence 1.
|
||||||
|
if (offset_cost_min < 1.) offset_cost_min = 1.;
|
||||||
|
|
||||||
|
size = 1 + (int)ceil((manager->max_cost_cache_ - manager->min_cost_cache_) /
|
||||||
|
offset_cost_min);
|
||||||
|
// Empirically, we usually end up with a value below 100.
|
||||||
|
if (size > MAX_LENGTH) size = MAX_LENGTH;
|
||||||
|
|
||||||
|
manager->interval_ends_ =
|
||||||
|
(int*)WebPSafeMalloc(size, sizeof(*manager->interval_ends_));
|
||||||
|
if (manager->interval_ends_ == NULL) {
|
||||||
|
CostManagerClear(manager);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
manager->interval_ends_size_ = size;
|
||||||
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1158,53 +1218,56 @@ static int BackwardReferencesHashChainDistanceOnly(
|
|||||||
const int code = DistanceToPlaneCode(xsize, offset);
|
const int code = DistanceToPlaneCode(xsize, offset);
|
||||||
const double offset_cost = GetDistanceCost(cost_model, code);
|
const double offset_cost = GetDistanceCost(cost_model, code);
|
||||||
const int first_i = i;
|
const int first_i = i;
|
||||||
int j_max;
|
int j_max = 0, interval_ends_index = 0;
|
||||||
|
const int is_offset_zero = (offset_cost == 0.);
|
||||||
|
|
||||||
if (offset_cost == 0.) {
|
if (!is_offset_zero) {
|
||||||
|
j_max = (int)ceil(
|
||||||
|
(cost_manager->max_cost_cache_ - cost_manager->min_cost_cache_) /
|
||||||
|
offset_cost);
|
||||||
|
if (j_max < 1) {
|
||||||
j_max = 1;
|
j_max = 1;
|
||||||
} else {
|
} else if (j_max > cost_manager->interval_ends_size_ - 1) {
|
||||||
j_max = (int)ceil(cost_manager->min_cost_cache_ / offset_cost);
|
// This could only happen in the case of MAX_LENGTH.
|
||||||
assert(j_max >= 1);
|
j_max = cost_manager->interval_ends_size_ - 1;
|
||||||
}
|
}
|
||||||
|
} // else j_max is unused anyway.
|
||||||
|
|
||||||
// With the values we currently use for the model, offset_cost is
|
// Instead of considering all contributions from a pixel i by calling:
|
||||||
// between 10 and 20 and j_max ends up at 2 or 3.
|
// PushInterval(cost_manager, prev_cost + offset_cost, i, len);
|
||||||
if (j_max > MAX_LENGTH - 1) j_max = MAX_LENGTH - 1;
|
// we optimize these contributions in case offset_cost stays the same for
|
||||||
|
// consecutive pixels. This describes a set of pixels similar to a
|
||||||
// The cost to get to pixel i+k is:
|
// previous set (e.g. constant color regions).
|
||||||
// prev_cost + best weight of path going from i to l.
|
|
||||||
// For the last cost, if the path were composed of several segments
|
|
||||||
// (like [i,j), [j,k), [k,l) ), its cost would be:
|
|
||||||
// offset cost + manager->cost_cache_[j-i] +
|
|
||||||
// offset cost + manager->cost_cache_[k-j] +
|
|
||||||
// offset cost + manager->cost_cache_[l-j]
|
|
||||||
// As the offset_cost is close to the values in cost_cache_ (both are the
|
|
||||||
// cost to store a number, whether it's an offset or a length), the best
|
|
||||||
// paths are therefore the ones involving one hop.
|
|
||||||
for (; i < pix_count - 1; ++i) {
|
for (; i < pix_count - 1; ++i) {
|
||||||
int offset_next, len_next;
|
int offset_next, len_next;
|
||||||
double distance_cost;
|
|
||||||
|
|
||||||
prev_cost = cost_manager->costs_[i - 1];
|
prev_cost = cost_manager->costs_[i - 1];
|
||||||
distance_cost = prev_cost + offset_cost;
|
|
||||||
|
|
||||||
if (len != MAX_LENGTH || i == first_i) {
|
if (is_offset_zero) {
|
||||||
|
// No optimization can be made so we just push all of the
|
||||||
|
// contributions from i.
|
||||||
|
PushInterval(cost_manager, prev_cost, i, len);
|
||||||
|
} else {
|
||||||
|
// j_max is chosen as the smallest j such that:
|
||||||
|
// max of cost_cache_ < j*offset cost + min of cost_cache_
|
||||||
|
// Therefore, the pixel influenced by i-j_max, cannot be influenced
|
||||||
|
// by i. Only the costs after the end of what i contributed need to be
|
||||||
|
// updated. cost_manager->interval_ends_ is a circular buffer that
|
||||||
|
// stores those ends.
|
||||||
|
const double distance_cost = prev_cost + offset_cost;
|
||||||
|
int j = cost_manager->interval_ends_[interval_ends_index];
|
||||||
|
if (i - first_i <= j_max ||
|
||||||
|
!IsCostCacheIntervalWritable(j, i + len)) {
|
||||||
PushInterval(cost_manager, distance_cost, i, len);
|
PushInterval(cost_manager, distance_cost, i, len);
|
||||||
} else {
|
} else {
|
||||||
int j;
|
for (; j < i + len; ++j) {
|
||||||
// The pixel at i + MAX_LENGTH - 1 is influenced by all the pixels
|
UpdateCost(cost_manager, j, i, distance_cost);
|
||||||
// before at i + MAX_LENGTH - j. Then again, the distance cost
|
|
||||||
// provided by those is increased by offset_cost*j. Therefore, only
|
|
||||||
// the pixels providing a length cost inferior to offset_cost*j can be
|
|
||||||
// considered.
|
|
||||||
// As we know this cost is at least cost_manager->min_cost_cache_:
|
|
||||||
// therefore only the first j_max pixels from i influence
|
|
||||||
// i + MAX_LENGTH - 1.
|
|
||||||
// Reciprocally, pixel i only influences the last j_max pixels.
|
|
||||||
for (j = 1; j <= j_max; ++j) {
|
|
||||||
UpdateCost(cost_manager, i + MAX_LENGTH - j, i, distance_cost);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Store the new end in the circular buffer.
|
||||||
|
assert(interval_ends_index < cost_manager->interval_ends_size_);
|
||||||
|
cost_manager->interval_ends_[interval_ends_index] = i + len;
|
||||||
|
if (++interval_ends_index > j_max) interval_ends_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Check whether i is the last pixel to consider, as it is handled
|
// Check whether i is the last pixel to consider, as it is handled
|
||||||
// differently.
|
// differently.
|
||||||
|
Loading…
Reference in New Issue
Block a user