| // 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/physical_astc_block.h" |
| #include "src/base/math_utils.h" |
| #include "src/base/optional.h" |
| #include "src/base/uint128.h" |
| #include "src/decoder/integer_sequence_codec.h" |
| |
| #include <array> |
| #include <cmath> |
| |
| namespace astc_codec { |
| |
| namespace { |
| |
| static_assert(static_cast<int>(ColorEndpointMode::kNumColorEndpointModes) == 16, |
| "There are only sixteen color endpoint modes defined in the " |
| "ASTC specification. If this is false, then the enum may be " |
| "incorrect."); |
| |
| constexpr int kASTCBlockSizeBits = 128; |
| constexpr int kASTCBlockSizeBytes = kASTCBlockSizeBits / 8; |
| constexpr uint32_t kVoidExtentMaskBits = 9; |
| constexpr uint32_t kVoidExtentMask = 0x1FC; |
| constexpr int kWeightGridMinBitLength = 24; |
| constexpr int kWeightGridMaxBitLength = 96; |
| constexpr int kMaxNumPartitions = 4; |
| constexpr int kMaxNumWeights = 64; |
| |
| // These are the overall block modes defined in table C.2.8. There are 10 |
| // weight grid encoding schemes + void extent. |
| enum class BlockMode { |
| kB4_A2, |
| kB8_A2, |
| kA2_B8, |
| kA2_B6, |
| kB2_A2, |
| k12_A2, |
| kA2_12, |
| k6_10, |
| k10_6, |
| kA6_B6, |
| kVoidExtent, |
| }; |
| |
| struct WeightGridProperties { |
| int width; |
| int height; |
| int range; |
| }; |
| |
| // Local function prototypes |
| base::Optional<BlockMode> DecodeBlockMode(const base::UInt128 astc_bits); |
| base::Optional<WeightGridProperties> DecodeWeightProps( |
| const base::UInt128 astc_bits, std::string* error); |
| std::array<int, 4> DecodeVoidExtentCoords(const base::UInt128 astc_bits); |
| bool DecodeDualPlaneBit(const base::UInt128 astc_bits); |
| int DecodeNumPartitions(const base::UInt128 astc_bits); |
| int DecodeNumWeightBits(const base::UInt128 astc_bits); |
| int DecodeDualPlaneBitStartPos(const base::UInt128 astc_bits); |
| ColorEndpointMode DecodeEndpointMode(const base::UInt128 astc_bits, |
| int partition); |
| int DecodeNumColorValues(const base::UInt128 astc_bits); |
| |
| // Returns the block mode, if it's valid. |
| base::Optional<BlockMode> DecodeBlockMode(const base::UInt128 astc_bits) { |
| using Result = base::Optional<BlockMode>; |
| const uint64_t low_bits = astc_bits.LowBits(); |
| if (base::GetBits(low_bits, 0, kVoidExtentMaskBits) == kVoidExtentMask) { |
| return Result(BlockMode::kVoidExtent); |
| } |
| |
| if (base::GetBits(low_bits, 0, 2) != 0) { |
| const uint64_t mode_bits = base::GetBits(low_bits, 2, 2); |
| switch (mode_bits) { |
| case 0: return Result(BlockMode::kB4_A2); |
| case 1: return Result(BlockMode::kB8_A2); |
| case 2: return Result(BlockMode::kA2_B8); |
| case 3: return base::GetBits(low_bits, 8, 1) ? |
| Result(BlockMode::kB2_A2) : Result(BlockMode::kA2_B6); |
| } |
| } else { |
| const uint64_t mode_bits = base::GetBits(low_bits, 5, 4); |
| if ((mode_bits & 0xC) == 0x0) { |
| if (base::GetBits(low_bits, 0, 4) == 0) { |
| // Reserved. |
| return Result(); |
| } else { |
| return Result(BlockMode::k12_A2); |
| } |
| } else if ((mode_bits & 0xC) == 0x4) { |
| return Result(BlockMode::kA2_12); |
| } else if (mode_bits == 0xC) { |
| return Result(BlockMode::k6_10); |
| } else if (mode_bits == 0xD) { |
| return Result(BlockMode::k10_6); |
| } else if ((mode_bits & 0xC) == 0x8) { |
| return Result(BlockMode::kA6_B6); |
| } |
| } |
| |
| return Result(); |
| } |
| |
| base::Optional<WeightGridProperties> DecodeWeightProps( |
| const base::UInt128 astc_bits, std::string* error) { |
| auto block_mode = DecodeBlockMode(astc_bits); |
| if (!block_mode) { |
| *error = "Reserved block mode"; |
| return {}; |
| } |
| |
| // The dimensions of the weight grid and their range |
| WeightGridProperties props; |
| |
| // Determine the weight extents based on the block mode |
| const uint32_t low_bits = |
| static_cast<uint32_t>(astc_bits.LowBits() & 0xFFFFFFFF); |
| switch (block_mode.value()) { |
| case BlockMode::kB4_A2: { |
| int a = base::GetBits(low_bits, 5, 2); |
| int b = base::GetBits(low_bits, 7, 2); |
| props.width = b + 4; |
| props.height = a + 2; |
| } |
| break; |
| |
| case BlockMode::kB8_A2: { |
| int a = base::GetBits(low_bits, 5, 2); |
| int b = base::GetBits(low_bits, 7, 2); |
| props.width = b + 8; |
| props.height = a + 2; |
| } |
| break; |
| |
| case BlockMode::kA2_B8: { |
| int a = base::GetBits(low_bits, 5, 2); |
| int b = base::GetBits(low_bits, 7, 2); |
| props.width = a + 2; |
| props.height = b + 8; |
| } |
| break; |
| |
| case BlockMode::kA2_B6: { |
| int a = base::GetBits(low_bits, 5, 2); |
| int b = base::GetBits(low_bits, 7, 1); |
| props.width = a + 2; |
| props.height = b + 6; |
| } |
| break; |
| |
| case BlockMode::kB2_A2: { |
| int a = base::GetBits(low_bits, 5, 2); |
| int b = base::GetBits(low_bits, 7, 1); |
| props.width = b + 2; |
| props.height = a + 2; |
| } |
| break; |
| |
| case BlockMode::k12_A2: { |
| int a = base::GetBits(low_bits, 5, 2); |
| props.width = 12; |
| props.height = a + 2; |
| } |
| break; |
| |
| case BlockMode::kA2_12: { |
| int a = base::GetBits(low_bits, 5, 2); |
| props.width = a + 2; |
| props.height = 12; |
| } |
| break; |
| |
| case BlockMode::k6_10: { |
| props.width = 6; |
| props.height = 10; |
| } |
| break; |
| |
| case BlockMode::k10_6: { |
| props.width = 10; |
| props.height = 6; |
| } |
| break; |
| |
| case BlockMode::kA6_B6: { |
| int a = base::GetBits(low_bits, 5, 2); |
| int b = base::GetBits(low_bits, 9, 2); |
| props.width = a + 6; |
| props.height = b + 6; |
| } |
| break; |
| |
| // Void extent blocks have no weight grid. |
| case BlockMode::kVoidExtent: |
| *error = "Void extent block has no weight grid"; |
| return {}; |
| |
| // We have a valid block mode which isn't a void extent? We |
| // should be able to decode the weight grid dimensions. |
| default: |
| assert(false && "Error decoding weight grid"); |
| *error = "Internal error"; |
| return {}; |
| } |
| |
| // Determine the weight range based on the block mode |
| uint32_t r = base::GetBits(low_bits, 4, 1); |
| switch (block_mode.value()) { |
| case BlockMode::kB4_A2: |
| case BlockMode::kB8_A2: |
| case BlockMode::kA2_B8: |
| case BlockMode::kA2_B6: |
| case BlockMode::kB2_A2: { |
| r |= base::GetBits(low_bits, 0, 2) << 1; |
| } |
| break; |
| |
| case BlockMode::k12_A2: |
| case BlockMode::kA2_12: |
| case BlockMode::k6_10: |
| case BlockMode::k10_6: |
| case BlockMode::kA6_B6: { |
| r |= base::GetBits(low_bits, 2, 2) << 1; |
| } |
| break; |
| |
| // We have a valid block mode which doesn't have weights? We |
| // should have caught this earlier. |
| case BlockMode::kVoidExtent: |
| default: |
| assert(false && "Error decoding weight grid"); |
| *error = "Internal error"; |
| return {}; |
| } |
| |
| // Decode the range... |
| // High bit is in bit 9 unless we're using a particular block mode |
| uint32_t h = base::GetBits(low_bits, 9, 1); |
| if (block_mode == BlockMode::kA6_B6) { |
| h = 0; |
| } |
| |
| // Figure out the range of the weights (Table C.2.7) |
| constexpr std::array<int, 16> kWeightRanges = {{ |
| -1, -1, 1, 2, 3, 4, 5, 7, -1, -1, 9, 11, 15, 19, 23, 31 |
| }}; |
| |
| assert(((h << 3) | r) < kWeightRanges.size()); |
| |
| props.range = kWeightRanges.at((h << 3) | r); |
| if (props.range < 0) { |
| *error = "Reserved range for weight bits"; |
| return {}; |
| } |
| |
| // Error checking -- do we have too many weights? |
| int num_weights = props.width * props.height; |
| if (DecodeDualPlaneBit(astc_bits)) { |
| num_weights *= 2; |
| } |
| |
| if (kMaxNumWeights < num_weights) { |
| *error = "Too many weights specified"; |
| return {}; |
| } |
| |
| // Do we have too many weight bits? |
| const int bit_count = |
| IntegerSequenceCodec::GetBitCountForRange(num_weights, props.range); |
| |
| if (bit_count < kWeightGridMinBitLength) { |
| *error = "Too few bits required for weight grid"; |
| return {}; |
| } |
| |
| if (kWeightGridMaxBitLength < bit_count) { |
| *error = "Too many bits required for weight grid"; |
| return {}; |
| } |
| |
| return props; |
| } |
| |
| // Returns the four 13-bit integers that define the range of texture |
| // coordinates present in a void extent block as defined in Section |
| // C.2.23 of the specification. The coordinates returned are of |
| // the form (min_s, max_s, min_t, max_t) |
| std::array<int, 4> DecodeVoidExtentCoords(const base::UInt128 astc_bits) { |
| const uint64_t low_bits = astc_bits.LowBits(); |
| |
| std::array<int, 4> coords; |
| for (int i = 0; i < 4; ++i) { |
| coords[i] = static_cast<int>(base::GetBits(low_bits, 12 + 13 * i, 13)); |
| } |
| |
| return coords; |
| } |
| |
| bool DecodeDualPlaneBit(const base::UInt128 astc_bits) { |
| base::Optional<BlockMode> block_mode = DecodeBlockMode(astc_bits); |
| |
| // Void extent blocks certainly aren't dual-plane. |
| if (block_mode == BlockMode::kVoidExtent) { |
| return false; |
| } |
| |
| // One special block mode doesn't have any dual plane bit |
| if (block_mode == BlockMode::kA6_B6) { |
| return false; |
| } |
| |
| // Otherwise, dual plane is determined by the 10th bit. |
| constexpr int kDualPlaneBitPosition = 10; |
| return base::GetBits(astc_bits, kDualPlaneBitPosition, 1) != 0; |
| } |
| |
| int DecodeNumPartitions(const base::UInt128 astc_bits) { |
| constexpr int kNumPartitionsBitPosition = 11; |
| constexpr int kNumPartitionsBitLength = 2; |
| |
| // Non-void extent blocks |
| const uint64_t low_bits = astc_bits.LowBits(); |
| const int num_partitions = 1 + static_cast<int>( |
| base::GetBits(low_bits, |
| kNumPartitionsBitPosition, |
| kNumPartitionsBitLength)); |
| assert(num_partitions > 0); |
| assert(num_partitions <= kMaxNumPartitions); |
| |
| return num_partitions; |
| } |
| |
| int DecodeNumWeightBits(const base::UInt128 astc_bits) { |
| std::string error; |
| auto maybe_weight_props = DecodeWeightProps(astc_bits, &error); |
| if (!maybe_weight_props.hasValue()) { |
| return 0; // No weights? No weight bits... |
| } |
| |
| const auto weight_props = maybe_weight_props.value(); |
| |
| // Figure out the number of weights |
| int num_weights = weight_props.width * weight_props.height; |
| if (DecodeDualPlaneBit(astc_bits)) { |
| num_weights *= 2; |
| } |
| |
| // The number of bits is determined by the number of values |
| // that are going to be encoded using the given ise_counts. |
| return IntegerSequenceCodec::GetBitCountForRange( |
| num_weights, weight_props.range); |
| } |
| |
| // Returns the number of bits after the weight data used to |
| // store additional CEM bits. |
| int DecodeNumExtraCEMBits(const base::UInt128 astc_bits) { |
| const int num_partitions = DecodeNumPartitions(astc_bits); |
| |
| // Do we only have one partition? |
| if (num_partitions == 1) { |
| return 0; |
| } |
| |
| // Do we have a shared CEM? |
| constexpr int kSharedCEMBitPosition = 23; |
| constexpr int kSharedCEMBitLength = 2; |
| const base::UInt128 shared_cem = |
| base::GetBits(astc_bits, kSharedCEMBitPosition, kSharedCEMBitLength); |
| if (shared_cem == 0) { |
| return 0; |
| } |
| |
| const std::array<int, 4> extra_cem_bits_for_partition = {{ 0, 2, 5, 8 }}; |
| return extra_cem_bits_for_partition[num_partitions - 1]; |
| } |
| |
| // Returns the starting position of the dual plane channel. This comes |
| // before the weight data and extra CEM bits. |
| int DecodeDualPlaneBitStartPos(const base::UInt128 astc_bits) { |
| const int start_pos = kASTCBlockSizeBits |
| - DecodeNumWeightBits(astc_bits) |
| - DecodeNumExtraCEMBits(astc_bits); |
| |
| if (DecodeDualPlaneBit(astc_bits)) { |
| return start_pos - 2; |
| } else { |
| return start_pos; |
| } |
| } |
| |
| // Decodes a CEM mode based on the partition number. |
| ColorEndpointMode DecodeEndpointMode(const base::UInt128 astc_bits, |
| int partition) { |
| int num_partitions = DecodeNumPartitions(astc_bits); |
| assert(partition >= 0); |
| assert(partition < num_partitions); |
| |
| // Do we only have one partition? |
| uint64_t low_bits = astc_bits.LowBits(); |
| if (num_partitions == 1) { |
| uint64_t cem = base::GetBits(low_bits, 13, 4); |
| return static_cast<ColorEndpointMode>(cem); |
| } |
| |
| // More than one partition ... do we have a shared CEM? |
| if (DecodeNumExtraCEMBits(astc_bits) == 0) { |
| const uint64_t shared_cem = base::GetBits(low_bits, 25, 4); |
| return static_cast<ColorEndpointMode>(shared_cem); |
| } |
| |
| // More than one partition and no shared CEM... |
| uint64_t cem = base::GetBits(low_bits, 23, 6); |
| const int base_cem = static_cast<int>(((cem & 0x3) - 1) * 4); |
| cem >>= 2; // Skip the base CEM bits |
| |
| // The number of extra CEM bits at the end of the weight grid is |
| // determined by the number of partitions and what the base cem mode is... |
| const int num_extra_cem_bits = DecodeNumExtraCEMBits(astc_bits); |
| const int extra_cem_start_pos = kASTCBlockSizeBits |
| - num_extra_cem_bits |
| - DecodeNumWeightBits(astc_bits); |
| |
| base::UInt128 extra_cem = |
| base::GetBits(astc_bits, extra_cem_start_pos, num_extra_cem_bits); |
| cem |= extra_cem.LowBits() << 4; |
| |
| // Decode C and M per Figure C.4 |
| int c = -1, m = -1; |
| for (int i = 0; i < num_partitions; ++i) { |
| if (i == partition) { |
| c = cem & 0x1; |
| } |
| cem >>= 1; |
| } |
| |
| for (int i = 0; i < num_partitions; ++i) { |
| if (i == partition) { |
| m = cem & 0x3; |
| } |
| cem >>= 2; |
| } |
| |
| assert(c >= 0); |
| assert(m >= 0); |
| |
| // Compute the mode based on C and M |
| const int mode = base_cem + 4 * c + m; |
| assert(mode < static_cast<int>(ColorEndpointMode::kNumColorEndpointModes)); |
| return static_cast<ColorEndpointMode>(mode); |
| } |
| |
| int DecodeNumColorValues(const base::UInt128 astc_bits) { |
| int num_color_values = 0; |
| auto num_partitions = DecodeNumPartitions(astc_bits); |
| for (int i = 0; i < num_partitions; ++i) { |
| ColorEndpointMode endpoint_mode = DecodeEndpointMode(astc_bits, i); |
| num_color_values += NumColorValuesForEndpointMode(endpoint_mode); |
| } |
| |
| return num_color_values; |
| } |
| |
| } // namespace |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| static_assert(sizeof(PhysicalASTCBlock) == PhysicalASTCBlock::kSizeInBytes, |
| "The size of the struct should be the size of the block so that" |
| "we can effectively use them contiguously in memory."); |
| |
| PhysicalASTCBlock::PhysicalASTCBlock(const base::UInt128 astc_block) |
| : astc_bits_(astc_block) {} |
| |
| PhysicalASTCBlock::PhysicalASTCBlock(const std::string& encoded_block) |
| : astc_bits_([&encoded_block]() { |
| assert(encoded_block.size() == PhysicalASTCBlock::kSizeInBytes); |
| base::UInt128 astc_bits = 0; |
| int shift = 0; |
| for (const unsigned char c : encoded_block) { |
| astc_bits |= base::UInt128(static_cast<uint64_t>(c)) << shift; |
| shift += 8; |
| } |
| return astc_bits; |
| }()) |
| { } |
| |
| base::Optional<std::string> PhysicalASTCBlock::IsIllegalEncoding() const { |
| // If the block is not a void extent block, then it must have |
| // weights specified. DecodeWeightProps will return the weight specifications |
| // if they exist and are legal according to C.2.24, and will otherwise be |
| // empty. |
| base::Optional<BlockMode> block_mode = DecodeBlockMode(astc_bits_); |
| if (block_mode != BlockMode::kVoidExtent) { |
| std::string error; |
| auto maybe_weight_props = DecodeWeightProps(astc_bits_, &error); |
| if (!maybe_weight_props.hasValue()) { |
| return error; |
| } |
| } |
| |
| // Check void extent blocks... |
| if (block_mode == BlockMode::kVoidExtent) { |
| // ... for reserved bits incorrectly set |
| if (base::GetBits(astc_bits_, 10, 2) != 0x3) { |
| return std::string("Reserved bits set for void extent block"); |
| } |
| |
| // ... for incorrectly defined texture coordinates |
| std::array<int, 4> coords = DecodeVoidExtentCoords(astc_bits_); |
| |
| bool coords_all_1s = true; |
| for (const auto coord : coords) { |
| coords_all_1s &= coord == ((1 << 13) - 1); |
| } |
| |
| if (!coords_all_1s && (coords[0] >= coords[1] || coords[2] >= coords[3])) { |
| return std::string("Void extent texture coordinates are invalid"); |
| } |
| } |
| |
| // If the number of color values exceeds a threshold and it isn't a void |
| // extent block then we've run into an error |
| if (block_mode != BlockMode::kVoidExtent) { |
| int num_color_vals = DecodeNumColorValues(astc_bits_); |
| if (num_color_vals > 18) { |
| return std::string("Too many color values"); |
| } |
| |
| // The maximum number of available color bits is the number of |
| // bits between the dual plane bits and the base CEM. This must |
| // be larger than a threshold defined in C.2.24. |
| |
| // Dual plane bit starts after weight bits and CEM |
| const int num_partitions = DecodeNumPartitions(astc_bits_); |
| const int dual_plane_start_pos = DecodeDualPlaneBitStartPos(astc_bits_); |
| const int color_start_bit = (num_partitions == 1) ? 17 : 29; |
| |
| const int required_color_bits = ((13 * num_color_vals) + 4) / 5; |
| const int available_color_bits = dual_plane_start_pos - color_start_bit; |
| if (available_color_bits < required_color_bits) { |
| return std::string("Not enough color bits"); |
| } |
| |
| // If we have four partitions and a dual plane then we have a problem. |
| if (num_partitions == 4 && DecodeDualPlaneBit(astc_bits_)) { |
| return std::string("Both four partitions and dual plane specified"); |
| } |
| } |
| |
| // Otherwise we're OK |
| return { }; |
| } |
| |
| bool PhysicalASTCBlock::IsVoidExtent() const { |
| // If it's an error block, it's not a void extent block. |
| if (IsIllegalEncoding()) { |
| return false; |
| } |
| |
| return DecodeBlockMode(astc_bits_) == BlockMode::kVoidExtent; |
| } |
| |
| base::Optional<std::array<int, 4>> PhysicalASTCBlock::VoidExtentCoords() const { |
| if (IsIllegalEncoding() || !IsVoidExtent()) { |
| return { }; |
| } |
| |
| // If void extent coords are all 1's then these are not valid void extent |
| // coords |
| const uint64_t ve_mask = 0xFFFFFFFFFFFFFDFFULL; |
| const uint64_t const_blk_mode = 0xFFFFFFFFFFFFFDFCULL; |
| if ((ve_mask & astc_bits_.LowBits()) == const_blk_mode) { |
| return {}; |
| } |
| |
| return DecodeVoidExtentCoords(astc_bits_); |
| } |
| |
| bool PhysicalASTCBlock::IsDualPlane() const { |
| // If it's an error block, then we aren't a dual plane block |
| if (IsIllegalEncoding()) { |
| return false; |
| } |
| |
| return DecodeDualPlaneBit(astc_bits_); |
| } |
| |
| // Returns the number of weight bits present in this block |
| base::Optional<int> PhysicalASTCBlock::NumWeightBits() const { |
| // If it's an error block, then we have no weight bits. |
| if (IsIllegalEncoding()) return { }; |
| |
| // If it's a void extent block, we have no weight bits |
| if (IsVoidExtent()) return { }; |
| |
| return DecodeNumWeightBits(astc_bits_); |
| } |
| |
| base::Optional<int> PhysicalASTCBlock::WeightStartBit() const { |
| if (IsIllegalEncoding()) return { }; |
| if (IsVoidExtent()) return { }; |
| |
| return kASTCBlockSizeBits - DecodeNumWeightBits(astc_bits_); |
| } |
| |
| base::Optional<std::array<int, 2>> PhysicalASTCBlock::WeightGridDims() const { |
| std::string error; |
| auto weight_props = DecodeWeightProps(astc_bits_, &error); |
| |
| if (!weight_props.hasValue()) return { }; |
| if (IsIllegalEncoding()) return { }; |
| |
| const auto props = weight_props.value(); |
| return {{{ props.width, props.height }}}; |
| } |
| |
| base::Optional<int> PhysicalASTCBlock::WeightRange() const { |
| std::string error; |
| auto weight_props = DecodeWeightProps(astc_bits_, &error); |
| |
| if (!weight_props.hasValue()) return { }; |
| if (IsIllegalEncoding()) return { }; |
| |
| return weight_props.value().range; |
| } |
| |
| base::Optional<int> PhysicalASTCBlock::DualPlaneChannel() const { |
| if (!IsDualPlane()) return { }; |
| |
| int dual_plane_start_pos = DecodeDualPlaneBitStartPos(astc_bits_); |
| auto plane_bits = base::GetBits(astc_bits_, dual_plane_start_pos, 2); |
| return base::Optional<int>(static_cast<int>(plane_bits.LowBits())); |
| } |
| |
| base::Optional<int> PhysicalASTCBlock::ColorStartBit() const { |
| if (IsVoidExtent()) { |
| return 64; |
| } |
| |
| auto num_partitions = NumPartitions(); |
| if (!num_partitions) return { }; |
| |
| return (num_partitions == 1) ? 17 : 29; |
| } |
| |
| base::Optional<int> PhysicalASTCBlock::NumColorValues() const { |
| // If we have a void extent block, then we have four color values |
| if (IsVoidExtent()) { |
| return 4; |
| } |
| |
| // If we have an illegal encoding, then we have no color values |
| if (IsIllegalEncoding()) return { }; |
| |
| return DecodeNumColorValues(astc_bits_); |
| } |
| |
| void PhysicalASTCBlock::GetColorValuesInfo(int* const color_bits, |
| int* const color_range) const { |
| // Figure out the range possible for the number of values we have... |
| const int dual_plane_start_pos = DecodeDualPlaneBitStartPos(astc_bits_); |
| const int max_color_bits = dual_plane_start_pos - ColorStartBit().value(); |
| const int num_color_values = NumColorValues().value(); |
| for (int range = 255; range > 0; --range) { |
| const int bitcount = |
| IntegerSequenceCodec::GetBitCountForRange(num_color_values, range); |
| if (bitcount <= max_color_bits) { |
| if (color_bits != nullptr) { |
| *color_bits = bitcount; |
| } |
| |
| if (color_range != nullptr) { |
| *color_range = range; |
| } |
| return; |
| } |
| } |
| |
| assert(false && |
| "This means that even if we have a range of one there aren't " |
| "enough bits to store the color values, and our encoding is " |
| "illegal."); |
| } |
| |
| base::Optional<int> PhysicalASTCBlock::NumColorBits() const { |
| if (IsIllegalEncoding()) return { }; |
| |
| if (IsVoidExtent()) { |
| return 64; |
| } |
| |
| int color_bits; |
| GetColorValuesInfo(&color_bits, nullptr); |
| return color_bits; |
| } |
| |
| base::Optional<int> PhysicalASTCBlock::ColorValuesRange() const { |
| if (IsIllegalEncoding()) return { }; |
| |
| if (IsVoidExtent()) { |
| return (1 << 16) - 1; |
| } |
| |
| int color_range; |
| GetColorValuesInfo(nullptr, &color_range); |
| return color_range; |
| } |
| |
| base::Optional<int> PhysicalASTCBlock::NumPartitions() const { |
| // Error blocks have no partitions |
| if (IsIllegalEncoding()) return { }; |
| |
| // Void extent blocks have no partitions either |
| if (DecodeBlockMode(astc_bits_) == BlockMode::kVoidExtent) { |
| return { }; |
| } |
| |
| // All others have some number of partitions |
| return DecodeNumPartitions(astc_bits_); |
| } |
| |
| base::Optional<int> PhysicalASTCBlock::PartitionID() const { |
| auto num_partitions = NumPartitions(); |
| if (!num_partitions || num_partitions == 1) return { }; |
| |
| const uint64_t low_bits = astc_bits_.LowBits(); |
| return static_cast<int>(base::GetBits(low_bits, 13, 10)); |
| } |
| |
| base::Optional<ColorEndpointMode> PhysicalASTCBlock::GetEndpointMode( |
| int partition) const { |
| // Error block? |
| if (IsIllegalEncoding()) return { }; |
| |
| // Void extent blocks have no endpoint modes |
| if (DecodeBlockMode(astc_bits_) == BlockMode::kVoidExtent) { |
| return { }; |
| } |
| |
| // Do we even have a CEM for this partition? |
| if (partition < 0 || DecodeNumPartitions(astc_bits_) <= partition) { |
| return { }; |
| } |
| |
| return DecodeEndpointMode(astc_bits_, partition); |
| } |
| |
| } // namespace astc_codec |