// 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 #include namespace astc_codec { namespace { static_assert(static_cast(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 DecodeBlockMode(const base::UInt128 astc_bits); base::Optional DecodeWeightProps( const base::UInt128 astc_bits, std::string* error); std::array 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 DecodeBlockMode(const base::UInt128 astc_bits) { using Result = base::Optional; 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 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(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 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 DecodeVoidExtentCoords(const base::UInt128 astc_bits) { const uint64_t low_bits = astc_bits.LowBits(); std::array coords; for (int i = 0; i < 4; ++i) { coords[i] = static_cast(base::GetBits(low_bits, 12 + 13 * i, 13)); } return coords; } bool DecodeDualPlaneBit(const base::UInt128 astc_bits) { base::Optional 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( 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 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(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(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(((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(ColorEndpointMode::kNumColorEndpointModes)); return static_cast(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(c)) << shift; shift += 8; } return astc_bits; }()) { } base::Optional 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 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 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> 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 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 PhysicalASTCBlock::WeightStartBit() const { if (IsIllegalEncoding()) return { }; if (IsVoidExtent()) return { }; return kASTCBlockSizeBits - DecodeNumWeightBits(astc_bits_); } base::Optional> 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 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 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(static_cast(plane_bits.LowBits())); } base::Optional PhysicalASTCBlock::ColorStartBit() const { if (IsVoidExtent()) { return 64; } auto num_partitions = NumPartitions(); if (!num_partitions) return { }; return (num_partitions == 1) ? 17 : 29; } base::Optional 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 PhysicalASTCBlock::NumColorBits() const { if (IsIllegalEncoding()) return { }; if (IsVoidExtent()) { return 64; } int color_bits; GetColorValuesInfo(&color_bits, nullptr); return color_bits; } base::Optional PhysicalASTCBlock::ColorValuesRange() const { if (IsIllegalEncoding()) return { }; if (IsVoidExtent()) { return (1 << 16) - 1; } int color_range; GetColorValuesInfo(nullptr, &color_range); return color_range; } base::Optional 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 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(base::GetBits(low_bits, 13, 10)); } base::Optional 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