| // 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. |
| |
| // astc_inspector_cli collects the various statistics of a stream of ASTC data |
| // stored in an ASTC file. |
| // |
| // Example usage: |
| // To dump statistics about an ASTC file, use: |
| // astc_inspector_cli <filename> |
| // |
| // To dump statistics on a specific block in an ASTC file, use: |
| // astc_inspector_cli <filename> <number> |
| |
| #include <algorithm> |
| #include <array> |
| #include <fstream> |
| #include <functional> |
| #include <iomanip> |
| #include <iostream> |
| #include <memory> |
| #include <numeric> |
| #include <sstream> |
| #include <string> |
| #include <unordered_map> |
| #include <unordered_set> |
| #include <vector> |
| |
| #include "src/base/string_utils.h" |
| #include "src/decoder/astc_file.h" |
| #include "src/decoder/endpoint_codec.h" |
| #include "src/decoder/intermediate_astc_block.h" |
| #include "src/decoder/partition.h" |
| #include "src/decoder/quantization.h" |
| #include "src/decoder/weight_infill.h" |
| |
| using astc_codec::ASTCFile; |
| using astc_codec::ColorEndpointMode; |
| using astc_codec::IntermediateBlockData; |
| using astc_codec::PhysicalASTCBlock; |
| using astc_codec::RgbaColor; |
| using astc_codec::VoidExtentData; |
| using astc_codec::base::Optional; |
| |
| namespace { |
| |
| constexpr int kNumEndpointModes = |
| static_cast<int>(ColorEndpointMode::kNumColorEndpointModes); |
| constexpr std::array<const char*, kNumEndpointModes> kModeStrings {{ |
| "kLDRLumaDirect", "kLDRLumaBaseOffset", "kHDRLumaLargeRange", |
| "kHDRLumaSmallRange", "kLDRLumaAlphaDirect", "kLDRLumaAlphaBaseOffset", |
| "kLDRRGBBaseScale", "kHDRRGBBaseScale", "kLDRRGBDirect", |
| "kLDRRGBBaseOffset", "kLDRRGBBaseScaleTwoA", "kHDRRGBDirect", |
| "kLDRRGBADirect", "kLDRRGBABaseOffset", "kHDRRGBDirectLDRAlpha", |
| "kHDRRGBDirectHDRAlpha" }}; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // A generic stat that should be tracked via an instance of ASTCFileStats. |
| class Stat { |
| public: |
| explicit Stat(const std::vector<IntermediateBlockData>* blocks, size_t total) |
| : blocks_(blocks), total_(total) { } |
| virtual ~Stat() { } |
| |
| virtual std::ostream& PrintToStream(std::ostream& out) const = 0; |
| |
| protected: |
| // Utility function to iterate over all of the blocks that are not void-extent |
| // blocks. FoldFn optionally allows a value to accumulate. It should be of the |
| // type: |
| // (const IntermediateBlockData&, T x) -> T |
| template<typename T, typename FoldFn> |
| T IterateBlocks(T initial, FoldFn f) const { |
| T result = initial; |
| for (const auto& block : *blocks_) { |
| result = f(block, std::move(result)); |
| } |
| return result; |
| } |
| |
| size_t NumBlocks() const { return total_; } |
| |
| private: |
| const std::vector<IntermediateBlockData>* const blocks_; |
| const size_t total_; |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Computes the number of void extent blocks. |
| class VoidExtentCount : public Stat { |
| public: |
| VoidExtentCount(const std::vector<IntermediateBlockData>* blocks, |
| size_t total, std::string description) |
| : Stat(blocks, total), description_(std::move(description)), |
| count_(total - blocks->size()) { } |
| |
| std::ostream& PrintToStream(std::ostream& out) const override { |
| return out << description_ << ": " << count_ |
| << " (" << (count_ * 100 / NumBlocks()) << "%)" << std::endl; |
| }; |
| |
| private: |
| const std::string description_; |
| const size_t count_; |
| }; |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // Computes a per-block stat and reports it as an average over all blocks. |
| class PerBlockAverage : public Stat { |
| public: |
| PerBlockAverage(const std::vector<IntermediateBlockData>* blocks, |
| size_t total, std::string description, |
| const std::function<int(const IntermediateBlockData&)> &fn) |
| : Stat(blocks, total), |
| description_(std::move(description)) { |
| int sum = 0; |
| size_t count = 0; |
| for (const auto& block : *blocks) { |
| sum += fn(block); |
| ++count; |
| } |
| average_ = sum / count; |
| } |
| |
| std::ostream& PrintToStream(std::ostream& out) const override { |
| return out << description_ << ": " << average_ << std::endl; |
| } |
| |
| private: |
| size_t average_; |
| std::string description_; |
| }; |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // Computes a per-block true or false value and reports how many blocks return |
| // true with a percentage of total blocks. |
| class PerBlockPredicate : public Stat { |
| public: |
| PerBlockPredicate(const std::vector<IntermediateBlockData>* blocks, |
| size_t total, std::string description, |
| const std::function<bool(const IntermediateBlockData&)> &fn) |
| : Stat(blocks, total), |
| description_(std::move(description)), |
| count_(std::count_if(blocks->begin(), blocks->end(), fn)) { } |
| |
| std::ostream& PrintToStream(std::ostream& out) const override { |
| return out << description_ << ": " << count_ |
| << " (" << (count_ * 100 / NumBlocks()) << "%)" << std::endl; |
| }; |
| |
| private: |
| const std::string description_; |
| const size_t count_; |
| }; |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // Returns a histogram of the number of occurrences of each endpoint mode in |
| // the list of blocks. Note, due to multi-subset blocks, the sum of these |
| // values will not match the total number of blocks. |
| class ModeCountsStat : public Stat { |
| public: |
| explicit ModeCountsStat(const std::vector<IntermediateBlockData>* blocks, |
| size_t total) |
| : Stat(blocks, total), |
| mode_counts_(IterateBlocks<ModeArray>( |
| {}, [](const IntermediateBlockData& data, ModeArray&& m) { |
| auto result = m; |
| for (const auto& ep : data.endpoints) { |
| result[static_cast<int>(ep.mode)]++; |
| } |
| return result; |
| })) { } |
| |
| std::ostream& PrintToStream(std::ostream& out) const override { |
| const size_t total_modes_used = |
| std::accumulate(mode_counts_.begin(), mode_counts_.end(), 0); |
| |
| out << "Endpoint modes used: " << std::endl; |
| for (size_t i = 0; i < kNumEndpointModes; ++i) { |
| out << " "; |
| out << std::setw(30) << std::left << std::setfill('.') << kModeStrings[i]; |
| out << std::setw(8) << std::right << std::setfill('.') << mode_counts_[i]; |
| |
| std::stringstream pct; |
| pct << " (" << (mode_counts_[i] * 100 / total_modes_used) << "%)"; |
| |
| out << std::setw(6) << std::right << std::setfill(' ') << pct.str(); |
| out << std::endl; |
| } |
| |
| return out; |
| } |
| |
| private: |
| using ModeArray = std::array<int, kNumEndpointModes>; |
| const ModeArray mode_counts_; |
| }; |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // Counts the number of unique endpoints used across all blocks. |
| class UniqueEndpointsCount : public Stat { |
| public: |
| explicit UniqueEndpointsCount( |
| const std::vector<IntermediateBlockData>* blocks, size_t total) |
| : Stat(blocks, total), |
| unique_endpoints_(IterateBlocks<UniqueEndpointSet>( |
| UniqueEndpointSet(), |
| [](const IntermediateBlockData& data, UniqueEndpointSet&& eps) { |
| UniqueEndpointSet result(eps); |
| for (const auto& ep : data.endpoints) { |
| RgbaColor ep_one, ep_two; |
| DecodeColorsForMode(ep.colors, data.endpoint_range.value(), |
| ep.mode, &ep_one, &ep_two); |
| result.insert(PackEndpoint(ep_one)); |
| result.insert(PackEndpoint(ep_two)); |
| } |
| return result; |
| })) { } |
| |
| std::ostream& PrintToStream(std::ostream& out) const override { |
| out << "Num unique endpoints: " << unique_endpoints_.size() << std::endl; |
| return out; |
| } |
| |
| private: |
| static uint32_t PackEndpoint(const RgbaColor& color) { |
| uint32_t result = 0; |
| for (const int& c : color) { |
| constexpr int kSaturatedChannelValue = 0xFF; |
| assert(c >= 0); |
| assert(c <= kSaturatedChannelValue); |
| result <<= 8; |
| result |= c; |
| } |
| return result; |
| } |
| |
| using UniqueEndpointSet = std::unordered_set<uint32_t>; |
| const UniqueEndpointSet unique_endpoints_; |
| }; |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // Computes a histogram of the number of occurrences of 1-4 subset partitions. |
| class PartitionCountStat : public Stat { |
| public: |
| explicit PartitionCountStat(const std::vector<IntermediateBlockData>* blocks, |
| size_t total) |
| : Stat(blocks, total) |
| , part_counts_(IterateBlocks<PartCount>( |
| {}, [](const IntermediateBlockData& data, PartCount&& m) { |
| PartCount result = m; |
| result[data.endpoints.size() - 1]++; |
| return result; |
| })) { } |
| |
| std::ostream& PrintToStream(std::ostream& out) const override { |
| out << "Num partitions used: " << std::endl; |
| for (size_t i = 0; i < part_counts_.size(); ++i) { |
| out << " " << i + 1 << ": " << part_counts_[i] << std::endl; |
| } |
| return out; |
| } |
| |
| private: |
| using PartCount = std::array<int, 4>; |
| const PartCount part_counts_; |
| }; |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // For each block that uses dual-plane mode, computes and stores the dual-plane |
| // channels in a vector. Outputs the number of each channel used across all |
| // blocks |
| class DualChannelStat : public Stat { |
| private: |
| static constexpr auto kNumDualPlaneChannels = |
| std::tuple_size<astc_codec::Endpoint>::value; |
| using CountsArray = std::array<int, kNumDualPlaneChannels>; |
| |
| public: |
| explicit DualChannelStat(const std::vector<IntermediateBlockData>* blocks, |
| size_t total) |
| : Stat(blocks, total), |
| dual_channels_(IterateBlocks( |
| std::vector<int>(), |
| [](const IntermediateBlockData& data, std::vector<int>&& input) { |
| auto result = input; |
| if (data.dual_plane_channel) { |
| result.push_back(data.dual_plane_channel.value()); |
| } |
| return result; |
| })) { } |
| |
| std::ostream& PrintToStream(std::ostream& out) const override { |
| // Similar to the number of partitions, the number of dual plane blocks |
| // can be determined by parsing the next four fields and summing them. |
| const int num_dual_plane_blocks = dual_channels_.size(); |
| out << "Number of dual-plane blocks: " << num_dual_plane_blocks |
| << " (" << (num_dual_plane_blocks * 100) / NumBlocks() << "%)" |
| << std::endl; |
| |
| CountsArray counts = GetCounts(); |
| assert(counts.size() == kNumDualPlaneChannels); |
| |
| for (size_t i = 0; i < counts.size(); ++i) { |
| out << " " << i << ": " << counts[i] << std::endl; |
| } |
| return out; |
| } |
| |
| private: |
| CountsArray GetCounts() const { |
| CountsArray counts; |
| for (size_t i = 0; i < kNumDualPlaneChannels; ++i) { |
| counts[i] = |
| std::count_if(dual_channels_.begin(), dual_channels_.end(), |
| [i](int channel) { return i == channel; }); |
| } |
| return counts; |
| } |
| |
| const std::vector<int> dual_channels_; |
| }; |
| |
| |
| // Stores the intermediate block representations of the blocks associated with |
| // an ASTCFile. Also provides various facilities for extracting aggregate data |
| // from these blocks. |
| class ASTCFileStats { |
| public: |
| explicit ASTCFileStats(const std::unique_ptr<ASTCFile>& astc_file) { |
| const size_t total = astc_file->NumBlocks(); |
| |
| for (size_t block_idx = 0; block_idx < astc_file->NumBlocks(); ++block_idx) { |
| const PhysicalASTCBlock pb = astc_file->GetBlock(block_idx); |
| assert(!pb.IsIllegalEncoding()); |
| if (pb.IsIllegalEncoding()) { |
| std::cerr << "WARNING: Block " << block_idx << " has illegal encoding." << std::endl; |
| continue; |
| } |
| |
| if (!pb.IsVoidExtent()) { |
| Optional<IntermediateBlockData> block = UnpackIntermediateBlock(pb); |
| if (!block) { |
| std::cerr << "WARNING: Block " << block_idx << " failed to unpack." << std::endl; |
| continue; |
| } |
| |
| blocks_.push_back(block.value()); |
| } |
| } |
| |
| stats_.emplace_back(new UniqueEndpointsCount(&blocks_, total)); |
| stats_.emplace_back(new VoidExtentCount( |
| &blocks_, total, "Num void extent blocks")); |
| |
| stats_.emplace_back(new PerBlockAverage( |
| &blocks_, total, "Average weight range", |
| [](const IntermediateBlockData& b) { return b.weight_range; })); |
| |
| stats_.emplace_back(new PerBlockAverage( |
| &blocks_, total, "Average number of weights", |
| [](const IntermediateBlockData& b) { return b.weights.size(); })); |
| |
| stats_.emplace_back(new PerBlockPredicate( |
| &blocks_, total, "Num blocks that use blue contract mode", |
| [](const IntermediateBlockData& block) { |
| for (const auto& ep : block.endpoints) { |
| if (UsesBlueContract( |
| block.endpoint_range.valueOr(255), ep.mode, ep.colors)) { |
| return true; |
| } |
| } |
| |
| return false; |
| })); |
| |
| stats_.emplace_back(new ModeCountsStat(&blocks_, total)); |
| |
| stats_.emplace_back(new PerBlockPredicate( |
| &blocks_, total, "Num multi-part blocks", |
| [](const IntermediateBlockData& block) { |
| return block.endpoints.size() > 1; |
| })); |
| stats_.emplace_back(new PartitionCountStat(&blocks_, total)); |
| |
| stats_.emplace_back(new DualChannelStat(&blocks_, total)); |
| } |
| |
| // Returns a sorted list of pairs of the form (part_id, count) where the |
| // |part_id| is the partition ID used for 2-subset blocks, and |count| is the |
| // number of times that particular ID was used. |
| std::vector<std::pair<int, int>> ComputePartIDHistogram() const { |
| std::vector<int> part_ids(1 << 11, 0); |
| std::iota(part_ids.begin(), part_ids.end(), 0); |
| |
| // The histogram will then pair IDs with counts so that we can sort by |
| // the number of instances later on. |
| std::vector<std::pair<int, int>> part_id_histogram; |
| std::transform(part_ids.begin(), part_ids.end(), |
| std::back_inserter(part_id_histogram), |
| [](const int& x) { return std::make_pair(x, 0); }); |
| |
| // Actually count the IDs in the list of blocks. |
| for (const auto& block : blocks_) { |
| if (block.endpoints.size() == 2) { |
| const int id = block.partition_id.value(); |
| assert(part_id_histogram[id].first == id); |
| part_id_histogram[id].second++; |
| } |
| } |
| |
| struct OrderBySecondGreater { |
| typedef std::pair<int, int> PairType; |
| bool operator()(const PairType& lhs, const PairType& rhs) { |
| return lhs.second > rhs.second; |
| } |
| }; |
| |
| // Sort by descending numbers of occurrence for each partition ID |
| std::sort(part_id_histogram.begin(), part_id_histogram.end(), |
| OrderBySecondGreater()); |
| |
| return part_id_histogram; |
| } |
| |
| // Weights range from 2x2 - 12x12. For simplicity define buckets for every |
| // pair in [0, 12]^2. |
| constexpr static int kResolutionBuckets = 13; |
| // Returns a linear array of buckets over all pairs of grid resolutions, |
| // x-major in memory. |
| std::vector<int> ComputeWeightResolutionHistogram() const { |
| // Allocate one bucket for every grid resolution. |
| std::vector<int> resolution_histogram( |
| kResolutionBuckets * kResolutionBuckets, 0); |
| |
| // Count the weight resolutions in the list of blocks. |
| for (const auto& block : blocks_) { |
| const int dim_x = block.weight_grid_dim_x; |
| const int dim_y = block.weight_grid_dim_y; |
| assert(dim_x > 0); |
| assert(dim_x < kResolutionBuckets); |
| assert(dim_y > 0); |
| assert(dim_y < kResolutionBuckets); |
| ++resolution_histogram[dim_x + dim_y * kResolutionBuckets]; |
| } |
| |
| return resolution_histogram; |
| } |
| |
| // Runs through each defined statistic and prints it out to stdout. Also |
| // prints a histogram of partition ids used for the given blocks. |
| void PrintStats() const { |
| for (const auto& stat : stats_) { |
| stat->PrintToStream(std::cout); |
| } |
| |
| // We also want to find if there are any 2-subset partition IDs that are |
| // used disproportionately often. Since partition IDs are 11 bits long, we |
| // can have as many as (1 << 11) used IDs in a given sequence of blocks. |
| const auto part_id_histogram = ComputePartIDHistogram(); |
| const int total_part_ids = std::accumulate( |
| part_id_histogram.begin(), part_id_histogram.end(), 0, |
| [](const int& x, const std::pair<int, int>& hist) { |
| return x + hist.second; |
| }); |
| |
| if (total_part_ids > 0) { |
| // Display numbers until we either: |
| // A. Display the top 90% of used partitions |
| // B. Reach a point where the remaining partition IDs constitute < 1% of |
| // the total number of IDs used. |
| const auto prepare_part_entry = []() -> std::ostream& { |
| return std::cout << std::setw(6) << std::left << std::setfill('.'); |
| }; |
| int part_accum = 0; |
| std::cout << "Two subset partition ID histogram: " << std::endl; |
| std::cout << " "; |
| prepare_part_entry() << "ID" << "Count" << std::endl; |
| for (const auto& hist : part_id_histogram) { |
| part_accum += hist.second; |
| if ((hist.second * 100 / total_part_ids) < 1 || |
| (100 * (total_part_ids - part_accum)) / total_part_ids < 10) { |
| const int num_to_display = (total_part_ids - part_accum); |
| std::cout << " rest: " << num_to_display |
| << " (" << (num_to_display * 100 / total_part_ids) |
| << "%)" << std::endl; |
| break; |
| } else { |
| std::cout << " "; |
| prepare_part_entry() << hist.first << hist.second |
| << " (" << (hist.second * 100 / total_part_ids) |
| << "%)" << std::endl; |
| } |
| } |
| } |
| |
| // Build the 2D histogram of resolutions. |
| std::vector<int> weight_histogram = ComputeWeightResolutionHistogram(); |
| // Labels the weight resolution table. |
| std::cout << "Weight resolutions:" << std::endl; |
| const auto prepare_weight_entry = []() -> std::ostream& { |
| return std::cout << std::setw(6) << std::left << std::setfill(' '); |
| }; |
| prepare_weight_entry() << "H W"; |
| for (int resolution_x = 2; resolution_x < kResolutionBuckets; |
| ++resolution_x) { |
| prepare_weight_entry() << resolution_x; |
| } |
| std::cout << std::endl; |
| |
| // Displays table; skips rows/cols {0, 1} since they will always be empty. |
| for (int resolution_y = 2; resolution_y < kResolutionBuckets; |
| ++resolution_y) { |
| prepare_weight_entry() << resolution_y; |
| for (int resolution_x = 2; resolution_x < kResolutionBuckets; |
| ++resolution_x) { |
| const int count = |
| weight_histogram[resolution_x + resolution_y * kResolutionBuckets]; |
| prepare_weight_entry(); |
| if (!count) { |
| std::cout << "*"; |
| } else { |
| std::cout << count; |
| } |
| } |
| std::cout << std::endl; |
| } |
| } |
| |
| size_t NumBlocks() const { return blocks_.size(); } |
| |
| private: |
| std::vector<std::unique_ptr<Stat>> stats_; |
| std::vector<IntermediateBlockData> blocks_; |
| }; |
| |
| std::ostream& operator<<(std::ostream& stream, const RgbaColor& color) { |
| stream << "{"; |
| constexpr int kNumChannels = std::tuple_size<RgbaColor>::value; |
| for (int i = 0; i < kNumChannels; ++i) { |
| stream << color[i]; |
| if (i < (kNumChannels - 1)) { |
| stream << ", "; |
| } |
| } |
| return stream << "}"; |
| } |
| |
| void PrintStatsForBlock(const PhysicalASTCBlock& pb, |
| astc_codec::Footprint footprint) { |
| const auto print_void_extent = [&pb](const VoidExtentData& void_extent_data) { |
| std::cout << "Void extent block:" << std::endl; |
| std::cout << " 16-bit RGBA: {" |
| << void_extent_data.r << ", " |
| << void_extent_data.g << ", " |
| << void_extent_data.b << ", " |
| << void_extent_data.a << "}" << std::endl; |
| if (pb.VoidExtentCoords()) { |
| std::cout << " Extent (S): {" |
| << void_extent_data.coords[0] << ", " |
| << void_extent_data.coords[1] << "}" << std::endl; |
| std::cout << " Extent (T): {" |
| << void_extent_data.coords[2] << ", " |
| << void_extent_data.coords[3] << "}" << std::endl; |
| } else { |
| std::cout << " No valid extent data" << std::endl; |
| } |
| }; |
| |
| const auto print_endpoint_data = |
| [](ColorEndpointMode mode, int endpoint_range, |
| const std::vector<int>& encoded_vals) { |
| std::cout << " Endpoint mode: " |
| << kModeStrings[static_cast<int>(mode)] << std::endl; |
| std::cout << " Uses blue-contract mode: " |
| << (UsesBlueContract(endpoint_range, mode, encoded_vals) |
| ? "true" : "false") |
| << std::endl; |
| |
| RgbaColor endpoint_low, endpoint_high; |
| DecodeColorsForMode(encoded_vals, endpoint_range, mode, |
| &endpoint_low, &endpoint_high); |
| |
| std::cout << " Low endpoint: " << endpoint_low << std::endl; |
| std::cout << " High endpoint: " << endpoint_high << std::endl; |
| }; |
| |
| const auto print_color_data = |
| [&print_endpoint_data, &footprint](const IntermediateBlockData& ib_data) { |
| const int endpoint_range = ib_data.endpoint_range.value(); |
| std::cout << "Endpoint range: " << endpoint_range << std::endl; |
| |
| const int num_parts = ib_data.endpoints.size(); |
| if (ib_data.partition_id.hasValue()) { |
| const int part_id = ib_data.partition_id.value(); |
| std::cout << "Parititon ID: " << part_id << std::endl; |
| |
| const auto part = GetASTCPartition(footprint, num_parts, part_id); |
| assert(part.assignment.size() == footprint.Height() * footprint.Width()); |
| |
| std::cout << "Assignment:" << std::endl; |
| for (int y = 0; y < footprint.Height(); ++y) { |
| std::cout << " "; |
| for (int x = 0; x < footprint.Width(); ++x) { |
| const int texel_index = y * footprint.Width() + x; |
| std::cout << " " << part.assignment[texel_index]; |
| } |
| std::cout << std::endl; |
| } |
| } else { |
| std::cout << "Single partition" << std::endl; |
| } |
| |
| int endpoint_index = 0; |
| for (const auto& ep_data : ib_data.endpoints) { |
| if (num_parts == 1) { |
| std::cout << "Endpoints:" << std::endl; |
| } else { |
| std::cout << "Endpoint " << (endpoint_index++) << ": " << std::endl; |
| } |
| print_endpoint_data(ep_data.mode, endpoint_range, ep_data.colors); |
| } |
| |
| if (ib_data.dual_plane_channel) { |
| std::cout << "Dual plane channel: " |
| << ib_data.dual_plane_channel.value() << std::endl; |
| } else { |
| std::cout << "Single plane" << std::endl; |
| } |
| }; |
| |
| const auto print_weight_data = |
| [&footprint](const IntermediateBlockData& ib_data) { |
| std::cout << "Weight grid dimensions: " |
| << ib_data.weight_grid_dim_x << "x" << ib_data.weight_grid_dim_y |
| << std::endl; |
| std::cout << "Weight range: " << ib_data.weight_range << std::endl; |
| |
| std::cout << "Encoded weight grid: " << std::endl; |
| int weight_idx = 0; |
| for (int j = 0; j < ib_data.weight_grid_dim_y; ++j) { |
| std::cout << " "; |
| for (int i = 0; i < ib_data.weight_grid_dim_x; ++i) { |
| std::cout << std::setw(3) << std::left << std::setfill(' ') |
| << ib_data.weights[weight_idx++]; |
| } |
| std::cout << std::endl; |
| } |
| |
| std::cout << "Actual weight grid: " << std::endl; |
| std::vector<int> actual_weights = ib_data.weights; |
| for (auto& weight : actual_weights) { |
| weight = astc_codec::UnquantizeWeightFromRange( |
| weight, ib_data.weight_range); |
| } |
| |
| actual_weights = astc_codec::InfillWeights( |
| actual_weights, footprint, ib_data.weight_grid_dim_x, |
| ib_data.weight_grid_dim_y); |
| |
| weight_idx = 0; |
| for (int j = 0; j < footprint.Height(); ++j) { |
| std::cout << " "; |
| for (int i = 0; i < footprint.Width(); ++i) { |
| std::cout << std::setw(3) << std::left << std::setfill(' ') |
| << actual_weights[weight_idx++]; |
| } |
| std::cout << std::endl; |
| } |
| }; |
| |
| if (pb.IsVoidExtent()) { |
| Optional<VoidExtentData> ve = astc_codec::UnpackVoidExtent(pb); |
| if (!ve) { |
| std::cerr << "ERROR: Failed to unpack void extent block." << std::endl; |
| } else { |
| print_void_extent(ve.value()); |
| } |
| } else { |
| Optional<IntermediateBlockData> ib = |
| astc_codec::UnpackIntermediateBlock(pb); |
| if (!ib) { |
| std::cerr << "ERROR: Failed to unpack intermediate block." << std::endl; |
| } else { |
| const auto& ib_data = ib.value(); |
| print_color_data(ib_data); |
| print_weight_data(ib_data); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| int main(int argc, char* argv[]) { |
| bool error = false; |
| |
| std::string filename; |
| size_t block_index = 0; |
| bool has_block_index = false; |
| |
| if (argc >= 2) { |
| filename = argv[1]; |
| |
| if (argc == 3) { |
| int32_t param = astc_codec::base::ParseInt32(argv[2], -1); |
| if (param < 0) { |
| std::cerr << "ERROR: Invalid block index." << std::endl; |
| error = true; |
| } else { |
| block_index = static_cast<size_t>(param); |
| has_block_index = true; |
| } |
| } else if (argc != 2) { |
| std::cerr << "ERROR: Too many parameters." << std::endl; |
| error = true; |
| } |
| } else { |
| error = true; |
| } |
| |
| if (error) { |
| std::cout << ((argc >= 0) ? argv[0] : "astc_inspector_cli") |
| << " <filename> [<block index>]" << std::endl |
| << std::endl |
| << "Collects the various statistics of a stream of ASTC data " |
| << "stored in an ASTC file." << std::endl |
| << std::endl |
| << " filename ASTC file path." << std::endl |
| << " block index If specified, show detailed information about a block" |
| << std::endl; |
| return 1; |
| } |
| |
| std::string error_string; |
| std::unique_ptr<ASTCFile> astc_file = ASTCFile::LoadFile(argv[1], &error_string); |
| if (!astc_file) { |
| std::cerr << "ERROR: " << error_string << std::endl; |
| return 2; |
| } |
| |
| if (has_block_index) { |
| Optional<astc_codec::Footprint> footprint = |
| astc_codec::Footprint::Parse(astc_file->GetFootprintString().c_str()); |
| if (!footprint) { |
| std::cerr << "ERROR: Invalid footprint \"" << astc_file->GetFootprintString() << "\"" << std::endl; |
| return 3; |
| } |
| |
| PrintStatsForBlock(astc_file->GetBlock(block_index), footprint.value()); |
| } else { |
| std::cout << "Dimensions: " << astc_file->GetWidth() << "x" |
| << astc_file->GetHeight() << ", depth " << astc_file->GetDepth() |
| << std::endl; |
| |
| ASTCFileStats stats(astc_file); |
| |
| std::cout << std::endl |
| << "Total bits used: " << 128 * astc_file->NumBlocks() |
| << " (" << astc_file->NumBlocks() << " blocks, " |
| << (astc_file->NumBlocks() * 16) << " bytes)" |
| << std::endl << std::endl; |
| |
| stats.PrintStats(); |
| } |
| |
| return 0; |
| } |