Coverage Report

Created: 2026-07-25 07:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libjxl/lib/jxl/modular/encoding/enc_ma.cc
Line
Count
Source
1
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
2
//
3
// Use of this source code is governed by a BSD-style
4
// license that can be found in the LICENSE file.
5
6
#include "lib/jxl/modular/encoding/enc_ma.h"
7
8
#include <algorithm>
9
#include <cstdint>
10
#include <cstdlib>
11
#include <cstring>
12
#include <limits>
13
#include <numeric>
14
#include <queue>
15
#include <vector>
16
17
#include "lib/jxl/ans_params.h"
18
#include "lib/jxl/base/bits.h"
19
#include "lib/jxl/base/common.h"
20
#include "lib/jxl/base/compiler_specific.h"
21
#include "lib/jxl/base/status.h"
22
#include "lib/jxl/dec_ans.h"
23
#include "lib/jxl/modular/encoding/dec_ma.h"
24
#include "lib/jxl/modular/encoding/ma_common.h"
25
#include "lib/jxl/modular/modular_image.h"
26
27
#undef HWY_TARGET_INCLUDE
28
#define HWY_TARGET_INCLUDE "lib/jxl/modular/encoding/enc_ma.cc"
29
#include <hwy/foreach_target.h>
30
#include <hwy/highway.h>
31
32
#include "lib/jxl/base/fast_math-inl.h"
33
#include "lib/jxl/base/random.h"
34
#include "lib/jxl/enc_ans.h"
35
#include "lib/jxl/modular/encoding/context_predict.h"
36
#include "lib/jxl/modular/options.h"
37
#include "lib/jxl/pack_signed.h"
38
HWY_BEFORE_NAMESPACE();
39
namespace jxl {
40
namespace HWY_NAMESPACE {
41
42
// These templates are not found via ADL.
43
using hwy::HWY_NAMESPACE::Eq;
44
using hwy::HWY_NAMESPACE::IfThenElse;
45
using hwy::HWY_NAMESPACE::Lt;
46
using hwy::HWY_NAMESPACE::Max;
47
48
const HWY_FULL(float) df;
49
const HWY_FULL(int32_t) di;
50
// Pad array size for use in EstimateBits.
51
15.2M
size_t Padded(size_t x) {
52
15.2M
  JXL_DASSERT(Lanes(df) == Lanes(di));
53
15.2M
  return RoundUpTo(x, Lanes(df));
54
15.2M
}
Unexecuted instantiation: jxl::N_SSE4::Padded(unsigned long)
jxl::N_AVX2::Padded(unsigned long)
Line
Count
Source
51
15.2M
size_t Padded(size_t x) {
52
15.2M
  JXL_DASSERT(Lanes(df) == Lanes(di));
53
15.2M
  return RoundUpTo(x, Lanes(df));
54
15.2M
}
Unexecuted instantiation: jxl::N_AVX3::Padded(unsigned long)
Unexecuted instantiation: jxl::N_AVX3_ZEN4::Padded(unsigned long)
Unexecuted instantiation: jxl::N_AVX3_SPR::Padded(unsigned long)
Unexecuted instantiation: jxl::N_SSE2::Padded(unsigned long)
55
56
// Compute entropy of the histogram, taking into account the minimum probability
57
// for symbols with non-zero counts.
58
15.0M
float EstimateBits(const int32_t *counts, size_t num_symbols) {
59
15.0M
  JXL_DASSERT(num_symbols == Padded(num_symbols));
60
15.0M
  auto total_v = Zero(di);
61
85.7M
  for (size_t i = 0; i < num_symbols; i += Lanes(di)) {
62
70.6M
    const auto counts_iv = LoadU(di, &counts[i]);
63
70.6M
    total_v = Add(total_v, counts_iv);
64
70.6M
  }
65
15.0M
  total_v = SumOfLanes(di, total_v);
66
67
15.0M
  const auto minprob = Set(df, 1.0f / ANS_TAB_SIZE);
68
15.0M
  const auto inv_total = Set(df, 1.0f / GetLane(total_v));
69
15.0M
  auto bits_lanes = Zero(df);
70
85.7M
  for (size_t i = 0; i < num_symbols; i += Lanes(df)) {
71
70.6M
    const auto counts_iv = LoadU(di, &counts[i]);
72
70.6M
    const auto counts_fv = ConvertTo(df, counts_iv);
73
70.6M
    const auto probs = Mul(counts_fv, inv_total);
74
70.6M
    const auto mprobs = Max(probs, minprob);
75
70.6M
    const auto nbps = IfThenZeroElse(Eq(counts_iv, total_v),
76
70.6M
                                     BitCast(di, FastLog2f(df, mprobs)));
77
70.6M
    bits_lanes = Sub(bits_lanes, Mul(counts_fv, BitCast(df, nbps)));
78
70.6M
  }
79
15.0M
  return GetLane(SumOfLanes(df, bits_lanes));
80
15.0M
}
Unexecuted instantiation: jxl::N_SSE4::EstimateBits(int const*, unsigned long)
jxl::N_AVX2::EstimateBits(int const*, unsigned long)
Line
Count
Source
58
15.0M
float EstimateBits(const int32_t *counts, size_t num_symbols) {
59
15.0M
  JXL_DASSERT(num_symbols == Padded(num_symbols));
60
15.0M
  auto total_v = Zero(di);
61
85.7M
  for (size_t i = 0; i < num_symbols; i += Lanes(di)) {
62
70.6M
    const auto counts_iv = LoadU(di, &counts[i]);
63
70.6M
    total_v = Add(total_v, counts_iv);
64
70.6M
  }
65
15.0M
  total_v = SumOfLanes(di, total_v);
66
67
15.0M
  const auto minprob = Set(df, 1.0f / ANS_TAB_SIZE);
68
15.0M
  const auto inv_total = Set(df, 1.0f / GetLane(total_v));
69
15.0M
  auto bits_lanes = Zero(df);
70
85.7M
  for (size_t i = 0; i < num_symbols; i += Lanes(df)) {
71
70.6M
    const auto counts_iv = LoadU(di, &counts[i]);
72
70.6M
    const auto counts_fv = ConvertTo(df, counts_iv);
73
70.6M
    const auto probs = Mul(counts_fv, inv_total);
74
70.6M
    const auto mprobs = Max(probs, minprob);
75
70.6M
    const auto nbps = IfThenZeroElse(Eq(counts_iv, total_v),
76
70.6M
                                     BitCast(di, FastLog2f(df, mprobs)));
77
70.6M
    bits_lanes = Sub(bits_lanes, Mul(counts_fv, BitCast(df, nbps)));
78
70.6M
  }
79
15.0M
  return GetLane(SumOfLanes(df, bits_lanes));
80
15.0M
}
Unexecuted instantiation: jxl::N_AVX3::EstimateBits(int const*, unsigned long)
Unexecuted instantiation: jxl::N_AVX3_ZEN4::EstimateBits(int const*, unsigned long)
Unexecuted instantiation: jxl::N_AVX3_SPR::EstimateBits(int const*, unsigned long)
Unexecuted instantiation: jxl::N_SSE2::EstimateBits(int const*, unsigned long)
81
82
void MakeSplitNode(size_t pos, int property, int splitval, Predictor lpred,
83
65.4k
                   int64_t loff, Predictor rpred, int64_t roff, Tree *tree) {
84
  // Note that the tree splits on *strictly greater*.
85
65.4k
  (*tree)[pos].lchild = tree->size();
86
65.4k
  (*tree)[pos].rchild = tree->size() + 1;
87
65.4k
  (*tree)[pos].splitval = splitval;
88
65.4k
  (*tree)[pos].property = property;
89
65.4k
  tree->emplace_back();
90
65.4k
  tree->back().property = -1;
91
65.4k
  tree->back().predictor = rpred;
92
65.4k
  tree->back().predictor_offset = roff;
93
65.4k
  tree->back().multiplier = 1;
94
65.4k
  tree->emplace_back();
95
65.4k
  tree->back().property = -1;
96
65.4k
  tree->back().predictor = lpred;
97
65.4k
  tree->back().predictor_offset = loff;
98
65.4k
  tree->back().multiplier = 1;
99
65.4k
}
Unexecuted instantiation: jxl::N_SSE4::MakeSplitNode(unsigned long, int, int, jxl::Predictor, long, jxl::Predictor, long, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)
jxl::N_AVX2::MakeSplitNode(unsigned long, int, int, jxl::Predictor, long, jxl::Predictor, long, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)
Line
Count
Source
83
65.4k
                   int64_t loff, Predictor rpred, int64_t roff, Tree *tree) {
84
  // Note that the tree splits on *strictly greater*.
85
65.4k
  (*tree)[pos].lchild = tree->size();
86
65.4k
  (*tree)[pos].rchild = tree->size() + 1;
87
65.4k
  (*tree)[pos].splitval = splitval;
88
65.4k
  (*tree)[pos].property = property;
89
65.4k
  tree->emplace_back();
90
65.4k
  tree->back().property = -1;
91
65.4k
  tree->back().predictor = rpred;
92
65.4k
  tree->back().predictor_offset = roff;
93
65.4k
  tree->back().multiplier = 1;
94
65.4k
  tree->emplace_back();
95
65.4k
  tree->back().property = -1;
96
65.4k
  tree->back().predictor = lpred;
97
65.4k
  tree->back().predictor_offset = loff;
98
65.4k
  tree->back().multiplier = 1;
99
65.4k
}
Unexecuted instantiation: jxl::N_AVX3::MakeSplitNode(unsigned long, int, int, jxl::Predictor, long, jxl::Predictor, long, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)
Unexecuted instantiation: jxl::N_AVX3_ZEN4::MakeSplitNode(unsigned long, int, int, jxl::Predictor, long, jxl::Predictor, long, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)
Unexecuted instantiation: jxl::N_AVX3_SPR::MakeSplitNode(unsigned long, int, int, jxl::Predictor, long, jxl::Predictor, long, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)
Unexecuted instantiation: jxl::N_SSE2::MakeSplitNode(unsigned long, int, int, jxl::Predictor, long, jxl::Predictor, long, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)
100
101
enum class IntersectionType { kNone, kPartial, kInside };
102
IntersectionType BoxIntersects(StaticPropRange needle, StaticPropRange haystack,
103
133k
                               uint32_t &partial_axis, uint32_t &partial_val) {
104
133k
  bool partial = false;
105
399k
  for (size_t i = 0; i < kNumStaticProperties; i++) {
106
266k
    if (haystack[i][0] >= needle[i][1]) {
107
0
      return IntersectionType::kNone;
108
0
    }
109
266k
    if (haystack[i][1] <= needle[i][0]) {
110
0
      return IntersectionType::kNone;
111
0
    }
112
266k
    if (haystack[i][0] <= needle[i][0] && haystack[i][1] >= needle[i][1]) {
113
266k
      continue;
114
266k
    }
115
0
    partial = true;
116
0
    partial_axis = i;
117
0
    if (haystack[i][0] > needle[i][0] && haystack[i][0] < needle[i][1]) {
118
0
      partial_val = haystack[i][0] - 1;
119
0
    } else {
120
0
      JXL_DASSERT(haystack[i][1] > needle[i][0] &&
121
0
                  haystack[i][1] < needle[i][1]);
122
0
      partial_val = haystack[i][1] - 1;
123
0
    }
124
0
  }
125
133k
  return partial ? IntersectionType::kPartial : IntersectionType::kInside;
126
133k
}
Unexecuted instantiation: jxl::N_SSE4::BoxIntersects(std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, unsigned int&, unsigned int&)
jxl::N_AVX2::BoxIntersects(std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, unsigned int&, unsigned int&)
Line
Count
Source
103
133k
                               uint32_t &partial_axis, uint32_t &partial_val) {
104
133k
  bool partial = false;
105
399k
  for (size_t i = 0; i < kNumStaticProperties; i++) {
106
266k
    if (haystack[i][0] >= needle[i][1]) {
107
0
      return IntersectionType::kNone;
108
0
    }
109
266k
    if (haystack[i][1] <= needle[i][0]) {
110
0
      return IntersectionType::kNone;
111
0
    }
112
266k
    if (haystack[i][0] <= needle[i][0] && haystack[i][1] >= needle[i][1]) {
113
266k
      continue;
114
266k
    }
115
0
    partial = true;
116
0
    partial_axis = i;
117
0
    if (haystack[i][0] > needle[i][0] && haystack[i][0] < needle[i][1]) {
118
0
      partial_val = haystack[i][0] - 1;
119
0
    } else {
120
0
      JXL_DASSERT(haystack[i][1] > needle[i][0] &&
121
0
                  haystack[i][1] < needle[i][1]);
122
0
      partial_val = haystack[i][1] - 1;
123
0
    }
124
0
  }
125
133k
  return partial ? IntersectionType::kPartial : IntersectionType::kInside;
126
133k
}
Unexecuted instantiation: jxl::N_AVX3::BoxIntersects(std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, unsigned int&, unsigned int&)
Unexecuted instantiation: jxl::N_AVX3_ZEN4::BoxIntersects(std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, unsigned int&, unsigned int&)
Unexecuted instantiation: jxl::N_AVX3_SPR::BoxIntersects(std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, unsigned int&, unsigned int&)
Unexecuted instantiation: jxl::N_SSE2::BoxIntersects(std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, unsigned int&, unsigned int&)
127
128
template<bool S>
129
void SplitTreeSamples(TreeSamples &tree_samples, size_t begin, size_t pos,
130
65.4k
                      size_t end, size_t prop, uint32_t val) {
131
65.4k
  size_t begin_pos = begin;
132
65.4k
  size_t end_pos = pos;
133
7.09M
  do {
134
18.1M
    while (begin_pos < pos &&
135
18.1M
           tree_samples.Property<S>(prop, begin_pos) <= val) {
136
11.0M
      ++begin_pos;
137
11.0M
    }
138
18.6M
    while (end_pos < end && tree_samples.Property<S>(prop, end_pos) > val) {
139
11.5M
      ++end_pos;
140
11.5M
    }
141
7.09M
    if (begin_pos < pos && end_pos < end) {
142
7.08M
      tree_samples.Swap(begin_pos, end_pos);
143
7.08M
    }
144
7.09M
    ++begin_pos;
145
7.09M
    ++end_pos;
146
7.09M
  } while (begin_pos < pos && end_pos < end);
147
65.4k
}
Unexecuted instantiation: void jxl::N_SSE4::SplitTreeSamples<true>(jxl::TreeSamples&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned int)
Unexecuted instantiation: void jxl::N_SSE4::SplitTreeSamples<false>(jxl::TreeSamples&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned int)
void jxl::N_AVX2::SplitTreeSamples<true>(jxl::TreeSamples&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned int)
Line
Count
Source
130
7.63k
                      size_t end, size_t prop, uint32_t val) {
131
7.63k
  size_t begin_pos = begin;
132
7.63k
  size_t end_pos = pos;
133
910k
  do {
134
3.66M
    while (begin_pos < pos &&
135
3.66M
           tree_samples.Property<S>(prop, begin_pos) <= val) {
136
2.75M
      ++begin_pos;
137
2.75M
    }
138
3.69M
    while (end_pos < end && tree_samples.Property<S>(prop, end_pos) > val) {
139
2.78M
      ++end_pos;
140
2.78M
    }
141
910k
    if (begin_pos < pos && end_pos < end) {
142
906k
      tree_samples.Swap(begin_pos, end_pos);
143
906k
    }
144
910k
    ++begin_pos;
145
910k
    ++end_pos;
146
910k
  } while (begin_pos < pos && end_pos < end);
147
7.63k
}
void jxl::N_AVX2::SplitTreeSamples<false>(jxl::TreeSamples&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned int)
Line
Count
Source
130
57.8k
                      size_t end, size_t prop, uint32_t val) {
131
57.8k
  size_t begin_pos = begin;
132
57.8k
  size_t end_pos = pos;
133
6.18M
  do {
134
14.4M
    while (begin_pos < pos &&
135
14.4M
           tree_samples.Property<S>(prop, begin_pos) <= val) {
136
8.25M
      ++begin_pos;
137
8.25M
    }
138
14.9M
    while (end_pos < end && tree_samples.Property<S>(prop, end_pos) > val) {
139
8.79M
      ++end_pos;
140
8.79M
    }
141
6.18M
    if (begin_pos < pos && end_pos < end) {
142
6.17M
      tree_samples.Swap(begin_pos, end_pos);
143
6.17M
    }
144
6.18M
    ++begin_pos;
145
6.18M
    ++end_pos;
146
6.18M
  } while (begin_pos < pos && end_pos < end);
147
57.8k
}
Unexecuted instantiation: void jxl::N_AVX3::SplitTreeSamples<true>(jxl::TreeSamples&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned int)
Unexecuted instantiation: void jxl::N_AVX3::SplitTreeSamples<false>(jxl::TreeSamples&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned int)
Unexecuted instantiation: void jxl::N_AVX3_ZEN4::SplitTreeSamples<true>(jxl::TreeSamples&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned int)
Unexecuted instantiation: void jxl::N_AVX3_ZEN4::SplitTreeSamples<false>(jxl::TreeSamples&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned int)
Unexecuted instantiation: void jxl::N_AVX3_SPR::SplitTreeSamples<true>(jxl::TreeSamples&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned int)
Unexecuted instantiation: void jxl::N_AVX3_SPR::SplitTreeSamples<false>(jxl::TreeSamples&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned int)
Unexecuted instantiation: void jxl::N_SSE2::SplitTreeSamples<true>(jxl::TreeSamples&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned int)
Unexecuted instantiation: void jxl::N_SSE2::SplitTreeSamples<false>(jxl::TreeSamples&, unsigned long, unsigned long, unsigned long, unsigned long, unsigned int)
148
149
template <bool S>
150
void CollectExtraBitsIncrease(TreeSamples &tree_samples,
151
                              const std::vector<ResidualToken> &rtokens,
152
                              std::vector<int> &count_increase,
153
                              std::vector<size_t> &extra_bits_increase,
154
                              size_t begin, size_t end, size_t prop_idx,
155
911k
                              size_t max_symbols) {
156
300M
  for (size_t i2 = begin; i2 < end; i2++) {
157
299M
    const ResidualToken &rt = rtokens[i2];
158
299M
    size_t cnt = tree_samples.Count(i2);
159
299M
    size_t p = tree_samples.Property<S>(prop_idx, i2);
160
299M
    size_t sym = rt.tok;
161
299M
    size_t ebi = rt.nbits * cnt;
162
299M
    count_increase[p * max_symbols + sym] += cnt;
163
299M
    extra_bits_increase[p] += ebi;
164
299M
  }
165
911k
}
Unexecuted instantiation: void jxl::N_SSE4::CollectExtraBitsIncrease<true>(jxl::TreeSamples&, std::__1::vector<jxl::ResidualToken, std::__1::allocator<jxl::ResidualToken> > const&, std::__1::vector<int, std::__1::allocator<int> >&, std::__1::vector<unsigned long, std::__1::allocator<unsigned long> >&, unsigned long, unsigned long, unsigned long, unsigned long)
Unexecuted instantiation: void jxl::N_SSE4::CollectExtraBitsIncrease<false>(jxl::TreeSamples&, std::__1::vector<jxl::ResidualToken, std::__1::allocator<jxl::ResidualToken> > const&, std::__1::vector<int, std::__1::allocator<int> >&, std::__1::vector<unsigned long, std::__1::allocator<unsigned long> >&, unsigned long, unsigned long, unsigned long, unsigned long)
void jxl::N_AVX2::CollectExtraBitsIncrease<true>(jxl::TreeSamples&, std::__1::vector<jxl::ResidualToken, std::__1::allocator<jxl::ResidualToken> > const&, std::__1::vector<int, std::__1::allocator<int> >&, std::__1::vector<unsigned long, std::__1::allocator<unsigned long> >&, unsigned long, unsigned long, unsigned long, unsigned long)
Line
Count
Source
155
260k
                              size_t max_symbols) {
156
85.7M
  for (size_t i2 = begin; i2 < end; i2++) {
157
85.4M
    const ResidualToken &rt = rtokens[i2];
158
85.4M
    size_t cnt = tree_samples.Count(i2);
159
85.4M
    size_t p = tree_samples.Property<S>(prop_idx, i2);
160
85.4M
    size_t sym = rt.tok;
161
85.4M
    size_t ebi = rt.nbits * cnt;
162
85.4M
    count_increase[p * max_symbols + sym] += cnt;
163
85.4M
    extra_bits_increase[p] += ebi;
164
85.4M
  }
165
260k
}
void jxl::N_AVX2::CollectExtraBitsIncrease<false>(jxl::TreeSamples&, std::__1::vector<jxl::ResidualToken, std::__1::allocator<jxl::ResidualToken> > const&, std::__1::vector<int, std::__1::allocator<int> >&, std::__1::vector<unsigned long, std::__1::allocator<unsigned long> >&, unsigned long, unsigned long, unsigned long, unsigned long)
Line
Count
Source
155
651k
                              size_t max_symbols) {
156
214M
  for (size_t i2 = begin; i2 < end; i2++) {
157
213M
    const ResidualToken &rt = rtokens[i2];
158
213M
    size_t cnt = tree_samples.Count(i2);
159
213M
    size_t p = tree_samples.Property<S>(prop_idx, i2);
160
213M
    size_t sym = rt.tok;
161
213M
    size_t ebi = rt.nbits * cnt;
162
213M
    count_increase[p * max_symbols + sym] += cnt;
163
213M
    extra_bits_increase[p] += ebi;
164
213M
  }
165
651k
}
Unexecuted instantiation: void jxl::N_AVX3::CollectExtraBitsIncrease<true>(jxl::TreeSamples&, std::__1::vector<jxl::ResidualToken, std::__1::allocator<jxl::ResidualToken> > const&, std::__1::vector<int, std::__1::allocator<int> >&, std::__1::vector<unsigned long, std::__1::allocator<unsigned long> >&, unsigned long, unsigned long, unsigned long, unsigned long)
Unexecuted instantiation: void jxl::N_AVX3::CollectExtraBitsIncrease<false>(jxl::TreeSamples&, std::__1::vector<jxl::ResidualToken, std::__1::allocator<jxl::ResidualToken> > const&, std::__1::vector<int, std::__1::allocator<int> >&, std::__1::vector<unsigned long, std::__1::allocator<unsigned long> >&, unsigned long, unsigned long, unsigned long, unsigned long)
Unexecuted instantiation: void jxl::N_AVX3_ZEN4::CollectExtraBitsIncrease<true>(jxl::TreeSamples&, std::__1::vector<jxl::ResidualToken, std::__1::allocator<jxl::ResidualToken> > const&, std::__1::vector<int, std::__1::allocator<int> >&, std::__1::vector<unsigned long, std::__1::allocator<unsigned long> >&, unsigned long, unsigned long, unsigned long, unsigned long)
Unexecuted instantiation: void jxl::N_AVX3_ZEN4::CollectExtraBitsIncrease<false>(jxl::TreeSamples&, std::__1::vector<jxl::ResidualToken, std::__1::allocator<jxl::ResidualToken> > const&, std::__1::vector<int, std::__1::allocator<int> >&, std::__1::vector<unsigned long, std::__1::allocator<unsigned long> >&, unsigned long, unsigned long, unsigned long, unsigned long)
Unexecuted instantiation: void jxl::N_AVX3_SPR::CollectExtraBitsIncrease<true>(jxl::TreeSamples&, std::__1::vector<jxl::ResidualToken, std::__1::allocator<jxl::ResidualToken> > const&, std::__1::vector<int, std::__1::allocator<int> >&, std::__1::vector<unsigned long, std::__1::allocator<unsigned long> >&, unsigned long, unsigned long, unsigned long, unsigned long)
Unexecuted instantiation: void jxl::N_AVX3_SPR::CollectExtraBitsIncrease<false>(jxl::TreeSamples&, std::__1::vector<jxl::ResidualToken, std::__1::allocator<jxl::ResidualToken> > const&, std::__1::vector<int, std::__1::allocator<int> >&, std::__1::vector<unsigned long, std::__1::allocator<unsigned long> >&, unsigned long, unsigned long, unsigned long, unsigned long)
Unexecuted instantiation: void jxl::N_SSE2::CollectExtraBitsIncrease<true>(jxl::TreeSamples&, std::__1::vector<jxl::ResidualToken, std::__1::allocator<jxl::ResidualToken> > const&, std::__1::vector<int, std::__1::allocator<int> >&, std::__1::vector<unsigned long, std::__1::allocator<unsigned long> >&, unsigned long, unsigned long, unsigned long, unsigned long)
Unexecuted instantiation: void jxl::N_SSE2::CollectExtraBitsIncrease<false>(jxl::TreeSamples&, std::__1::vector<jxl::ResidualToken, std::__1::allocator<jxl::ResidualToken> > const&, std::__1::vector<int, std::__1::allocator<int> >&, std::__1::vector<unsigned long, std::__1::allocator<unsigned long> >&, unsigned long, unsigned long, unsigned long, unsigned long)
166
167
void FindBestSplit(TreeSamples &tree_samples, float threshold,
168
                   const std::vector<ModularMultiplierInfo> &mul_info,
169
                   StaticPropRange initial_static_prop_range,
170
2.19k
                   float fast_decode_multiplier, Tree *tree) {
171
2.19k
  struct NodeInfo {
172
2.19k
    size_t pos;
173
2.19k
    size_t begin;
174
2.19k
    size_t end;
175
2.19k
    StaticPropRange static_prop_range;
176
2.19k
  };
177
2.19k
  std::vector<NodeInfo> nodes;
178
2.19k
  nodes.push_back(NodeInfo{0, 0, tree_samples.NumDistinctSamples(),
179
2.19k
                           initial_static_prop_range});
180
181
2.19k
  size_t num_predictors = tree_samples.NumPredictors();
182
2.19k
  size_t num_properties = tree_samples.NumProperties();
183
184
  // TODO(veluca): consider parallelizing the search (processing multiple nodes
185
  // at a time).
186
135k
  while (!nodes.empty()) {
187
133k
    size_t pos = nodes.back().pos;
188
133k
    size_t begin = nodes.back().begin;
189
133k
    size_t end = nodes.back().end;
190
191
133k
    StaticPropRange static_prop_range = nodes.back().static_prop_range;
192
133k
    nodes.pop_back();
193
133k
    if (begin == end) continue;
194
195
133k
    struct SplitInfo {
196
133k
      size_t prop = 0;
197
133k
      uint32_t val = 0;
198
133k
      size_t pos = 0;
199
133k
      float lcost = std::numeric_limits<float>::max();
200
133k
      float rcost = std::numeric_limits<float>::max();
201
133k
      Predictor lpred = Predictor::Zero;
202
133k
      Predictor rpred = Predictor::Zero;
203
8.16M
      float Cost() const { return lcost + rcost; }
Unexecuted instantiation: enc_ma.cc:jxl::N_SSE4::FindBestSplit(jxl::TreeSamples&, float, std::__1::vector<jxl::ModularMultiplierInfo, std::__1::allocator<jxl::ModularMultiplierInfo> > const&, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, float, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)::SplitInfo::Cost() const
enc_ma.cc:jxl::N_AVX2::FindBestSplit(jxl::TreeSamples&, float, std::__1::vector<jxl::ModularMultiplierInfo, std::__1::allocator<jxl::ModularMultiplierInfo> > const&, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, float, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)::SplitInfo::Cost() const
Line
Count
Source
203
8.16M
      float Cost() const { return lcost + rcost; }
Unexecuted instantiation: enc_ma.cc:jxl::N_AVX3::FindBestSplit(jxl::TreeSamples&, float, std::__1::vector<jxl::ModularMultiplierInfo, std::__1::allocator<jxl::ModularMultiplierInfo> > const&, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, float, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)::SplitInfo::Cost() const
Unexecuted instantiation: enc_ma.cc:jxl::N_AVX3_ZEN4::FindBestSplit(jxl::TreeSamples&, float, std::__1::vector<jxl::ModularMultiplierInfo, std::__1::allocator<jxl::ModularMultiplierInfo> > const&, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, float, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)::SplitInfo::Cost() const
Unexecuted instantiation: enc_ma.cc:jxl::N_AVX3_SPR::FindBestSplit(jxl::TreeSamples&, float, std::__1::vector<jxl::ModularMultiplierInfo, std::__1::allocator<jxl::ModularMultiplierInfo> > const&, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, float, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)::SplitInfo::Cost() const
Unexecuted instantiation: enc_ma.cc:jxl::N_SSE2::FindBestSplit(jxl::TreeSamples&, float, std::__1::vector<jxl::ModularMultiplierInfo, std::__1::allocator<jxl::ModularMultiplierInfo> > const&, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, float, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)::SplitInfo::Cost() const
204
133k
    };
205
206
133k
    SplitInfo best_split_static_constant;
207
133k
    SplitInfo best_split_static;
208
133k
    SplitInfo best_split_nonstatic;
209
133k
    SplitInfo best_split_nowp;
210
211
133k
    JXL_DASSERT(begin <= end);
212
133k
    JXL_DASSERT(end <= tree_samples.NumDistinctSamples());
213
214
    // Compute the maximum token in the range.
215
133k
    size_t max_symbols = 0;
216
266k
    for (size_t pred = 0; pred < num_predictors; pred++) {
217
42.9M
      for (size_t i = begin; i < end; i++) {
218
42.8M
        uint32_t tok = tree_samples.Token(pred, i);
219
42.8M
        max_symbols = max_symbols > tok + 1 ? max_symbols : tok + 1;
220
42.8M
      }
221
133k
    }
222
133k
    max_symbols = Padded(max_symbols);
223
133k
    std::vector<int32_t> counts(max_symbols * num_predictors);
224
133k
    std::vector<uint32_t> tot_extra_bits(num_predictors);
225
266k
    for (size_t pred = 0; pred < num_predictors; pred++) {
226
133k
      size_t extra_bits = 0;
227
133k
      const std::vector<ResidualToken>& rtokens = tree_samples.RTokens(pred);
228
42.9M
      for (size_t i = begin; i < end; i++) {
229
42.8M
        const ResidualToken& rt = rtokens[i];
230
42.8M
        size_t count = tree_samples.Count(i);
231
42.8M
        size_t eb = rt.nbits * count;
232
42.8M
        counts[pred * max_symbols + rt.tok] += count;
233
42.8M
        extra_bits += eb;
234
42.8M
      }
235
133k
      tot_extra_bits[pred] = extra_bits;
236
133k
    }
237
238
133k
    float base_bits;
239
133k
    {
240
133k
      size_t pred = tree_samples.PredictorIndex((*tree)[pos].predictor);
241
133k
      base_bits =
242
133k
          EstimateBits(counts.data() + pred * max_symbols, max_symbols) +
243
133k
          tot_extra_bits[pred];
244
133k
    }
245
246
133k
    SplitInfo *best = &best_split_nonstatic;
247
248
133k
    SplitInfo forced_split;
249
    // The multiplier ranges cut halfway through the current ranges of static
250
    // properties. We do this even if the current node is not a leaf, to
251
    // minimize the number of nodes in the resulting tree.
252
133k
    for (const auto &mmi : mul_info) {
253
133k
      uint32_t axis;
254
133k
      uint32_t val;
255
133k
      IntersectionType t =
256
133k
          BoxIntersects(static_prop_range, mmi.range, axis, val);
257
133k
      if (t == IntersectionType::kNone) continue;
258
133k
      if (t == IntersectionType::kInside) {
259
133k
        (*tree)[pos].multiplier = mmi.multiplier;
260
133k
        break;
261
133k
      }
262
0
      if (t == IntersectionType::kPartial) {
263
0
        JXL_DASSERT(axis < kNumStaticProperties);
264
0
        forced_split.val = tree_samples.QuantizeStaticProperty(axis, val);
265
0
        forced_split.prop = axis;
266
0
        forced_split.lcost = forced_split.rcost = base_bits / 2 - threshold;
267
0
        forced_split.lpred = forced_split.rpred = (*tree)[pos].predictor;
268
0
        best = &forced_split;
269
0
        best->pos = begin;
270
0
        JXL_DASSERT(best->prop == tree_samples.PropertyFromIndex(best->prop));
271
0
        if (best->prop < tree_samples.NumStaticProps()) {
272
0
        for (size_t x = begin; x < end; x++) {
273
0
          if (tree_samples.Property<true>(best->prop, x) <= best->val) {
274
0
            best->pos++;
275
0
          }
276
0
        }
277
0
      } else {
278
0
        size_t prop = best->prop - tree_samples.NumStaticProps();
279
0
        for (size_t x = begin; x < end; x++) {
280
0
          if (tree_samples.Property<false>(prop, x) <= best->val) {
281
0
            best->pos++;
282
0
          }
283
0
        }
284
0
      }
285
0
        break;
286
0
      }
287
0
    }
288
289
133k
    if (best != &forced_split) {
290
133k
      std::vector<int> prop_value_used_count;
291
133k
      std::vector<int> count_increase;
292
133k
      std::vector<size_t> extra_bits_increase;
293
      // For each property, compute which of its values are used, and what
294
      // tokens correspond to those usages. Then, iterate through the values,
295
      // and compute the entropy of each side of the split (of the form `prop >
296
      // threshold`). Finally, find the split that minimizes the cost.
297
133k
      struct CostInfo {
298
133k
        float cost = std::numeric_limits<float>::max();
299
133k
        float extra_cost = 0;
300
14.9M
        float Cost() const { return cost + extra_cost; }
Unexecuted instantiation: enc_ma.cc:jxl::N_SSE4::FindBestSplit(jxl::TreeSamples&, float, std::__1::vector<jxl::ModularMultiplierInfo, std::__1::allocator<jxl::ModularMultiplierInfo> > const&, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, float, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)::CostInfo::Cost() const
enc_ma.cc:jxl::N_AVX2::FindBestSplit(jxl::TreeSamples&, float, std::__1::vector<jxl::ModularMultiplierInfo, std::__1::allocator<jxl::ModularMultiplierInfo> > const&, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, float, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)::CostInfo::Cost() const
Line
Count
Source
300
14.9M
        float Cost() const { return cost + extra_cost; }
Unexecuted instantiation: enc_ma.cc:jxl::N_AVX3::FindBestSplit(jxl::TreeSamples&, float, std::__1::vector<jxl::ModularMultiplierInfo, std::__1::allocator<jxl::ModularMultiplierInfo> > const&, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, float, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)::CostInfo::Cost() const
Unexecuted instantiation: enc_ma.cc:jxl::N_AVX3_ZEN4::FindBestSplit(jxl::TreeSamples&, float, std::__1::vector<jxl::ModularMultiplierInfo, std::__1::allocator<jxl::ModularMultiplierInfo> > const&, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, float, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)::CostInfo::Cost() const
Unexecuted instantiation: enc_ma.cc:jxl::N_AVX3_SPR::FindBestSplit(jxl::TreeSamples&, float, std::__1::vector<jxl::ModularMultiplierInfo, std::__1::allocator<jxl::ModularMultiplierInfo> > const&, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, float, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)::CostInfo::Cost() const
Unexecuted instantiation: enc_ma.cc:jxl::N_SSE2::FindBestSplit(jxl::TreeSamples&, float, std::__1::vector<jxl::ModularMultiplierInfo, std::__1::allocator<jxl::ModularMultiplierInfo> > const&, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, float, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)::CostInfo::Cost() const
301
133k
        Predictor pred;  // will be uninitialized in some cases, but never used.
302
133k
      };
303
133k
      std::vector<CostInfo> costs_l;
304
133k
      std::vector<CostInfo> costs_r;
305
306
133k
      std::vector<int32_t> counts_above(max_symbols);
307
133k
      std::vector<int32_t> counts_below(max_symbols);
308
309
      // The lower the threshold, the higher the expected noisiness of the
310
      // estimate. Thus, discourage changing predictors.
311
133k
      float change_pred_penalty = 800.0f / (100.0f + threshold);
312
1.04M
      for (size_t prop = 0; prop < num_properties && base_bits > threshold;
313
911k
           prop++) {
314
911k
        costs_l.clear();
315
911k
        costs_r.clear();
316
911k
        size_t prop_size = tree_samples.NumPropertyValues(prop);
317
911k
        if (extra_bits_increase.size() < prop_size) {
318
261k
          count_increase.resize(prop_size * max_symbols);
319
261k
          extra_bits_increase.resize(prop_size);
320
261k
        }
321
        // Clear prop_value_used_count (which cannot be cleared "on the go")
322
911k
        prop_value_used_count.clear();
323
911k
        prop_value_used_count.resize(prop_size);
324
325
911k
        size_t first_used = prop_size;
326
911k
        size_t last_used = 0;
327
328
        // TODO(veluca): consider finding multiple splits along a single
329
        // property at the same time, possibly with a bottom-up approach.
330
911k
        if (prop < tree_samples.NumStaticProps()) {
331
85.7M
          for (size_t i = begin; i < end; i++) {
332
85.4M
            size_t p = tree_samples.Property<true>(prop, i);
333
85.4M
            prop_value_used_count[p]++;
334
85.4M
            last_used = std::max(last_used, p);
335
85.4M
            first_used = std::min(first_used, p);
336
85.4M
          }
337
651k
        } else {
338
651k
          size_t prop_idx = prop - tree_samples.NumStaticProps();
339
214M
          for (size_t i = begin; i < end; i++) {
340
213M
            size_t p = tree_samples.Property<false>(prop_idx, i);
341
213M
            prop_value_used_count[p]++;
342
213M
            last_used = std::max(last_used, p);
343
213M
            first_used = std::min(first_used, p);
344
213M
          }
345
651k
        }
346
911k
        costs_l.resize(last_used - first_used);
347
911k
        costs_r.resize(last_used - first_used);
348
        // For all predictors, compute the right and left costs of each split.
349
1.82M
        for (size_t pred = 0; pred < num_predictors; pred++) {
350
          // Compute cost and histogram increments for each property value.
351
911k
          const std::vector<ResidualToken> &rtokens =
352
911k
              tree_samples.RTokens(pred);
353
911k
          if (prop < tree_samples.NumStaticProps()) {
354
260k
            CollectExtraBitsIncrease<true>(tree_samples, rtokens,
355
260k
                                           count_increase, extra_bits_increase,
356
260k
                                           begin, end, prop, max_symbols);
357
651k
          } else {
358
651k
            CollectExtraBitsIncrease<false>(
359
651k
                tree_samples, rtokens, count_increase, extra_bits_increase,
360
651k
                begin, end, prop - tree_samples.NumStaticProps(), max_symbols);
361
651k
          }
362
911k
          memcpy(counts_above.data(), counts.data() + pred * max_symbols,
363
911k
                 max_symbols * sizeof counts_above[0]);
364
911k
          memset(counts_below.data(), 0, max_symbols * sizeof counts_below[0]);
365
911k
          size_t extra_bits_below = 0;
366
          // Exclude last used: this ensures neither counts_above nor
367
          // counts_below is empty.
368
12.4M
          for (size_t i = first_used; i < last_used; i++) {
369
11.5M
            if (!prop_value_used_count[i]) continue;
370
7.47M
            extra_bits_below += extra_bits_increase[i];
371
            // The increase for this property value has been used, and will not
372
            // be used again: clear it. Also below.
373
7.47M
            extra_bits_increase[i] = 0;
374
287M
            for (size_t sym = 0; sym < max_symbols; sym++) {
375
280M
              counts_above[sym] -= count_increase[i * max_symbols + sym];
376
280M
              counts_below[sym] += count_increase[i * max_symbols + sym];
377
280M
              count_increase[i * max_symbols + sym] = 0;
378
280M
            }
379
7.47M
            float rcost = EstimateBits(counts_above.data(), max_symbols) +
380
7.47M
                          tot_extra_bits[pred] - extra_bits_below;
381
7.47M
            float lcost = EstimateBits(counts_below.data(), max_symbols) +
382
7.47M
                          extra_bits_below;
383
7.47M
            JXL_DASSERT(extra_bits_below <= tot_extra_bits[pred]);
384
7.47M
            float penalty = 0;
385
            // Never discourage moving away from the Weighted predictor.
386
7.47M
            if (tree_samples.PredictorFromIndex(pred) !=
387
7.47M
                    (*tree)[pos].predictor &&
388
0
                (*tree)[pos].predictor != Predictor::Weighted) {
389
0
              penalty = change_pred_penalty;
390
0
            }
391
            // If everything else is equal, disfavour Weighted (slower) and
392
            // favour Zero (faster if it's the only predictor used in a
393
            // group+channel combination)
394
7.47M
            if (tree_samples.PredictorFromIndex(pred) == Predictor::Weighted) {
395
0
              penalty += 1e-8;
396
0
            }
397
7.47M
            if (tree_samples.PredictorFromIndex(pred) == Predictor::Zero) {
398
0
              penalty -= 1e-8;
399
0
            }
400
7.47M
            if (rcost + penalty < costs_r[i - first_used].Cost()) {
401
7.47M
              costs_r[i - first_used].cost = rcost;
402
7.47M
              costs_r[i - first_used].extra_cost = penalty;
403
7.47M
              costs_r[i - first_used].pred =
404
7.47M
                  tree_samples.PredictorFromIndex(pred);
405
7.47M
            }
406
7.47M
            if (lcost + penalty < costs_l[i - first_used].Cost()) {
407
7.47M
              costs_l[i - first_used].cost = lcost;
408
7.47M
              costs_l[i - first_used].extra_cost = penalty;
409
7.47M
              costs_l[i - first_used].pred =
410
7.47M
                  tree_samples.PredictorFromIndex(pred);
411
7.47M
            }
412
7.47M
          }
413
911k
        }
414
        // Iterate through the possible splits and find the one with minimum sum
415
        // of costs of the two sides.
416
911k
        size_t split = begin;
417
12.4M
        for (size_t i = first_used; i < last_used; i++) {
418
11.5M
          if (!prop_value_used_count[i]) continue;
419
7.47M
          split += prop_value_used_count[i];
420
7.47M
          float rcost = costs_r[i - first_used].cost;
421
7.47M
          float lcost = costs_l[i - first_used].cost;
422
423
7.47M
          bool uses_wp = tree_samples.PropertyFromIndex(prop) == kWPProp ||
424
4.10M
                         costs_l[i - first_used].pred == Predictor::Weighted ||
425
4.10M
                         costs_r[i - first_used].pred == Predictor::Weighted;
426
7.47M
          bool zero_entropy_side = rcost == 0 || lcost == 0;
427
428
7.47M
          SplitInfo &best_ref =
429
7.47M
              tree_samples.PropertyFromIndex(prop) < kNumStaticProperties
430
7.47M
                  ? (zero_entropy_side ? best_split_static_constant
431
42.8k
                                       : best_split_static)
432
7.47M
                  : (uses_wp ? best_split_nonstatic : best_split_nowp);
433
7.47M
          if (lcost + rcost < best_ref.Cost()) {
434
1.48M
            best_ref.prop = prop;
435
1.48M
            best_ref.val = i;
436
1.48M
            best_ref.pos = split;
437
1.48M
            best_ref.lcost = lcost;
438
1.48M
            best_ref.lpred = costs_l[i - first_used].pred;
439
1.48M
            best_ref.rcost = rcost;
440
1.48M
            best_ref.rpred = costs_r[i - first_used].pred;
441
1.48M
          }
442
7.47M
        }
443
        // Clear extra_bits_increase and cost_increase for last_used.
444
911k
        extra_bits_increase[last_used] = 0;
445
33.9M
        for (size_t sym = 0; sym < max_symbols; sym++) {
446
33.0M
          count_increase[last_used * max_symbols + sym] = 0;
447
33.0M
        }
448
911k
      }
449
450
      // Try to avoid introducing WP.
451
133k
      if (best_split_nowp.Cost() + threshold < base_bits &&
452
62.6k
          best_split_nowp.Cost() <= fast_decode_multiplier * best->Cost()) {
453
60.7k
        best = &best_split_nowp;
454
60.7k
      }
455
      // Split along static props if possible and not significantly more
456
      // expensive.
457
133k
      if (best_split_static.Cost() + threshold < base_bits &&
458
15.5k
          best_split_static.Cost() <= fast_decode_multiplier * best->Cost()) {
459
7.56k
        best = &best_split_static;
460
7.56k
      }
461
      // Split along static props to create constant nodes if possible.
462
133k
      if (best_split_static_constant.Cost() + threshold < base_bits) {
463
75
        best = &best_split_static_constant;
464
75
      }
465
133k
    }
466
467
133k
    if (best->Cost() + threshold < base_bits) {
468
65.4k
      uint32_t p = tree_samples.PropertyFromIndex(best->prop);
469
65.4k
      pixel_type dequant =
470
65.4k
          tree_samples.UnquantizeProperty(best->prop, best->val);
471
      // Split node and try to split children.
472
65.4k
      MakeSplitNode(pos, p, dequant, best->lpred, 0, best->rpred, 0, tree);
473
      // "Sort" according to winning property
474
65.4k
      if (best->prop < tree_samples.NumStaticProps()) {
475
7.63k
        SplitTreeSamples<true>(tree_samples, begin, best->pos, end, best->prop,
476
7.63k
                               best->val);
477
57.8k
      } else {
478
57.8k
        SplitTreeSamples<false>(tree_samples, begin, best->pos, end,
479
57.8k
                                best->prop - tree_samples.NumStaticProps(),
480
57.8k
                                best->val);
481
57.8k
      }
482
65.4k
      auto new_sp_range = static_prop_range;
483
65.4k
      if (p < kNumStaticProperties) {
484
7.63k
        JXL_DASSERT(static_cast<uint32_t>(dequant + 1) <= new_sp_range[p][1]);
485
7.63k
        new_sp_range[p][1] = dequant + 1;
486
7.63k
        JXL_DASSERT(new_sp_range[p][0] < new_sp_range[p][1]);
487
7.63k
      }
488
65.4k
      nodes.push_back(
489
65.4k
          NodeInfo{(*tree)[pos].rchild, begin, best->pos, new_sp_range});
490
65.4k
      new_sp_range = static_prop_range;
491
65.4k
      if (p < kNumStaticProperties) {
492
7.63k
        JXL_DASSERT(new_sp_range[p][0] <= static_cast<uint32_t>(dequant + 1));
493
7.63k
        new_sp_range[p][0] = dequant + 1;
494
7.63k
        JXL_DASSERT(new_sp_range[p][0] < new_sp_range[p][1]);
495
7.63k
      }
496
65.4k
      nodes.push_back(
497
65.4k
          NodeInfo{(*tree)[pos].lchild, best->pos, end, new_sp_range});
498
65.4k
    }
499
133k
  }
500
2.19k
}
Unexecuted instantiation: jxl::N_SSE4::FindBestSplit(jxl::TreeSamples&, float, std::__1::vector<jxl::ModularMultiplierInfo, std::__1::allocator<jxl::ModularMultiplierInfo> > const&, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, float, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)
jxl::N_AVX2::FindBestSplit(jxl::TreeSamples&, float, std::__1::vector<jxl::ModularMultiplierInfo, std::__1::allocator<jxl::ModularMultiplierInfo> > const&, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, float, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)
Line
Count
Source
170
2.19k
                   float fast_decode_multiplier, Tree *tree) {
171
2.19k
  struct NodeInfo {
172
2.19k
    size_t pos;
173
2.19k
    size_t begin;
174
2.19k
    size_t end;
175
2.19k
    StaticPropRange static_prop_range;
176
2.19k
  };
177
2.19k
  std::vector<NodeInfo> nodes;
178
2.19k
  nodes.push_back(NodeInfo{0, 0, tree_samples.NumDistinctSamples(),
179
2.19k
                           initial_static_prop_range});
180
181
2.19k
  size_t num_predictors = tree_samples.NumPredictors();
182
2.19k
  size_t num_properties = tree_samples.NumProperties();
183
184
  // TODO(veluca): consider parallelizing the search (processing multiple nodes
185
  // at a time).
186
135k
  while (!nodes.empty()) {
187
133k
    size_t pos = nodes.back().pos;
188
133k
    size_t begin = nodes.back().begin;
189
133k
    size_t end = nodes.back().end;
190
191
133k
    StaticPropRange static_prop_range = nodes.back().static_prop_range;
192
133k
    nodes.pop_back();
193
133k
    if (begin == end) continue;
194
195
133k
    struct SplitInfo {
196
133k
      size_t prop = 0;
197
133k
      uint32_t val = 0;
198
133k
      size_t pos = 0;
199
133k
      float lcost = std::numeric_limits<float>::max();
200
133k
      float rcost = std::numeric_limits<float>::max();
201
133k
      Predictor lpred = Predictor::Zero;
202
133k
      Predictor rpred = Predictor::Zero;
203
133k
      float Cost() const { return lcost + rcost; }
204
133k
    };
205
206
133k
    SplitInfo best_split_static_constant;
207
133k
    SplitInfo best_split_static;
208
133k
    SplitInfo best_split_nonstatic;
209
133k
    SplitInfo best_split_nowp;
210
211
133k
    JXL_DASSERT(begin <= end);
212
133k
    JXL_DASSERT(end <= tree_samples.NumDistinctSamples());
213
214
    // Compute the maximum token in the range.
215
133k
    size_t max_symbols = 0;
216
266k
    for (size_t pred = 0; pred < num_predictors; pred++) {
217
42.9M
      for (size_t i = begin; i < end; i++) {
218
42.8M
        uint32_t tok = tree_samples.Token(pred, i);
219
42.8M
        max_symbols = max_symbols > tok + 1 ? max_symbols : tok + 1;
220
42.8M
      }
221
133k
    }
222
133k
    max_symbols = Padded(max_symbols);
223
133k
    std::vector<int32_t> counts(max_symbols * num_predictors);
224
133k
    std::vector<uint32_t> tot_extra_bits(num_predictors);
225
266k
    for (size_t pred = 0; pred < num_predictors; pred++) {
226
133k
      size_t extra_bits = 0;
227
133k
      const std::vector<ResidualToken>& rtokens = tree_samples.RTokens(pred);
228
42.9M
      for (size_t i = begin; i < end; i++) {
229
42.8M
        const ResidualToken& rt = rtokens[i];
230
42.8M
        size_t count = tree_samples.Count(i);
231
42.8M
        size_t eb = rt.nbits * count;
232
42.8M
        counts[pred * max_symbols + rt.tok] += count;
233
42.8M
        extra_bits += eb;
234
42.8M
      }
235
133k
      tot_extra_bits[pred] = extra_bits;
236
133k
    }
237
238
133k
    float base_bits;
239
133k
    {
240
133k
      size_t pred = tree_samples.PredictorIndex((*tree)[pos].predictor);
241
133k
      base_bits =
242
133k
          EstimateBits(counts.data() + pred * max_symbols, max_symbols) +
243
133k
          tot_extra_bits[pred];
244
133k
    }
245
246
133k
    SplitInfo *best = &best_split_nonstatic;
247
248
133k
    SplitInfo forced_split;
249
    // The multiplier ranges cut halfway through the current ranges of static
250
    // properties. We do this even if the current node is not a leaf, to
251
    // minimize the number of nodes in the resulting tree.
252
133k
    for (const auto &mmi : mul_info) {
253
133k
      uint32_t axis;
254
133k
      uint32_t val;
255
133k
      IntersectionType t =
256
133k
          BoxIntersects(static_prop_range, mmi.range, axis, val);
257
133k
      if (t == IntersectionType::kNone) continue;
258
133k
      if (t == IntersectionType::kInside) {
259
133k
        (*tree)[pos].multiplier = mmi.multiplier;
260
133k
        break;
261
133k
      }
262
0
      if (t == IntersectionType::kPartial) {
263
0
        JXL_DASSERT(axis < kNumStaticProperties);
264
0
        forced_split.val = tree_samples.QuantizeStaticProperty(axis, val);
265
0
        forced_split.prop = axis;
266
0
        forced_split.lcost = forced_split.rcost = base_bits / 2 - threshold;
267
0
        forced_split.lpred = forced_split.rpred = (*tree)[pos].predictor;
268
0
        best = &forced_split;
269
0
        best->pos = begin;
270
0
        JXL_DASSERT(best->prop == tree_samples.PropertyFromIndex(best->prop));
271
0
        if (best->prop < tree_samples.NumStaticProps()) {
272
0
        for (size_t x = begin; x < end; x++) {
273
0
          if (tree_samples.Property<true>(best->prop, x) <= best->val) {
274
0
            best->pos++;
275
0
          }
276
0
        }
277
0
      } else {
278
0
        size_t prop = best->prop - tree_samples.NumStaticProps();
279
0
        for (size_t x = begin; x < end; x++) {
280
0
          if (tree_samples.Property<false>(prop, x) <= best->val) {
281
0
            best->pos++;
282
0
          }
283
0
        }
284
0
      }
285
0
        break;
286
0
      }
287
0
    }
288
289
133k
    if (best != &forced_split) {
290
133k
      std::vector<int> prop_value_used_count;
291
133k
      std::vector<int> count_increase;
292
133k
      std::vector<size_t> extra_bits_increase;
293
      // For each property, compute which of its values are used, and what
294
      // tokens correspond to those usages. Then, iterate through the values,
295
      // and compute the entropy of each side of the split (of the form `prop >
296
      // threshold`). Finally, find the split that minimizes the cost.
297
133k
      struct CostInfo {
298
133k
        float cost = std::numeric_limits<float>::max();
299
133k
        float extra_cost = 0;
300
133k
        float Cost() const { return cost + extra_cost; }
301
133k
        Predictor pred;  // will be uninitialized in some cases, but never used.
302
133k
      };
303
133k
      std::vector<CostInfo> costs_l;
304
133k
      std::vector<CostInfo> costs_r;
305
306
133k
      std::vector<int32_t> counts_above(max_symbols);
307
133k
      std::vector<int32_t> counts_below(max_symbols);
308
309
      // The lower the threshold, the higher the expected noisiness of the
310
      // estimate. Thus, discourage changing predictors.
311
133k
      float change_pred_penalty = 800.0f / (100.0f + threshold);
312
1.04M
      for (size_t prop = 0; prop < num_properties && base_bits > threshold;
313
911k
           prop++) {
314
911k
        costs_l.clear();
315
911k
        costs_r.clear();
316
911k
        size_t prop_size = tree_samples.NumPropertyValues(prop);
317
911k
        if (extra_bits_increase.size() < prop_size) {
318
261k
          count_increase.resize(prop_size * max_symbols);
319
261k
          extra_bits_increase.resize(prop_size);
320
261k
        }
321
        // Clear prop_value_used_count (which cannot be cleared "on the go")
322
911k
        prop_value_used_count.clear();
323
911k
        prop_value_used_count.resize(prop_size);
324
325
911k
        size_t first_used = prop_size;
326
911k
        size_t last_used = 0;
327
328
        // TODO(veluca): consider finding multiple splits along a single
329
        // property at the same time, possibly with a bottom-up approach.
330
911k
        if (prop < tree_samples.NumStaticProps()) {
331
85.7M
          for (size_t i = begin; i < end; i++) {
332
85.4M
            size_t p = tree_samples.Property<true>(prop, i);
333
85.4M
            prop_value_used_count[p]++;
334
85.4M
            last_used = std::max(last_used, p);
335
85.4M
            first_used = std::min(first_used, p);
336
85.4M
          }
337
651k
        } else {
338
651k
          size_t prop_idx = prop - tree_samples.NumStaticProps();
339
214M
          for (size_t i = begin; i < end; i++) {
340
213M
            size_t p = tree_samples.Property<false>(prop_idx, i);
341
213M
            prop_value_used_count[p]++;
342
213M
            last_used = std::max(last_used, p);
343
213M
            first_used = std::min(first_used, p);
344
213M
          }
345
651k
        }
346
911k
        costs_l.resize(last_used - first_used);
347
911k
        costs_r.resize(last_used - first_used);
348
        // For all predictors, compute the right and left costs of each split.
349
1.82M
        for (size_t pred = 0; pred < num_predictors; pred++) {
350
          // Compute cost and histogram increments for each property value.
351
911k
          const std::vector<ResidualToken> &rtokens =
352
911k
              tree_samples.RTokens(pred);
353
911k
          if (prop < tree_samples.NumStaticProps()) {
354
260k
            CollectExtraBitsIncrease<true>(tree_samples, rtokens,
355
260k
                                           count_increase, extra_bits_increase,
356
260k
                                           begin, end, prop, max_symbols);
357
651k
          } else {
358
651k
            CollectExtraBitsIncrease<false>(
359
651k
                tree_samples, rtokens, count_increase, extra_bits_increase,
360
651k
                begin, end, prop - tree_samples.NumStaticProps(), max_symbols);
361
651k
          }
362
911k
          memcpy(counts_above.data(), counts.data() + pred * max_symbols,
363
911k
                 max_symbols * sizeof counts_above[0]);
364
911k
          memset(counts_below.data(), 0, max_symbols * sizeof counts_below[0]);
365
911k
          size_t extra_bits_below = 0;
366
          // Exclude last used: this ensures neither counts_above nor
367
          // counts_below is empty.
368
12.4M
          for (size_t i = first_used; i < last_used; i++) {
369
11.5M
            if (!prop_value_used_count[i]) continue;
370
7.47M
            extra_bits_below += extra_bits_increase[i];
371
            // The increase for this property value has been used, and will not
372
            // be used again: clear it. Also below.
373
7.47M
            extra_bits_increase[i] = 0;
374
287M
            for (size_t sym = 0; sym < max_symbols; sym++) {
375
280M
              counts_above[sym] -= count_increase[i * max_symbols + sym];
376
280M
              counts_below[sym] += count_increase[i * max_symbols + sym];
377
280M
              count_increase[i * max_symbols + sym] = 0;
378
280M
            }
379
7.47M
            float rcost = EstimateBits(counts_above.data(), max_symbols) +
380
7.47M
                          tot_extra_bits[pred] - extra_bits_below;
381
7.47M
            float lcost = EstimateBits(counts_below.data(), max_symbols) +
382
7.47M
                          extra_bits_below;
383
7.47M
            JXL_DASSERT(extra_bits_below <= tot_extra_bits[pred]);
384
7.47M
            float penalty = 0;
385
            // Never discourage moving away from the Weighted predictor.
386
7.47M
            if (tree_samples.PredictorFromIndex(pred) !=
387
7.47M
                    (*tree)[pos].predictor &&
388
0
                (*tree)[pos].predictor != Predictor::Weighted) {
389
0
              penalty = change_pred_penalty;
390
0
            }
391
            // If everything else is equal, disfavour Weighted (slower) and
392
            // favour Zero (faster if it's the only predictor used in a
393
            // group+channel combination)
394
7.47M
            if (tree_samples.PredictorFromIndex(pred) == Predictor::Weighted) {
395
0
              penalty += 1e-8;
396
0
            }
397
7.47M
            if (tree_samples.PredictorFromIndex(pred) == Predictor::Zero) {
398
0
              penalty -= 1e-8;
399
0
            }
400
7.47M
            if (rcost + penalty < costs_r[i - first_used].Cost()) {
401
7.47M
              costs_r[i - first_used].cost = rcost;
402
7.47M
              costs_r[i - first_used].extra_cost = penalty;
403
7.47M
              costs_r[i - first_used].pred =
404
7.47M
                  tree_samples.PredictorFromIndex(pred);
405
7.47M
            }
406
7.47M
            if (lcost + penalty < costs_l[i - first_used].Cost()) {
407
7.47M
              costs_l[i - first_used].cost = lcost;
408
7.47M
              costs_l[i - first_used].extra_cost = penalty;
409
7.47M
              costs_l[i - first_used].pred =
410
7.47M
                  tree_samples.PredictorFromIndex(pred);
411
7.47M
            }
412
7.47M
          }
413
911k
        }
414
        // Iterate through the possible splits and find the one with minimum sum
415
        // of costs of the two sides.
416
911k
        size_t split = begin;
417
12.4M
        for (size_t i = first_used; i < last_used; i++) {
418
11.5M
          if (!prop_value_used_count[i]) continue;
419
7.47M
          split += prop_value_used_count[i];
420
7.47M
          float rcost = costs_r[i - first_used].cost;
421
7.47M
          float lcost = costs_l[i - first_used].cost;
422
423
7.47M
          bool uses_wp = tree_samples.PropertyFromIndex(prop) == kWPProp ||
424
4.10M
                         costs_l[i - first_used].pred == Predictor::Weighted ||
425
4.10M
                         costs_r[i - first_used].pred == Predictor::Weighted;
426
7.47M
          bool zero_entropy_side = rcost == 0 || lcost == 0;
427
428
7.47M
          SplitInfo &best_ref =
429
7.47M
              tree_samples.PropertyFromIndex(prop) < kNumStaticProperties
430
7.47M
                  ? (zero_entropy_side ? best_split_static_constant
431
42.8k
                                       : best_split_static)
432
7.47M
                  : (uses_wp ? best_split_nonstatic : best_split_nowp);
433
7.47M
          if (lcost + rcost < best_ref.Cost()) {
434
1.48M
            best_ref.prop = prop;
435
1.48M
            best_ref.val = i;
436
1.48M
            best_ref.pos = split;
437
1.48M
            best_ref.lcost = lcost;
438
1.48M
            best_ref.lpred = costs_l[i - first_used].pred;
439
1.48M
            best_ref.rcost = rcost;
440
1.48M
            best_ref.rpred = costs_r[i - first_used].pred;
441
1.48M
          }
442
7.47M
        }
443
        // Clear extra_bits_increase and cost_increase for last_used.
444
911k
        extra_bits_increase[last_used] = 0;
445
33.9M
        for (size_t sym = 0; sym < max_symbols; sym++) {
446
33.0M
          count_increase[last_used * max_symbols + sym] = 0;
447
33.0M
        }
448
911k
      }
449
450
      // Try to avoid introducing WP.
451
133k
      if (best_split_nowp.Cost() + threshold < base_bits &&
452
62.6k
          best_split_nowp.Cost() <= fast_decode_multiplier * best->Cost()) {
453
60.7k
        best = &best_split_nowp;
454
60.7k
      }
455
      // Split along static props if possible and not significantly more
456
      // expensive.
457
133k
      if (best_split_static.Cost() + threshold < base_bits &&
458
15.5k
          best_split_static.Cost() <= fast_decode_multiplier * best->Cost()) {
459
7.56k
        best = &best_split_static;
460
7.56k
      }
461
      // Split along static props to create constant nodes if possible.
462
133k
      if (best_split_static_constant.Cost() + threshold < base_bits) {
463
75
        best = &best_split_static_constant;
464
75
      }
465
133k
    }
466
467
133k
    if (best->Cost() + threshold < base_bits) {
468
65.4k
      uint32_t p = tree_samples.PropertyFromIndex(best->prop);
469
65.4k
      pixel_type dequant =
470
65.4k
          tree_samples.UnquantizeProperty(best->prop, best->val);
471
      // Split node and try to split children.
472
65.4k
      MakeSplitNode(pos, p, dequant, best->lpred, 0, best->rpred, 0, tree);
473
      // "Sort" according to winning property
474
65.4k
      if (best->prop < tree_samples.NumStaticProps()) {
475
7.63k
        SplitTreeSamples<true>(tree_samples, begin, best->pos, end, best->prop,
476
7.63k
                               best->val);
477
57.8k
      } else {
478
57.8k
        SplitTreeSamples<false>(tree_samples, begin, best->pos, end,
479
57.8k
                                best->prop - tree_samples.NumStaticProps(),
480
57.8k
                                best->val);
481
57.8k
      }
482
65.4k
      auto new_sp_range = static_prop_range;
483
65.4k
      if (p < kNumStaticProperties) {
484
7.63k
        JXL_DASSERT(static_cast<uint32_t>(dequant + 1) <= new_sp_range[p][1]);
485
7.63k
        new_sp_range[p][1] = dequant + 1;
486
7.63k
        JXL_DASSERT(new_sp_range[p][0] < new_sp_range[p][1]);
487
7.63k
      }
488
65.4k
      nodes.push_back(
489
65.4k
          NodeInfo{(*tree)[pos].rchild, begin, best->pos, new_sp_range});
490
65.4k
      new_sp_range = static_prop_range;
491
65.4k
      if (p < kNumStaticProperties) {
492
7.63k
        JXL_DASSERT(new_sp_range[p][0] <= static_cast<uint32_t>(dequant + 1));
493
7.63k
        new_sp_range[p][0] = dequant + 1;
494
7.63k
        JXL_DASSERT(new_sp_range[p][0] < new_sp_range[p][1]);
495
7.63k
      }
496
65.4k
      nodes.push_back(
497
65.4k
          NodeInfo{(*tree)[pos].lchild, best->pos, end, new_sp_range});
498
65.4k
    }
499
133k
  }
500
2.19k
}
Unexecuted instantiation: jxl::N_AVX3::FindBestSplit(jxl::TreeSamples&, float, std::__1::vector<jxl::ModularMultiplierInfo, std::__1::allocator<jxl::ModularMultiplierInfo> > const&, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, float, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)
Unexecuted instantiation: jxl::N_AVX3_ZEN4::FindBestSplit(jxl::TreeSamples&, float, std::__1::vector<jxl::ModularMultiplierInfo, std::__1::allocator<jxl::ModularMultiplierInfo> > const&, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, float, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)
Unexecuted instantiation: jxl::N_AVX3_SPR::FindBestSplit(jxl::TreeSamples&, float, std::__1::vector<jxl::ModularMultiplierInfo, std::__1::allocator<jxl::ModularMultiplierInfo> > const&, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, float, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)
Unexecuted instantiation: jxl::N_SSE2::FindBestSplit(jxl::TreeSamples&, float, std::__1::vector<jxl::ModularMultiplierInfo, std::__1::allocator<jxl::ModularMultiplierInfo> > const&, std::__1::array<std::__1::array<unsigned int, 2ul>, 2ul>, float, std::__1::vector<jxl::PropertyDecisionNode, std::__1::allocator<jxl::PropertyDecisionNode> >*)
501
502
// NOLINTNEXTLINE(google-readability-namespace-comments)
503
}  // namespace HWY_NAMESPACE
504
}  // namespace jxl
505
HWY_AFTER_NAMESPACE();
506
507
#if HWY_ONCE
508
namespace jxl {
509
510
HWY_EXPORT(FindBestSplit);  // Local function.
511
512
Status ComputeBestTree(TreeSamples &tree_samples, float threshold,
513
                       const std::vector<ModularMultiplierInfo> &mul_info,
514
                       StaticPropRange static_prop_range,
515
2.19k
                       float fast_decode_multiplier, Tree *tree) {
516
  // TODO(veluca): take into account that different contexts can have different
517
  // uint configs.
518
  //
519
  // Initialize tree.
520
2.19k
  tree->emplace_back();
521
2.19k
  tree->back().property = -1;
522
2.19k
  tree->back().predictor = tree_samples.PredictorFromIndex(0);
523
2.19k
  tree->back().predictor_offset = 0;
524
2.19k
  tree->back().multiplier = 1;
525
2.19k
  JXL_ENSURE(tree_samples.NumProperties() < 64);
526
527
2.19k
  JXL_ENSURE(tree_samples.NumDistinctSamples() <=
528
2.19k
             std::numeric_limits<uint32_t>::max());
529
2.19k
  HWY_DYNAMIC_DISPATCH(FindBestSplit)
530
2.19k
  (tree_samples, threshold, mul_info, static_prop_range, fast_decode_multiplier,
531
2.19k
   tree);
532
2.19k
  return true;
533
2.19k
}
534
535
#if JXL_CXX_LANG < JXL_CXX_17
536
constexpr int32_t TreeSamples::kPropertyRange;
537
constexpr uint32_t TreeSamples::kDedupEntryUnused;
538
#endif
539
540
Status TreeSamples::SetPredictor(Predictor predictor,
541
2.19k
                                 ModularOptions::TreeMode wp_tree_mode) {
542
2.19k
  if (wp_tree_mode == ModularOptions::TreeMode::kWPOnly) {
543
0
    predictors = {Predictor::Weighted};
544
0
    residuals.resize(1);
545
0
    return true;
546
0
  }
547
2.19k
  if (wp_tree_mode == ModularOptions::TreeMode::kNoWP &&
548
0
      predictor == Predictor::Weighted) {
549
0
    return JXL_FAILURE("Invalid predictor settings");
550
0
  }
551
2.19k
  if (predictor == Predictor::Variable) {
552
0
    for (size_t i = 0; i < kNumModularPredictors; i++) {
553
0
      predictors.push_back(static_cast<Predictor>(i));
554
0
    }
555
0
    std::swap(predictors[0], predictors[static_cast<int>(Predictor::Weighted)]);
556
0
    std::swap(predictors[1], predictors[static_cast<int>(Predictor::Gradient)]);
557
2.19k
  } else if (predictor == Predictor::Best) {
558
0
    predictors = {Predictor::Weighted, Predictor::Gradient};
559
2.19k
  } else {
560
2.19k
    predictors = {predictor};
561
2.19k
  }
562
2.19k
  if (wp_tree_mode == ModularOptions::TreeMode::kNoWP) {
563
0
    predictors.erase(
564
0
        std::remove(predictors.begin(), predictors.end(), Predictor::Weighted),
565
0
        predictors.end());
566
0
  }
567
2.19k
  residuals.resize(predictors.size());
568
2.19k
  return true;
569
2.19k
}
570
571
Status TreeSamples::SetProperties(const std::vector<uint32_t> &properties,
572
2.19k
                                  ModularOptions::TreeMode wp_tree_mode) {
573
2.19k
  props_to_use = properties;
574
2.19k
  if (wp_tree_mode == ModularOptions::TreeMode::kWPOnly) {
575
0
    props_to_use = {static_cast<uint32_t>(kWPProp)};
576
0
  }
577
2.19k
  if (wp_tree_mode == ModularOptions::TreeMode::kGradientOnly) {
578
0
    props_to_use = {static_cast<uint32_t>(kGradientProp)};
579
0
  }
580
2.19k
  if (wp_tree_mode == ModularOptions::TreeMode::kNoWP) {
581
0
    props_to_use.erase(
582
0
        std::remove(props_to_use.begin(), props_to_use.end(), kWPProp),
583
0
        props_to_use.end());
584
0
  }
585
2.19k
  if (props_to_use.empty()) {
586
0
    return JXL_FAILURE("Invalid property set configuration");
587
0
  }
588
2.19k
  num_static_props = 0;
589
  // Check that if static properties present, then those are at the beginning.
590
17.5k
  for (size_t i = 0; i < props_to_use.size(); ++i) {
591
15.3k
    uint32_t prop = props_to_use[i];
592
15.3k
    if (prop < kNumStaticProperties) {
593
4.39k
      JXL_DASSERT(i == prop);
594
4.39k
      num_static_props++;
595
4.39k
    }
596
15.3k
  }
597
2.19k
  props.resize(props_to_use.size() - num_static_props);
598
2.19k
  return true;
599
2.19k
}
600
601
6.59k
void TreeSamples::InitTable(size_t log_size) {
602
6.59k
  size_t size = 1ULL << log_size;
603
6.59k
  if (dedup_table_.size() == size) return;
604
4.85k
  dedup_table_.resize(size, kDedupEntryUnused);
605
2.96M
  for (size_t i = 0; i < NumDistinctSamples(); i++) {
606
2.96M
    if (sample_counts[i] != std::numeric_limits<uint16_t>::max()) {
607
2.96M
      AddToTable(i);
608
2.96M
    }
609
2.96M
  }
610
4.85k
}
611
612
14.0M
bool TreeSamples::AddToTableAndMerge(size_t a) {
613
14.0M
  size_t pos1 = Hash1(a);
614
14.0M
  size_t pos2 = Hash2(a);
615
14.0M
  if (dedup_table_[pos1] != kDedupEntryUnused &&
616
10.6M
      IsSameSample(a, dedup_table_[pos1])) {
617
6.39M
    JXL_DASSERT(sample_counts[a] == 1);
618
6.39M
    sample_counts[dedup_table_[pos1]]++;
619
    // Remove from hash table samples that are saturated.
620
6.39M
    if (sample_counts[dedup_table_[pos1]] ==
621
6.39M
        std::numeric_limits<uint16_t>::max()) {
622
0
      dedup_table_[pos1] = kDedupEntryUnused;
623
0
    }
624
6.39M
    return true;
625
6.39M
  }
626
7.64M
  if (dedup_table_[pos2] != kDedupEntryUnused &&
627
3.72M
      IsSameSample(a, dedup_table_[pos2])) {
628
1.89M
    JXL_DASSERT(sample_counts[a] == 1);
629
1.89M
    sample_counts[dedup_table_[pos2]]++;
630
    // Remove from hash table samples that are saturated.
631
1.89M
    if (sample_counts[dedup_table_[pos2]] ==
632
1.89M
        std::numeric_limits<uint16_t>::max()) {
633
0
      dedup_table_[pos2] = kDedupEntryUnused;
634
0
    }
635
1.89M
    return true;
636
1.89M
  }
637
5.74M
  AddToTable(a);
638
5.74M
  return false;
639
7.64M
}
640
641
8.70M
void TreeSamples::AddToTable(size_t a) {
642
8.70M
  size_t pos1 = Hash1(a);
643
8.70M
  size_t pos2 = Hash2(a);
644
8.70M
  if (dedup_table_[pos1] == kDedupEntryUnused) {
645
4.48M
    dedup_table_[pos1] = a;
646
4.48M
  } else if (dedup_table_[pos2] == kDedupEntryUnused) {
647
2.40M
    dedup_table_[pos2] = a;
648
2.40M
  }
649
8.70M
}
650
651
6.59k
void TreeSamples::PrepareForSamples(size_t extra_num_samples) {
652
6.59k
  for (auto &res : residuals) {
653
6.59k
    res.reserve(res.size() + extra_num_samples);
654
6.59k
  }
655
19.7k
  for (size_t i = 0; i < num_static_props; ++i) {
656
13.1k
    static_props[i].reserve(static_props[i].size() + extra_num_samples);
657
13.1k
  }
658
32.9k
  for (auto &p : props) {
659
32.9k
    p.reserve(p.size() + extra_num_samples);
660
32.9k
  }
661
6.59k
  size_t total_num_samples = extra_num_samples + sample_counts.size();
662
6.59k
  size_t next_size = CeilLog2Nonzero(total_num_samples * 3 / 2);
663
6.59k
  InitTable(next_size);
664
6.59k
}
665
666
22.7M
size_t TreeSamples::Hash1(size_t a) const {
667
22.7M
  constexpr uint64_t constant = 0x1e35a7bd;
668
22.7M
  uint64_t h = constant;
669
22.7M
  for (const auto &r : residuals) {
670
22.7M
    h = h * constant + r[a].tok;
671
22.7M
    h = h * constant + r[a].nbits;
672
22.7M
  }
673
68.2M
  for (size_t i = 0; i < num_static_props; ++i) {
674
45.4M
    h = h * constant + static_props[i][a];
675
45.4M
  }
676
113M
  for (const auto &p : props) {
677
113M
    h = h * constant + p[a];
678
113M
  }
679
22.7M
  return (h >> 16) & (dedup_table_.size() - 1);
680
22.7M
}
681
22.7M
size_t TreeSamples::Hash2(size_t a) const {
682
22.7M
  constexpr uint64_t constant = 0x1e35a7bd1e35a7bd;
683
22.7M
  uint64_t h = constant;
684
68.2M
  for (size_t i = 0; i < num_static_props; ++i) {
685
45.4M
    h = h * constant ^ static_props[i][a];
686
45.4M
  }
687
113M
  for (const auto &p : props) {
688
113M
    h = h * constant ^ p[a];
689
113M
  }
690
22.7M
  for (const auto &r : residuals) {
691
22.7M
    h = h * constant ^ r[a].tok;
692
22.7M
    h = h * constant ^ r[a].nbits;
693
22.7M
  }
694
22.7M
  return (h >> 16) & (dedup_table_.size() - 1);
695
22.7M
}
696
697
14.4M
bool TreeSamples::IsSameSample(size_t a, size_t b) const {
698
14.4M
  bool ret = true;
699
14.4M
  for (const auto &r : residuals) {
700
14.4M
    if (r[a].tok != r[b].tok) {
701
3.67M
      ret = false;
702
3.67M
    }
703
14.4M
    if (r[a].nbits != r[b].nbits) {
704
1.98M
      ret = false;
705
1.98M
    }
706
14.4M
  }
707
43.2M
  for (size_t i = 0; i < num_static_props; ++i) {
708
28.8M
    if (static_props[i][a] != static_props[i][b]) {
709
3.34M
      ret = false;
710
3.34M
    }
711
28.8M
  }
712
72.0M
  for (const auto &p : props) {
713
72.0M
    if (p[a] != p[b]) {
714
20.6M
      ret = false;
715
20.6M
    }
716
72.0M
  }
717
14.4M
  return ret;
718
14.4M
}
719
720
void TreeSamples::AddSample(pixel_type_w pixel, const Properties &properties,
721
14.0M
                            const pixel_type_w *predictions) {
722
28.0M
  for (size_t i = 0; i < predictors.size(); i++) {
723
14.0M
    pixel_type v = pixel - predictions[static_cast<int>(predictors[i])];
724
14.0M
    uint32_t tok, nbits, bits;
725
14.0M
    HybridUintConfig(4, 1, 2).Encode(PackSigned(v), &tok, &nbits, &bits);
726
14.0M
    JXL_DASSERT(tok < 256);
727
14.0M
    JXL_DASSERT(nbits < 256);
728
14.0M
    ResidualToken token = {static_cast<uint8_t>(tok),
729
14.0M
                           static_cast<uint8_t>(nbits)};
730
14.0M
    residuals[i].push_back(token);
731
14.0M
  }
732
42.1M
  for (size_t i = 0; i < num_static_props; ++i) {
733
28.0M
    static_props[i].push_back(QuantizeStaticProperty(i, properties[i]));
734
28.0M
  }
735
84.2M
  for (size_t i = num_static_props; i < props_to_use.size(); i++) {
736
70.1M
    props[i - num_static_props].push_back(QuantizeProperty(i, properties[props_to_use[i]]));
737
70.1M
  }
738
14.0M
  sample_counts.push_back(1);
739
14.0M
  num_samples++;
740
14.0M
  if (AddToTableAndMerge(sample_counts.size() - 1)) {
741
8.29M
    for (auto &r : residuals) r.pop_back();
742
24.8M
    for (size_t i = 0; i < num_static_props; ++i) static_props[i].pop_back();
743
41.4M
    for (auto &p : props) p.pop_back();
744
8.29M
    sample_counts.pop_back();
745
8.29M
  }
746
14.0M
}
747
748
7.08M
void TreeSamples::Swap(size_t a, size_t b) {
749
7.08M
  if (a == b) return;
750
7.08M
  for (auto &r : residuals) {
751
7.08M
    std::swap(r[a], r[b]);
752
7.08M
  }
753
21.2M
  for (size_t i = 0; i < num_static_props; ++i) {
754
14.1M
    std::swap(static_props[i][a], static_props[i][b]);
755
14.1M
  }
756
35.4M
  for (auto &p : props) {
757
35.4M
    std::swap(p[a], p[b]);
758
35.4M
  }
759
7.08M
  std::swap(sample_counts[a], sample_counts[b]);
760
7.08M
}
761
762
namespace {
763
std::vector<int32_t> QuantizeHistogram(const std::vector<uint32_t> &histogram,
764
6.60k
                                       size_t num_chunks) {
765
6.60k
  if (histogram.empty() || num_chunks == 0) return {};
766
6.60k
  uint64_t sum = std::accumulate(histogram.begin(), histogram.end(), 0LU);
767
6.60k
  if (sum == 0) return {};
768
  // TODO(veluca): selecting distinct quantiles is likely not the best
769
  // way to go about this.
770
6.60k
  std::vector<int32_t> thresholds;
771
6.60k
  uint64_t cumsum = 0;
772
6.60k
  uint64_t threshold = 1;
773
2.28M
  for (size_t i = 0; i < histogram.size(); i++) {
774
2.27M
    cumsum += histogram[i];
775
2.27M
    if (cumsum * num_chunks >= threshold * sum) {
776
54.6k
      thresholds.push_back(i);
777
688k
      while (cumsum * num_chunks >= threshold * sum) threshold++;
778
54.6k
    }
779
2.27M
  }
780
6.60k
  JXL_DASSERT(thresholds.size() <= num_chunks);
781
  // last value collects all histogram and is not really a threshold
782
6.60k
  thresholds.pop_back();
783
6.60k
  return thresholds;
784
6.60k
}
785
786
std::vector<int32_t> QuantizeSamples(const std::vector<int32_t> &samples,
787
2.21k
                                     size_t num_chunks) {
788
2.21k
  if (samples.empty()) return {};
789
2.21k
  int min = *std::min_element(samples.begin(), samples.end());
790
2.21k
  constexpr int kRange = 512;
791
2.21k
  min = jxl::Clamp1(min, -kRange, kRange);
792
2.21k
  std::vector<uint32_t> counts(2 * kRange + 1);
793
1.28M
  for (int s : samples) {
794
1.28M
    uint32_t sample_offset = jxl::Clamp1(s, -kRange, kRange) - min;
795
1.28M
    counts[sample_offset]++;
796
1.28M
  }
797
2.21k
  std::vector<int32_t> thresholds = QuantizeHistogram(counts, num_chunks);
798
43.6k
  for (auto &v : thresholds) v += min;
799
2.21k
  return thresholds;
800
2.21k
}
801
802
// `to[i]` is assigned value `v` conforming `from[v] <= i && from[v-1] > i`.
803
// This is because the decision node in the tree splits on (property) > i,
804
// hence everything that is not > of a threshold should be clustered
805
// together.
806
template <typename T>
807
void QuantMap(const std::vector<int32_t> &from, std::vector<T> &to,
808
15.3k
              size_t num_pegs, int bias) {
809
15.3k
  to.resize(num_pegs);
810
15.3k
  size_t mapped = 0;
811
15.7M
  for (size_t i = 0; i < num_pegs; i++) {
812
16.0M
    while (mapped < from.size() && static_cast<int>(i) - bias > from[mapped]) {
813
299k
      mapped++;
814
299k
    }
815
15.7M
    JXL_DASSERT(static_cast<T>(mapped) == mapped);
816
15.7M
    to[i] = mapped;
817
15.7M
  }
818
15.3k
}
enc_ma.cc:void jxl::(anonymous namespace)::QuantMap<unsigned short>(std::__1::vector<int, std::__1::allocator<int> > const&, std::__1::vector<unsigned short, std::__1::allocator<unsigned short> >&, unsigned long, int)
Line
Count
Source
808
4.39k
              size_t num_pegs, int bias) {
809
4.39k
  to.resize(num_pegs);
810
4.39k
  size_t mapped = 0;
811
4.49M
  for (size_t i = 0; i < num_pegs; i++) {
812
4.49M
    while (mapped < from.size() && static_cast<int>(i) - bias > from[mapped]) {
813
4.39k
      mapped++;
814
4.39k
    }
815
4.49M
    JXL_DASSERT(static_cast<T>(mapped) == mapped);
816
4.49M
    to[i] = mapped;
817
4.49M
  }
818
4.39k
}
enc_ma.cc:void jxl::(anonymous namespace)::QuantMap<unsigned char>(std::__1::vector<int, std::__1::allocator<int> > const&, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> >&, unsigned long, int)
Line
Count
Source
808
10.9k
              size_t num_pegs, int bias) {
809
10.9k
  to.resize(num_pegs);
810
10.9k
  size_t mapped = 0;
811
11.2M
  for (size_t i = 0; i < num_pegs; i++) {
812
11.5M
    while (mapped < from.size() && static_cast<int>(i) - bias > from[mapped]) {
813
295k
      mapped++;
814
295k
    }
815
11.2M
    JXL_DASSERT(static_cast<T>(mapped) == mapped);
816
11.2M
    to[i] = mapped;
817
11.2M
  }
818
10.9k
}
819
}  // namespace
820
821
void TreeSamples::PreQuantizeProperties(
822
    const StaticPropRange &range,
823
    const std::vector<ModularMultiplierInfo> &multiplier_info,
824
    const std::vector<uint32_t> &group_pixel_count,
825
    const std::vector<uint32_t> &channel_pixel_count,
826
    std::vector<pixel_type> &pixel_samples,
827
2.19k
    std::vector<pixel_type> &diff_samples, size_t max_property_values) {
828
  // If we have forced splits because of multipliers, choose channel and group
829
  // thresholds accordingly.
830
2.19k
  std::vector<int32_t> group_multiplier_thresholds;
831
2.19k
  std::vector<int32_t> channel_multiplier_thresholds;
832
2.19k
  for (const auto &v : multiplier_info) {
833
2.19k
    if (v.range[0][0] != range[0][0]) {
834
0
      channel_multiplier_thresholds.push_back(v.range[0][0] - 1);
835
0
    }
836
2.19k
    if (v.range[0][1] != range[0][1]) {
837
0
      channel_multiplier_thresholds.push_back(v.range[0][1] - 1);
838
0
    }
839
2.19k
    if (v.range[1][0] != range[1][0]) {
840
0
      group_multiplier_thresholds.push_back(v.range[1][0] - 1);
841
0
    }
842
2.19k
    if (v.range[1][1] != range[1][1]) {
843
0
      group_multiplier_thresholds.push_back(v.range[1][1] - 1);
844
0
    }
845
2.19k
  }
846
2.19k
  std::sort(channel_multiplier_thresholds.begin(),
847
2.19k
            channel_multiplier_thresholds.end());
848
2.19k
  channel_multiplier_thresholds.resize(
849
2.19k
      std::unique(channel_multiplier_thresholds.begin(),
850
2.19k
                  channel_multiplier_thresholds.end()) -
851
2.19k
      channel_multiplier_thresholds.begin());
852
2.19k
  std::sort(group_multiplier_thresholds.begin(),
853
2.19k
            group_multiplier_thresholds.end());
854
2.19k
  group_multiplier_thresholds.resize(
855
2.19k
      std::unique(group_multiplier_thresholds.begin(),
856
2.19k
                  group_multiplier_thresholds.end()) -
857
2.19k
      group_multiplier_thresholds.begin());
858
859
2.19k
  compact_properties.resize(props_to_use.size());
860
2.19k
  auto quantize_channel = [&]() {
861
2.19k
    if (!channel_multiplier_thresholds.empty()) {
862
0
      return channel_multiplier_thresholds;
863
0
    }
864
2.19k
    return QuantizeHistogram(channel_pixel_count, max_property_values);
865
2.19k
  };
866
2.19k
  auto quantize_group_id = [&]() {
867
2.19k
    if (!group_multiplier_thresholds.empty()) {
868
0
      return group_multiplier_thresholds;
869
0
    }
870
2.19k
    return QuantizeHistogram(group_pixel_count, max_property_values);
871
2.19k
  };
872
2.19k
  auto quantize_coordinate = [&]() {
873
0
    std::vector<int32_t> quantized;
874
0
    quantized.reserve(max_property_values - 1);
875
0
    for (size_t i = 0; i + 1 < max_property_values; i++) {
876
0
      quantized.push_back((i + 1) * 256 / max_property_values - 1);
877
0
    }
878
0
    return quantized;
879
0
  };
880
2.19k
  std::vector<int32_t> abs_pixel_thresholds;
881
2.19k
  std::vector<int32_t> pixel_thresholds;
882
2.19k
  auto quantize_pixel_property = [&]() {
883
0
    if (pixel_thresholds.empty()) {
884
0
      pixel_thresholds = QuantizeSamples(pixel_samples, max_property_values);
885
0
    }
886
0
    return pixel_thresholds;
887
0
  };
888
2.19k
  auto quantize_abs_pixel_property = [&]() {
889
0
    if (abs_pixel_thresholds.empty()) {
890
0
      quantize_pixel_property();  // Compute the non-abs thresholds.
891
0
      for (auto &v : pixel_samples) v = std::abs(v);
892
0
      abs_pixel_thresholds =
893
0
          QuantizeSamples(pixel_samples, max_property_values);
894
0
    }
895
0
    return abs_pixel_thresholds;
896
0
  };
897
2.19k
  std::vector<int32_t> abs_diff_thresholds;
898
2.19k
  std::vector<int32_t> diff_thresholds;
899
8.78k
  auto quantize_diff_property = [&]() {
900
8.78k
    if (diff_thresholds.empty()) {
901
2.21k
      diff_thresholds = QuantizeSamples(diff_samples, max_property_values);
902
2.21k
    }
903
8.78k
    return diff_thresholds;
904
8.78k
  };
905
2.19k
  auto quantize_abs_diff_property = [&]() {
906
0
    if (abs_diff_thresholds.empty()) {
907
0
      quantize_diff_property();  // Compute the non-abs thresholds.
908
0
      for (auto &v : diff_samples) v = std::abs(v);
909
0
      abs_diff_thresholds = QuantizeSamples(diff_samples, max_property_values);
910
0
    }
911
0
    return abs_diff_thresholds;
912
0
  };
913
2.19k
  auto quantize_wp = [&]() {
914
2.19k
    if (max_property_values < 32) {
915
0
      return std::vector<int32_t>{-127, -63, -31, -15, -7, -3, -1, 0,
916
0
                                  1,    3,   7,   15,  31, 63, 127};
917
0
    }
918
2.19k
    if (max_property_values < 64) {
919
0
      return std::vector<int32_t>{-255, -191, -127, -95, -63, -47, -31, -23,
920
0
                                  -15,  -11,  -7,   -5,  -3,  -1,  0,   1,
921
0
                                  3,    5,    7,    11,  15,  23,  31,  47,
922
0
                                  63,   95,   127,  191, 255};
923
0
    }
924
2.19k
    return std::vector<int32_t>{
925
2.19k
        -255, -223, -191, -159, -127, -111, -95, -79, -63, -55, -47,
926
2.19k
        -39,  -31,  -27,  -23,  -19,  -15,  -13, -11, -9,  -7,  -6,
927
2.19k
        -5,   -4,   -3,   -2,   -1,   0,    1,   2,   3,   4,   5,
928
2.19k
        6,    7,    9,    11,   13,   15,   19,  23,  27,  31,  39,
929
2.19k
        47,   55,   63,   79,   95,   111,  127, 159, 191, 223, 255};
930
2.19k
  };
931
932
2.19k
  property_mapping.resize(props_to_use.size() - num_static_props);
933
17.5k
  for (size_t i = 0; i < props_to_use.size(); i++) {
934
15.3k
    if (props_to_use[i] == 0) {
935
2.19k
      compact_properties[i] = quantize_channel();
936
13.1k
    } else if (props_to_use[i] == 1) {
937
2.19k
      compact_properties[i] = quantize_group_id();
938
10.9k
    } else if (props_to_use[i] == 2 || props_to_use[i] == 3) {
939
0
      compact_properties[i] = quantize_coordinate();
940
10.9k
    } else if (props_to_use[i] == 6 || props_to_use[i] == 7 ||
941
10.9k
               props_to_use[i] == 8 ||
942
10.9k
               (props_to_use[i] >= kNumNonrefProperties &&
943
0
                (props_to_use[i] - kNumNonrefProperties) % 4 == 1)) {
944
0
      compact_properties[i] = quantize_pixel_property();
945
10.9k
    } else if (props_to_use[i] == 4 || props_to_use[i] == 5 ||
946
10.9k
               (props_to_use[i] >= kNumNonrefProperties &&
947
0
                (props_to_use[i] - kNumNonrefProperties) % 4 == 0)) {
948
0
      compact_properties[i] = quantize_abs_pixel_property();
949
10.9k
    } else if (props_to_use[i] >= kNumNonrefProperties &&
950
0
               (props_to_use[i] - kNumNonrefProperties) % 4 == 2) {
951
0
      compact_properties[i] = quantize_abs_diff_property();
952
10.9k
    } else if (props_to_use[i] == kWPProp) {
953
2.19k
      compact_properties[i] = quantize_wp();
954
8.78k
    } else {
955
8.78k
      compact_properties[i] = quantize_diff_property();
956
8.78k
    }
957
15.3k
    if (i < num_static_props) {
958
4.39k
      QuantMap(compact_properties[i], static_property_mapping[i],
959
4.39k
               kPropertyRange * 2 + 1, kPropertyRange);
960
10.9k
    } else {
961
10.9k
      QuantMap(compact_properties[i], property_mapping[i - num_static_props],
962
10.9k
               kPropertyRange * 2 + 1, kPropertyRange);
963
10.9k
    }
964
15.3k
  }
965
2.19k
}
966
967
void CollectPixelSamples(const Image &image, const ModularOptions &options,
968
                         uint32_t group_id,
969
                         std::vector<uint32_t> &group_pixel_count,
970
                         std::vector<uint32_t> &channel_pixel_count,
971
                         std::vector<pixel_type> &pixel_samples,
972
2.19k
                         std::vector<pixel_type> &diff_samples) {
973
2.19k
  if (options.nb_repeats == 0) return;
974
2.19k
  if (group_pixel_count.size() <= group_id) {
975
2.19k
    group_pixel_count.resize(group_id + 1);
976
2.19k
  }
977
2.19k
  if (channel_pixel_count.size() < image.channel.size()) {
978
2.19k
    channel_pixel_count.resize(image.channel.size());
979
2.19k
  }
980
2.19k
  Rng rng(group_id);
981
  // Sample 10% of the final number of samples for property quantization.
982
2.19k
  float fraction = std::min(options.nb_repeats * 0.1, 0.99);
983
2.19k
  Rng::GeometricDistribution dist = Rng::MakeGeometric(fraction);
984
2.19k
  size_t total_pixels = 0;
985
2.19k
  std::vector<size_t> channel_ids;
986
8.78k
  for (size_t i = 0; i < image.channel.size(); i++) {
987
6.59k
    if (i >= image.nb_meta_channels &&
988
6.59k
        (image.channel[i].w > options.max_chan_size ||
989
6.59k
         image.channel[i].h > options.max_chan_size)) {
990
0
      break;
991
0
    }
992
6.59k
    if (image.channel[i].w <= 1 || image.channel[i].h == 0) {
993
0
      continue;  // skip empty or width-1 channels.
994
0
    }
995
6.59k
    channel_ids.push_back(i);
996
6.59k
    group_pixel_count[group_id] += image.channel[i].w * image.channel[i].h;
997
6.59k
    channel_pixel_count[i] += image.channel[i].w * image.channel[i].h;
998
6.59k
    total_pixels += image.channel[i].w * image.channel[i].h;
999
6.59k
  }
1000
2.19k
  if (channel_ids.empty()) return;
1001
2.19k
  pixel_samples.reserve(pixel_samples.size() + fraction * total_pixels);
1002
2.19k
  diff_samples.reserve(diff_samples.size() + fraction * total_pixels);
1003
2.19k
  size_t i = 0;
1004
2.19k
  size_t y = 0;
1005
2.19k
  size_t x = 0;
1006
1.29M
  auto advance = [&](size_t amount) {
1007
1.29M
    x += amount;
1008
    // Detect row overflow (rare).
1009
1.56M
    while (x >= image.channel[channel_ids[i]].w) {
1010
274k
      x -= image.channel[channel_ids[i]].w;
1011
274k
      y++;
1012
      // Detect end-of-channel (even rarer).
1013
274k
      if (y == image.channel[channel_ids[i]].h) {
1014
6.59k
        i++;
1015
6.59k
        y = 0;
1016
6.59k
        if (i >= channel_ids.size()) {
1017
2.19k
          return;
1018
2.19k
        }
1019
6.59k
      }
1020
274k
    }
1021
1.29M
  };
1022
2.19k
  advance(rng.Geometric(dist));
1023
1.29M
  for (; i < channel_ids.size(); advance(rng.Geometric(dist) + 1)) {
1024
1.28M
    const pixel_type *row = image.channel[channel_ids[i]].Row(y);
1025
1.28M
    pixel_samples.push_back(row[x]);
1026
1.28M
    size_t xp = x == 0 ? 1 : x - 1;
1027
1.28M
    diff_samples.push_back(static_cast<int64_t>(row[x]) - row[xp]);
1028
1.28M
  }
1029
2.19k
}
1030
1031
// TODO(veluca): very simple encoding scheme. This should be improved.
1032
Status TokenizeTree(const Tree &tree, std::vector<Token> *tokens,
1033
6.74k
                    Tree *decoder_tree) {
1034
6.74k
  JXL_ENSURE(tree.size() <= kMaxTreeSize);
1035
6.74k
  std::queue<int> q;
1036
6.74k
  q.push(0);
1037
6.74k
  size_t leaf_id = 0;
1038
6.74k
  decoder_tree->clear();
1039
287k
  while (!q.empty()) {
1040
280k
    int cur = q.front();
1041
280k
    q.pop();
1042
280k
    JXL_ENSURE(tree[cur].property >= -1);
1043
280k
    tokens->emplace_back(kPropertyContext, tree[cur].property + 1);
1044
280k
    if (tree[cur].property == -1) {
1045
143k
      tokens->emplace_back(kPredictorContext,
1046
143k
                           static_cast<int>(tree[cur].predictor));
1047
143k
      tokens->emplace_back(kOffsetContext,
1048
143k
                           PackSigned(tree[cur].predictor_offset));
1049
143k
      uint32_t mul_log = Num0BitsBelowLS1Bit_Nonzero(tree[cur].multiplier);
1050
143k
      uint32_t mul_bits = (tree[cur].multiplier >> mul_log) - 1;
1051
143k
      tokens->emplace_back(kMultiplierLogContext, mul_log);
1052
143k
      tokens->emplace_back(kMultiplierBitsContext, mul_bits);
1053
143k
      JXL_ENSURE(tree[cur].predictor < Predictor::Best);
1054
143k
      decoder_tree->emplace_back(
1055
143k
          -1, 0, static_cast<int>(leaf_id), 0, tree[cur].predictor,
1056
143k
          tree[cur].predictor_offset, tree[cur].multiplier);
1057
143k
      leaf_id++;
1058
143k
      continue;
1059
143k
    }
1060
137k
    decoder_tree->emplace_back(
1061
137k
        tree[cur].property, tree[cur].splitval,
1062
137k
        static_cast<int>(decoder_tree->size() + q.size() + 1),
1063
137k
        static_cast<int>(decoder_tree->size() + q.size() + 2), Predictor::Zero,
1064
137k
        0, 1);
1065
137k
    q.push(tree[cur].lchild);
1066
137k
    q.push(tree[cur].rchild);
1067
137k
    tokens->emplace_back(kSplitValContext, PackSigned(tree[cur].splitval));
1068
137k
  }
1069
6.74k
  return true;
1070
6.74k
}
1071
1072
}  // namespace jxl
1073
#endif  // HWY_ONCE