1 // Copyright 2018 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "src/decoder/partition.h"
16 #include "src/base/bottom_n.h"
17 #include "src/base/utils.h"
18 #include "src/decoder/footprint.h"
19 
20 #include <algorithm>
21 #include <array>
22 #include <limits>
23 #include <memory>
24 #include <numeric>
25 #include <queue>
26 #include <set>
27 #include <unordered_set>
28 #include <utility>
29 
30 namespace astc_codec {
31 
32 namespace {
33 
34 // The maximum number of partitions supported by ASTC is four.
35 constexpr int kMaxNumSubsets = 4;
36 
37 // Partition selection function based on the ASTC specification.
38 // See section C.2.21
SelectASTCPartition(int seed,int x,int y,int z,int partitioncount,int num_pixels)39 int SelectASTCPartition(int seed, int x, int y, int z, int partitioncount,
40                         int num_pixels) {
41   if (partitioncount <= 1) {
42     return 0;
43   }
44 
45   if (num_pixels < 31) {
46     x <<= 1;
47     y <<= 1;
48     z <<= 1;
49   }
50 
51   seed += (partitioncount - 1) * 1024;
52 
53   uint32_t rnum = seed;
54   rnum ^= rnum >> 15;
55   rnum -= rnum << 17;
56   rnum += rnum << 7;
57   rnum += rnum << 4;
58   rnum ^= rnum >> 5;
59   rnum += rnum << 16;
60   rnum ^= rnum >> 7;
61   rnum ^= rnum >> 3;
62   rnum ^= rnum << 6;
63   rnum ^= rnum >> 17;
64 
65   uint8_t seed1 = rnum & 0xF;
66   uint8_t seed2 = (rnum >> 4) & 0xF;
67   uint8_t seed3 = (rnum >> 8) & 0xF;
68   uint8_t seed4 = (rnum >> 12) & 0xF;
69   uint8_t seed5 = (rnum >> 16) & 0xF;
70   uint8_t seed6 = (rnum >> 20) & 0xF;
71   uint8_t seed7 = (rnum >> 24) & 0xF;
72   uint8_t seed8 = (rnum >> 28) & 0xF;
73   uint8_t seed9 = (rnum >> 18) & 0xF;
74   uint8_t seed10 = (rnum >> 22) & 0xF;
75   uint8_t seed11 = (rnum >> 26) & 0xF;
76   uint8_t seed12 = ((rnum >> 30) | (rnum << 2)) & 0xF;
77 
78   seed1 *= seed1;
79   seed2 *= seed2;
80   seed3 *= seed3;
81   seed4 *= seed4;
82   seed5 *= seed5;
83   seed6 *= seed6;
84   seed7 *= seed7;
85   seed8 *= seed8;
86   seed9 *= seed9;
87   seed10 *= seed10;
88   seed11 *= seed11;
89   seed12 *= seed12;
90 
91   int sh1, sh2, sh3;
92   if (seed & 1) {
93     sh1 = (seed & 2 ? 4 : 5);
94     sh2 = (partitioncount == 3 ? 6 : 5);
95   } else {
96     sh1 = (partitioncount == 3 ? 6 : 5);
97     sh2 = (seed & 2 ? 4 : 5);
98   }
99   sh3 = (seed & 0x10) ? sh1 : sh2;
100 
101   seed1 >>= sh1;
102   seed2 >>= sh2;
103   seed3 >>= sh1;
104   seed4 >>= sh2;
105   seed5 >>= sh1;
106   seed6 >>= sh2;
107   seed7 >>= sh1;
108   seed8 >>= sh2;
109 
110   seed9 >>= sh3;
111   seed10 >>= sh3;
112   seed11 >>= sh3;
113   seed12 >>= sh3;
114 
115   int a = seed1 * x + seed2 * y + seed11 * z + (rnum >> 14);
116   int b = seed3 * x + seed4 * y + seed12 * z + (rnum >> 10);
117   int c = seed5 * x + seed6 * y + seed9  * z + (rnum >> 06);
118   int d = seed7 * x + seed8 * y + seed10 * z + (rnum >> 02);
119 
120   a &= 0x3F;
121   b &= 0x3F;
122   c &= 0x3F;
123   d &= 0x3F;
124 
125   if (partitioncount <= 3) {
126     d = 0;
127   }
128   if (partitioncount <= 2) {
129     c = 0;
130   }
131 
132   if (a >= b && a >= c && a >= d) {
133     return 0;
134   } else if (b >= c && b >= d) {
135     return 1;
136   } else if (c >= d) {
137     return 2;
138   } else {
139     return 3;
140   }
141 }
142 
143 // A partition hash that we can pass to containers like std::unordered_set
144 struct PartitionHasher {
operator ()astc_codec::__anon57d69f970111::PartitionHasher145   size_t operator()(const Partition& part) const {
146     // The issue here is that if we have two different partitions, A and B, then
147     // their hash should be equal if A and B are equal. We define the distance
148     // between A and B using PartitionMetric, but internally that finds a 1-1
149     // mapping from labels in A to labels in B.
150     //
151     // With that in mind, when we define a hash for partitions, we need to find
152     // a 1-1 mapping to a 'universal' labeling scheme. Here we define that as
153     // the heuristic: the first encountered label will be 0, the second will be
154     // 1, etc. This creates a unique 1-1 mapping scheme from any partition.
155     //
156     // Note, we can't use this heuristic for the PartitionMetric, as it will
157     // generate very large discrepancies between similar labellings (for example
158     // 000...001 vs 011...111). We are just looking for a boolean distinction
159     // whether or not two partitions are different and don't care how different
160     // they are.
161     std::array<int, kMaxNumSubsets> mapping {{ -1, -1, -1, -1 }};
162     int next_subset = 0;
163     for (int subset : part.assignment) {
164       if (mapping[subset] < 0) {
165         mapping[subset] = next_subset++;
166       }
167     }
168     assert(next_subset <= kMaxNumSubsets);
169 
170     // The return value will be the hash of the assignment according to this
171     // mapping
172     const size_t seed0 = 0;
173     return std::accumulate(part.assignment.begin(), part.assignment.end(), seed0,
174                            [&mapping](size_t seed, const int& subset) {
175                              std::hash<size_t> hasher;
176                              const int s = mapping[subset];
177                              return hasher(seed) ^ hasher(static_cast<size_t>(s));
178                            });
179   }
180 };
181 
182 // Construct a VP-Tree of partitions. Since our PartitionMetric satisfies
183 // the triangle inequality, we can use this general higher-dimensional space
184 // partitioning tree to organize our partitions.
185 //
186 // TODO(google): !SPEED! Right now this tree stores an actual linked
187 // structure of pointers which is likely very slow during construction and
188 // very not cache-coherent during traversal, so it'd probably be good to
189 // switch to a flattened binary tree structure if performance becomes an
190 // issue.
191 class PartitionTree {
192  public:
193   // Unclear what it means to have an uninitialized tree, so delete default
194   // constructors, but allow the tree to be moved
195   PartitionTree() = delete;
196   PartitionTree(const PartitionTree&) = delete;
197   PartitionTree(PartitionTree&& t) = default;
198 
199   // Generate a PartitionTree from iterators over |Partition|s
200   template<typename Itr>
PartitionTree(Itr begin,Itr end)201   PartitionTree(Itr begin, Itr end) : parts_(begin, end) {
202     std::vector<int> part_indices(parts_.size());
203     std::iota(part_indices.begin(), part_indices.end(), 0);
204     root_ = std::unique_ptr<PartitionTreeNode>(
205         new PartitionTreeNode(parts_, part_indices));
206   }
207 
208   // Search for the k-nearest partitions that are closest to part based on
209   // the result of PartitionMetric
Search(const Partition & part,int k,std::vector<const Partition * > * const results,std::vector<int> * const distances) const210   void Search(const Partition& part, int k,
211               std::vector<const Partition*>* const results,
212               std::vector<int>* const distances) const {
213     ResultHeap heap(k);
214     SearchNode(root_, part, &heap);
215 
216     results->clear();
217     if (nullptr != distances) {
218       distances->clear();
219     }
220 
221     std::vector<ResultNode> search_results = heap.Pop();
222     for (const auto& result : search_results) {
223       results->push_back(&parts_[result.part_idx]);
224       if (nullptr != distances) {
225         distances->push_back(result.distance);
226       }
227     }
228 
229     assert(results->size() == size_t(k));
230   }
231 
232  private:
233   // Heap elements to be stored while searching the tree. The two relevant
234   // pieces of information are the partition index and it's distance from the
235   // queried partition.
236   struct ResultNode {
237     int part_idx;
238     int distance;
239 
240     // Heap based on distance from query point.
operator <astc_codec::__anon57d69f970111::PartitionTree::ResultNode241     bool operator<(const ResultNode& other) const {
242       return distance < other.distance;
243     }
244   };
245 
246   using ResultHeap = base::BottomN<ResultNode>;
247 
248   struct PartitionTreeNode {
249     int part_idx;
250     int split_dist;
251 
252     std::unique_ptr<PartitionTreeNode> left;
253     std::unique_ptr<PartitionTreeNode> right;
254 
PartitionTreeNodeastc_codec::__anon57d69f970111::PartitionTree::PartitionTreeNode255     PartitionTreeNode(const std::vector<Partition> &parts,
256                       const std::vector<int> &part_indices)
257         : split_dist(-1) {
258       assert(part_indices.size() > 0);
259 
260       right.reset(nullptr);
261       left.reset(nullptr);
262 
263       // Store the first node as our vantage point
264       part_idx = part_indices[0];
265       const Partition& vantage_point = parts[part_indices[0]];
266 
267       // Calculate the distances of the remaining nodes against the vantage
268       // point.
269       std::vector<std::pair<int, int>> part_dists;
270       for (size_t i = 1; i < part_indices.size(); ++i) {
271         const int idx = part_indices[i];
272         const int dist = PartitionMetric(vantage_point, parts[idx]);
273         if (dist > 0) {
274           part_dists.push_back(std::make_pair(idx, dist));
275         }
276       }
277 
278       // If there are no more different parts, then this is a leaf node
279       if (part_dists.empty()) {
280         return;
281       }
282 
283       struct OrderBySecond {
284         typedef std::pair<int, int> PairType;
285           bool operator()(const PairType& lhs, const PairType& rhs) {
286             return lhs.second < rhs.second;
287           }
288       };
289 
290       // We want to partition the set such that the points are ordered
291       // based on their distances from the vantage point. We can do this
292       // using the partial sort of nth element.
293       std::nth_element(
294           part_dists.begin(), part_dists.begin() + part_dists.size() / 2,
295           part_dists.end(), OrderBySecond());
296 
297       // Once that's done, our split position is in the middle
298       const auto split_iter = part_dists.begin() + part_dists.size() / 2;
299       split_dist = split_iter->second;
300 
301       // Recurse down the right and left sub-trees with the indices of the
302       // parts that are farther and closer respectively
303       std::vector<int> right_indices;
304       for (auto itr = split_iter; itr != part_dists.end(); ++itr) {
305         right_indices.push_back(itr->first);
306       }
307 
308       if (!right_indices.empty()) {
309         right.reset(new PartitionTreeNode(parts, right_indices));
310       }
311 
312       std::vector<int> left_indices;
313       for (auto itr = part_dists.begin(); itr != split_iter; ++itr) {
314         left_indices.push_back(itr->first);
315       }
316 
317       if (!left_indices.empty()) {
318         left.reset(new PartitionTreeNode(parts, left_indices));
319       }
320     }
321   };
322 
SearchNode(const std::unique_ptr<PartitionTreeNode> & node,const Partition & p,ResultHeap * const heap) const323   void SearchNode(const std::unique_ptr<PartitionTreeNode>& node,
324                   const Partition& p, ResultHeap* const heap) const {
325     if (nullptr == node) {
326       return;
327     }
328 
329     // Calculate distance against current node
330     const int dist = PartitionMetric(parts_[node->part_idx], p);
331 
332     // Push it onto the heap and remove the top-most nodes to maintain
333     // closest k indices.
334     ResultNode result;
335     result.part_idx = node->part_idx;
336     result.distance = dist;
337     heap->Push(result);
338 
339     // If the split distance is uninitialized, it means we have no children.
340     if (node->split_dist < 0) {
341       assert(nullptr == node->left);
342       assert(nullptr == node->right);
343       return;
344     }
345 
346     // Next we need to check the left and right trees if their distance
347     // is closer/farther than the farthest element on the heap
348     const int tau = heap->Top().distance;
349     if (dist + tau < node->split_dist || dist - tau < node->split_dist) {
350       SearchNode(node->left, p, heap);
351     }
352 
353     if (dist + tau > node->split_dist || dist - tau > node->split_dist) {
354       SearchNode(node->right, p, heap);
355     }
356   }
357 
358   std::vector<Partition> parts_;
359   std::unique_ptr<PartitionTreeNode> root_;
360 };
361 
362 // A helper function that generates all of the partitions for each number of
363 // subsets in ASTC blocks and stores them in a PartitionTree for fast retrieval.
364 const int kNumASTCPartitionIDBits = 10;
GenerateASTCPartitionTree(Footprint footprint)365 PartitionTree GenerateASTCPartitionTree(Footprint footprint) {
366   std::unordered_set<Partition, PartitionHasher> parts;
367   for (int num_parts = 2; num_parts <= kMaxNumSubsets; ++num_parts) {
368     for (int id = 0; id < (1 << kNumASTCPartitionIDBits); ++id) {
369       Partition part = GetASTCPartition(footprint, num_parts, id);
370 
371       // Make sure we're not using a degenerate partition assignment that wastes
372       // an endpoint pair...
373       bool valid_part = true;
374       for (int i = 0; i < num_parts; ++i) {
375         if (std::find(part.assignment.begin(), part.assignment.end(), i) ==
376             part.assignment.end()) {
377           valid_part = false;
378           break;
379         }
380       }
381 
382       if (valid_part) {
383         parts.insert(std::move(part));
384       }
385     }
386   }
387 
388   return PartitionTree(parts.begin(), parts.end());
389 }
390 
391 // To avoid needing any fancy boilerplate for mapping from a width, height
392 // tuple, we can define a simple encoding for the block mode:
EncodeDims(int width,int height)393 constexpr int EncodeDims(int width, int height) {
394   return (width << 16) | height;
395 }
396 
397 }  // namespace
398 
399 ////////////////////////////////////////////////////////////////////////////////
400 
PartitionMetric(const Partition & a,const Partition & b)401 int PartitionMetric(const Partition& a, const Partition& b) {
402   // Make sure that one partition is at least a subset of the other...
403   UTILS_RELEASE_ASSERT(a.footprint == b.footprint);
404 
405   // Make sure that the number of parts is within our limits. ASTC has a maximum
406   // of four subsets per block according to the specification.
407   UTILS_RELEASE_ASSERT(a.num_parts <= kMaxNumSubsets);
408   UTILS_RELEASE_ASSERT(b.num_parts <= kMaxNumSubsets);
409 
410   const int w = a.footprint.Width();
411   const int h = b.footprint.Height();
412 
413   struct PairCount {
414     int a;
415     int b;
416     int count;
417 
418     // Comparison needed for sort below.
419     bool operator>(const PairCount& other) const {
420       return count > other.count;
421     }
422   };
423 
424   // Since we need to find the smallest mapping from labels in A to labels in B,
425   // we need to store each label pair in a structure that can later be sorted.
426   // The maximum number of subsets in an ASTC block is four, meaning that
427   // between the two partitions, we can have up to sixteen different pairs.
428   std::array<PairCount, 16> pair_counts;
429   for (int y = 0; y < 4; ++y) {
430     for (int x = 0; x < 4; ++x) {
431       const int idx = y * 4 + x;
432       pair_counts[idx].a = x;
433       pair_counts[idx].b = y;
434       pair_counts[idx].count = 0;
435     }
436   }
437 
438   // Count how many times we see each pair of assigned values (order matters!)
439   for (int y = 0; y < h; ++y) {
440     for (int x = 0; x < w; ++x) {
441       const int idx = y * w + x;
442 
443       const int a_val = a.assignment[idx];
444       const int b_val = b.assignment[idx];
445 
446       assert(a_val >= 0);
447       assert(b_val >= 0);
448 
449       assert(a_val < 4);
450       assert(b_val < 4);
451 
452       ++(pair_counts[b_val * 4 + a_val].count);
453     }
454   }
455 
456   // Sort the pairs in descending order based on their count
457   std::sort(pair_counts.begin(), pair_counts.end(), std::greater<PairCount>());
458 
459   // Now assign pairs one by one until we have no more pairs to assign. Once
460   // a value from A is assigned to a value in B, it can no longer be reassigned,
461   // so we can keep track of this in a matrix. Similarly, to keep the assignment
462   // one-to-one, once a value in B has been assigned to, it cannot be assigned
463   // to again.
464   std::array<std::array<bool, kMaxNumSubsets>, kMaxNumSubsets> assigned { };
465 
466   int pixels_matched = 0;
467   for (const auto& pair_count : pair_counts) {
468     bool is_assigned = false;
469     for (int i = 0; i < kMaxNumSubsets; ++i) {
470       is_assigned |= assigned.at(pair_count.a).at(i);
471       is_assigned |= assigned.at(i).at(pair_count.b);
472     }
473 
474     if (!is_assigned) {
475       assigned.at(pair_count.a).at(pair_count.b) = true;
476       pixels_matched += pair_count.count;
477     }
478   }
479 
480   // The difference is the number of pixels that had an assignment versus the
481   // total number of pixels.
482   return w * h - pixels_matched;
483 }
484 
485 // Generates the partition assignment for the given block attributes.
GetASTCPartition(const Footprint & footprint,int num_parts,int partition_id)486 Partition GetASTCPartition(const Footprint& footprint, int num_parts,
487                            int partition_id) {
488   // Partitions must have at least one subset but may have at most four
489   assert(num_parts >= 0);
490   assert(num_parts <= kMaxNumSubsets);
491 
492   // Partition ID can be no more than 10 bits.
493   assert(partition_id >= 0);
494   assert(partition_id < 1 << 10);
495 
496   Partition part = {footprint, num_parts, partition_id, /* assignment = */ {}};
497   part.assignment.reserve(footprint.NumPixels());
498 
499   // Maintain column-major order so that we match all of the image processing
500   // algorithms that depend on this class.
501   for (int y = 0; y < footprint.Height(); ++y) {
502     for (int x = 0; x < footprint.Width(); ++x) {
503       const int p = SelectASTCPartition(partition_id, x, y, 0, num_parts,
504                                         footprint.NumPixels());
505       part.assignment.push_back(p);
506     }
507   }
508 
509   return part;
510 }
511 
FindKClosestASTCPartitions(const Partition & candidate,int k)512 const std::vector<const Partition*> FindKClosestASTCPartitions(
513     const Partition& candidate, int k) {
514   const int encoded_dims = EncodeDims(candidate.footprint.Width(),
515                                       candidate.footprint.Height());
516 
517   int index = 0;
518   switch (encoded_dims) {
519     case EncodeDims(4, 4): index = 0; break;
520     case EncodeDims(5, 4): index = 1; break;
521     case EncodeDims(5, 5): index = 2; break;
522     case EncodeDims(6, 5): index = 3; break;
523     case EncodeDims(6, 6): index = 4; break;
524     case EncodeDims(8, 5): index = 5; break;
525     case EncodeDims(8, 6): index = 6; break;
526     case EncodeDims(8, 8): index = 7; break;
527     case EncodeDims(10, 5): index = 8; break;
528     case EncodeDims(10, 6): index = 9; break;
529     case EncodeDims(10, 8): index = 10; break;
530     case EncodeDims(10, 10): index = 11; break;
531     case EncodeDims(12, 10): index = 12; break;
532     case EncodeDims(12, 12): index = 13; break;
533     default:
534       assert(false && "Unknown footprint dimensions. This should have been caught sooner.");
535       break;
536   }
537 
538   static const auto* const kASTCPartitionTrees =
539       new std::array<PartitionTree, Footprint::NumValidFootprints()> {{
540       GenerateASTCPartitionTree(Footprint::Get4x4()),
541       GenerateASTCPartitionTree(Footprint::Get5x4()),
542       GenerateASTCPartitionTree(Footprint::Get5x5()),
543       GenerateASTCPartitionTree(Footprint::Get6x5()),
544       GenerateASTCPartitionTree(Footprint::Get6x6()),
545       GenerateASTCPartitionTree(Footprint::Get8x5()),
546       GenerateASTCPartitionTree(Footprint::Get8x6()),
547       GenerateASTCPartitionTree(Footprint::Get8x8()),
548       GenerateASTCPartitionTree(Footprint::Get10x5()),
549       GenerateASTCPartitionTree(Footprint::Get10x6()),
550       GenerateASTCPartitionTree(Footprint::Get10x8()),
551       GenerateASTCPartitionTree(Footprint::Get10x10()),
552       GenerateASTCPartitionTree(Footprint::Get12x10()),
553       GenerateASTCPartitionTree(Footprint::Get12x12()),
554     }};
555 
556   const PartitionTree& parts_vptree = kASTCPartitionTrees->at(index);
557   std::vector<const Partition*> results;
558   parts_vptree.Search(candidate, k, &results, nullptr);
559   return results;
560 }
561 
562 // Returns the valid ASTC partition that is closest to the candidate based on
563 // the PartitionMetric defined above.
FindClosestASTCPartition(const Partition & candidate)564 const Partition& FindClosestASTCPartition(const Partition& candidate) {
565   // Given a candidate, the closest valid partition will likely not be an exact
566   // match. Consider all of the texels for which this valid partition differs
567   // with the candidate.
568   //
569   // If the valid partition has more subsets than the candidate, then all of the
570   // highest subset will be included in the mismatched texels. Since the number
571   // of possible partitions with increasing subsets grows exponentially, the
572   // chance that a valid partition with fewer subsets appears within the first
573   // few closest partitions is relatively high. Empirically, we can usually find
574   // a partition with at most |candidate.num_parts| number of subsets within the
575   // first four closest partitions.
576   constexpr int kSearchItems = 4;
577 
578   const std::vector<const Partition*> results =
579       FindKClosestASTCPartitions(candidate, kSearchItems);
580 
581   // Optimistically, look for result with the same number of subsets.
582   for (const auto& result : results) {
583     if (result->num_parts == candidate.num_parts) {
584       return *result;
585     }
586   }
587 
588   // If all else fails, then at least find the result with fewer subsets than
589   // we asked for.
590   for (const auto& result : results) {
591     if (result->num_parts < candidate.num_parts) {
592       return *result;
593     }
594   }
595 
596   assert(false &&
597          "Could not find partition with acceptable number of subsets!");
598   return *(results[0]);
599 }
600 
601 }  // namespace astc_codec
602