aboutsummaryrefslogtreecommitdiff
path: root/src/decoder/intermediate_astc_block.cc
blob: e03af1eb488e0b0e6540ca638ba935ae493f8f39 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "src/decoder/intermediate_astc_block.h"
#include "src/decoder/integer_sequence_codec.h"
#include "src/base/bit_stream.h"
#include "src/base/math_utils.h"
#include "src/base/optional.h"
#include "src/base/uint128.h"

#include <algorithm>
#include <numeric>
#include <sstream>

namespace astc_codec {

namespace {

constexpr int kEndpointRange_ReturnInvalidWeightDims = -1;
constexpr int kEndpointRange_ReturnNotEnoughColorBits = -2;

base::UInt128 PackVoidExtentBlock(uint16_t r, uint16_t g, uint16_t b,
                                  uint16_t a, std::array<uint16_t, 4> coords) {
  base::BitStream<base::UInt128> bit_sink;

  // Put void extent mode...
  bit_sink.PutBits(0xDFC, 12);

  // Each of the coordinates goes in 13 bits at a time.
  for (auto coord : coords) {
    assert(coord < 1 << 13);
    bit_sink.PutBits(coord, 13);
  }
  assert(bit_sink.Bits() == 64);

  // Then we add R, G, B, and A in order
  bit_sink.PutBits(r, 16);
  bit_sink.PutBits(g, 16);
  bit_sink.PutBits(b, 16);
  bit_sink.PutBits(a, 16);

  assert(bit_sink.Bits() == 128);

  base::UInt128 result;
  bit_sink.GetBits(128, &result);
  return result;
}

base::Optional<std::string> GetEncodedWeightRange(int range,
                                                  std::array<int, 3>* const r) {
  const std::array<std::array<int, 3>, 12> kValidRangeEncodings =
      {{ {{ 0, 1, 0 }}, {{ 1, 1, 0 }}, {{ 0, 0, 1 }},
         {{ 1, 0, 1 }}, {{ 0, 1, 1 }}, {{ 1, 1, 1 }},
         {{ 0, 1, 0 }}, {{ 1, 1, 0 }}, {{ 0, 0, 1 }},
         {{ 1, 0, 1 }}, {{ 0, 1, 1 }}, {{ 1, 1, 1 }} }};

  // If our range is larger than all available ranges, this is an error.
  const int smallest_range = kValidWeightRanges.front();
  const int largest_range = kValidWeightRanges.back();
  if (range < smallest_range || largest_range < range) {
    std::stringstream strm;
    strm << "Could not find block mode. Invalid weight range: "
         << range << " not in [" << smallest_range << ", "
         << largest_range << std::endl;
    return strm.str();
  }

  // Find the upper bound on the range, otherwise.
  const auto range_iter = std::lower_bound(
      kValidWeightRanges.cbegin(), kValidWeightRanges.cend(), range);
  auto enc_iter = kValidRangeEncodings.cbegin();
  enc_iter += std::distance(kValidWeightRanges.cbegin(), range_iter);
  *r = *enc_iter;
  return {};
}

struct BlockModeInfo {
  int min_weight_grid_dim_x;
  int max_weight_grid_dim_x;
  int min_weight_grid_dim_y;
  int max_weight_grid_dim_y;
  int r0_bit_pos;
  int r1_bit_pos;
  int r2_bit_pos;
  int weight_grid_x_offset_bit_pos;
  int weight_grid_y_offset_bit_pos;
  bool require_single_plane_low_prec;
};

constexpr int kNumBlockModes = 10;
const std::array<BlockModeInfo, kNumBlockModes> kBlockModeInfo {{
  { 4, 7, 2, 5, 4, 0, 1, 7, 5, false },      // B+4 A+2
  { 8, 11, 2, 5, 4, 0, 1, 7, 5, false },     // B+8 A+2
  { 2, 5, 8, 11, 4, 0, 1, 5, 7, false },     // A+2 B+8
  { 2, 5, 6, 7, 4, 0, 1, 5, 7, false },      // A+2 B+6
  { 2, 3, 2, 5, 4, 0, 1, 7, 5, false },      // B+2 A+2
  { 12, 12, 2, 5, 4, 2, 3, -1, 5, false },   // 12  A+2
  { 2, 5, 12, 12, 4, 2, 3, 5, -1, false },   // A+2 12
  { 6, 6, 10, 10, 4, 2, 3, -1, -1, false },  // 6   10
  { 10, 10, 6, 6, 4, 2, 3, -1, -1, false },  // 10  6
  { 6, 9, 6, 9, 4, 2, 3, 5, 9, true }        // A+6 B+6
}};

// These are the bits that must be set for ASTC to recognize a given
// block mode. They are the 1's set in table C.2.8 of the spec.
const std::array<int, kNumBlockModes> kBlockModeMask = {{
  0x0, 0x4, 0x8, 0xC, 0x10C, 0x0, 0x80, 0x180, 0x1A0, 0x100
}};

static base::Optional<std::string> PackBlockMode(int dim_x, int dim_y, int range,
                                          bool dual_plane,
                                          base::BitStream<base::UInt128>* const bit_sink) {
  // We need to set the high precision bit if our range is too high...
  bool high_prec = range > 7;

  std::array<int, 3> r;
  const auto result = GetEncodedWeightRange(range, &r);
  if (result) {
    return result;
  }

  // The high two bits of R must not be zero. If this happens then it's
  // an illegal encoding according to Table C.2.7 that should have gotten
  // caught in GetEncodedWeightRange
  assert((r[1] | r[2]) > 0);

  // Just go through the table and see if any of the modes can handle
  // the given dimensions.
  for (int mode = 0; mode < kNumBlockModes; ++mode) {
    const BlockModeInfo& block_mode = kBlockModeInfo[mode];

    bool is_valid_mode = true;
    is_valid_mode &= block_mode.min_weight_grid_dim_x <= dim_x;
    is_valid_mode &= dim_x <= block_mode.max_weight_grid_dim_x;
    is_valid_mode &= block_mode.min_weight_grid_dim_y <= dim_y;
    is_valid_mode &= dim_y <= block_mode.max_weight_grid_dim_y;
    is_valid_mode &= !(block_mode.require_single_plane_low_prec && dual_plane);
    is_valid_mode &= !(block_mode.require_single_plane_low_prec && high_prec);

    if (!is_valid_mode) {
      continue;
    }

    // Initialize to the bits we must set.
    uint32_t encoded_mode = kBlockModeMask[mode];
    auto setBit = [&encoded_mode](const uint32_t value, const uint32_t offset) {
      encoded_mode = (encoded_mode & ~(1 << offset)) | ((value & 1) << offset);
    };

    // Set all the bits we need to set
    setBit(r[0], block_mode.r0_bit_pos);
    setBit(r[1], block_mode.r1_bit_pos);
    setBit(r[2], block_mode.r2_bit_pos);

    // Find our width and height offset from the base width and height weight
    // grid dimension for the given block mode. These are the 1-2 bits that
    // get encoded in the block mode used to calculate the final weight grid
    // width and height.
    const int offset_x = dim_x - block_mode.min_weight_grid_dim_x;
    const int offset_y = dim_y - block_mode.min_weight_grid_dim_y;

    // If we don't have an offset position then our offset better be zero.
    // If this isn't the case, then this isn't a viable block mode and we
    // should have caught this sooner.
    assert(block_mode.weight_grid_x_offset_bit_pos >= 0 || offset_x == 0);
    assert(block_mode.weight_grid_y_offset_bit_pos >= 0 || offset_y == 0);

    encoded_mode |= offset_x << block_mode.weight_grid_x_offset_bit_pos;
    encoded_mode |= offset_y << block_mode.weight_grid_y_offset_bit_pos;

    if (!block_mode.require_single_plane_low_prec) {
      setBit(high_prec, 9);
      setBit(dual_plane, 10);
    }

    // Make sure that the mode is the first thing the bit sink is writing to
    assert(bit_sink->Bits() == 0);
    bit_sink->PutBits(encoded_mode, 11);

    return {};
  }

  return std::string("Could not find viable block mode");
}

// Returns true if all endpoint modes are equal.
bool SharedEndpointModes(const IntermediateBlockData& data) {
  return std::accumulate(
      data.endpoints.begin(), data.endpoints.end(), true,
      [&data](const bool& a, const IntermediateEndpointData& b) {
        return a && b.mode == data.endpoints[0].mode;
      });
}

// Returns the starting bit (between 0 and 128) where the extra CEM and
// dual plane info is stored in the ASTC block.
int ExtraConfigBitPosition(const IntermediateBlockData& data) {
  const bool has_dual_channel = data.dual_plane_channel.hasValue();
  const int num_weights = data.weight_grid_dim_x * data.weight_grid_dim_y *
      (has_dual_channel ? 2 : 1);
  const int num_weight_bits =
      IntegerSequenceCodec::GetBitCountForRange(num_weights, data.weight_range);

  int extra_config_bits = 0;
  if (!SharedEndpointModes(data)) {
    const int num_encoded_cem_bits = 2 + data.endpoints.size() * 3;
    extra_config_bits = num_encoded_cem_bits - 6;
  }

  if (has_dual_channel) {
    extra_config_bits += 2;
  }

  return 128 - num_weight_bits - extra_config_bits;
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////

base::Optional<IntermediateBlockData> UnpackIntermediateBlock(
    const PhysicalASTCBlock& pb) {
  if (pb.IsIllegalEncoding()) {
    return {};
  }

  if (pb.IsVoidExtent()) {
    return {};
  }

  // Non void extent? Then let's try to decode everything else.
  IntermediateBlockData data;

  // All blocks have color values...
  const base::UInt128 color_bits_mask =
      (base::UInt128(1) << pb.NumColorBits().value()) - 1;
  const base::UInt128 color_bits =
      (pb.GetBlockBits() >> pb.ColorStartBit().value()) & color_bits_mask;
  base::BitStream<base::UInt128> bit_src(color_bits, 128);

  IntegerSequenceDecoder color_decoder(pb.ColorValuesRange().value());
  const int num_colors_in_block = pb.NumColorValues().value();
  std::vector<int> colors = color_decoder.Decode(num_colors_in_block, &bit_src);

  // Decode simple info
  const auto weight_dims = pb.WeightGridDims();
  data.weight_grid_dim_x = weight_dims->at(0);
  data.weight_grid_dim_y = weight_dims->at(1);
  data.weight_range = pb.WeightRange().value();

  data.partition_id = pb.PartitionID();
  data.dual_plane_channel = pb.DualPlaneChannel();

  auto colors_iter = colors.begin();
  for (int i = 0; i < pb.NumPartitions().value(); ++i) {
    IntermediateEndpointData ep_data;
    ep_data.mode = pb.GetEndpointMode(i).value();

    const int num_colors = NumColorValuesForEndpointMode(ep_data.mode);
    ep_data.colors.insert(ep_data.colors.end(), colors_iter,
                          colors_iter + num_colors);
    colors_iter += num_colors;

    data.endpoints.push_back(ep_data);
  }
  assert(colors_iter == colors.end());
  data.endpoint_range = pb.ColorValuesRange().value();

  // Finally decode the weights
  const base::UInt128 weight_bits_mask =
      (base::UInt128(1) << pb.NumWeightBits().value()) - 1;
  const base::UInt128 weight_bits =
      base::ReverseBits(pb.GetBlockBits()) & weight_bits_mask;
  bit_src = base::BitStream<base::UInt128>(weight_bits, 128);

  IntegerSequenceDecoder weight_decoder(data.weight_range);
  int num_weights = data.weight_grid_dim_x * data.weight_grid_dim_y;
  num_weights *= pb.IsDualPlane() ? 2 : 1;
  data.weights = weight_decoder.Decode(num_weights, &bit_src);

  return data;
}

int EndpointRangeForBlock(const IntermediateBlockData& data) {
  // First check to see if we exceed the number of bits allotted for weights, as
  // specified in C.2.24. If so, then the endpoint range is meaningless, but not
  // because we had an overzealous color endpoint mode, so return a different
  // error code.
  if (IntegerSequenceCodec::GetBitCountForRange(
          data.weight_grid_dim_x * data.weight_grid_dim_y *
          (data.dual_plane_channel.hasValue() ? 2 : 1),
          data.weight_range) > 96) {
    return kEndpointRange_ReturnInvalidWeightDims;
  }

  const int num_partitions = data.endpoints.size();

  // Calculate the number of bits that we would write prior to getting to the
  // color endpoint data
  const int bits_written =
      11   // Block mode
      + 2  // Num partitions
      + ((num_partitions > 1) ? 10 : 0)  // Partition ID
      + ((num_partitions == 1) ? 4 : 6);  // Shared CEM bits

  // We can determine the range based on how many bits we have between the start
  // of the color endpoint data and the next section, which is the extra config
  // bit position
  const int color_bits_available = ExtraConfigBitPosition(data) - bits_written;

  int num_color_values = 0;
  for (const auto& ep_data : data.endpoints) {
    num_color_values += NumColorValuesForEndpointMode(ep_data.mode);
  }

  // There's no way any valid ASTC encoding has no room left for any color
  // values. If we hit this then something is wrong in the caller -- abort.
  // According to section C.2.24, the smallest number of bits available is
  // ceil(13*C/5), where C is the number of color endpoint integers needed.
  const int bits_needed = (13 * num_color_values + 4) / 5;
  if (color_bits_available < bits_needed) {
    return kEndpointRange_ReturnNotEnoughColorBits;
  }

  int color_value_range = 255;
  for (; color_value_range > 1; --color_value_range) {
    const int bits_for_range = IntegerSequenceCodec::GetBitCountForRange(
        num_color_values, color_value_range);
    if (bits_for_range <= color_bits_available) {
      break;
    }
  }

  return color_value_range;
}

base::Optional<VoidExtentData> UnpackVoidExtent(const PhysicalASTCBlock& pb) {
  if (pb.IsIllegalEncoding()) {
    return {};
  }

  if (!pb.IsVoidExtent()) {
    return {};
  }

  // All blocks have color values...
  const base::UInt128 color_bits_mask =
      (base::UInt128(1) << pb.NumColorBits().value()) - 1;
  const uint64_t color_bits = (
      (pb.GetBlockBits() >> pb.ColorStartBit().value()) & color_bits_mask).LowBits();

  assert(pb.NumColorValues().value() == 4);
  VoidExtentData data;
  data.r = static_cast<uint16_t>((color_bits >>  0) & 0xFFFF);
  data.g = static_cast<uint16_t>((color_bits >> 16) & 0xFFFF);
  data.b = static_cast<uint16_t>((color_bits >> 32) & 0xFFFF);
  data.a = static_cast<uint16_t>((color_bits >> 48) & 0xFFFF);

  const auto void_extent_coords = pb.VoidExtentCoords();
  if (void_extent_coords) {
    data.coords[0] = void_extent_coords->at(0);
    data.coords[1] = void_extent_coords->at(1);
    data.coords[2] = void_extent_coords->at(2);
    data.coords[3] = void_extent_coords->at(3);
  } else {
    uint16_t all_ones = (1 << 13) - 1;
    for (auto& coord : data.coords) {
      coord = all_ones;
    }
  }

  return data;
}

// Packs the given intermediate block into a physical block. Returns false if
// the provided values in the intermediate block emit an illegal ASTC
// encoding.
base::Optional<std::string> Pack(const IntermediateBlockData& data,
                                 base::UInt128* pb) {
  if (data.weights.size() !=
      data.weight_grid_dim_x * data.weight_grid_dim_y *
      (data.dual_plane_channel.hasValue() ? 2 : 1)) {
    return std::string("Incorrect number of weights!");
  }

  // If it's not a void extent block, then it gets a bit more tricky...
  base::BitStream<base::UInt128> bit_sink;

  // First we need to encode the block mode.
  const auto error_string = PackBlockMode(
      data.weight_grid_dim_x, data.weight_grid_dim_y, data.weight_range,
      data.dual_plane_channel.hasValue(), &bit_sink);
  if (error_string) {
    return error_string;
  }

  // Next, we place the number of partitions minus one.
  const int num_partitions = data.endpoints.size();
  bit_sink.PutBits(num_partitions - 1, 2);

  // If we have more than one partition, then we also have a partition ID.
  if (num_partitions > 1) {
    const int id = data.partition_id.value();
    assert(id >= 0);
    bit_sink.PutBits(id, 10);
  }

  // Take a detour, let's encode the weights so that we know how many bits they
  // consume.
  base::BitStream<base::UInt128> weight_sink;

  IntegerSequenceEncoder weight_enc(data.weight_range);
  for (auto weight : data.weights) {
    weight_enc.AddValue(weight);
  }
  weight_enc.Encode(&weight_sink);

  const int num_weight_bits = weight_sink.Bits();
  assert(num_weight_bits ==
           IntegerSequenceCodec::GetBitCountForRange(
               data.weights.size(), data.weight_range));

  // Let's continue... how much after the color data do we need to write?
  int extra_config = 0;

  // Determine if all endpoint pairs share the same endpoint mode
  assert(data.endpoints.size() > 0);
  bool shared_endpoint_mode = SharedEndpointModes(data);

  // The first part of the endpoint mode (CEM) comes directly after the
  // partition info, if it exists. If there is no partition info, the CEM comes
  // right after the block mode. In the single-partition case, we just write out
  // the entire singular CEM, but in the multi-partition case, if all CEMs are
  // the same then their shared CEM is specified directly here, too. In both
  // cases, shared_endpoint_mode is true (in the singular case,
  // shared_endpoint_mode is trivially true).
  if (shared_endpoint_mode) {
    if (num_partitions > 1) {
      bit_sink.PutBits(0, 2);
    }
    bit_sink.PutBits(static_cast<int>(data.endpoints[0].mode), 4);
  } else {
    // Here, the CEM is not shared across all endpoint pairs, and we need to
    // figure out what to place here, and what to place in the extra config
    // bits before the weight data...

    // Non-shared config modes must all be within the same class (out of four)
    // See Section C.2.11
    int min_class = 2;  // We start with 2 here instead of three because it's
                        // the highest that can be encoded -- even if all modes
                        // are class 3.
    int max_class = 0;
    for (const auto& ep_data : data.endpoints) {
      const int ep_mode_class = static_cast<int>(ep_data.mode) >> 2;
      min_class = std::min(min_class, ep_mode_class);
      max_class = std::max(max_class, ep_mode_class);
    }

    assert(max_class >= min_class);

    if (max_class - min_class > 1) {
      return std::string("Endpoint modes are invalid");
    }

    // Construct the CEM mode -- six of its bits will fit here, but otherwise
    // the rest will go in the extra configuration bits.
    base::BitStream<uint32_t> cem_encoder;

    // First encode the base class
    assert(min_class >= 0);
    assert(min_class < 3);
    cem_encoder.PutBits(min_class + 1, 2);

    // Next, encode the class selector bits -- this is simply the offset
    // from the base class
    for (const auto& ep_data : data.endpoints) {
      const int ep_mode_class = static_cast<int>(ep_data.mode) >> 2;
      const int class_selector_bit = ep_mode_class - min_class;
      assert(class_selector_bit == 0 || class_selector_bit == 1);
      cem_encoder.PutBits(class_selector_bit, 1);
    }

    // Finally, we need to choose from each class which actual mode
    // we belong to and encode those.
    for (const auto& ep_data : data.endpoints) {
      const int ep_mode = static_cast<int>(ep_data.mode) & 3;
      assert(ep_mode < 4);
      cem_encoder.PutBits(ep_mode, 2);
    }
    assert(cem_encoder.Bits() == 2 + num_partitions * 3);

    uint32_t encoded_cem;
    cem_encoder.GetBits(2 + num_partitions * 3, &encoded_cem);

    // Since only six bits fit here before the color endpoint data, the rest
    // need to go in the extra config data.
    extra_config = encoded_cem >> 6;

    // Write out the six bits we had
    bit_sink.PutBits(encoded_cem, 6);
  }

  // If we have a dual-plane channel, we can tack that onto our extra config
  // data
  if (data.dual_plane_channel.hasValue()) {
    const int channel = data.dual_plane_channel.value();
    assert(channel < 4);
    extra_config <<= 2;
    extra_config |= channel;
  }

  // Get the range of endpoint values. It can't be -1 because we should have
  // checked for that much earlier.
  const int color_value_range = data.endpoint_range
      ? data.endpoint_range.value()
      : EndpointRangeForBlock(data);

  assert(color_value_range != kEndpointRange_ReturnInvalidWeightDims);
  if (color_value_range == kEndpointRange_ReturnNotEnoughColorBits) {
    return { "Intermediate block emits illegal color range" };
  }

  IntegerSequenceEncoder color_enc(color_value_range);
  for (const auto& ep_data : data.endpoints) {
    for (int color : ep_data.colors) {
      if (color > color_value_range) {
        return { "Color outside available color range!" };
      }

      color_enc.AddValue(color);
    }
  }
  color_enc.Encode(&bit_sink);

  // Now we need to skip some bits to get to the extra configuration bits. The
  // number of bits we need to skip depends on where we are in the stream and
  // where we need to get to.
  const int extra_config_bit_position = ExtraConfigBitPosition(data);
  const int extra_config_bits =
      128 - num_weight_bits - extra_config_bit_position;
  assert(extra_config_bits >= 0);
  assert(extra_config < 1 << extra_config_bits);

  // Make sure the color encoder didn't write more than we thought it would.
  int bits_to_skip = extra_config_bit_position - bit_sink.Bits();
  assert(bits_to_skip >= 0);

  while (bits_to_skip > 0) {
    const int skipping = std::min(32, bits_to_skip);
    bit_sink.PutBits(0, skipping);
    bits_to_skip -= skipping;
  }

  // Finally, write out the rest of the config bits.
  bit_sink.PutBits(extra_config, extra_config_bits);

  // We should be right up to the weight bits...
  assert(bit_sink.Bits() == 128 - num_weight_bits);

  // Flush out our bit writer and write out the weight bits
  base::UInt128 astc_bits;
  bit_sink.GetBits(128 - num_weight_bits, &astc_bits);

  base::UInt128 rev_weight_bits;
  weight_sink.GetBits(weight_sink.Bits(), &rev_weight_bits);

  astc_bits |= base::ReverseBits(rev_weight_bits);

  // And we're done! Whew!
  *pb = astc_bits;
  return PhysicalASTCBlock(*pb).IsIllegalEncoding();
}

base::Optional<std::string> Pack(const VoidExtentData& data,
                                 base::UInt128* pb) {
  *pb = PackVoidExtentBlock(data.r, data.g, data.b, data.a, data.coords);
  return PhysicalASTCBlock(*pb).IsIllegalEncoding();
}

}  // namespace astc_codec