| // Copyright 2016 Google Inc. All Rights Reserved. |
| // |
| // 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 |
| // |
| // http://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 "cobalt/loader/mesh/projection_codec/projection_decoder.h" |
| |
| #include <zlib.h> |
| |
| #include <cmath> |
| #include <cstring> |
| #include <ios> |
| #include <limits> |
| #include <memory> |
| #include <string> |
| |
| #if defined(ENABLE_LOGGING) |
| #include "base/logging.h" |
| #endif // #if defined(ENABLE_LOGGING) |
| |
| #include "cobalt/loader/mesh/projection_codec/constants.h" |
| #include "cobalt/loader/mesh/projection_codec/indexed_vert.h" |
| |
| namespace { // anonymous namespace |
| |
| inline int32_t ZigZagDecode32(uint32_t n) { |
| return (n >> 1) ^ -static_cast<int32_t>(n & 1); |
| } |
| |
| // This is a simple class that overloads operator<< to consume any parameter and |
| // do nothing. |
| class NopLogger {}; |
| template <typename T> |
| NopLogger operator<<(NopLogger, const T&) { |
| return NopLogger(); |
| } |
| |
| #if !defined(LOG) |
| #define LOG(level) NopLogger() |
| #endif // #if !defined(LOG) |
| |
| #if !defined(VLOG) |
| #define VLOG(level) NopLogger() |
| #endif // #if !defined(VLOG) |
| |
| #if !defined(DVLOG) |
| #define DVLOG(level) NopLogger() |
| #endif // #if !defined(DVLOG) |
| |
| } // anonymous namespace |
| |
| namespace cobalt { |
| namespace loader { |
| namespace mesh { |
| namespace projection_codec { |
| |
| const uint32_t kErrorBits = 0xFFFFFFFF; |
| const uint8_t kErrorUint8 = 0xFF; |
| const uint32_t kErrorUint32 = 0xFFFFFFFF; |
| const float kErrorFloat = FP_NAN; |
| const char kError4cc[] = "ERR_"; |
| |
| const uint32_t ProjectionDecoder::kMaxCoordinateCount; |
| const uint32_t ProjectionDecoder::kMaxVertexCount; |
| const uint32_t ProjectionDecoder::kMaxMeshCount; |
| const uint32_t ProjectionDecoder::kMaxTriangleIndexCount; |
| |
| ProjectionDecoder::Sink::~Sink() {} |
| |
| ProjectionDecoder::BitReader::BitReader(const uint8_t* data, size_t data_size) |
| : data_begin_(data), |
| data_end_(data + data_size), |
| cached_bits_(0), |
| cached_bit_count_(0), |
| ok_(true) {} |
| |
| uint32_t ProjectionDecoder::BitReader::GetBits(int32_t num_bits) { |
| if (!ok()) return kErrorBits; |
| |
| if (num_bits <= 0) { |
| LOG(ERROR) << "Tried to read " << num_bits << " bits"; |
| ok_ = false; |
| return kErrorBits; |
| } |
| if (num_bits > 32) { |
| LOG(ERROR) << "Tried to read " << num_bits |
| << " bits, only <= 32 is allowed"; |
| ok_ = false; |
| return kErrorBits; |
| } |
| |
| if (num_bits > bits_remaining()) { |
| LOG(ERROR) << "Tried to read " << num_bits |
| << " bits, but only <= " << bits_remaining() << " remain"; |
| ok_ = false; |
| return kErrorBits; |
| } |
| |
| while (cached_bit_count_ < num_bits) { |
| uint8_t byte = *data_begin_; |
| ++data_begin_; |
| cached_bits_ |= static_cast<uint64_t>(byte) << (7 * 8 - cached_bit_count_); |
| cached_bit_count_ += 8; |
| } |
| |
| uint32_t value = static_cast<uint32_t>(cached_bits_ >> (64 - num_bits)); |
| |
| cached_bits_ <<= num_bits; |
| cached_bit_count_ -= num_bits; |
| |
| DVLOG(5) << " Got " << num_bits << " bits as " << value << ",0x" << std::hex |
| << value; |
| |
| return value; |
| } |
| |
| void ProjectionDecoder::BitReader::Align() { |
| int32_t discard_count = cached_bit_count_; |
| |
| cached_bits_ = 0; |
| cached_bit_count_ = 0; |
| |
| DVLOG(5) << " Aligned, discarded " << discard_count << " bits"; |
| } |
| |
| void ProjectionDecoder::BitReader::SkipBits(int64_t num_bits) { |
| if (!ok()) return; |
| |
| if (num_bits < 0) { |
| LOG(ERROR) << "Tried to skip " << num_bits << " bits"; |
| ok_ = false; |
| return; |
| } |
| |
| if (num_bits > bits_remaining()) { |
| LOG(ERROR) << "Tried to skip more bits than remain"; |
| ok_ = false; |
| return; |
| } |
| |
| if (num_bits <= cached_bit_count_) { |
| cached_bits_ <<= static_cast<uint64_t>(num_bits); |
| cached_bit_count_ -= static_cast<int32_t>(num_bits); |
| return; |
| } |
| |
| num_bits -= cached_bit_count_; |
| cached_bits_ = 0; |
| cached_bit_count_ = 0; |
| |
| int64_t num_bytes = num_bits / 8; |
| int32_t remainder_bits = static_cast<int32_t>(num_bits % 8); |
| |
| data_begin_ += num_bytes; |
| if (remainder_bits > 0) { |
| uint64_t byte = *data_begin_; |
| ++data_begin_; |
| cached_bits_ |= byte << static_cast<uint64_t>(7 * 8 + remainder_bits); |
| cached_bit_count_ = 8 - remainder_bits; |
| } |
| } |
| |
| const uint8_t* ProjectionDecoder::BitReader::aligned_raw_bytes() const { |
| return data_begin_; |
| } |
| |
| int64_t ProjectionDecoder::BitReader::bits_remaining() const { |
| if (!ok()) return 0; |
| return static_cast<int64_t>(data_end_ - data_begin_) * 8 + cached_bit_count_; |
| } |
| |
| bool ProjectionDecoder::BitReader::is_aligned() const { |
| return cached_bit_count_ == 0; |
| } |
| |
| bool ProjectionDecoder::BitReader::ok() const { return ok_; } |
| |
| bool ProjectionDecoder::DecodeToSink(const uint8_t* data, size_t data_size, |
| Sink* sink) { |
| ProjectionDecoder mesh_decoder(data, data_size, sink); |
| mesh_decoder.DecodeProjectionBox(); |
| return !mesh_decoder.error_; |
| } |
| |
| bool ProjectionDecoder::DecodeBoxContentsToSink(uint8_t version, uint32_t flags, |
| const uint8_t* data, |
| size_t data_size, Sink* sink) { |
| ProjectionDecoder mesh_decoder(data, data_size, sink); |
| mesh_decoder.DecodeProjectionBoxContents(version, flags); |
| return !mesh_decoder.error_; |
| } |
| |
| bool ProjectionDecoder::DecodeMeshesToSink(uint8_t version, uint32_t flags, |
| uint32_t crc, |
| const std::string& compression, |
| const uint8_t* data, |
| size_t data_size, Sink* sink) { |
| ProjectionDecoder mesh_decoder(data, data_size, sink); |
| mesh_decoder.DecodeCompressedProjectionBoxContents(version, flags, crc, |
| compression); |
| return !mesh_decoder.error_; |
| } |
| |
| ProjectionDecoder::ProjectionDecoder(const uint8_t* data, size_t data_size, |
| Sink* sink) |
| : error_(false), |
| sink_(sink), |
| reader_(data, data_size), |
| version_(0), |
| flags_(0), |
| crc_(0) {} |
| |
| bool ProjectionDecoder::VerifyGreater(uint32_t a, uint32_t b) { |
| if (a > b) { |
| return true; |
| } |
| LOG(ERROR) << "Expected " << a << " > " << b; |
| error_ = true; |
| return false; |
| } |
| |
| bool ProjectionDecoder::VerifyEqual(uint32_t a, uint32_t b) { |
| if (a == b) { |
| return true; |
| } |
| LOG(ERROR) << "Expected " << a << " == " << b; |
| error_ = true; |
| return false; |
| } |
| |
| bool ProjectionDecoder::VerifyEqual(const std::string& a, |
| const std::string& b) { |
| if (a == b) { |
| return true; |
| } |
| LOG(ERROR) << "Expected " << a << " == " << b; |
| error_ = true; |
| return false; |
| } |
| |
| bool ProjectionDecoder::VerifyAligned() { |
| if (!reader_.is_aligned()) { |
| LOG(ERROR) << "Alignment error"; |
| error_ = true; |
| return false; |
| } |
| return true; |
| } |
| |
| uint32_t ProjectionDecoder::GetBits(int32_t num_bits) { |
| if (error_) return kErrorBits; |
| |
| const uint32_t res = reader_.GetBits(num_bits); |
| if (!reader_.ok()) { |
| LOG(ERROR) << "Read error"; |
| error_ = true; |
| return kErrorBits; |
| } |
| |
| DVLOG(5) << " Got " << num_bits << " bits as " << res << ",0x" << std::hex |
| << res; |
| |
| return res; |
| } |
| |
| uint8_t ProjectionDecoder::GetUInt8() { |
| VerifyAligned(); |
| if (error_) return kErrorUint8; |
| |
| const uint8_t res = static_cast<uint8_t>(reader_.GetBits(8)); |
| if (!reader_.ok()) { |
| LOG(ERROR) << "Read error"; |
| error_ = true; |
| return kErrorUint8; |
| } |
| |
| DVLOG(5) << " Got uint8 as " << res << ",0x" << std::hex << res; |
| |
| return res; |
| } |
| |
| uint32_t ProjectionDecoder::GetUInt32() { |
| VerifyAligned(); |
| if (error_) return kErrorUint32; |
| |
| const uint32_t res = reader_.GetBits(32); |
| if (!reader_.ok()) { |
| LOG(ERROR) << "Read error"; |
| error_ = true; |
| return kErrorUint32; |
| } |
| |
| DVLOG(5) << " Got uint32 as " << res << ",0x" << std::hex << res; |
| |
| return res; |
| } |
| |
| float ProjectionDecoder::GetFloat() { |
| VerifyAligned(); |
| if (error_) return kErrorFloat; |
| |
| // Read as a 32 bit uint, then reinterpret the memory as a float. |
| const uint32_t res_as_bytes = reader_.GetBits(32); |
| if (!reader_.ok()) { |
| LOG(ERROR) << "Read error"; |
| error_ = true; |
| return kErrorFloat; |
| } |
| |
| float res; |
| std::memcpy(&res, &res_as_bytes, 4); |
| |
| DVLOG(5) << " Got float " << res; |
| |
| return res; |
| } |
| |
| std::string ProjectionDecoder::GetFourCC() { |
| VerifyAligned(); |
| if (error_) return kError4cc; |
| |
| // Read 4 individual bytes. Note that reading many is OK before checking for |
| // errors, nothing bad will happen, though the bytes will be 0xFF. |
| const char buf[] = {static_cast<char>(reader_.GetBits(8)), |
| static_cast<char>(reader_.GetBits(8)), |
| static_cast<char>(reader_.GetBits(8)), |
| static_cast<char>(reader_.GetBits(8)), 0}; |
| if (!reader_.ok()) { |
| LOG(ERROR) << "Read error"; |
| error_ = true; |
| return kError4cc; |
| } |
| |
| std::string res(reinterpret_cast<const char*>(buf)); |
| |
| DVLOG(5) << " Got fourcc " << res; |
| |
| return res; |
| } |
| |
| void ProjectionDecoder::SkipBits(int64_t num_bits) { |
| if (error_) return; |
| |
| reader_.SkipBits(num_bits); |
| if (!reader_.ok()) { |
| LOG(ERROR) << "Failed to skip " << num_bits << " bits"; |
| error_ = true; |
| } |
| } |
| |
| void ProjectionDecoder::Align() { |
| if (error_) return; |
| reader_.Align(); |
| } |
| |
| void ProjectionDecoder::DecodeProjectionBox() { |
| DVLOG(2) << "Getting projection box"; |
| |
| // Read base box() header. |
| uint32_t projection_box_size = GetUInt32(); |
| if (!VerifyGreater(projection_box_size, 20)) return; |
| |
| std::string box_name_4cc = GetFourCC(); |
| if (box_name_4cc != codec_strings::kMeshProjBoxType4cc && |
| box_name_4cc != codec_strings::kMeshProjAlternateBoxType4cc) { |
| LOG(ERROR) << "Box name " << box_name_4cc << " is not " |
| << codec_strings::kMeshProjBoxType4cc << " or " |
| << codec_strings::kMeshProjAlternateBoxType4cc; |
| error_ = true; |
| return; |
| } |
| |
| // Read full_box() header. |
| uint8_t version = GetUInt8(); |
| uint32_t flags = GetBits(24); |
| |
| DecodeProjectionBoxContents(version, flags); |
| } |
| |
| void ProjectionDecoder::DecodeProjectionBoxContents(uint8_t version, |
| uint32_t flags) { |
| // mesh_projection_box() header. |
| uint32_t crc = GetUInt32(); |
| std::string compression = GetFourCC(); |
| if (error_) return; |
| |
| DecodeCompressedProjectionBoxContents(version, flags, crc, compression); |
| } |
| |
| void ProjectionDecoder::DecodeCompressedProjectionBoxContents( |
| uint8_t version, uint32_t flags, uint32_t crc, |
| const std::string& compression) { |
| if (!VerifyEqual(version, 0)) return; |
| if (!VerifyEqual(flags, 0)) return; |
| |
| version_ = version; |
| flags_ = flags; |
| |
| crc_ = crc; |
| if (sink_->IsCached(crc_)) return; |
| |
| compression_ = compression; |
| if (compression_ == codec_strings::kCompressionRaw4cc) { |
| // All good. |
| } else if (compression_ == codec_strings::kCompressionDeflate4cc) { |
| DeflateDecompress(); |
| } else { |
| LOG(ERROR) << "Invalid compression format " << compression_; |
| error_ = true; |
| } |
| |
| while (!error_ && reader_.bits_remaining() > 0) { |
| // Read the box. |
| int64_t beginning_bits_remaining = reader_.bits_remaining(); |
| uint32_t box_size = GetUInt32(); |
| std::string box_name_4cc = GetFourCC(); |
| |
| if (box_size < 8 || |
| static_cast<int64_t>(box_size) > beginning_bits_remaining / 8) { |
| LOG(ERROR) << "Invalid box size " << box_size; |
| error_ = true; |
| return; |
| } |
| |
| if (box_name_4cc == codec_strings::kMeshBoxType4cc) { |
| DecodeMeshData(); |
| } |
| |
| // Make sure we didn't overflow the box's bounds. |
| int64_t bits_read = beginning_bits_remaining - reader_.bits_remaining(); |
| if (static_cast<int64_t>(box_size) * 8 < bits_read) { |
| LOG(ERROR) << "Box overflowed its bounds"; |
| error_ = true; |
| return; |
| } else if (!reader_.is_aligned()) { |
| LOG(ERROR) << "Reader finished box in a misaligned state"; |
| error_ = true; |
| return; |
| } |
| |
| SkipBits(static_cast<int64_t>(box_size) * 8 - bits_read); |
| } |
| } |
| |
| void ProjectionDecoder::DeflateDecompress() { |
| VLOG(1) << "Decompressing DEFLATE"; |
| if (!reader_.is_aligned()) { |
| LOG(ERROR) << "Data is not 8-bit aligned"; |
| error_ = true; |
| return; |
| } |
| |
| size_t size = static_cast<size_t>(reader_.bits_remaining() / 8); |
| if (size > std::numeric_limits<uInt>::max()) { |
| LOG(ERROR) << "Data is bigger than zlib's uInt max"; |
| error_ = true; |
| return; |
| } |
| |
| z_stream stream = {0}; |
| int status = inflateInit2(&stream, -MAX_WBITS); |
| if (status != Z_OK) { |
| LOG(ERROR) << "zlib inflateInit2 error: " << status; |
| error_ = true; |
| return; |
| } |
| |
| stream.avail_in = static_cast<uInt>(size); |
| stream.next_in = const_cast<Bytef*>(reader_.aligned_raw_bytes()); |
| |
| do { |
| Bytef buf[1024]; |
| stream.avail_out = sizeof(buf); |
| stream.next_out = buf; |
| status = inflate(&stream, Z_NO_FLUSH); |
| if (status != Z_STREAM_END && (status != Z_OK || stream.avail_out != 0)) { |
| LOG(ERROR) << "zlib inflate error: " << status; |
| error_ = true; |
| inflateEnd(&stream); |
| return; |
| } |
| decompress_buffer_.insert(decompress_buffer_.end(), buf, |
| buf + (sizeof(buf) - stream.avail_out)); |
| } while (status != Z_STREAM_END); |
| |
| status = inflateEnd(&stream); |
| if (status != Z_OK) { |
| LOG(ERROR) << "zlib inflateEnd error: " << status; |
| error_ = true; |
| return; |
| } |
| |
| // Note that resetting the bit reader resets its position. This is okay here, |
| // because we aren't tracking the reader's position or bit count before/after |
| // decompression. |
| size_t buffer_size = decompress_buffer_.size(); |
| reader_ = BitReader(buffer_size ? &decompress_buffer_[0] : NULL, |
| decompress_buffer_.size()); |
| } |
| |
| void ProjectionDecoder::DecodeMeshData() { |
| if (error_) return; |
| |
| sink_->BeginMeshCollection(); |
| |
| // Read coordinate list. |
| DVLOG(2) << "Getting coordinates"; |
| uint32_t num_coordinates = GetUInt32(); |
| if (num_coordinates > kMaxCoordinateCount) { |
| LOG(ERROR) << "Coordinate count of " << num_coordinates |
| << " exceeds maximum of " << kMaxCoordinateCount; |
| error_ = true; |
| return; |
| } |
| if (num_coordinates == 0) { |
| LOG(ERROR) << "Too few coordinates"; |
| error_ = true; |
| return; |
| } |
| |
| for (uint32_t i = 0; i < num_coordinates; ++i) { |
| sink_->AddCoordinate(GetFloat()); |
| } |
| if (error_) return; |
| |
| // Read vertex list. Note that the verts are recorded as zig-zag deltas |
| // so we need to track the previous vertex's indices. |
| DVLOG(2) << "Getting vertex indices"; |
| uint32_t num_verts = GetUInt32(); |
| if (num_verts > kMaxVertexCount) { |
| LOG(ERROR) << "Vertex count of " << num_verts << " exceeds maximum of " |
| << kMaxVertexCount; |
| error_ = true; |
| return; |
| } |
| if (num_verts == 0) { |
| LOG(ERROR) << "Too few vertices"; |
| error_ = true; |
| return; |
| } |
| |
| int bits_per_coordinate_index = |
| static_cast<int>(ceil(log2(static_cast<double>(num_coordinates * 2)))); |
| IndexedVert vert; // Initialized to 0's. |
| for (uint32_t i = 0; i < num_verts; ++i) { |
| IndexedVert delta; |
| delta.x = ZigZagDecode32(GetBits(bits_per_coordinate_index)); |
| delta.y = ZigZagDecode32(GetBits(bits_per_coordinate_index)); |
| delta.z = ZigZagDecode32(GetBits(bits_per_coordinate_index)); |
| delta.u = ZigZagDecode32(GetBits(bits_per_coordinate_index)); |
| delta.v = ZigZagDecode32(GetBits(bits_per_coordinate_index)); |
| |
| vert += delta; |
| |
| // Silence compiler warning of signed/unsigned mismatch. |
| int32_t signed_num_coordinates = static_cast<int32_t>(num_coordinates); |
| // Check that the vertex refers to valid coordinates. |
| if (vert.x < 0 || vert.x >= signed_num_coordinates || // |
| vert.y < 0 || vert.y >= signed_num_coordinates || // |
| vert.z < 0 || vert.z >= signed_num_coordinates || // |
| vert.u < 0 || vert.u >= signed_num_coordinates || // |
| vert.v < 0 || vert.v >= signed_num_coordinates) { |
| LOG(ERROR) << "Coordinate index OOB"; |
| error_ = true; |
| return; |
| } |
| sink_->AddVertex(vert); |
| } |
| Align(); // Pad. |
| if (error_) return; |
| |
| // Read mesh instance count. |
| DVLOG(2) << "Getting mesh instance count"; |
| uint32_t mesh_instance_count = GetUInt32(); |
| if (mesh_instance_count > kMaxMeshCount) { |
| LOG(ERROR) << "Mesh instance count of " << mesh_instance_count |
| << " exceeds maximum of " << kMaxMeshCount; |
| error_ = true; |
| return; |
| } |
| |
| for (uint32_t i = 0; i < mesh_instance_count; ++i) { |
| sink_->BeginMeshInstance(); |
| |
| sink_->SetTextureId(GetUInt8()); |
| |
| // Read mesh geometry type. |
| DVLOG(2) << "Getting mesh geometry type"; |
| uint8_t geometryType = GetUInt8(); |
| switch (geometryType) { |
| case 0: |
| sink_->SetMeshGeometryType(kTriangles); |
| break; |
| case 1: |
| sink_->SetMeshGeometryType(kTriangleStrip); |
| break; |
| case 2: |
| sink_->SetMeshGeometryType(kTriangleFan); |
| break; |
| default: |
| LOG(ERROR) << "Unknown mesh geometry type " << geometryType; |
| error_ = true; |
| return; |
| } |
| |
| // Read triangle vertex indices. |
| DVLOG(2) << "Getting triangle vertex indices"; |
| uint32_t num_indices = GetUInt32(); |
| if (num_indices > kMaxTriangleIndexCount) { |
| LOG(ERROR) << "Vertex indices count of " << num_indices |
| << " exceeds the maximum of " << kMaxTriangleIndexCount; |
| error_ = true; |
| return; |
| } |
| |
| int v_index = 0; |
| int bits_per_vertex_index = |
| static_cast<int>(ceil(log2(static_cast<double>(num_verts * 2)))); |
| for (uint32_t i = 0; i < num_indices; ++i) { |
| v_index += ZigZagDecode32(GetBits(bits_per_vertex_index)); |
| |
| // Check that this vertex index is in range before sending it off. |
| if (v_index < 0 || v_index >= static_cast<int32_t>(num_verts)) { |
| LOG(ERROR) << "Triangle vertex index " << v_index << " is OOB"; |
| error_ = true; |
| return; |
| } |
| |
| sink_->AddVertexIndex(v_index); |
| } |
| Align(); // Pad. |
| sink_->EndMeshInstance(); |
| |
| if (error_) return; |
| } |
| sink_->EndMeshCollection(); |
| } |
| |
| } // namespace projection_codec |
| } // namespace mesh |
| } // namespace loader |
| } // namespace cobalt |