Added astc-codec decoder.

This commit is contained in:
Бранимир Караџић
2019-02-16 13:11:06 -08:00
parent c299a8d593
commit 7ad9c896ba
130 changed files with 13342 additions and 57 deletions

5
3rdparty/astc-codec/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
bazel-*
.bazelrc
build
.vs
.vscode

29
3rdparty/astc-codec/BUILD.bazel vendored Normal file
View File

@@ -0,0 +1,29 @@
# 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.
licenses(["notice"])
cc_library(
name = "api",
hdrs = ["include/astc-codec/astc-codec.h"],
visibility = ["//src/decoder:__pkg__"],
)
cc_library(
name = "astc_codec",
hdrs = ["include/astc-codec/astc-codec.h"],
includes = ["include"],
visibility = ["//visibility:public"],
deps = ["//src/decoder:codec"],
)

46
3rdparty/astc-codec/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,46 @@
# 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.
cmake_minimum_required(VERSION 3.1.0)
project(astc-codec)
option(OPTION_ASTC_TESTS "Build all the unit tests." ON)
# TODO add support for the fuzzer, it has some additional dependencies we are not
# yet bringing in.
option(OPTION_BUILD_FUZZER "Build the fuzzer tests." OFF)
set (CMAKE_CXX_STANDARD 11)
if(OPTION_ASTC_TESTS)
enable_testing()
# No need to build gmock if an external project defines it.
if(NOT TARGET gmock_main)
# We use the approach suggested by https://crascit.com/2015/07/25/cmake-gtest/ to download gtest.
include(ExternalProject)
# Download and unpack googletest at configure time
configure_file(GoogleTest-CMakeLists.txt.in googletest-download/CMakeLists.txt)
execute_process(COMMAND "${CMAKE_COMMAND}" -G "${CMAKE_GENERATOR}" .
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/googletest-download")
execute_process(COMMAND "${CMAKE_COMMAND}" --build . WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/googletest-download")
# Prevent GoogleTest from overriding our compiler/linker options when building with Visual Studio
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
# Add googletest directly to our build. This adds the following targets: gtest, gtest_main, gmock and gmock_main
add_subdirectory("${CMAKE_BINARY_DIR}/googletest-src" "${CMAKE_BINARY_DIR}/googletest-build")
endif()
endif()
add_subdirectory(src/base)
add_subdirectory(src/decoder)

28
3rdparty/astc-codec/CONTRIBUTING.md vendored Normal file
View File

@@ -0,0 +1,28 @@
# How to Contribute
We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.
## Contributor License Agreement
Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution;
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to <https://cla.developers.google.com/> to see
your current agreements on file or to sign a new one.
You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.
## Code reviews
All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.
## Community Guidelines
This project follows [Google's Open Source Community
Guidelines](https://opensource.google.com/conduct/).

View File

@@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 2.8.2)
project(googletest-download NONE)
include(ExternalProject)
ExternalProject_Add(googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG "release-1.8.1"
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-src"
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-build"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
)

202
3rdparty/astc-codec/LICENSE vendored Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

71
3rdparty/astc-codec/README.md vendored Normal file
View File

@@ -0,0 +1,71 @@
# astc-codec
astc-codec is a software ASTC decoder implementation, which supports the ASTC
LDR profile.
Example usage:
```
#include <astc-codec/astc-codec.h>
// ...
std::vector<uint8_t> astc = LoadMyASTCData();
const size_t width = 640;
const size_t height = 480;
std::vector<uint8_t> result;
result.resize(width * height * 4);
bool success = astc_codec::ASTCDecompressToRGBA(
astc.data(), astc.size(), width, height, astc_codec::FootprintType::k4x4,
result.data(), result.size(), /* stride */ width * 4);
```
## Building
### With bazel
Install [Bazel](https://bazel.build/), and then run:
```
bazel build :astc_codec -c opt
```
astc-codec has been tested on Mac and Linux.
### Run Tests
```
bazel test //...
```
### With CMake
Install [CMake](https://cmake.org/), and the run:
```
mkdir build && cd build && cmake .. && make
```
Or open the project in your favorite IDE and import CMakeLists.txt.
### Run Tests
In the build directory, execute:
```
ctest
```
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) for important contributing requirements.
## License
astc-codec project is licensed under the Apache License Version 2.0. You can
find a copy of it in [LICENSE](LICENSE).
This is not an officially supported Google product.

37
3rdparty/astc-codec/WORKSPACE vendored Normal file
View File

@@ -0,0 +1,37 @@
# 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.
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
git_repository(
name = "gtest",
remote = "https://github.com/google/googletest.git",
commit = "ba96d0b1161f540656efdaed035b3c062b60e006",
)
http_archive(
name = "honggfuzz",
url = "https://github.com/google/honggfuzz/archive/1.7.zip",
sha256 = "9d420326979fed4a065fa6176d5e09bd513cd2820fe216ae8b684aa6780d72b2",
build_file = "//third_party:honggfuzz.BUILD",
strip_prefix = "honggfuzz-1.7",
)
http_archive(
name = "benchmark",
url = "https://github.com/google/benchmark/archive/v1.4.1.zip",
sha256 = "61ae07eb5d4a0b02753419eb17a82b7d322786bb36ab62bd3df331a4d47c00a7",
strip_prefix = "benchmark-1.4.1",
)

38
3rdparty/astc-codec/cmake-format.json vendored Normal file
View File

@@ -0,0 +1,38 @@
{
"line_width": 120,
"dangle_parens": false,
"first_comment_is_literal": true,
"algorithm_order": [
0,
1,
2,
3
],
"command_case": "lower",
"additional_commands": {
"foo": {
"flags": [
"BAR",
"BAZ"
],
"kwargs": {
"HEADERS": "*",
"DEPENDS": "*",
"SOURCES": "*"
}
}
},
"separate_fn_name_with_space": false,
"always_wrap": [],
"separate_ctrl_name_with_space": false,
"max_subargs_per_line": 5,
"fence_pattern": "^\\s*([`~]{3}[`~]*)(.*)$",
"enable_markup": true,
"ruler_pattern": "^\\s*[^\\w\\s]{3}.*[^\\w\\s]{3}$",
"tab_size": 2,
"keyword_case": "unchanged",
"enum_char": ".",
"literal_comment_pattern": null,
"bullet_char": "*",
"line_ending": "unix"
}

View File

@@ -0,0 +1,75 @@
// 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.
#ifndef ASTC_CODEC_ASTC_CODEC_H_
#define ASTC_CODEC_ASTC_CODEC_H_
#include <cstddef>
#include <cstdint>
namespace astc_codec {
// These are the valid ASTC footprints according to the specification in
// Section C.2.7.
enum class FootprintType {
k4x4,
k5x4,
k5x5,
k6x5,
k6x6,
k8x5,
k8x6,
k10x5,
k10x6,
k8x8,
k10x8,
k10x10,
k12x10,
k12x12,
kCount
};
// Decompresses ASTC LDR image data to a RGBA32 buffer.
//
// Supports formats defined in the KHR_texture_compression_astc_ldr spec and
// returns UNORM8 values. sRGB is not supported, and should be implemented
// by the caller.
//
// |astc_data| - Compressed ASTC image buffer, must be at least |astc_data_size|
// bytes long.
// |astc_data_size| - The size of |astc_data|, in bytes.
// |width| - Image width, in pixels.
// |height| - Image height, in pixels.
// |footprint| - The ASTC footprint (block size) of the compressed image buffer.
// |out_buffer| - Pointer to a buffer where the decompressed image will be
// stored, must be at least |out_buffer_size| bytes long.
// |out_buffer_size| - The size of |out_buffer|, in bytes, at least
// height*out_buffer_stride. If this is too small, this
// function will return false and no data will be
// decompressed.
// |out_buffer_stride| - The stride that should be used to store rows of the
// decoded image, must be at least 4*width bytes.
//
// Returns true if the decompression succeeded, or false if decompression
// failed, or if the astc_data_size was too small for the given width, height,
// and footprint, or if out_buffer_size is too small.
bool ASTCDecompressToRGBA(const uint8_t* astc_data, size_t astc_data_size,
size_t width, size_t height, FootprintType footprint,
uint8_t* out_buffer, size_t out_buffer_size,
size_t out_buffer_stride);
} // namespace astc_codec
#endif // ASTC_CODEC_ASTC_CODEC_H_

4
3rdparty/astc-codec/src/.clang-format vendored Normal file
View File

@@ -0,0 +1,4 @@
BasedOnStyle: Google
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Inline
SpaceAfterTemplateKeyword: false

View File

@@ -0,0 +1,49 @@
# 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.
licenses(["notice"])
cc_library(
name = "base",
hdrs = [
"bit_stream.h",
"bottom_n.h",
"math_utils.h",
"optional.h",
"string_utils.h",
"type_traits.h",
"uint128.h",
"utils.h",
],
features = ["-parse_headers"],
visibility = ["//src/decoder:__pkg__"],
)
cc_test(
name = "base_test",
srcs = [
"test/bit_stream_test.cpp",
"test/bottom_n_test.cpp",
"test/math_utils_test.cpp",
"test/optional_test.cpp",
"test/string_utils_test.cpp",
"test/type_traits_test.cpp",
"test/uint128_test.cpp",
],
deps = [
"@gtest//:gtest_main",
":base",
],
)

View File

@@ -0,0 +1,27 @@
# 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.
add_library(base INTERFACE)
target_include_directories(base INTERFACE ../..)
if(OPTION_ASTC_TESTS)
add_executable(base_test
test/bit_stream_test.cpp
test/bottom_n_test.cpp
test/math_utils_test.cpp
test/optional_test.cpp
test/string_utils_test.cpp
test/type_traits_test.cpp
test/uint128_test.cpp)
target_link_libraries(base_test base gmock_main)
add_test(NAME base_test COMMAND base_test WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
endif()

View File

@@ -0,0 +1,77 @@
// 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.
#ifndef ASTC_CODEC_BASE_BIT_STREAM_H_
#define ASTC_CODEC_BASE_BIT_STREAM_H_
#include <cassert>
#include <cstdint>
namespace astc_codec {
namespace base {
// Represents a stream of bits that can be read or written in arbitrary-sized
// chunks.
template<typename IntType = uint64_t>
class BitStream {
public:
// Creates an empty BitStream.
BitStream() = default;
BitStream(IntType data, uint32_t data_size)
: data_(data), data_size_(data_size) {
assert(data_size_ <= sizeof(data_) * 8);
}
// Return the number of bits in the stream.
uint32_t Bits() const { return data_size_; }
// Put |size| bits into the stream.
// Fails if there is not enough space in the buffer to store the bits.
template<typename ResultType>
void PutBits(ResultType x, uint32_t size) {
assert(data_size_ + size <= sizeof(data_) * 8);
data_ |= (IntType(x) & MaskFor(size)) << data_size_;
data_size_ += size;
}
// Get |count| bits from the stream.
// Returns true if |count| bits were successfully retrieved.
template<typename ResultType>
bool GetBits(uint32_t count, ResultType* result) {
if (count <= data_size_) {
*result = static_cast<ResultType>(data_ & MaskFor(count));
data_ = data_ >> count;
data_size_ -= count;
return true;
} else {
*result = ResultType();
return false;
}
}
private:
IntType MaskFor(uint32_t bits) const {
return (bits == sizeof(IntType) * 8) ? ~IntType(0)
: (IntType(1) << bits) - 1;
}
IntType data_ = IntType();
uint32_t data_size_ = 0;
};
} // namespace base
} // namespace astc_codec
#endif // ASTC_CODEC_BASE_BIT_STREAM_H_

78
3rdparty/astc-codec/src/base/bottom_n.h vendored Normal file
View File

@@ -0,0 +1,78 @@
// 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.
#ifndef ASTC_CODEC_BASE_BOTTOM_N_H_
#define ASTC_CODEC_BASE_BOTTOM_N_H_
#include <algorithm>
#include <functional>
#include <vector>
namespace astc_codec {
namespace base {
// Used to aggregate the lowest N values of data supplied.
template<typename T, typename CompareFn = std::less<T>>
class BottomN {
public:
typedef std::vector<T> ContainerType;
// Creates an empty BottomN with limit |max_size|.
BottomN(size_t max_size) : max_size_(max_size) { }
bool Empty() const { return data_.empty(); }
size_t Size() const { return data_.size(); }
const T& Top() const { return data_.front(); }
void Push(const T& value) {
if (data_.size() < max_size_ || compare_(value, Top())) {
data_.push_back(value);
std::push_heap(data_.begin(), data_.end(), compare_);
if (Size() > max_size_) {
PopTop();
}
}
}
std::vector<T> Pop() {
const size_t len = Size();
std::vector<T> result(len);
for (size_t i = 0; i < len; ++i) {
result[len - i - 1] = PopTop();
}
return result;
}
private:
T PopTop() {
std::pop_heap(data_.begin(), data_.end(), compare_);
T result = data_.back();
data_.pop_back();
return result;
}
ContainerType data_;
CompareFn compare_;
const size_t max_size_;
};
} // namespace base
} // namespace astc_codec
#endif // ASTC_CODEC_BASE_BOTTOM_N_H_

View File

@@ -0,0 +1,80 @@
// 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.
#ifndef ASTC_CODEC_BASE_MATH_UTILS_H_
#define ASTC_CODEC_BASE_MATH_UTILS_H_
#include "src/base/uint128.h"
#include <cassert>
#include <cstdint>
#include <type_traits>
namespace astc_codec {
namespace base {
inline int Log2Floor(uint32_t n) {
if (n == 0) {
return -1;
}
int log = 0;
uint32_t value = n;
for (int i = 4; i >= 0; --i) {
int shift = (1 << i);
uint32_t x = value >> shift;
if (x != 0) {
value = x;
log += shift;
}
}
assert(value == 1);
return log;
}
inline int CountOnes(uint32_t n) {
n -= ((n >> 1) & 0x55555555);
n = ((n >> 2) & 0x33333333) + (n & 0x33333333);
return static_cast<int>((((n + (n >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24);
}
template<typename T>
inline T ReverseBits(T value) {
uint32_t s = sizeof(value) * 8;
T mask = ~T(0);
while ((s >>= 1) > 0) {
mask ^= (mask << s);
value = ((value >> s) & mask) | ((value << s) & ~mask);
}
return value;
}
template<typename T>
inline T GetBits(T source, uint32_t offset, uint32_t count) {
static_assert(std::is_same<T, UInt128>::value || std::is_unsigned<T>::value,
"T must be unsigned.");
const uint32_t total_bits = sizeof(T) * 8;
assert(count > 0);
assert(offset + count <= total_bits);
const T mask = count == total_bits ? ~T(0) : ~T(0) >> (total_bits - count);
return (source >> offset) & mask;
}
} // namespace base
} // namespace astc_codec
#endif // ASTC_CODEC_BASE_MATH_UTILS_H_

520
3rdparty/astc-codec/src/base/optional.h vendored Normal file
View File

@@ -0,0 +1,520 @@
// 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.
#ifndef ASTC_CODEC_BASE_OPTIONAL_H_
#define ASTC_CODEC_BASE_OPTIONAL_H_
#include "src/base/type_traits.h"
#include <cassert>
#include <initializer_list>
#include <type_traits>
#include <utility>
#include <cstddef>
// Optional<T> - a template class to store an optional value of type T.
//
// Usage examples:
//
// Initialization and construction:
// Optional<Foo> foo; // |foo| doesn't contain a value.
// Optional<Foo> foo(Foo(10)); // |foo| contains a copy-constructed value.
// Optional<Foo> foo2(foo); // |foo2| contains a copy of |foo|'s value.
// Optional<Foo> foo3(std::move(foo2)); // Guess what?
//
// Assignment:
// Foo foo_value(0);
// Optional<Foo> foo; // |foo| is empty.
// Optional<Foo> foo2; // |foo2| is empty.
// foo2 = foo; // |foo2| is still empty.
// foo = foo_value; // set value of |foo| to a copy of |foo_value|
// foo = std::move(foo_value); // move |foo_value| into |foo|.
// foo2 = foo; // now |foo2| has a copy of |foo|'s value.
// foo = kNullopt; // unset |foo|, it has no value.
//
// Checking and accessing value:
// if (foo) {
// // |foo| has a value.
// doStuff(*foo); // |*foo| is the value inside |foo|.
// foo->callMethod(); // Same as (*foo).callMethod().
// } else {
// // |foo| is empty.
// }
//
// foo.value() // Same as *foo
// foo.valueOr(<default>) // Return <default> is |foo| has no value.
//
// In-place construction:
//
// Optional<Foo> foo; // |foo| is empty.
// foo.emplace(20); // |foo| now contains a value constructed as Foo(20)
//
// Optional<Foo> foo(kInplace, 20); // |foo| is initialized with a value
// // that is constructed in-place as
// // Foo(20).
//
// return makeOptional<Foo>(20); // Takes Foo constructor arguments
// // directly.
//
// Returning values:
//
// Optional<Foo> myFunc(...) {
// if (someCondition) {
// return Foo(10); // call Optional<Foo>(Foo&) constructor.
// } else {
// return {}; // call Optional<Foo>() constructor, which
// // builds an empty value.
// }
// }
//
// Memory layout:
// Optional<Foo> is equivalent to:
//
// struct {
// bool flag;
// Foo value;
// };
//
// in terms of memory layout. This means it *doubles* the size of integral
// types. Also:
//
// - Optional<Foo> can be constructed from anything that constructs a Foo.
//
// - Same with Optional<Foo>(kInplace, Args...) where Args... matches any
// arguments that can be passed to a Foo constructor.
//
// - Comparison operators are provided. Beware: an empty Optional<Foo>
// is always smaller than any Foo value.
namespace astc_codec {
namespace base {
namespace details {
// Base classes to reduce the number of instantiations of the Optional's
// internal members.
class OptionalFlagBase {
public:
void setConstructed(bool constructed) { mConstructed = constructed; }
constexpr bool constructed() const { return mConstructed; }
constexpr operator bool() const { return constructed(); }
bool hasValue() const { return constructed(); }
constexpr OptionalFlagBase(bool constructed = false)
: mConstructed(constructed) { }
private:
bool mConstructed = false;
};
template<size_t Size, size_t Align>
class OptionalStorageBase {
protected:
using StoreT = typename std::aligned_storage<Size, Align>::type;
StoreT mStorage = {};
};
} // namespace details
// A tag type for empty optional construction
struct NulloptT {
constexpr explicit NulloptT(int) { }
};
// A tag type for inplace value construction
struct InplaceT {
constexpr explicit InplaceT(int) { }
};
// Tag values for null optional and inplace construction
constexpr NulloptT kNullopt{1};
constexpr InplaceT kInplace{1};
// Forward declaration for an early use
template<class T>
class Optional;
// A type trait for checking if a type is an optional instantiation
// Note: if you want to refer to the template name inside the template,
// you need to declare this alias outside of it - because the
// class name inside of the template stands for an instantiated template
// E.g, for template <T> class Foo if you say 'Foo' inside the class, it
// actually means Foo<T>;
template<class U>
using is_any_optional =
is_template_instantiation_of<typename std::decay<U>::type, Optional>;
template<class T>
class Optional
: private details::OptionalFlagBase,
private details::OptionalStorageBase<sizeof(T),
std::alignment_of<T>::value> {
// make sure all optionals are buddies - this is needed to implement
// conversion from optionals of other types
template<class U>
friend class Optional;
template<class U>
using self = Optional<U>;
using base_flag = details::OptionalFlagBase;
using base_storage =
details::OptionalStorageBase<sizeof(T), std::alignment_of<T>::value>;
public:
// std::optional will have this, so let's provide it
using value_type = T;
// make sure we forbid some Optional instantiations where things may get
// really messy
static_assert(!std::is_same<typename std::decay<T>::type, NulloptT>::value,
"Optional of NulloptT is not allowed");
static_assert(!std::is_same<typename std::decay<T>::type, InplaceT>::value,
"Optional of InplaceT is not allowed");
static_assert(!std::is_reference<T>::value,
"Optional references are not allowed: use a pointer instead");
// constructors
constexpr Optional() { }
constexpr Optional(NulloptT) { }
Optional(const Optional& other) : base_flag(other.constructed()) {
if (this->constructed()) {
new (&get()) T(other.get());
}
}
Optional(Optional&& other) : base_flag(other.constructed()) {
if (this->constructed()) {
new (&get()) T(std::move(other.get()));
}
}
// Conversion constructor from optional of similar type
template<class U, class = enable_if_c<!is_any_optional<U>::value &&
std::is_constructible<T, U>::value>>
Optional(const Optional<U>& other) : base_flag(other.constructed()) {
if (this->constructed()) {
new (&get()) T(other.get());
}
}
// Move-conversion constructor
template<class U, class = enable_if_c<!is_any_optional<U>::value &&
std::is_constructible<T, U>::value>>
Optional(Optional<U>&& other) : base_flag(other.constructed()) {
if (this->constructed()) {
new (&get()) T(std::move(other.get()));
}
}
// Construction from a raw value
Optional(const T& value) : base_flag(true) { new (&get()) T(value); }
// Move construction from a raw value
Optional(T&& value) : base_flag(true) { new (&get()) T(std::move(value)); }
// Inplace construction from a list of |T|'s ctor arguments
template<class... Args>
Optional(InplaceT, Args&&... args) : base_flag(true) {
new (&get()) T(std::forward<Args>(args)...);
}
// Inplace construction from an initializer list passed into |T|'s ctor
template<class U, class = enable_if<
std::is_constructible<T, std::initializer_list<U>>>>
Optional(InplaceT, std::initializer_list<U> il) : base_flag(true) {
new (&get()) T(il);
}
// direct assignment
Optional& operator=(const Optional& other) {
if (&other == this) {
return *this;
}
if (this->constructed()) {
if (other.constructed()) {
get() = other.get();
} else {
destruct();
this->setConstructed(false);
}
} else {
if (other.constructed()) {
new (&get()) T(other.get());
this->setConstructed(true);
} else {
; // we're good
}
}
return *this;
}
// move assignment
Optional& operator=(Optional&& other) {
if (this->constructed()) {
if (other.constructed()) {
get() = std::move(other.get());
} else {
destruct();
this->setConstructed(false);
}
} else {
if (other.constructed()) {
new (&get()) T(std::move(other.get()));
this->setConstructed(true);
} else {
; // we're good
}
}
return *this;
}
// conversion assignment
template<class U,
class = enable_if_convertible<typename std::decay<U>::type, T>>
Optional& operator=(const Optional<U>& other) {
if (this->constructed()) {
if (other.constructed()) {
get() = other.get();
} else {
destruct();
this->setConstructed(false);
}
} else {
if (other.constructed()) {
new (&get()) T(other.get());
this->setConstructed(true);
} else {
; // we're good
}
}
return *this;
}
// conversion move assignment
template<class U,
class = enable_if_convertible<typename std::decay<U>::type, T>>
Optional& operator=(Optional<U>&& other) {
if (this->constructed()) {
if (other.constructed()) {
get() = std::move(other.get());
} else {
destruct();
this->setConstructed(false);
}
} else {
if (other.constructed()) {
new (&get()) T(std::move(other.get()));
this->setConstructed(true);
} else {
; // we're good
}
}
return *this;
}
// the most complicated one: forwarding constructor for anything convertible
// to |T|, excluding the stuff implemented above explicitly
template<class U,
class = enable_if_c<
!is_any_optional<typename std::decay<U>::type>::value &&
std::is_convertible<typename std::decay<U>::type, T>::value>>
Optional& operator=(U&& other) {
if (this->constructed()) {
get() = std::forward<U>(other);
} else {
new (&get()) T(std::forward<U>(other));
this->setConstructed(true);
}
return *this;
}
// Adopt value checkers from the parent
using base_flag::operator bool;
using base_flag::hasValue;
T& value() {
assert(this->constructed());
return get();
}
constexpr const T& value() const {
assert(this->constructed());
return get();
}
T* ptr() { return this->constructed() ? &get() : nullptr; }
constexpr const T* ptr() const {
return this->constructed() ? &get() : nullptr;
}
// Value getter with fallback
template<class U = T,
class = enable_if_convertible<typename std::decay<U>::type, T>>
constexpr T valueOr(U&& defaultValue) const {
return this->constructed() ? get() : std::move(defaultValue);
}
// Pointer-like operators
T& operator*() {
assert(this->constructed());
return get();
}
constexpr const T& operator*() const {
assert(this->constructed());
return get();
}
T* operator->() {
assert(this->constructed());
return &get();
}
constexpr const T* operator->() const {
assert(this->constructed());
return &get();
}
~Optional() {
if (this->constructed()) {
destruct();
}
}
void clear() {
if (this->constructed()) {
destruct();
this->setConstructed(false);
}
}
template<class U,
class = enable_if_convertible<typename std::decay<U>::type, T>>
void reset(U&& u) {
*this = std::forward<U>(u);
}
// In-place construction with possible destruction of the old value
template<class... Args>
void emplace(Args&&... args) {
if (this->constructed()) {
destruct();
}
new (&get()) T(std::forward<Args>(args)...);
this->setConstructed(true);
}
// In-place construction with possible destruction of the old value
// initializer-list version
template<class U, class = enable_if<
std::is_constructible<T, std::initializer_list<U>>>>
void emplace(std::initializer_list<U> il) {
if (this->constructed()) {
destruct();
}
new (&get()) T(il);
this->setConstructed(true);
}
private:
// A helper function to convert the internal raw storage to T&
constexpr const T& get() const {
return *reinterpret_cast<const T*>(
reinterpret_cast<const char*>(&this->mStorage));
}
// Same thing, mutable
T& get() { return const_cast<T&>(const_cast<const Optional*>(this)->get()); }
// Shortcut for a destructor call for the stored object
void destruct() { get().T::~T(); }
};
template<class T>
Optional<typename std::decay<T>::type> makeOptional(T&& t) {
return Optional<typename std::decay<T>::type>(std::forward<T>(t));
}
template<class T, class... Args>
Optional<typename std::decay<T>::type> makeOptional(Args&&... args) {
return Optional<typename std::decay<T>::type>(kInplace,
std::forward<Args>(args)...);
}
template<class T>
bool operator==(const Optional<T>& l, const Optional<T>& r) {
return l.hasValue() ? r.hasValue() && *l == *r : !r.hasValue();
}
template<class T>
bool operator==(const Optional<T>& l, NulloptT) {
return !l;
}
template<class T>
bool operator==(NulloptT, const Optional<T>& r) {
return !r;
}
template<class T>
bool operator==(const Optional<T>& l, const T& r) {
return bool(l) && *l == r;
}
template<class T>
bool operator==(const T& l, const Optional<T>& r) {
return bool(r) && l == *r;
}
template<class T>
bool operator!=(const Optional<T>& l, const Optional<T>& r) {
return !(l == r);
}
template<class T>
bool operator!=(const Optional<T>& l, NulloptT) {
return bool(l);
}
template<class T>
bool operator!=(NulloptT, const Optional<T>& r) {
return bool(r);
}
template<class T>
bool operator!=(const Optional<T>& l, const T& r) {
return !l || !(*l == r);
}
template<class T>
bool operator!=(const T& l, const Optional<T>& r) {
return !r || !(l == *r);
}
template<class T>
bool operator<(const Optional<T>& l, const Optional<T>& r) {
return !r ? false : (!l ? true : *l < *r);
}
template<class T>
bool operator<(const Optional<T>&, NulloptT) {
return false;
}
template<class T>
bool operator<(NulloptT, const Optional<T>& r) {
return bool(r);
}
template<class T>
bool operator<(const Optional<T>& l, const T& r) {
return !l || *l < r;
}
template<class T>
bool operator<(const T& l, const Optional<T>& r) {
return bool(r) && l < *r;
}
} // namespace base
} // namespace astc_codec
#endif // ASTC_CODEC_BASE_OPTIONAL_H_

View File

@@ -0,0 +1,68 @@
// 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.
#ifndef ASTC_CODEC_BASE_STRING_UTILS_H_
#define ASTC_CODEC_BASE_STRING_UTILS_H_
#include <limits>
#include <string>
namespace astc_codec {
namespace base {
// Iterates over a string's parts using |splitBy| as a delimiter.
// |splitBy| must be a nonempty string well, or it's a no-op.
// Otherwise, |func| is called on each of the splits, excluding the
// characters that are part of |splitBy|. If two |splitBy|'s occur in a row,
// |func| will be called on a StringView("") in between. See
// StringUtils_unittest.cpp for the full story.
template<class Func>
void Split(const std::string& str, const std::string& splitBy, Func func) {
if (splitBy.empty()) {
return;
}
size_t splitSize = splitBy.size();
size_t begin = 0;
size_t end = str.find(splitBy);
while (true) {
func(str.substr(begin, end - begin));
if (end == std::string::npos) {
return;
}
begin = end + splitSize;
end = str.find(splitBy, begin);
}
}
static int32_t ParseInt32(const char* str, int32_t deflt) {
using std::numeric_limits;
char* error = nullptr;
int64_t value = strtol(str, &error, 0);
// Limit long values to int32 min/max. Needed for lp64; no-op on 32 bits.
if (value > std::numeric_limits<int32_t>::max()) {
value = std::numeric_limits<int32_t>::max();
} else if (value < std::numeric_limits<int32_t>::min()) {
value = std::numeric_limits<int32_t>::min();
}
return (error == str) ? deflt : static_cast<int32_t>(value);
}
} // namespace base
} // namespace astc_codec
#endif // ASTC_CODEC_BASE_STRING_UTILS_H_

View File

@@ -0,0 +1,141 @@
// 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/base/bit_stream.h"
#include <gtest/gtest.h>
namespace astc_codec {
namespace base {
namespace {
static constexpr uint64_t kAllBits = 0xFFFFFFFFFFFFFFFF;
static constexpr uint64_t k40Bits = 0x000000FFFFFFFFFF;
}
TEST(BitStream, Decode) {
{
BitStream<uint64_t> stream(0, 1);
uint64_t bits = kAllBits;
EXPECT_TRUE(stream.GetBits(1, &bits));
EXPECT_EQ(bits, 0);
EXPECT_FALSE(stream.GetBits(1, &bits));
}
{
BitStream<uint64_t> stream(0b1010101010101010, 32);
EXPECT_EQ(stream.Bits(), 32);
uint64_t bits = 0;
EXPECT_TRUE(stream.GetBits(1, &bits));
EXPECT_EQ(bits, 0);
EXPECT_TRUE(stream.GetBits(3, &bits));
EXPECT_EQ(bits, 0b101);
EXPECT_TRUE(stream.GetBits(8, &bits));
EXPECT_EQ(bits, 0b10101010);
EXPECT_EQ(stream.Bits(), 20);
EXPECT_TRUE(stream.GetBits(20, &bits));
EXPECT_EQ(bits, 0b1010);
EXPECT_EQ(stream.Bits(), 0);
}
{
BitStream<uint64_t> stream(kAllBits, 64);
EXPECT_EQ(stream.Bits(), 64);
uint64_t bits = 0;
EXPECT_TRUE(stream.GetBits(64, &bits));
EXPECT_EQ(bits, kAllBits);
EXPECT_EQ(stream.Bits(), 0);
}
{
BitStream<uint64_t> stream(kAllBits, 64);
EXPECT_EQ(stream.Bits(), 64);
uint64_t bits = 0;
EXPECT_TRUE(stream.GetBits(40, &bits));
EXPECT_EQ(bits, k40Bits);
EXPECT_EQ(stream.Bits(), 24);
}
{
BitStream<uint64_t> stream(kAllBits, 32);
uint64_t bits = 0;
EXPECT_TRUE(stream.GetBits(0, &bits));
EXPECT_EQ(bits, 0);
EXPECT_TRUE(stream.GetBits(32, &bits));
EXPECT_EQ(bits, k40Bits & 0xFFFFFFFF);
EXPECT_TRUE(stream.GetBits(0, &bits));
EXPECT_EQ(bits, 0);
EXPECT_EQ(stream.Bits(), 0);
}
}
TEST(BitStream, Encode) {
{
BitStream<uint64_t> stream;
stream.PutBits(0, 1);
stream.PutBits(0b11, 2);
EXPECT_EQ(stream.Bits(), 3);
uint64_t bits = 0;
EXPECT_TRUE(stream.GetBits(3, &bits));
EXPECT_EQ(bits, 0b110);
}
{
BitStream<uint64_t> stream;
uint64_t bits = 0;
stream.PutBits(kAllBits, 64);
EXPECT_EQ(stream.Bits(), 64);
EXPECT_TRUE(stream.GetBits(64, &bits));
EXPECT_EQ(bits, kAllBits);
EXPECT_EQ(stream.Bits(), 0);
}
{
BitStream<uint64_t> stream;
stream.PutBits(kAllBits, 40);
uint64_t bits = 0;
EXPECT_TRUE(stream.GetBits(40, &bits));
EXPECT_EQ(bits, k40Bits);
EXPECT_EQ(stream.Bits(), 0);
}
{
BitStream<uint64_t> stream;
stream.PutBits(0, 0);
stream.PutBits(kAllBits, 32);
stream.PutBits(0, 0);
uint64_t bits = 0;
EXPECT_TRUE(stream.GetBits(32, &bits));
EXPECT_EQ(bits, k40Bits & 0xFFFFFFFF);
EXPECT_EQ(stream.Bits(), 0);
}
}
} // namespace base
} // namespace astc_codec

View File

@@ -0,0 +1,108 @@
// 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/base/bottom_n.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace astc_codec {
namespace base {
using ::testing::ElementsAre;
template<typename T, size_t N>
static void pushAll(BottomN<T>& heap, const T (&arr)[N]) {
for (auto i : arr) {
heap.Push(i);
}
}
TEST(BottomN, Sort) {
{
BottomN<int> heap(10);
EXPECT_TRUE(heap.Empty());
int list[] = { 1,2 };
pushAll(heap, list);
EXPECT_EQ(heap.Size(), 2);
EXPECT_FALSE(heap.Empty());
EXPECT_THAT(heap.Pop(), ElementsAre(1, 2));
}
{
BottomN<int> heap(6);
int list[] = {1, 4, 3, 2, 2, 1};
pushAll(heap, list);
EXPECT_EQ(heap.Size(), 6);
EXPECT_THAT(heap.Pop(), ElementsAre(1, 1, 2, 2, 3, 4));
}
}
TEST(BottomN, Bounds) {
{
BottomN<int> heap(4);
int list[] = { 1, 2, 3, 4 };
pushAll(heap, list);
EXPECT_EQ(heap.Size(), 4);
heap.Push(0);
EXPECT_EQ(heap.Size(), 4);
EXPECT_THAT(heap.Pop(), ElementsAre(0, 1, 2, 3));
}
{
BottomN<int> heap(4);
int list[] = { 4, 3, 2,1 };
pushAll(heap, list);
EXPECT_EQ(heap.Size(), 4);
int list2[] = { 4,4,4,4 };
pushAll(heap, list2);
EXPECT_EQ(heap.Size(), 4);
EXPECT_THAT(heap.Pop(), ElementsAre(1, 2, 3, 4));
}
{
BottomN<int> heap(4);
int list[] = { 4, 3, 2, 1 };
pushAll(heap, list);
EXPECT_EQ(heap.Size(), 4);
int list2[] = { 5, 5, 5, 5 };
pushAll(heap, list2);
EXPECT_EQ(heap.Size(), 4);
EXPECT_THAT(heap.Pop(), ElementsAre(1, 2, 3, 4));
}
{
BottomN<int> heap(4);
int list[] = { 4, 3, 2, 1 };
pushAll(heap, list);
EXPECT_EQ(heap.Size(), 4);
int list2[] = { 0, 0, 0, 0 };
pushAll(heap, list2);
EXPECT_EQ(heap.Size(), 4);
EXPECT_THAT(heap.Pop(), ElementsAre(0, 0, 0, 0));
}
}
} // namespace base
} // namespace astc_codec

View File

@@ -0,0 +1,78 @@
// 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/base/math_utils.h"
#include <gtest/gtest.h>
namespace astc_codec {
namespace base {
TEST(MathUtils, Log2Floor) {
EXPECT_EQ(-1, Log2Floor(0));
for (int i = 0; i < 32; i++) {
uint32_t n = 1U << i;
EXPECT_EQ(i, Log2Floor(n));
if (n > 2) {
EXPECT_EQ(i - 1, Log2Floor(n - 1));
EXPECT_EQ(i, Log2Floor(n + 1));
}
}
}
TEST(MathUtils, CountOnes) {
EXPECT_EQ(0, CountOnes(0));
EXPECT_EQ(1, CountOnes(1));
EXPECT_EQ(32, CountOnes(static_cast<uint32_t>(~0U)));
EXPECT_EQ(1, CountOnes(0x8000000));
for (int i = 0; i < 32; i++) {
EXPECT_EQ(1, CountOnes(1U << i));
EXPECT_EQ(31, CountOnes(static_cast<uint32_t>(~0U) ^ (1U << i)));
}
}
TEST(MathUtils, ReverseBits) {
EXPECT_EQ(ReverseBits(0u), 0u);
EXPECT_EQ(ReverseBits(1u), 1u << 31);
EXPECT_EQ(ReverseBits(0xffffffff), 0xffffffff);
EXPECT_EQ(ReverseBits(0x00000001), 0x80000000);
EXPECT_EQ(ReverseBits(0x80000000), 0x00000001);
EXPECT_EQ(ReverseBits(0xaaaaaaaa), 0x55555555);
EXPECT_EQ(ReverseBits(0x55555555), 0xaaaaaaaa);
EXPECT_EQ(ReverseBits(0x7d5d7f53), 0xcafebabe);
EXPECT_EQ(ReverseBits(0xcafebabe), 0x7d5d7f53);
}
TEST(MathUtils, GetBits) {
EXPECT_EQ(GetBits(0u, 0, 1), 0u);
EXPECT_EQ(GetBits(0u, 0, 32), 0u);
EXPECT_EQ(GetBits(0x00000001u, 0, 1), 0x00000001);
EXPECT_EQ(GetBits(0x00000001u, 0, 32), 0x00000001);
EXPECT_EQ(GetBits(0x00000001u, 1, 31), 0x00000000);
EXPECT_EQ(GetBits(0x00000001u, 31, 1), 0x00000000);
EXPECT_DEBUG_DEATH(GetBits(0x00000000u, 1, 32), "");
EXPECT_DEBUG_DEATH(GetBits(0x00000000u, 32, 0), "");
EXPECT_DEBUG_DEATH(GetBits(0x00000000u, 32, 1), "");
EXPECT_EQ(GetBits(0XFFFFFFFFu, 0, 4), 0x0000000F);
EXPECT_EQ(GetBits(0XFFFFFFFFu, 16, 16), 0xFFFF);
EXPECT_EQ(GetBits(0x80000000u, 31, 1), 1);
EXPECT_EQ(GetBits(0xCAFEBABEu, 24, 8), 0xCA);
}
} // namespace base
} // namespace astc_codec

View File

@@ -0,0 +1,481 @@
// 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/base/optional.h"
#include <gtest/gtest.h>
#include <memory>
#include <vector>
namespace astc_codec {
namespace base {
TEST(Optional, TypeProperties) {
// Making sure optional has the correct alignment and doesn't waste too much
// space
static_assert(sizeof(Optional<bool>) == 2, "bad Optional<bool> size");
static_assert(std::alignment_of<Optional<bool>>::value ==
std::alignment_of<bool>::value,
"bad Optional<bool> alignment");
static_assert(sizeof(Optional<char>) == 2, "bad Optional<char> size");
static_assert(std::alignment_of<Optional<char>>::value ==
std::alignment_of<char>::value,
"bad Optional<char> alignment");
static_assert(sizeof(Optional<int16_t>) == 4, "bad Optional<int16_t> size");
static_assert(std::alignment_of<Optional<int16_t>>::value ==
std::alignment_of<int16_t>::value,
"bad Optional<int16_t> alignment");
static_assert(sizeof(Optional<int32_t>) == 8, "bad Optional<int32_t> size");
static_assert(std::alignment_of<Optional<int32_t>>::value ==
std::alignment_of<int32_t>::value,
"bad Optional<int32_t> alignment");
static_assert(sizeof(Optional<int64_t>) == 16, "bad Optional<int64_t> size");
static_assert(std::alignment_of<Optional<int64_t>>::value ==
std::alignment_of<int64_t>::value,
"bad Optional<int64_t> alignment");
struct S128 {
int64_t data[2];
};
static_assert(sizeof(Optional<S128>) == 3 * sizeof(int64_t),
"bad Optional<S128> size");
static_assert(std::alignment_of<Optional<S128>>::value ==
std::alignment_of<S128>::value,
"bad Optional<S128> alignment");
}
TEST(Optional, ConstructFromValue) {
{
Optional<int> o;
EXPECT_FALSE(o);
}
{
Optional<int> o = {};
EXPECT_FALSE(o);
}
{
Optional<int> o = kNullopt;
EXPECT_FALSE(o);
}
{
Optional<int> o(1);
EXPECT_TRUE(o);
EXPECT_EQ(1, *o);
}
{
// check the std::decay<> constructor
Optional<int> o = static_cast<const short&>(1);
EXPECT_TRUE(o);
EXPECT_EQ(1, *o);
}
{
Optional<int> o = 1;
EXPECT_TRUE(o);
EXPECT_EQ(1, *o);
}
{
Optional<int> o{1};
EXPECT_TRUE(o);
EXPECT_EQ(1, *o);
}
{
short val = 10;
Optional<int> o = val;
EXPECT_TRUE(o);
EXPECT_EQ(10, *o);
}
{
Optional<std::vector<int>> o(kInplace, 10);
EXPECT_TRUE(o);
EXPECT_EQ((std::vector<int>(10)), *o);
}
{
Optional<std::vector<int>> o(kInplace, {1, 2, 3, 4});
EXPECT_TRUE(o);
EXPECT_EQ((std::vector<int>{1, 2, 3, 4}), *o);
}
}
TEST(Optional, ConstructFromOptional) {
{
Optional<int> o = Optional<int>();
EXPECT_FALSE(o);
}
{
Optional<short> o2;
Optional<int> o(o2);
EXPECT_FALSE(o);
}
{
Optional<short> o2 = 42;
Optional<int> o(o2);
EXPECT_TRUE(o);
EXPECT_EQ(42, *o);
}
{
Optional<int> o(Optional<int>(1));
EXPECT_TRUE(o);
EXPECT_EQ(1, *o);
}
{
Optional<int> o2 = 2;
Optional<int> o = o2;
EXPECT_TRUE(o);
EXPECT_EQ(2, *o);
}
{
Optional<std::vector<int>> o2 = std::vector<int>{20, 30, 40};
Optional<std::vector<int>> o = o2;
EXPECT_TRUE(o);
EXPECT_EQ((std::vector<int>{20, 30, 40}), *o);
}
}
TEST(Optional, Assign) {
{
Optional<int> o;
o = 1;
EXPECT_TRUE(o);
EXPECT_EQ(1, *o);
o = 2;
EXPECT_TRUE(o);
EXPECT_EQ(2, *o);
o = kNullopt;
EXPECT_FALSE(o);
o = Optional<int>(10);
EXPECT_TRUE(o);
EXPECT_EQ(10, *o);
Optional<int> o2;
o = o2;
EXPECT_FALSE(o);
o = 2u;
EXPECT_TRUE(o);
EXPECT_EQ(2, *o);
o = Optional<short>();
EXPECT_FALSE(o);
o = Optional<short>(20);
EXPECT_TRUE(o);
EXPECT_EQ(20, *o);
Optional<short> o3(200);
o = o3;
EXPECT_TRUE(o);
EXPECT_EQ(200, *o);
o = {};
EXPECT_FALSE(o);
// check the std::decay<> assignment
o = static_cast<const short&>(1);
EXPECT_TRUE(o);
EXPECT_EQ(1, *o);
}
}
TEST(Optional, MakeOptional) {
{
auto o = makeOptional(1);
static_assert(std::is_same<decltype(o), Optional<int>>::value,
"Bad type deduction in makeOptional()");
EXPECT_TRUE(o);
EXPECT_EQ(1, *o);
}
{
auto o = makeOptional(std::vector<char>{'1', '2'});
static_assert(std::is_same<decltype(o), Optional<std::vector<char>>>::value,
"Bad type deduction in makeOptional()");
EXPECT_TRUE(o);
EXPECT_EQ((std::vector<char>{'1', '2'}), *o);
}
{
// check std::decay<> in the factory function
auto o = makeOptional("String");
static_assert(std::is_same<decltype(o), Optional<const char*>>::value,
"Bad type deduction in makeOptional()");
EXPECT_TRUE(o);
EXPECT_STREQ("String", *o);
}
{
auto o = makeOptional<std::string>("String");
static_assert(std::is_same<decltype(o), Optional<std::string>>::value,
"Bad type deduction in makeOptional()");
EXPECT_TRUE(o);
EXPECT_STREQ("String", o->c_str());
}
{
auto o = makeOptional<std::string>(5, 'b');
static_assert(std::is_same<decltype(o), Optional<std::string>>::value,
"Bad type deduction in makeOptional()");
EXPECT_TRUE(o);
EXPECT_STREQ("bbbbb", o->c_str());
}
{
auto o = makeOptional<std::string>();
static_assert(std::is_same<decltype(o), Optional<std::string>>::value,
"Bad type deduction in makeOptional()");
EXPECT_TRUE(o);
EXPECT_STREQ("", o->c_str());
}
}
TEST(Optional, Move) {
auto o = makeOptional(std::unique_ptr<int>(new int(10)));
{
decltype(o) o2 = std::move(o);
EXPECT_TRUE(o);
EXPECT_TRUE(o2);
EXPECT_FALSE(bool(*o));
EXPECT_TRUE(bool(*o2));
EXPECT_EQ(10, **o2);
decltype(o) o3;
o3 = std::move(o2);
EXPECT_TRUE(o2);
EXPECT_TRUE(o3);
EXPECT_FALSE(bool(*o2));
EXPECT_TRUE(bool(*o3));
EXPECT_EQ(10, **o3);
o3 = std::move(o2);
EXPECT_TRUE(o2);
EXPECT_TRUE(o3);
EXPECT_FALSE(bool(*o2));
EXPECT_FALSE(bool(*o3));
}
{
decltype(o) o1;
decltype(o) o2 = std::move(o1);
EXPECT_FALSE(o1);
EXPECT_FALSE(o2);
o2 = std::move(o1);
EXPECT_FALSE(o1);
EXPECT_FALSE(o2);
decltype(o) o3{kInplace, new int(20)};
o3 = std::move(o1);
EXPECT_FALSE(o1);
EXPECT_FALSE(o3);
}
}
TEST(Optional, Value) {
auto o = makeOptional(1);
EXPECT_EQ(1, o.value());
EXPECT_EQ(1, o.valueOr(2));
o = kNullopt;
EXPECT_EQ(2, o.valueOr(2));
}
TEST(Optional, Clear) {
auto o = makeOptional(1);
o.clear();
EXPECT_FALSE(o);
o.clear();
EXPECT_FALSE(o);
}
TEST(Optional, Emplace) {
auto o = makeOptional(std::vector<int>{1, 2, 3, 4});
o.emplace(3, 1);
EXPECT_TRUE(o);
EXPECT_EQ((std::vector<int>{1, 1, 1}), *o);
EXPECT_EQ(3U, o->capacity());
o.clear();
o.emplace({1, 2});
EXPECT_TRUE(o);
EXPECT_EQ((std::vector<int>{1, 2}), *o);
EXPECT_EQ(2U, o->capacity());
}
TEST(Optional, Reset) {
auto o = makeOptional(std::vector<int>{1, 2, 3, 4});
o.reset(std::vector<int>{4, 3});
EXPECT_TRUE(o);
EXPECT_EQ((std::vector<int>{4, 3}), *o);
EXPECT_EQ(2U, o->capacity());
o.clear();
o.reset(std::vector<int>{1});
EXPECT_EQ((std::vector<int>{1}), *o);
EXPECT_EQ(1U, o->capacity());
}
TEST(Optional, CompareEqual) {
EXPECT_TRUE(makeOptional(1) == makeOptional(1));
EXPECT_TRUE(makeOptional(1) == 1);
EXPECT_TRUE(1 == makeOptional(1));
EXPECT_FALSE(makeOptional(1) == makeOptional(2));
EXPECT_FALSE(makeOptional(2) == 1);
EXPECT_FALSE(2 == makeOptional(1));
EXPECT_TRUE(makeOptional(1) != makeOptional(2));
EXPECT_TRUE(makeOptional(1) != 2);
EXPECT_TRUE(1 != makeOptional(2));
EXPECT_FALSE(makeOptional(1) == kNullopt);
EXPECT_FALSE(makeOptional(1) == Optional<int>());
EXPECT_FALSE(kNullopt == makeOptional(1));
EXPECT_FALSE(Optional<int>() == makeOptional(1));
EXPECT_TRUE(makeOptional(1) != kNullopt);
EXPECT_TRUE(makeOptional(1) != Optional<int>());
EXPECT_TRUE(kNullopt != makeOptional(1));
EXPECT_TRUE(Optional<int>() != makeOptional(1));
EXPECT_TRUE(kNullopt == Optional<int>());
EXPECT_TRUE(kNullopt == Optional<char*>());
EXPECT_FALSE(kNullopt != Optional<int>());
EXPECT_FALSE(kNullopt != Optional<char*>());
EXPECT_TRUE(Optional<int>() == Optional<int>());
EXPECT_FALSE(Optional<int>() != Optional<int>());
}
TEST(Optional, CompareLess) {
EXPECT_TRUE(makeOptional(1) < makeOptional(2));
EXPECT_TRUE(1 < makeOptional(2));
EXPECT_TRUE(makeOptional(1) < 2);
EXPECT_FALSE(makeOptional(1) < makeOptional(1));
EXPECT_FALSE(1 < makeOptional(1));
EXPECT_FALSE(makeOptional(1) < 1);
EXPECT_FALSE(makeOptional(2) < makeOptional(1));
EXPECT_FALSE(2 < makeOptional(1));
EXPECT_FALSE(makeOptional(2) < 1);
EXPECT_TRUE(kNullopt < makeOptional(2));
EXPECT_TRUE(Optional<int>() < makeOptional(2));
EXPECT_TRUE(Optional<int>() < 2);
EXPECT_FALSE(makeOptional(2) < kNullopt);
EXPECT_FALSE(makeOptional(2) < Optional<int>());
EXPECT_FALSE(2 < Optional<int>());
EXPECT_FALSE(kNullopt < Optional<int>());
EXPECT_FALSE(Optional<int>() < kNullopt);
}
TEST(Optional, Destruction) {
// create a reference counting class to check if we delete everything
// we've created
struct Track {
Track(int& val) : mVal(val) { ++mVal.get(); }
Track(std::initializer_list<int*> vals) : mVal(**vals.begin()) {
++mVal.get();
}
Track(const Track& other) : mVal(other.mVal) { ++mVal.get(); }
Track(Track&& other) : mVal(other.mVal) { ++mVal.get(); }
Track& operator=(const Track& other) {
--mVal.get();
mVal = other.mVal;
++mVal.get();
return *this;
}
Track& operator=(Track&& other) {
--mVal.get();
mVal = other.mVal;
++mVal.get();
return *this;
}
~Track() { --mVal.get(); }
std::reference_wrapper<int> mVal;
};
int counter = 0;
{
auto o = makeOptional(Track(counter));
EXPECT_EQ(1, counter);
}
EXPECT_EQ(0, counter);
{
auto o = makeOptional(Track(counter));
EXPECT_EQ(1, counter);
o.clear();
EXPECT_EQ(0, counter);
}
EXPECT_EQ(0, counter);
{
auto o = makeOptional(Track(counter));
EXPECT_EQ(1, counter);
int counter2 = 0;
o.emplace(counter2);
EXPECT_EQ(0, counter);
EXPECT_EQ(1, counter2);
o = Track(counter);
EXPECT_EQ(1, counter);
EXPECT_EQ(0, counter2);
auto o2 = o;
EXPECT_EQ(2, counter);
EXPECT_EQ(0, counter2);
}
EXPECT_EQ(0, counter);
{
auto o = makeOptional(Track(counter));
auto o2 = std::move(o);
EXPECT_EQ(2, counter);
o = o2;
EXPECT_EQ(2, counter);
}
EXPECT_EQ(0, counter);
int counter2 = 0;
{
Optional<Track> o;
o.emplace(counter);
EXPECT_EQ(1, counter);
o.emplace(counter2);
EXPECT_EQ(0, counter);
EXPECT_EQ(1, counter2);
}
EXPECT_EQ(0, counter);
EXPECT_EQ(0, counter2);
{
Optional<Track> o;
o.emplace({&counter});
EXPECT_EQ(1, counter);
counter2 = 0;
o.emplace({&counter2});
EXPECT_EQ(0, counter);
EXPECT_EQ(1, counter2);
}
EXPECT_EQ(0, counter);
EXPECT_EQ(0, counter2);
}
} // namespace base
} // namespace astc_codec

View File

@@ -0,0 +1,110 @@
// 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/base/string_utils.h"
#include <gtest/gtest.h>
#include <list>
#include <string>
#include <vector>
namespace astc_codec {
namespace base {
TEST(StringUtils, Split) {
std::vector<std::string> results;
auto testFunc = [&results](std::string&& s) {
results.push_back(std::move(s));
};
Split("", "abc", testFunc);
EXPECT_EQ(results.size(), 1);
Split("abc", "", testFunc);
EXPECT_EQ(results.size(), 1);
results.clear();
Split("abc", "a", testFunc);
EXPECT_EQ(results.size(), 2);
EXPECT_EQ(results[0], "");
EXPECT_EQ(results[1], "bc");
results.clear();
Split("aaa", "a", testFunc);
EXPECT_EQ(4, results.size());
EXPECT_EQ("", results[0]);
EXPECT_EQ("", results[1]);
EXPECT_EQ("", results[2]);
EXPECT_EQ("", results[3]);
results.clear();
Split("1a2a3a4", "a", testFunc);
EXPECT_EQ(4, results.size());
EXPECT_EQ("1", results[0]);
EXPECT_EQ("2", results[1]);
EXPECT_EQ("3", results[2]);
EXPECT_EQ("4", results[3]);
results.clear();
Split("1a2aa3a4", "a", testFunc);
EXPECT_EQ(5, results.size());
EXPECT_EQ("1", results[0]);
EXPECT_EQ("2", results[1]);
EXPECT_EQ("", results[2]);
EXPECT_EQ("3", results[3]);
EXPECT_EQ("4", results[4]);
results.clear();
Split("The quick brown fox jumped over the lazy dog",
" ", testFunc);
EXPECT_EQ(9, results.size());
EXPECT_EQ("The", results[0]);
EXPECT_EQ("quick", results[1]);
EXPECT_EQ("brown", results[2]);
EXPECT_EQ("fox", results[3]);
EXPECT_EQ("jumped", results[4]);
EXPECT_EQ("over", results[5]);
EXPECT_EQ("the", results[6]);
EXPECT_EQ("lazy", results[7]);
EXPECT_EQ("dog", results[8]);
results.clear();
Split("a; b; c; d", "; ", testFunc);
EXPECT_EQ(4, results.size());
EXPECT_EQ("a", results[0]);
EXPECT_EQ("b", results[1]);
EXPECT_EQ("c", results[2]);
EXPECT_EQ("d", results[3]);
}
TEST(StringUtils, ParseInt32) {
EXPECT_EQ(ParseInt32("0", -1), 0);
EXPECT_EQ(ParseInt32("100", -1), 100);
EXPECT_EQ(ParseInt32("-100", -1), -100);
EXPECT_EQ(ParseInt32("", -1), -1);
EXPECT_EQ(ParseInt32("a", -1), -1);
EXPECT_EQ(ParseInt32("10x1", -1), 10);
EXPECT_EQ(ParseInt32("2147483647", -1), 2147483647);
EXPECT_EQ(ParseInt32("2147483648", -1), 2147483647);
EXPECT_EQ(ParseInt32("-2147483648", -1), -2147483648);
EXPECT_EQ(ParseInt32("-2147483649", -1), -2147483648);
}
} // namespace base
} // namespace astc_codec

View File

@@ -0,0 +1,130 @@
// 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/base/type_traits.h"
#include <gtest/gtest.h>
#include <array>
#include <functional>
#include <list>
#include <vector>
namespace astc_codec {
namespace base {
TEST(TypeTraits, IsCallable) {
class C;
C* c = nullptr;
auto lambda = [c](bool) -> C* { return nullptr; };
static_assert(is_callable_as<void(), void()>::value, "simple function");
static_assert(is_callable_as<void (&)(), void()>::value,
"function reference");
static_assert(is_callable_as<void (*)(), void()>::value, "function pointer");
static_assert(is_callable_as<int(C&, C*), int(C&, C*)>::value,
"function with arguments and return type");
static_assert(is_callable_as<decltype(lambda), C*(bool)>::value, "lambda");
static_assert(is_callable_as<std::function<bool(int)>, bool(int)>::value,
"std::function");
static_assert(!is_callable_as<int, void()>::value,
"int should not be callable");
static_assert(!is_callable_as<C, void()>::value, "incomplete type");
static_assert(!is_callable_as<void(), void(int)>::value,
"different arguments");
static_assert(!is_callable_as<int(), void()>::value,
"different return types");
static_assert(!is_callable_as<int(), short()>::value,
"slightly different return types");
static_assert(!is_callable_as<int(int), int(int, int)>::value,
"more arguments");
static_assert(!is_callable_as<int(int, int), int(int)>::value,
"less arguments");
static_assert(!is_callable_as<int(int), int>::value,
"bad required signature");
static_assert(is_callable_with_args<void(), void()>::value,
"simple function");
static_assert(is_callable_with_args<void (&)(), void()>::value,
"function reference");
static_assert(is_callable_with_args<void (*)(), void()>::value,
"function pointer");
static_assert(is_callable_with_args<int(C&, C*), int(C&, C*)>::value,
"function with arguments and return type");
static_assert(is_callable_with_args<decltype(lambda), C*(bool)>::value,
"lambda");
static_assert(
is_callable_with_args<std::function<bool(int)>, bool(int)>::value,
"std::function");
static_assert(!is_callable_with_args<int, void()>::value,
"int should not be callable");
static_assert(!is_callable_with_args<C, void()>::value, "incomplete type");
static_assert(!is_callable_with_args<void(), void(int)>::value,
"different arguments");
static_assert(is_callable_with_args<int(), void()>::value,
"different return types are ignored");
static_assert(is_callable_with_args<int(), short()>::value,
"slightly different return types are ignored");
static_assert(!is_callable_with_args<int(int), int(int, int)>::value,
"more arguments");
static_assert(!is_callable_with_args<int(int, int), int(int)>::value,
"less arguments");
static_assert(!is_callable_with_args<int(int), int>::value,
"bad required signature");
}
TEST(TypeTraits, IsTemplateInstantiation) {
static_assert(!is_template_instantiation_of<int, std::vector>::value,
"int is not an instance of vector");
static_assert(!is_template_instantiation_of<std::list<std::vector<int>>,
std::vector>::value,
"list is not an instance of vector");
static_assert(
is_template_instantiation_of<std::vector<int>, std::vector>::value,
"std::vector<int> is an instance of vector");
static_assert(
is_template_instantiation_of<std::vector<std::vector<std::vector<int>>>,
std::vector>::value,
"nested std::vector<> is an instance of vector");
}
#ifndef _MSC_VER
TEST(TypeTraits, IsRange) {
static_assert(is_range<std::vector<int>>::value,
"vector<> should be detected as a range");
static_assert(is_range<const std::list<std::function<void()>>>::value,
"const list<> should be detected as a range");
static_assert(is_range<std::array<std::vector<int>, 10>>::value,
"array<> should be detected as a range");
char arr[100];
static_assert(is_range<decltype(arr)>::value,
"C array should be detected as a range");
static_assert(is_range<decltype("string")>::value,
"String literal should be detected as a range");
static_assert(!is_range<int>::value, "int shouldn't be a range");
static_assert(!is_range<int*>::value, "int* shouldn't be a range");
static_assert(!is_range<const int*>::value,
"even const int* shouldn't be a range");
}
#endif
} // namespace base
} // namespace astc_codec

View File

@@ -0,0 +1,140 @@
// 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/base/uint128.h"
#include <gtest/gtest.h>
namespace astc_codec {
namespace base {
TEST(UInt128, Equality) {
const UInt128 zero(0);
const UInt128 max64(~0ULL);
EXPECT_EQ(zero, zero);
EXPECT_NE(zero, max64);
EXPECT_EQ(zero, UInt128(0));
EXPECT_NE(zero, UInt128(1));
EXPECT_EQ(max64, max64);
}
TEST(UInt128, Shifting) {
const UInt128 max64(~0ULL);
const UInt128 upper64(~0ULL, 0);
EXPECT_EQ(upper64.HighBits(), ~0ULL);
EXPECT_EQ(upper64.LowBits(), 0);
EXPECT_EQ(upper64 >> 64, max64);
EXPECT_EQ(UInt128(1) << 1, UInt128(2));
EXPECT_EQ(UInt128(0) << 0, UInt128(0));
EXPECT_EQ(max64 << 0, max64);
EXPECT_EQ(max64 >> 0, max64);
EXPECT_EQ(upper64 << 0, upper64);
EXPECT_EQ(upper64 >> 0, upper64);
{
const UInt128 bit63 = UInt128(1ULL << 62) << 1;
EXPECT_EQ(bit63.LowBits(), 1ULL << 63);
EXPECT_EQ(bit63.HighBits(), 0);
}
{
const UInt128 bit64 = UInt128(1ULL << 63) << 1;
EXPECT_EQ(bit64.LowBits(), 0);
EXPECT_EQ(bit64.HighBits(), 1);
EXPECT_EQ(bit64 >> 1, UInt128(1ULL << 63));
}
{
const UInt128 overshift = max64 << 128;
EXPECT_EQ(overshift.HighBits(), 0);
EXPECT_EQ(overshift.LowBits(), 0);
}
{
const UInt128 overlap = upper64 >> 32;
EXPECT_EQ(overlap.HighBits(), 0x00000000FFFFFFFF);
EXPECT_EQ(overlap.LowBits(), 0xFFFFFFFF00000000);
}
{
const UInt128 overlap = max64 << 32;
EXPECT_EQ(overlap.HighBits(), 0x00000000FFFFFFFF);
EXPECT_EQ(overlap.LowBits(), 0xFFFFFFFF00000000);
}
}
TEST(UInt128, LargeShift) {
const UInt128 base(0xFF);
EXPECT_EQ(base << 64, UInt128(0xFFULL, 0));
EXPECT_EQ(base << 72, UInt128(0xFF00ULL, 0));
EXPECT_EQ(base << 80, UInt128(0xFF0000ULL, 0));
EXPECT_EQ(base << 88, UInt128(0xFF000000ULL, 0));
EXPECT_EQ(base << 96, UInt128(0xFF00000000ULL, 0));
EXPECT_EQ(base << 104, UInt128(0xFF0000000000ULL, 0));
EXPECT_EQ(base << 112, UInt128(0xFF000000000000ULL, 0));
EXPECT_EQ(base << 120, UInt128(0xFF00000000000000ULL, 0));
const UInt128 upper(0xFF00000000000000ULL, 0);
EXPECT_EQ(upper >> 64, UInt128(0, 0xFF00000000000000ULL));
EXPECT_EQ(upper >> 72, UInt128(0, 0xFF000000000000ULL));
EXPECT_EQ(upper >> 80, UInt128(0, 0xFF0000000000ULL));
EXPECT_EQ(upper >> 88, UInt128(0, 0xFF00000000ULL));
EXPECT_EQ(upper >> 96, UInt128(0, 0xFF000000ULL));
EXPECT_EQ(upper >> 104, UInt128(0, 0xFF0000ULL));
EXPECT_EQ(upper >> 112, UInt128(0, 0xFF00ULL));
EXPECT_EQ(upper >> 120, UInt128(0, 0xFFULL));
}
TEST(UInt128, BooleanOperators) {
const UInt128 allOnes(~0ULL, ~0ULL);
EXPECT_EQ(allOnes.HighBits(), ~0ULL);
EXPECT_EQ(allOnes.LowBits(), ~0ULL);
EXPECT_EQ(~allOnes, UInt128(0));
EXPECT_EQ(~UInt128(0), allOnes);
EXPECT_EQ(UInt128(0xFFFF00) & UInt128(0x00FFFF), UInt128(0x00FF00));
EXPECT_EQ(UInt128(0xFFFF00) | UInt128(0x00FFFF), UInt128(0xFFFFFF));
EXPECT_EQ(UInt128(0xFFFF00) ^ UInt128(0x00FFFF), UInt128(0xFF00FF));
}
TEST(UInt128, Addition) {
const UInt128 bit63(1ULL << 63);
EXPECT_EQ(UInt128(1) + 1, UInt128(2));
EXPECT_EQ(bit63 + bit63, UInt128(1) << 64);
const UInt128 carryUp = UInt128(~0ULL) + 1;
EXPECT_EQ(carryUp.HighBits(), 1);
EXPECT_EQ(carryUp.LowBits(), 0);
const UInt128 allOnes(~0ULL, ~0ULL);
EXPECT_EQ(allOnes + 1, UInt128(0));
}
TEST(UInt128, Subtraction) {
const UInt128 bit64 = UInt128(1) << 64;
EXPECT_EQ(bit64 - 1, UInt128(~0ULL));
EXPECT_EQ(UInt128(1) - 1, UInt128(0));
const UInt128 allOnes(~0ULL, ~0ULL);
EXPECT_EQ(UInt128(0) - 1, allOnes);
}
} // namespace base
} // namespace astc_codec

View File

@@ -0,0 +1,172 @@
// 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.
#ifndef ASTC_CODEC_BASE_TYPE_TRAITS_H_
#define ASTC_CODEC_BASE_TYPE_TRAITS_H_
#include <iterator>
#include <type_traits>
namespace astc_codec {
namespace base {
namespace details {
// a simple helper class for SFINAE below.
template<class X = void>
struct dummy {
using type = X;
};
} // namespace details
// add some convenience shortcuts for an overly complex std::enable_if syntax
// Use 'enable_if<Predicate,Type>' instead of
// 'typename std::enable_if<Predicate::value,Type>::type'
template<class Predicate, class Type = void*>
using enable_if = typename std::enable_if<Predicate::value, Type>::type;
// Use 'enable_if_c<BooleanFlag,Type>' instead of
// 'typename std::enable_if<BooleanFlag,Type>::type'
template<bool predicate, class Type = void*>
using enable_if_c = typename std::enable_if<predicate, Type>::type;
// Use 'enable_if_convertible<From,To,Type>' instead of
// 'typename std::enable_if<std::is_convertible<From,To>::value, Type>::type'
template<class From, class To, class Type = void*>
using enable_if_convertible = enable_if<std::is_convertible<From, To>>;
// -----------------------------------------------------------------------------
// A predicate for checking if some object is callable with a specific
// signature. Examples:
//
// is_callable_as<int, void()>::value == false.
// is_callable_as<strcmp, void()>::value == false.
// is_callable_as<strcmp, int(const char*, const char*)>::value == true
//
template<class F, class Signature, class X = void>
struct is_callable_as : std::false_type {};
// This specialization is SFINAE-d out if template arguments can't be combined
// into a call expression F(), or if the result of that call is not |R|
template<class F, class R, class... Args>
struct is_callable_as<F, R(Args...),
typename std::enable_if<std::is_same<
typename details::dummy<decltype(std::declval<F>()(
std::declval<Args>()...))>::type,
R>::value>::type> : std::true_type {};
//
// A similar predicate to only check arguments of the function call and ignore
// the specified return type
//
// is_callable_as<strcmp, int(const char*, const char*)>::value == true
// is_callable_as<strcmp, void(const char*, const char*)>::value == false
// is_callable_with_args<strcmp, void(const char*, const char*)>::value == true
//
template<class F, class Signature, class X = void>
struct is_callable_with_args : std::false_type {};
template<class F, class R, class... Args>
struct is_callable_with_args<
F, R(Args...),
typename std::enable_if<
!std::is_same<typename details::dummy<decltype(
std::declval<F>()(std::declval<Args>()...))>::type,
F>::value>::type> : std::true_type {};
// -----------------------------------------------------------------------------
// Check if a type |T| is any instantiation of a template |U|. Examples:
//
// is_template_instantiation_of<int, std::vector>::value == false
// is_template_instantiation_of<
// std::list<std::vector<int>>, std::vector>::value == false
// is_template_instantiation_of<std::vector<int>, std::vector>::value == true
// is_template_instantiation_of<
// std::vector<std::vector<int>>, std::vector>::value == true
//
template<class T, template<class...> class U>
struct is_template_instantiation_of : std::false_type {};
template<template<class...> class U, class... Args>
struct is_template_instantiation_of<U<Args...>, U> : std::true_type {};
// -----------------------------------------------------------------------------
//
// is_range<T> - check if type |T| is a range-like type.
//
// It makes sure that expressions std::begin(t) and std::end(t) are well-formed
// and those return the same type.
//
// Note: with expression SFINAE from C++14 is_range_helper<> could be renamed to
// is_range<> with no extra code. C++11 needs an extra level of enable_if<>
// to make it work when the type isn't a range.
//
namespace details {
template<class T>
using is_range_helper = std::is_same<
decltype(std::begin(
std::declval<typename std::add_lvalue_reference<T>::type>())),
decltype(
std::end(std::declval<typename std::add_lvalue_reference<T>::type>()))>;
} // namespace details
template<class T, class = void>
struct is_range : std::false_type {};
template<class T>
struct is_range<
T, typename std::enable_if<details::is_range_helper<T>::value>::type>
: std::true_type {};
////////////////////////////////////////////////////////////////////////////////
//
// A class to incapsulate integer sequence 0, 1, ..., <num_args>
// Seq<int...>
// Useful to pass function parameters in an array/tuple to call it later.
//
template<int...>
struct Seq {};
// A 'maker' class to construct Seq<int...> given only <num_args>
// value.
// MakeSeq<N, S...> works this way, e.g.
//
// MakeSeq<2> inherits MakeSeq<2 - 1, 2 - 1> == MakeSeq<1, 1>
// MakeSeq<1, 1> : MakeSeq<1 - 1, 1 - 1, 1> == MakeSeq<0, 0, 1>
// MakeSeq<0, 0, 1> == MakeSeq<0, S...> and defines |type| = Seq<0, 1>
template<int N, int... S>
struct MakeSeq : MakeSeq<N - 1, N - 1, S...> {};
template<int... S>
struct MakeSeq<0, S...> {
using type = Seq<S...>;
};
//
// MakeSeqT alias to quickly create Seq<...>:
// MakeSeqT<3> == Seq<0, 1, 2>
template<int... S>
using MakeSeqT = typename MakeSeq<S...>::type;
} // namespace base
} // namespace astc_codec
#endif // ASTC_CODEC_BASE_TYPE_TRAITS_H_

175
3rdparty/astc-codec/src/base/uint128.h vendored Normal file
View File

@@ -0,0 +1,175 @@
// 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.
#ifndef ASTC_CODEC_BASE_UINT128_H_
#define ASTC_CODEC_BASE_UINT128_H_
#include <cassert>
#include <cstdint>
namespace astc_codec {
namespace base {
class UInt128 {
public:
UInt128() = default;
UInt128(uint64_t low) : low_(low) { }
UInt128(uint64_t high, uint64_t low) : low_(low), high_(high) { }
UInt128(const UInt128& other) : low_(other.low_), high_(other.high_) { }
uint64_t LowBits() const { return low_; }
uint64_t HighBits() const { return high_; }
// Allow explicit casts to uint64_t.
explicit operator uint64_t() const { return low_; }
// Copy operators.
UInt128& operator=(const UInt128& other) {
high_ = other.high_;
low_ = other.low_;
return *this;
}
// Equality operators.
bool operator==(const UInt128& other) const {
return high_ == other.high_ && low_ == other.low_;
}
bool operator!=(const UInt128& other) const {
return high_ != other.high_ || low_ != other.low_;
}
// Shifting.
UInt128& operator<<=(int shift) {
high_ = shift >= 64 ? (shift >= 128 ? 0 : low_ << (shift - 64))
: high_ << shift;
if (shift > 0 && shift < 64) {
const uint64_t overlappingBits = low_ >> (64 - shift);
high_ |= overlappingBits;
}
low_ = shift >= 64 ? 0 : low_ << shift;
return *this;
}
UInt128 operator<<(int shift) const {
UInt128 result = *this;
result <<= shift;
return result;
}
UInt128& operator>>=(int shift) {
low_ = shift >= 64 ? (shift >= 128 ? 0 : high_ >> (shift - 64))
: low_ >> shift;
if (shift > 0 && shift < 64) {
const uint64_t overlappingBits = high_ << (64 - shift);
low_ |= overlappingBits;
}
high_ = shift >= 64 ? 0 : high_ >> shift;
return *this;
}
UInt128 operator>>(int shift) const {
UInt128 result = *this;
result >>= shift;
return result;
}
// Binary operations.
UInt128& operator|=(const UInt128& other) {
high_ |= other.high_;
low_ |= other.low_;
return *this;
}
UInt128 operator|(const UInt128& other) const {
UInt128 result = *this;
result |= other;
return result;
}
UInt128& operator&=(const UInt128& other) {
high_ &= other.high_;
low_ &= other.low_;
return *this;
}
UInt128 operator&(const UInt128& other) const {
UInt128 result = *this;
result &= other;
return result;
}
UInt128& operator^=(const UInt128& other) {
high_ ^= other.high_;
low_ ^= other.low_;
return *this;
}
UInt128 operator^(const UInt128& other) const {
UInt128 result = *this;
result ^= other;
return result;
}
UInt128 operator~() const {
UInt128 result = *this;
result.high_ = ~high_;
result.low_ = ~low_;
return result;
}
// Addition/subtraction.
UInt128& operator+=(const UInt128& other) {
const uint64_t carry =
(((low_ & other.low_) & 1) + (low_ >> 1) + (other.low_ >> 1)) >> 63;
high_ += other.high_ + carry;
low_ += other.low_;
return *this;
}
UInt128 operator+(const UInt128& other) const {
UInt128 result = *this;
result += other;
return result;
}
UInt128& operator-=(const UInt128& other) {
low_ -= other.low_;
const uint64_t carry =
(((low_ & other.low_) & 1) + (low_ >> 1) + (other.low_ >> 1)) >> 63;
high_ -= other.high_ + carry;
return *this;
}
UInt128 operator-(const UInt128& other) const {
UInt128 result = *this;
result -= other;
return result;
}
private:
// TODO(google): Different order for little endian.
uint64_t low_ = 0;
uint64_t high_ = 0;
};
} // namespace base
} // namespace astc_codec
#endif // ASTC_CODEC_BASE_UINT128_H_

35
3rdparty/astc-codec/src/base/utils.h vendored Normal file
View File

@@ -0,0 +1,35 @@
// 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.
#ifndef ASTC_CODEC_BASE_UTILS_H_
#define ASTC_CODEC_BASE_UTILS_H_
#include <cassert>
#include <cstdio>
#include <cstdlib>
#ifdef NDEBUG
#define UTILS_RELEASE_ASSERT(x) \
do { \
const bool result = (x); \
if (!result) { \
fprintf(stderr, "Error: UTILS_RELEASE_ASSERT failed: %s\n", #x); \
abort(); \
} \
} while (false)
#else
#define UTILS_RELEASE_ASSERT(x) assert(x)
#endif
#endif // ASTC_CODEC_BASE_UTILS_H_

View File

@@ -0,0 +1,246 @@
# 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.
licenses(["notice"])
cc_library(
name = "footprint",
srcs = ["footprint.cc"],
hdrs = ["footprint.h"],
deps = [
"//:api",
"//src/base",
],
)
cc_library(
name = "astc_utils",
srcs = [
"astc_file.cc",
"endpoint_codec.cc",
"integer_sequence_codec.cc",
"intermediate_astc_block.cc",
"logical_astc_block.cc",
"partition.cc",
"physical_astc_block.cc",
"quantization.cc",
"weight_infill.cc",
],
hdrs = [
"astc_file.h",
"endpoint_codec.h",
"integer_sequence_codec.h",
"intermediate_astc_block.h",
"logical_astc_block.h",
"partition.h",
"physical_astc_block.h",
"quantization.h",
"types.h",
"weight_infill.h",
],
copts = [
"-Wno-unused-variable",
"-O3",
],
deps = [
":footprint",
"//src/base",
],
)
cc_library(
name = "codec",
srcs = ["codec.cc"],
hdrs = ["codec.h"],
visibility = ["//:__pkg__"],
deps = [
":astc_utils",
":footprint",
"//src/base",
],
)
cc_binary(
name = "astc_inspector_cli",
srcs = ["tools/astc_inspector_cli.cc"],
deps = [
":astc_utils",
"//src/base",
],
)
################################################################################
##
## Testing
##
################################################################################
cc_library(
name = "test",
testonly = 1,
hdrs = ["test/image_utils.h"],
deps = ["@gtest//:gtest"],
)
cc_test(
name = "physical_astc_block_test",
size = "small",
srcs = ["test/physical_astc_block_test.cc"],
deps = [
":astc_utils",
"@gtest//:gtest_main",
"//src/base",
],
)
cc_test(
name = "partition_test",
size = "medium",
srcs = ["test/partition_test.cc"],
deps = [
":astc_utils",
"@gtest//:gtest_main",
],
)
cc_test(
name = "integer_sequence_codec_test",
size = "small",
srcs = ["test/integer_sequence_codec_test.cc"],
deps = [
":astc_utils",
"@gtest//:gtest_main",
"//src/base",
],
)
cc_test(
name = "intermediate_astc_block_test",
size = "small",
srcs = ["test/intermediate_astc_block_test.cc"],
data = glob([
"testdata/checkered_*.astc",
]),
deps = [
":astc_utils",
":test",
"@gtest//:gtest_main",
],
)
cc_test(
name = "quantization_test",
size = "medium",
srcs = ["test/quantization_test.cc"],
deps = [
":astc_utils",
"@gtest//:gtest_main",
],
)
cc_test(
name = "weight_infill_test",
size = "small",
srcs = ["test/weight_infill_test.cc"],
deps = [
":astc_utils",
":footprint",
"@gtest//:gtest_main",
],
)
cc_test(
name = "endpoint_codec_test",
size = "small",
srcs = ["test/endpoint_codec_test.cc"],
data = [
":testdata/checkerboard.astc",
],
deps = [
":astc_utils",
":test",
"@gtest//:gtest_main",
],
)
cc_test(
name = "logical_astc_block_test",
size = "large",
srcs = ["test/logical_astc_block_test.cc"],
data = glob([
"testdata/atlas_small_*.astc",
"testdata/atlas_small_*.bmp",
"testdata/footprint_*.astc",
"testdata/footprint_*.bmp",
"testdata/rgb_*.astc",
"testdata/rgb_*.bmp",
]),
deps = [
":astc_utils",
":test",
"@gtest//:gtest_main",
],
)
cc_test(
name = "codec_test",
size = "large",
srcs = ["test/codec_test.cc"],
data = glob([
"testdata/atlas_small_*.astc",
"testdata/atlas_small_*.bmp",
]),
deps = [
":codec",
":test",
"@gtest//:gtest_main",
"//:api",
],
)
cc_test(
name = "footprint_test",
size = "small",
srcs = ["test/footprint_test.cc"],
deps = [
":footprint",
"@gtest//:gtest_main",
],
)
cc_test(
name = "astc_fuzzer",
srcs = ["test/astc_fuzzer.cc"],
copts = select({
# Clang-only flags. TODO: Find a better way to detect GCC/clang.
"@bazel_tools//src/conditions:darwin_x86_64": [
"-fsanitize-coverage=trace-pc-guard,indirect-calls,trace-cmp",
"-fsanitize-coverage=bb",
],
"@bazel_tools//src/conditions:darwin": [
"-fsanitize-coverage=trace-pc-guard,indirect-calls,trace-cmp",
"-fsanitize-coverage=bb",
],
# GCC-only flags.
"//conditions:default": [
"-finstrument-functions"
],
}),
deps = [
":codec",
"@honggfuzz//:honggfuzz",
"@benchmark//:benchmark",
],
linkstatic = 1,
)

View File

@@ -0,0 +1,95 @@
# 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.
add_library(footprint footprint.cc)
target_link_libraries(footprint base)
add_library(astc_utils
astc_file.cc
endpoint_codec.cc
integer_sequence_codec.cc
intermediate_astc_block.cc
logical_astc_block.cc
partition.cc
physical_astc_block.cc
quantization.cc
weight_infill.cc)
target_link_libraries(astc_utils PRIVATE base footprint)
target_include_directories(astc_utils PRIVATE ../..)
add_library(astc-codec codec.cc)
target_link_libraries(astc-codec PRIVATE astc_utils)
target_include_directories(astc-codec PUBLIC ../../include)
target_include_directories(astc-codec PRIVATE ../..)
add_executable(astc_inspector_cli tools/astc_inspector_cli.cc)
target_include_directories(astc_inspector_cli PRIVATE ../..)
target_link_libraries(astc_inspector_cli PRIVATE astc_utils)
#
# Testing
#
if(OPTION_ASTC_TESTS)
# Note that we will execute all the tests in the project directory.
# We do this to ensure the unit tests can pick up the required test data
# Create interface library exposing the root as an include directory
add_library(codec_test_dependencies INTERFACE)
target_include_directories(codec_test_dependencies INTERFACE ../..)
add_executable(physical_astc_block_test test/physical_astc_block_test.cc)
add_test(NAME physical_astc_block_test COMMAND physical_astc_block_test WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
target_link_libraries(physical_astc_block_test astc_utils codec_test_dependencies gmock_main)
add_executable(partition_test test/partition_test.cc)
add_test(NAME partition_test COMMAND partition_test WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
target_link_libraries(partition_test PRIVATE astc_utils codec_test_dependencies gmock_main)
add_executable(integer_sequence_codec_test test/integer_sequence_codec_test.cc)
target_link_libraries(integer_sequence_codec_test PRIVATE astc_utils codec_test_dependencies gmock_main)
add_executable(intermediate_astc_block_test test/intermediate_astc_block_test.cc)
add_test(NAME intermediate_astc_block_test COMMAND intermediate_astc_block_test WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
target_link_libraries(intermediate_astc_block_test PRIVATE astc_utils codec_test_dependencies gmock_main)
add_executable(quantization_test test/quantization_test.cc)
add_test(NAME quantization_test COMMAND quantization_test WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
target_link_libraries(quantization_test PRIVATE astc_utils codec_test_dependencies gmock_main)
add_executable(weight_infill_test test/weight_infill_test.cc)
add_test(NAME weight_infill_test COMMAND weight_infill_test WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
target_link_libraries(weight_infill_test PRIVATE astc_utils footprint codec_test_dependencies gmock_main)
add_executable(endpoint_codec_test test/endpoint_codec_test.cc)
add_test(NAME endpoint_codec_test COMMAND endpoint_codec_test WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
target_link_libraries(endpoint_codec_test PRIVATE astc_utils codec_test_dependencies gmock_main)
add_executable(logical_astc_block_test test/logical_astc_block_test.cc)
add_test(NAME logical_astc_block_test COMMAND logical_astc_block_test WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
target_link_libraries(logical_astc_block_test PRIVATE astc_utils codec_test_dependencies gmock_main)
add_executable(codec_test test/codec_test.cc)
add_test(NAME codec_test COMMAND codec_test WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
target_link_libraries(codec_test PRIVATE astc-codec codec_test_dependencies gmock_main)
add_executable(footprint_test test/footprint_test.cc)
add_test(NAME footprint_test COMMAND footprint_test WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
target_link_libraries(footprint_test PRIVATE footprint codec_test_dependencies gmock_main)
if(OPTION_BUILD_FUZZER)
message(FATAL_ERROR "Not yet supported due to missing dependencies")
add_executable(astc_fuzzer test/astc_fuzzer.cc codec_test_dependencies gmock_main)
target_link_libraries(astc_fuzzer PRIVATE astc-codec honggfuzz benchmark)
endif()
endif()

View File

@@ -0,0 +1,185 @@
// 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/astc_file.h"
#include <cstring>
#include <fstream>
#include <memory>
#include <sstream>
namespace astc_codec {
namespace {
static constexpr size_t kASTCHeaderSize = 16;
// Reads a value of size T from the buffer at the current offset, then
// increments the offset.
template<typename T>
inline T ReadVal(const char* file_data, size_t& offset) {
T x;
memcpy(&x, &file_data[offset], sizeof(T));
offset += sizeof(T);
return x;
}
} // namespace
ASTCFile::ASTCFile(Header&& header, std::string&& blocks)
: header_(std::move(header)), blocks_(std::move(blocks)) {}
std::unique_ptr<ASTCFile> ASTCFile::LoadFromMemory(const char* data,
size_t length,
std::string* error) {
if (length < kASTCHeaderSize) {
*error = "Incomplete header.";
return nullptr;
}
base::Optional<Header> header_opt = ParseHeader(data);
if (!header_opt) {
*error = "Invalid ASTC header.";
return nullptr;
}
Header header = header_opt.value();
if (header.block_width_ == 0 || header.block_height_ == 0) {
*error = "Invalid block size.";
return nullptr;
}
std::string blocks(data + kASTCHeaderSize, data + length);
// Check that this file has the expected number of blocks.
const size_t expected_block_count =
((header.width_ + header.block_width_ - 1) / header.block_width_) *
((header.height_ + header.block_height_ - 1) / header.block_height_);
if (blocks.size() % PhysicalASTCBlock::kSizeInBytes != 0 ||
blocks.size() / PhysicalASTCBlock::kSizeInBytes != expected_block_count) {
std::stringstream ss;
ss << "Unexpected file length " << blocks.size() << " expected "
<< kASTCHeaderSize +
expected_block_count * PhysicalASTCBlock::kSizeInBytes
<< " bytes.";
*error = ss.str();
return nullptr;
}
return std::unique_ptr<ASTCFile>(
new ASTCFile(std::move(header), std::move(blocks)));
}
std::unique_ptr<ASTCFile> ASTCFile::LoadFile(const std::string& path,
std::string* error) {
std::ifstream is(path, std::ios::binary);
if (!is) {
*error = "File not found: " + path;
return nullptr;
}
char header_data[kASTCHeaderSize] = {};
if (!is.read(header_data, kASTCHeaderSize)) {
*error = "Failed to load ASTC header.";
return nullptr;
}
base::Optional<Header> header_opt = ParseHeader(header_data);
if (!header_opt) {
*error = "Invalid ASTC header.";
return nullptr;
}
Header header = header_opt.value();
std::string blocks;
{
std::ostringstream ss;
ss << is.rdbuf();
blocks = ss.str();
}
// Check that this file has the expected number of blocks.
const size_t expected_block_count =
((header.width_ + header.block_width_ - 1) / header.block_width_) *
((header.height_ + header.block_height_ - 1) / header.block_height_);
if (blocks.size() % PhysicalASTCBlock::kSizeInBytes != 0 ||
blocks.size() / PhysicalASTCBlock::kSizeInBytes != expected_block_count) {
std::stringstream ss;
ss << "Unexpected file length " << blocks.size() << " expected "
<< kASTCHeaderSize +
expected_block_count * PhysicalASTCBlock::kSizeInBytes
<< " bytes.";
*error = ss.str();
return nullptr;
}
return std::unique_ptr<ASTCFile>(
new ASTCFile(std::move(header), std::move(blocks)));
}
base::Optional<Footprint> ASTCFile::GetFootprint() const {
return Footprint::FromDimensions(header_.block_width_, header_.block_height_);
}
std::string ASTCFile::GetFootprintString() const {
std::stringstream footprint;
footprint << header_.block_width_ << "x" << header_.block_height_;
return footprint.str();
}
const std::string& ASTCFile::GetRawBlockData() const {
return blocks_;
}
PhysicalASTCBlock ASTCFile::GetBlock(size_t block_idx) const {
const size_t sz = PhysicalASTCBlock::kSizeInBytes;
const size_t offset = PhysicalASTCBlock::kSizeInBytes * block_idx;
assert(offset <= blocks_.size() - sz);
return PhysicalASTCBlock(blocks_.substr(offset, sz));
}
base::Optional<ASTCFile::Header> ASTCFile::ParseHeader(const char* header) {
size_t offset = 0;
// TODO(google): Handle endianness.
const uint32_t magic = ReadVal<uint32_t>(header, offset);
if (magic != 0x5CA1AB13) {
return {};
}
const uint32_t block_width = ReadVal<uint8_t>(header, offset);
const uint32_t block_height = ReadVal<uint8_t>(header, offset);
const uint32_t block_depth = ReadVal<uint8_t>(header, offset);
uint32_t width = 0;
width |= ReadVal<uint8_t>(header, offset);
width |= ReadVal<uint8_t>(header, offset) << 8;
width |= ReadVal<uint8_t>(header, offset) << 16;
uint32_t height = 0;
height |= ReadVal<uint8_t>(header, offset);
height |= ReadVal<uint8_t>(header, offset) << 8;
height |= ReadVal<uint8_t>(header, offset) << 16;
uint32_t depth = 0;
depth |= ReadVal<uint8_t>(header, offset);
depth |= ReadVal<uint8_t>(header, offset) << 8;
depth |= ReadVal<uint8_t>(header, offset) << 16;
assert(offset == kASTCHeaderSize);
return Header(width, height, depth, block_width, block_height, block_depth);
}
} // namespace astc_codec

View File

@@ -0,0 +1,97 @@
// 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.
#ifndef ASTC_CODEC_DECODER_ASTC_FILE_H_
#define ASTC_CODEC_DECODER_ASTC_FILE_H_
#include "src/base/optional.h"
#include "src/decoder/footprint.h"
#include "src/decoder/physical_astc_block.h"
#include <memory>
#include <string>
namespace astc_codec {
// A thin wrapper around a .astc file on disk. This class simply reads the ASTC
// header, and stores the block data in memory.
class ASTCFile {
private:
struct Header {
Header(size_t width, size_t height, size_t depth, size_t block_width,
size_t block_height, size_t block_depth)
: width_(width),
height_(height),
depth_(depth),
block_width_(block_width),
block_height_(block_height),
block_depth_(block_depth) {}
size_t width_;
size_t height_;
size_t depth_;
size_t block_width_;
size_t block_height_;
size_t block_depth_;
};
ASTCFile(ASTCFile::Header&& header, std::string&& blocks);
public:
// Load an ASTC file from memory.
// If loading failed, nullptr is returned and an error string is populated
// in the error parameter.
static std::unique_ptr<ASTCFile> LoadFromMemory(const char* data,
size_t length,
std::string* error);
// Load an ASTC file from file.
// If loading failed, nullptr is returned and an error string is populated
// in the error parameter.
static std::unique_ptr<ASTCFile> LoadFile(const std::string& path,
std::string* error);
// Returns the footprint for the file, if it is considered to be a valid
// footprint.
base::Optional<Footprint> GetFootprint() const;
// Returns the string of the form "NxM" where N and M are the width and height
// of the block footprint, respectively.
std::string GetFootprintString() const;
// Get the raw block data for the astc file.
const std::string& GetRawBlockData() const;
// Returns the physical block at the associated block index.
PhysicalASTCBlock GetBlock(size_t block_idx) const;
size_t GetWidth() const { return header_.width_; }
size_t GetHeight() const { return header_.height_; }
size_t GetDepth() const { return header_.depth_; }
size_t NumBlocks() const {
return blocks_.size() / PhysicalASTCBlock::kSizeInBytes;
}
private:
static base::Optional<ASTCFile::Header> ParseHeader(const char* header);
const Header header_;
const std::string blocks_;
};
} // namespace astc_codec
#endif // ASTC_CODEC_DECODER_ASTC_FILE_H_

132
3rdparty/astc-codec/src/decoder/codec.cc vendored Normal file
View File

@@ -0,0 +1,132 @@
// 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/codec.h"
#include "src/base/uint128.h"
#include "src/decoder/logical_astc_block.h"
#include "src/decoder/physical_astc_block.h"
#include <cstring>
namespace astc_codec {
namespace {
static constexpr size_t kBytesPerPixelUNORM8 = 4;
}
bool DecompressToImage(const uint8_t* astc_data, size_t astc_data_size,
size_t width, size_t height, Footprint footprint,
uint8_t* out_buffer, size_t out_buffer_size,
size_t out_buffer_stride) {
const size_t block_width = footprint.Width();
const size_t block_height = footprint.Height();
assert(block_width != 0);
assert(block_height != 0);
if (width == 0 || height == 0) {
return false;
}
const size_t blocks_wide = (width + block_width - 1) / block_width;
assert(blocks_wide != 0);
// Check that this buffer has the expected number of blocks.
const size_t expected_block_count =
((width + block_width - 1) / block_width) *
((height + block_height - 1) / block_height);
if (astc_data_size % PhysicalASTCBlock::kSizeInBytes != 0 ||
astc_data_size / PhysicalASTCBlock::kSizeInBytes !=
expected_block_count) {
// TODO(google): Expose error?
return false;
}
if (kBytesPerPixelUNORM8 * width > out_buffer_stride ||
out_buffer_stride * height < out_buffer_size) {
// Output buffer too small.
return false;
}
base::UInt128 block;
static_assert(sizeof(block) == PhysicalASTCBlock::kSizeInBytes,
"Block size mismatch");
for (size_t i0 = 0; i0 < astc_data_size; i0 += PhysicalASTCBlock::kSizeInBytes) {
const size_t block_index = i0 / PhysicalASTCBlock::kSizeInBytes;
const size_t block_x = block_index % blocks_wide;
const size_t block_y = block_index / blocks_wide;
block = *(base::UInt128*)(astc_data + i0);
PhysicalASTCBlock physical_block(block);
auto lb = UnpackLogicalBlock(footprint, physical_block);
if (!lb) {
return false;
}
LogicalASTCBlock logical_block = lb.value();
for (size_t y = 0; y < block_height; ++y) {
const size_t py = block_height * block_y + y;
uint8_t* out_row = out_buffer + py * out_buffer_stride;
for (size_t x = 0; x < block_width; ++x) {
const size_t px = block_width * block_x + x;
// Skip out of bounds.
if (px >= width || py >= height) {
continue;
}
uint8_t* pixel = out_row + px * kBytesPerPixelUNORM8;
const RgbaColor decoded_color = logical_block.ColorAt(x, y);
for (size_t i = 0; i < kBytesPerPixelUNORM8; ++i) {
pixel[i] = static_cast<uint8_t>(decoded_color[i]);
}
}
}
}
return true;
}
bool DecompressToImage(const ASTCFile& file, uint8_t* out_buffer,
size_t out_buffer_size, size_t out_buffer_stride) {
base::Optional<Footprint> footprint = file.GetFootprint();
if (!footprint) {
return false;
}
return DecompressToImage(
reinterpret_cast<const uint8_t*>(file.GetRawBlockData().c_str()),
file.GetRawBlockData().size(), file.GetWidth(), file.GetHeight(),
footprint.value(), out_buffer, out_buffer_size, out_buffer_stride);
}
bool ASTCDecompressToRGBA(const uint8_t* astc_data, size_t astc_data_size,
size_t width, size_t height, FootprintType footprint,
uint8_t* out_buffer, size_t out_buffer_size,
size_t out_buffer_stride) {
base::Optional<Footprint> footprint_opt =
Footprint::FromFootprintType(footprint);
if (!footprint_opt) {
return false;
}
return DecompressToImage(astc_data, astc_data_size, width, height,
footprint_opt.value(), out_buffer, out_buffer_size,
out_buffer_stride);
}
} // namespace astc_codec

41
3rdparty/astc-codec/src/decoder/codec.h vendored Normal file
View File

@@ -0,0 +1,41 @@
// 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.
#ifndef ASTC_CODEC_DECODER_CODEC_H_
#define ASTC_CODEC_DECODER_CODEC_H_
#include "src/decoder/astc_file.h"
#include "src/decoder/footprint.h"
#include <string>
namespace astc_codec {
// Decompresses ASTC blocks to an image buffer.
// Returns true if the decompression succeeded and the out buffer has been
// filled.
bool DecompressToImage(const uint8_t* astc_data, size_t astc_data_size,
size_t width, size_t height, Footprint footprint,
uint8_t* out_buffer, size_t out_buffer_size,
size_t out_buffer_stride);
// Decompresses an ASTC file to an image buffer.
// Returns true if the decompression succeeded and the out buffer has been
// filled.
bool DecompressToImage(const ASTCFile& file, uint8_t* out_buffer,
size_t out_buffer_size, size_t out_buffer_stride);
} // namespace astc_codec
#endif // ASTC_CODEC_DECODER_CODEC_H_

View File

@@ -0,0 +1,963 @@
// 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/endpoint_codec.h"
#include "src/decoder/quantization.h"
#include <algorithm>
#include <array>
#include <numeric>
#include <utility>
namespace astc_codec {
namespace {
template<typename T>
T Clamp(T value, T min, T max) {
return value < min ? min : (value > max ? max : value);
}
// This is the 'blue_contract' function defined in Section C.2.14 of the ASTC
// specification.
template<typename ArrayType>
void BlueContract(ArrayType* const cptr) {
ArrayType& c = *cptr;
c[0] = (c[0] + c[2]) >> 1;
c[1] = (c[1] + c[2]) >> 1;
}
// Returns the inverse of values in BlueContract, subjected to the constraint
// that the new values are stored in the range [0, 255].
template<typename ArrayType>
ArrayType InvertBlueContract(const ArrayType& c) {
ArrayType result = c;
result[0] = Clamp(2 * c[0] - c[2], 0, 255);
result[1] = Clamp(2 * c[1] - c[2], 0, 255);
return result;
}
// This is the 'bit_transfer_signed' function defined in Section C.2.14 of the
// ASTC specification.
void BitTransferSigned(int* const a, int* const b) {
*b >>= 1;
*b |= *a & 0x80;
*a >>= 1;
*a &= 0x3F;
if ((*a & 0x20) != 0) {
*a -= 0x40;
}
}
// Takes two values, |a| in the range [-32, 31], and |b| in the range [0, 255],
// and returns the two values in [0, 255] that will reconstruct |a| and |b| when
// passed to the BitTransferSigned function.
void InvertBitTransferSigned(int* const a, int* const b) {
assert(*a >= -32); assert(*a < 32);
assert(*b >= 0); assert(*b < 256);
if (*a < 0) {
*a += 0x40;
}
*a <<= 1;
*a |= (*b & 0x80);
*b <<= 1;
*b &= 0xff;
}
template<typename ContainerType>
void Quantize(ContainerType* const c, size_t max_value) {
for (auto& x : *c) {
x = QuantizeCEValueToRange(x, max_value);
}
}
template<typename ArrayType>
ArrayType QuantizeColor(const ArrayType& c, size_t max_value) {
ArrayType result = c;
Quantize(&result, max_value);
return result;
}
template<typename ContainerType>
void Unquantize(ContainerType* const c, size_t max_value) {
for (auto& x : *c) {
x = UnquantizeCEValueFromRange(x, max_value);
}
}
template<typename ArrayType>
ArrayType UnquantizeColor(const ArrayType& c, size_t max_value) {
ArrayType result = c;
Unquantize(&result, max_value);
return result;
}
// Returns the average of the three RGB channels.
template<typename ContainerType>
int AverageRGB(const ContainerType& c) {
// Each channel can be in the range [0, 255], and we need to divide by three.
// However, we want to round the error properly. Both (x + 1) / 3 and
// (x + 2) / 3 are relatively imprecise when it comes to rounding, so instead
// we increase the precision by multiplying our numerator by some arbitrary
// number. Here, we choose 256 to get 8 additional bits and maintain
// performance since it turns into a shift rather than a multiply. Our
// denominator then becomes 3 * 256 = 768.
return (std::accumulate(c.begin(), c.begin() + 3, 0) * 256 + 384) / 768;
}
// Returns the sum of squared differences between each element of |a| and |b|,
// which are assumed to contain the same number of elements.
template<typename ContainerType>
const typename ContainerType::value_type SquaredError(
const ContainerType& a, const ContainerType& b,
size_t num_channels = std::tuple_size<ContainerType>::value) {
using ValueTy = typename ContainerType::value_type;
static_assert(std::is_signed<ValueTy>::value,
"Value type assumed to be signed to avoid branch below.");
ValueTy result = ValueTy(0);
for (size_t i = 0; i < num_channels; ++i) {
ValueTy error = a[i] - b[i];
result += error * error;
}
return result;
}
constexpr int MaxValuesForModes(ColorEndpointMode mode_a,
ColorEndpointMode mode_b) {
return (NumColorValuesForEndpointMode(mode_a) >
NumColorValuesForEndpointMode(mode_b))
? NumColorValuesForEndpointMode(mode_a)
: NumColorValuesForEndpointMode(mode_b);
}
// This function takes the two colors in |endpoint_low| and |endpoint_high| and
// encodes them into |vals| according to the ASTC spec in section C.2.14. It
// assumes that the two colors are close enough to grayscale that the encoding
// should use the ColorEndpointMode kLDRLumaBaseOffset or kLDRLumaDirect. Which
// one is chosen depends on which produces smaller error for the given
// quantization value stored in |max_value|
bool EncodeColorsLuma(const RgbaColor& endpoint_low,
const RgbaColor& endpoint_high,
int max_value, ColorEndpointMode* const astc_mode,
std::vector<int>* const vals) {
assert(vals->size() ==
NumValuesForEncodingMode(EndpointEncodingMode::kDirectLuma));
int avg1 = AverageRGB(endpoint_low);
int avg2 = AverageRGB(endpoint_high);
// For the offset mode, L1 is strictly greater than L2, so if we are using
// it to encode the color values, we need to swap the weights and
// endpoints so that the larger of the two is the second endpoint.
bool needs_weight_swap = false;
if (avg1 > avg2) {
needs_weight_swap = true;
std::swap(avg1, avg2);
}
assert(avg1 <= avg2);
// Now, the first endpoint is based on the low-order six bits of the first
// value, and the high order two bits of the second value. The low order
// six bits of the second value are used as the (strictly positive) offset
// from the first value.
const int offset = std::min(avg2 - avg1, 0x3F);
const int quant_off_low =
QuantizeCEValueToRange((avg1 & 0x3F) << 2, max_value);
const int quant_off_high =
QuantizeCEValueToRange((avg1 & 0xC0) | offset, max_value);
const int quant_low = QuantizeCEValueToRange(avg1, max_value);
const int quant_high = QuantizeCEValueToRange(avg2, max_value);
RgbaColor unquant_off_low, unquant_off_high;
RgbaColor unquant_low, unquant_high;
(*vals)[0] = quant_off_low;
(*vals)[1] = quant_off_high;
DecodeColorsForMode(
*vals, max_value, ColorEndpointMode::kLDRLumaBaseOffset,
&unquant_off_low, &unquant_off_high);
(*vals)[0] = quant_low;
(*vals)[1] = quant_high;
DecodeColorsForMode(*vals, max_value, ColorEndpointMode::kLDRLumaDirect,
&unquant_low, &unquant_high);
const auto calculate_error =
[needs_weight_swap, &endpoint_low, &endpoint_high]
(const RgbaColor& low, const RgbaColor& high) {
int error = 0;
if (needs_weight_swap) {
error += SquaredError(low, endpoint_high);
error += SquaredError(high, endpoint_low);
} else {
error += SquaredError(low, endpoint_low);
error += SquaredError(high, endpoint_high);
}
return error;
};
const int direct_error = calculate_error(unquant_low, unquant_high);
const int off_error = calculate_error(unquant_off_low, unquant_off_high);
if (direct_error <= off_error) {
(*vals)[0] = quant_low;
(*vals)[1] = quant_high;
*astc_mode = ColorEndpointMode::kLDRLumaDirect;
} else {
(*vals)[0] = quant_off_low;
(*vals)[1] = quant_off_high;
*astc_mode = ColorEndpointMode::kLDRLumaBaseOffset;
}
return needs_weight_swap;
}
class QuantizedEndpointPair {
public:
QuantizedEndpointPair(const RgbaColor& c_low, const RgbaColor& c_high,
int max_value)
: orig_low_(c_low),
orig_high_(c_high),
quant_low_(QuantizeColor(c_low, max_value)),
quant_high_(QuantizeColor(c_high, max_value)),
unquant_low_(UnquantizeColor(quant_low_, max_value)),
unquant_high_(UnquantizeColor(quant_high_, max_value)) { }
const RgbaColor& QuantizedLow() const { return quant_low_; }
const RgbaColor& QuantizedHigh() const { return quant_high_; }
const RgbaColor& UnquantizedLow() const { return unquant_low_; }
const RgbaColor& UnquantizedHigh() const { return unquant_high_; }
const RgbaColor& OriginalLow() const { return orig_low_; }
const RgbaColor& OriginalHigh() const { return orig_high_; }
private:
RgbaColor orig_low_;
RgbaColor orig_high_;
RgbaColor quant_low_;
RgbaColor quant_high_;
RgbaColor unquant_low_;
RgbaColor unquant_high_;
};
class CEEncodingOption {
public:
CEEncodingOption() { }
CEEncodingOption(
int squared_error, const QuantizedEndpointPair* quantized_endpoints,
bool swap_endpoints, bool blue_contract, bool use_offset_mode)
: squared_error_(squared_error),
quantized_endpoints_(quantized_endpoints),
swap_endpoints_(swap_endpoints),
blue_contract_(blue_contract),
use_offset_mode_(use_offset_mode) { }
// Returns true if able to generate valid |astc_mode| and |vals|. In some
// instances, such as if the endpoints reprsent a base/offset pair, we may not
// be able to guarantee blue-contract encoding due to how the base/offset pair
// are represented and the specifics of the decoding procedure. Similarly,
// some direct RGBA encodings also may not be able to emit blue-contract modes
// due to an unlucky combination of channels. In these instances, this
// function will return false, and all pointers will remain unmodified.
bool Pack(bool with_alpha, ColorEndpointMode* const astc_mode,
std::vector<int>* const vals, bool* const needs_weight_swap) const {
auto unquantized_low = quantized_endpoints_->UnquantizedLow();
auto unquantized_high = quantized_endpoints_->UnquantizedHigh();
// In offset mode, we do BitTransferSigned before analyzing the values
// of the endpoints in order to determine whether or not we're going to
// be using blue-contract mode.
if (use_offset_mode_) {
for (size_t i = 0; i < std::tuple_size<RgbaColor>::value; ++i) {
BitTransferSigned(&unquantized_high[i], &unquantized_low[i]);
}
}
// Define variables as outlined in the ASTC spec C.2.14 for the RGB[A]
// direct and base-offset modes
int s0 = 0, s1 = 0;
for (int i = 0; i < 3; ++i) {
s0 += unquantized_low[i];
s1 += unquantized_high[i];
}
// Can we guarantee a blue-contract mode if we want it? In other words,
// if we swap which endpoint is high and which endpoint is low, can we
// guarantee that we will hit the corresponding decode path?
bool swap_vals = false;
if (use_offset_mode_) {
if (blue_contract_) {
swap_vals = s1 >= 0;
} else {
swap_vals = s1 < 0;
}
// In offset mode, we have two different measurements that swap the
// endpoints prior to encoding, so we don't need to swap them here.
// If we need to swap them to guarantee a blue-contract mode, then
// abort and wait until we get the other error measurement.
if (swap_vals) {
return false;
}
} else {
if (blue_contract_) {
// If we want a blue_contract path, but s1 == s0, then swapping the
// values will have no effect.
if (s1 == s0) {
return false;
}
swap_vals = s1 > s0;
// If we're encoding blue contract mode directly, then we implicitly
// swap the endpoints during decode, meaning that we need to take
// note of that here.
*needs_weight_swap = !(*needs_weight_swap);
} else {
swap_vals = s1 < s0;
}
}
const auto* quantized_low = &(quantized_endpoints_->QuantizedLow());
const auto* quantized_high = &(quantized_endpoints_->QuantizedHigh());
if (swap_vals) {
assert(!use_offset_mode_);
std::swap(quantized_low, quantized_high);
*needs_weight_swap = !(*needs_weight_swap);
}
(*vals)[0] = quantized_low->at(0);
(*vals)[1] = quantized_high->at(0);
(*vals)[2] = quantized_low->at(1);
(*vals)[3] = quantized_high->at(1);
(*vals)[4] = quantized_low->at(2);
(*vals)[5] = quantized_high->at(2);
if (use_offset_mode_) {
*astc_mode = ColorEndpointMode::kLDRRGBBaseOffset;
} else {
*astc_mode = ColorEndpointMode::kLDRRGBDirect;
}
if (with_alpha) {
(*vals)[6] = quantized_low->at(3);
(*vals)[7] = quantized_high->at(3);
if (use_offset_mode_) {
*astc_mode = ColorEndpointMode::kLDRRGBABaseOffset;
} else {
*astc_mode = ColorEndpointMode::kLDRRGBADirect;
}
}
// If we swapped them to measure, then they need to be swapped after
// decoding
if (swap_endpoints_) {
*needs_weight_swap = !(*needs_weight_swap);
}
return true;
}
bool BlueContract() const { return blue_contract_; }
int Error() const { return squared_error_; }
private:
int squared_error_;
const QuantizedEndpointPair* quantized_endpoints_;
bool swap_endpoints_;
bool blue_contract_;
bool use_offset_mode_;
};
bool EncodeColorsRGBA(const RgbaColor& endpoint_low_rgba,
const RgbaColor& endpoint_high_rgba,
int max_value, bool with_alpha,
ColorEndpointMode* const astc_mode,
std::vector<int>* const vals) {
const size_t num_channels = with_alpha ? std::tuple_size<RgbaColor>::value : 3;
// The difficulty of encoding into this mode is determining whether or
// not we'd like to use the 'blue contract' function to reconstruct
// the endpoints and whether or not we'll be more accurate by using the
// base/offset color modes instead of quantizing the color channels
// directly. With that in mind, we:
// 1. Generate the inverted values for blue-contract and offset modes.
// 2. Quantize all of the different endpoints.
// 3. Unquantize each sets and decide which one gives least error
// 4. Encode the values correspondingly.
// 1. Generate the inverted values for blue-contract and offset modes.
const auto inv_bc_low = InvertBlueContract(endpoint_low_rgba);
const auto inv_bc_high = InvertBlueContract(endpoint_high_rgba);
RgbaColor direct_base, direct_offset;
for (size_t i = 0; i < std::tuple_size<RgbaColor>::value; ++i) {
direct_base[i] = endpoint_low_rgba[i];
direct_offset[i] =
Clamp(endpoint_high_rgba[i] - endpoint_low_rgba[i], -32, 31);
InvertBitTransferSigned(&direct_offset[i], &direct_base[i]);
}
RgbaColor inv_bc_base, inv_bc_offset;
for (size_t i = 0; i < std::tuple_size<RgbaColor>::value; ++i) {
// Remember, for blue-contract'd offset modes, the base is compared
// against the second endpoint and not the first.
inv_bc_base[i] = inv_bc_high[i];
inv_bc_offset[i] = Clamp(inv_bc_low[i] - inv_bc_high[i], -32, 31);
InvertBitTransferSigned(&inv_bc_offset[i], &inv_bc_base[i]);
}
// The order of the endpoints for offset modes may determine how well they
// approximate the given endpoints. It may be that the quantization value
// produces more accurate values for the base than the offset or
// vice/versa. For this reason, we need to generate quantized versions of
// the endpoints as if they were swapped to see if we get better error
// out of it.
RgbaColor direct_base_swapped, direct_offset_swapped;
for (size_t i = 0; i < std::tuple_size<RgbaColor>::value; ++i) {
direct_base_swapped[i] = endpoint_high_rgba[i];
direct_offset_swapped[i] =
Clamp(endpoint_low_rgba[i] - endpoint_high_rgba[i], -32, 31);
InvertBitTransferSigned(&direct_offset_swapped[i], &direct_base_swapped[i]);
}
RgbaColor inv_bc_base_swapped, inv_bc_offset_swapped;
for (size_t i = 0; i < std::tuple_size<RgbaColor>::value; ++i) {
// Remember, for blue-contract'd offset modes, the base is compared
// against the second endpoint and not the first. Hence, the swapped
// version will compare the base against the first endpoint.
inv_bc_base_swapped[i] = inv_bc_low[i];
inv_bc_offset_swapped[i] = Clamp(inv_bc_high[i] - inv_bc_low[i], -32, 31);
InvertBitTransferSigned(&inv_bc_offset_swapped[i], &inv_bc_base_swapped[i]);
}
// 2. Quantize the endpoints directly.
const QuantizedEndpointPair direct_quantized(
endpoint_low_rgba, endpoint_high_rgba, max_value);
const QuantizedEndpointPair bc_quantized(
inv_bc_low, inv_bc_high, max_value);
const QuantizedEndpointPair offset_quantized(
direct_base, direct_offset, max_value);
const QuantizedEndpointPair bc_offset_quantized(
inv_bc_base, inv_bc_offset, max_value);
const QuantizedEndpointPair offset_swapped_quantized(
direct_base_swapped, direct_offset_swapped, max_value);
const QuantizedEndpointPair bc_offset_swapped_quantized(
inv_bc_base_swapped, inv_bc_offset_swapped, max_value);
// 3. Unquantize each set and decide which one gives least error.
std::array<CEEncodingOption, 6> errors;
auto errors_itr = errors.begin();
// 3.1 regular unquantized error
{
const auto rgba_low = direct_quantized.UnquantizedLow();
const auto rgba_high = direct_quantized.UnquantizedHigh();
const int sq_rgb_error =
SquaredError(rgba_low, endpoint_low_rgba, num_channels) +
SquaredError(rgba_high, endpoint_high_rgba, num_channels);
const bool swap_endpoints = false;
const bool blue_contract = false;
const bool offset_mode = false;
*(errors_itr++) = CEEncodingOption(
sq_rgb_error, &direct_quantized,
swap_endpoints, blue_contract, offset_mode);
}
// 3.2 Compute blue-contract'd error.
{
auto bc_low = bc_quantized.UnquantizedLow();
auto bc_high = bc_quantized.UnquantizedHigh();
BlueContract(&bc_low);
BlueContract(&bc_high);
const int sq_bc_error =
SquaredError(bc_low, endpoint_low_rgba, num_channels) +
SquaredError(bc_high, endpoint_high_rgba, num_channels);
const bool swap_endpoints = false;
const bool blue_contract = true;
const bool offset_mode = false;
*(errors_itr++) = CEEncodingOption(
sq_bc_error, &bc_quantized,
swap_endpoints, blue_contract, offset_mode);
}
// 3.3 Compute base/offset unquantized error.
const auto compute_base_offset_error =
[num_channels, &errors_itr, &endpoint_low_rgba, &endpoint_high_rgba]
(const QuantizedEndpointPair& pair, bool swapped) {
auto base = pair.UnquantizedLow();
auto offset = pair.UnquantizedHigh();
for (size_t i = 0; i < num_channels; ++i) {
BitTransferSigned(&offset[i], &base[i]);
offset[i] = Clamp(base[i] + offset[i], 0, 255);
}
int base_offset_error = 0;
// If we swapped the endpoints going in, then without blue contract
// we should be comparing the base against the high endpoint.
if (swapped) {
base_offset_error =
SquaredError(base, endpoint_high_rgba, num_channels) +
SquaredError(offset, endpoint_low_rgba, num_channels);
} else {
base_offset_error =
SquaredError(base, endpoint_low_rgba, num_channels) +
SquaredError(offset, endpoint_high_rgba, num_channels);
}
const bool blue_contract = false;
const bool offset_mode = true;
*(errors_itr++) = CEEncodingOption(
base_offset_error, &pair, swapped, blue_contract, offset_mode);
};
compute_base_offset_error(offset_quantized, false);
// 3.4 Compute base/offset blue-contract error.
const auto compute_base_offset_blue_contract_error =
[num_channels, &errors_itr, &endpoint_low_rgba, &endpoint_high_rgba]
(const QuantizedEndpointPair& pair, bool swapped) {
auto base = pair.UnquantizedLow();
auto offset = pair.UnquantizedHigh();
for (size_t i = 0; i < num_channels; ++i) {
BitTransferSigned(&offset[i], &base[i]);
offset[i] = Clamp(base[i] + offset[i], 0, 255);
}
BlueContract(&base);
BlueContract(&offset);
int sq_bc_error = 0;
// Remember, for blue-contract'd offset modes, the base is compared
// against the second endpoint and not the first. So, we compare
// against the first if we swapped the endpoints going in.
if (swapped) {
sq_bc_error =
SquaredError(base, endpoint_low_rgba, num_channels) +
SquaredError(offset, endpoint_high_rgba, num_channels);
} else {
sq_bc_error =
SquaredError(base, endpoint_high_rgba, num_channels) +
SquaredError(offset, endpoint_low_rgba, num_channels);
}
const bool blue_contract = true;
const bool offset_mode = true;
*(errors_itr++) = CEEncodingOption(sq_bc_error, &pair,
swapped, blue_contract, offset_mode);
};
compute_base_offset_blue_contract_error(bc_offset_quantized, false);
// 3.5 Compute swapped base/offset error.
compute_base_offset_error(offset_swapped_quantized, true);
// 3.6 Compute swapped base/offset blue-contract error.
compute_base_offset_blue_contract_error(
bc_offset_swapped_quantized, true);
std::sort(errors.begin(), errors.end(),
[](const CEEncodingOption& a, const CEEncodingOption& b) {
return a.Error() < b.Error();
});
// 4. Encode the values correspondingly.
// For this part, we go through each measurement in order of increasing
// error. Based on the properties of each measurement, we decide how to
// best encode the quantized endpoints that produced that error value. If
// for some reason we cannot encode that metric, then we skip it and move
// to the next one.
for (const auto& measurement : errors) {
bool needs_weight_swap = false;
if (measurement.Pack(with_alpha, astc_mode, vals, &needs_weight_swap)) {
// Make sure that if we ask for a blue-contract mode that we get it *and*
// if we don't ask for it then we don't get it.
assert(!(measurement.BlueContract() ^
UsesBlueContract(max_value, *astc_mode, *vals)));
// We encoded what we got.
return needs_weight_swap;
}
}
assert(false && "Shouldn't have reached this point -- some combination of "
"endpoints should be possible to encode!");
return false;
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
bool UsesBlueContract(int max_value, ColorEndpointMode mode,
const std::vector<int>& vals) {
assert(vals.size() >= size_t(NumColorValuesForEndpointMode(mode)));
switch (mode) {
case ColorEndpointMode::kLDRRGBDirect:
case ColorEndpointMode::kLDRRGBADirect: {
constexpr int kNumVals = MaxValuesForModes(
ColorEndpointMode::kLDRRGBDirect, ColorEndpointMode::kLDRRGBADirect);
std::array<int, kNumVals> v {};
std::copy(vals.begin(), vals.end(), v.begin());
Unquantize(&v, max_value);
const int s0 = v[0] + v[2] + v[4];
const int s1 = v[1] + v[3] + v[5];
return s0 > s1;
}
case ColorEndpointMode::kLDRRGBBaseOffset:
case ColorEndpointMode::kLDRRGBABaseOffset: {
constexpr int kNumVals = MaxValuesForModes(
ColorEndpointMode::kLDRRGBBaseOffset,
ColorEndpointMode::kLDRRGBABaseOffset);
std::array<int, kNumVals> v {};
std::copy(vals.begin(), vals.end(), v.begin());
Unquantize(&v, max_value);
BitTransferSigned(&v[1], &v[0]);
BitTransferSigned(&v[3], &v[2]);
BitTransferSigned(&v[5], &v[4]);
return v[1] + v[3] + v[5] < 0;
}
default:
return false;
}
}
bool EncodeColorsForMode(
const RgbaColor& endpoint_low_rgba, const RgbaColor& endpoint_high_rgba,
int max_value, EndpointEncodingMode encoding_mode,
ColorEndpointMode* const astc_mode, std::vector<int>* const vals) {
bool needs_weight_swap = false;
vals->resize(NumValuesForEncodingMode(encoding_mode));
switch (encoding_mode) {
case EndpointEncodingMode::kDirectLuma:
return EncodeColorsLuma(
endpoint_low_rgba, endpoint_high_rgba, max_value, astc_mode, vals);
case EndpointEncodingMode::kDirectLumaAlpha: {
// TODO(google): See if luma-alpha base-offset is better
const int avg1 = AverageRGB(endpoint_low_rgba);
const int avg2 = AverageRGB(endpoint_high_rgba);
(*vals)[0] = QuantizeCEValueToRange(avg1, max_value);
(*vals)[1] = QuantizeCEValueToRange(avg2, max_value);
(*vals)[2] = QuantizeCEValueToRange(endpoint_low_rgba[3], max_value);
(*vals)[3] = QuantizeCEValueToRange(endpoint_high_rgba[3], max_value);
*astc_mode = ColorEndpointMode::kLDRLumaAlphaDirect;
}
break;
case EndpointEncodingMode::kBaseScaleRGB:
case EndpointEncodingMode::kBaseScaleRGBA: {
RgbaColor base = endpoint_high_rgba;
RgbaColor scaled = endpoint_low_rgba;
// Similar to luma base-offset, the scaled value is strictly less than
// the base value here according to the decode procedure. In this case,
// if the base is larger than the scale then we need to swap.
int num_channels_ge = 0;
for (int i = 0; i < 3; ++i) {
num_channels_ge +=
static_cast<int>(endpoint_high_rgba[i] >= endpoint_low_rgba[i]);
}
if (num_channels_ge < 2) {
needs_weight_swap = true;
std::swap(base, scaled);
}
// Since the second endpoint is just a direct copy of the RGB values, we
// can start by quantizing them.
const auto q_base = QuantizeColor(base, max_value);
const auto uq_base = UnquantizeColor(q_base, max_value);
// The first endpoint (scaled) is defined by piecewise multiplying the
// second endpoint (base) by the scale factor and then dividing by 256.
// This means that the inverse operation is to first piecewise multiply
// the first endpoint by 256 and then divide by the unquantized second
// endpoint. We take the average of each of each of these scale values as
// our final scale value.
// TODO(google): Is this the best way to determine the scale factor?
int num_samples = 0;
int scale_sum = 0;
for (int i = 0; i < 3; ++i) {
int x = uq_base[i];
if (x != 0) {
++num_samples;
scale_sum += (scaled[i] * 256) / x;
}
}
(*vals)[0] = q_base[0];
(*vals)[1] = q_base[1];
(*vals)[2] = q_base[2];
if (num_samples > 0) {
const int avg_scale = Clamp(scale_sum / num_samples, 0, 255);
(*vals)[3] = QuantizeCEValueToRange(avg_scale, max_value);
} else {
// In this case, all of the base values are zero, so we can use whatever
// we want as the scale -- it won't affect the outcome.
(*vals)[3] = max_value;
}
*astc_mode = ColorEndpointMode::kLDRRGBBaseScale;
if (encoding_mode == EndpointEncodingMode::kBaseScaleRGBA) {
(*vals)[4] = QuantizeCEValueToRange(scaled[3], max_value);
(*vals)[5] = QuantizeCEValueToRange(base[3], max_value);
*astc_mode = ColorEndpointMode::kLDRRGBBaseScaleTwoA;
}
}
break;
case EndpointEncodingMode::kDirectRGB:
case EndpointEncodingMode::kDirectRGBA:
return EncodeColorsRGBA(
endpoint_low_rgba, endpoint_high_rgba, max_value,
encoding_mode == EndpointEncodingMode::kDirectRGBA, astc_mode, vals);
default:
assert(false && "Unimplemented color encoding.");
}
return needs_weight_swap;
}
// These decoding procedures follow the code outlined in Section C.2.14 of
// the ASTC specification.
void DecodeColorsForMode(const std::vector<int>& vals,
int max_value, ColorEndpointMode mode,
RgbaColor* const endpoint_low_rgba,
RgbaColor* const endpoint_high_rgba) {
assert(vals.size() >= size_t(NumColorValuesForEndpointMode(mode)));
switch (mode) {
case ColorEndpointMode::kLDRLumaDirect: {
const int l0 = UnquantizeCEValueFromRange(vals[0], max_value);
const int l1 = UnquantizeCEValueFromRange(vals[1], max_value);
*endpoint_low_rgba = {{ l0, l0, l0, 255 }};
*endpoint_high_rgba = {{ l1, l1, l1, 255 }};
}
break;
case ColorEndpointMode::kLDRLumaBaseOffset: {
const int v0 = UnquantizeCEValueFromRange(vals[0], max_value);
const int v1 = UnquantizeCEValueFromRange(vals[1], max_value);
const int l0 = (v0 >> 2) | (v1 & 0xC0);
const int l1 = std::min(l0 + (v1 & 0x3F), 0xFF);
*endpoint_low_rgba = {{ l0, l0, l0, 255 }};
*endpoint_high_rgba = {{ l1, l1, l1, 255 }};
}
break;
case ColorEndpointMode::kLDRLumaAlphaDirect: {
constexpr int kNumVals =
NumColorValuesForEndpointMode(ColorEndpointMode::kLDRLumaAlphaDirect);
std::array<int, kNumVals> v;
std::copy(vals.begin(), vals.end(), v.begin());
Unquantize(&v, max_value);
*endpoint_low_rgba = {{ v[0], v[0], v[0], v[2] }};
*endpoint_high_rgba = {{ v[1], v[1], v[1], v[3] }};
}
break;
case ColorEndpointMode::kLDRLumaAlphaBaseOffset: {
constexpr int kNumVals = NumColorValuesForEndpointMode(
ColorEndpointMode::kLDRLumaAlphaBaseOffset);
std::array<int, kNumVals> v;
std::copy(vals.begin(), vals.end(), v.begin());
Unquantize(&v, max_value);
BitTransferSigned(&v[1], &v[0]);
BitTransferSigned(&v[3], &v[2]);
*endpoint_low_rgba = {{ v[0], v[0], v[0], v[2] }};
const int high_luma = v[0] + v[1];
*endpoint_high_rgba = {{ high_luma, high_luma, high_luma, v[2] + v[3] }};
for (auto& c : *endpoint_low_rgba) { c = Clamp(c, 0, 255); }
for (auto& c : *endpoint_high_rgba) { c = Clamp(c, 0, 255); }
}
break;
case ColorEndpointMode::kLDRRGBBaseScale: {
constexpr int kNumVals =
NumColorValuesForEndpointMode(ColorEndpointMode::kLDRRGBBaseScale);
std::array<int, kNumVals> v;
std::copy(vals.begin(), vals.end(), v.begin());
Unquantize(&v, max_value);
*endpoint_high_rgba = {{ v[0], v[1], v[2], 255 }};
for (int i = 0; i < 3; ++i) {
const int x = endpoint_high_rgba->at(i);
endpoint_low_rgba->at(i) = (x * v[3]) >> 8;
}
endpoint_low_rgba->at(3) = 255;
}
break;
case ColorEndpointMode::kLDRRGBDirect: {
constexpr int kNumVals =
NumColorValuesForEndpointMode(ColorEndpointMode::kLDRRGBDirect);
std::array<int, kNumVals> v;
std::copy(vals.begin(), vals.end(), v.begin());
Unquantize(&v, max_value);
const int s0 = v[0] + v[2] + v[4];
const int s1 = v[1] + v[3] + v[5];
*endpoint_low_rgba = {{ v[0], v[2], v[4], 255 }};
*endpoint_high_rgba = {{ v[1], v[3], v[5], 255 }};
if (s1 < s0) {
std::swap(*endpoint_low_rgba, *endpoint_high_rgba);
BlueContract(endpoint_low_rgba);
BlueContract(endpoint_high_rgba);
}
}
break;
case ColorEndpointMode::kLDRRGBBaseOffset: {
constexpr int kNumVals =
NumColorValuesForEndpointMode(ColorEndpointMode::kLDRRGBBaseOffset);
std::array<int, kNumVals> v;
std::copy(vals.begin(), vals.end(), v.begin());
Unquantize(&v, max_value);
BitTransferSigned(&v[1], &v[0]);
BitTransferSigned(&v[3], &v[2]);
BitTransferSigned(&v[5], &v[4]);
*endpoint_low_rgba = {{ v[0], v[2], v[4], 255 }};
*endpoint_high_rgba = {{ v[0] + v[1], v[2] + v[3], v[4] + v[5], 255 }};
if (v[1] + v[3] + v[5] < 0) {
std::swap(*endpoint_low_rgba, *endpoint_high_rgba);
BlueContract(endpoint_low_rgba);
BlueContract(endpoint_high_rgba);
}
for (auto& c : *endpoint_low_rgba) { c = Clamp(c, 0, 255); }
for (auto& c : *endpoint_high_rgba) { c = Clamp(c, 0, 255); }
}
break;
case ColorEndpointMode::kLDRRGBBaseScaleTwoA: {
constexpr int kNumVals = NumColorValuesForEndpointMode(
ColorEndpointMode::kLDRRGBBaseScaleTwoA);
std::array<int, kNumVals> v;
std::copy(vals.begin(), vals.end(), v.begin());
Unquantize(&v, max_value);
// Base
*endpoint_low_rgba = *endpoint_high_rgba = {{ v[0], v[1], v[2], 255 }};
// Scale
for (int i = 0; i < 3; ++i) {
auto& x = endpoint_low_rgba->at(i);
x = (x * v[3]) >> 8;
}
// Two A
endpoint_low_rgba->at(3) = v[4];
endpoint_high_rgba->at(3) = v[5];
}
break;
case ColorEndpointMode::kLDRRGBADirect: {
constexpr int kNumVals =
NumColorValuesForEndpointMode(ColorEndpointMode::kLDRRGBADirect);
std::array<int, kNumVals> v;
std::copy(vals.begin(), vals.end(), v.begin());
Unquantize(&v, max_value);
const int s0 = v[0] + v[2] + v[4];
const int s1 = v[1] + v[3] + v[5];
*endpoint_low_rgba = {{ v[0], v[2], v[4], v[6] }};
*endpoint_high_rgba = {{ v[1], v[3], v[5], v[7] }};
if (s1 < s0) {
std::swap(*endpoint_low_rgba, *endpoint_high_rgba);
BlueContract(endpoint_low_rgba);
BlueContract(endpoint_high_rgba);
}
}
break;
case ColorEndpointMode::kLDRRGBABaseOffset: {
constexpr int kNumVals =
NumColorValuesForEndpointMode(ColorEndpointMode::kLDRRGBABaseOffset);
std::array<int, kNumVals> v;
std::copy(vals.begin(), vals.end(), v.begin());
Unquantize(&v, max_value);
BitTransferSigned(&v[1], &v[0]);
BitTransferSigned(&v[3], &v[2]);
BitTransferSigned(&v[5], &v[4]);
BitTransferSigned(&v[7], &v[6]);
*endpoint_low_rgba = {{ v[0], v[2], v[4], v[6] }};
*endpoint_high_rgba = {{
v[0] + v[1], v[2] + v[3], v[4] + v[5], v[6] + v[7] }};
if (v[1] + v[3] + v[5] < 0) {
std::swap(*endpoint_low_rgba, *endpoint_high_rgba);
BlueContract(endpoint_low_rgba);
BlueContract(endpoint_high_rgba);
}
for (auto& c : *endpoint_low_rgba) { c = Clamp(c, 0, 255); }
for (auto& c : *endpoint_high_rgba) { c = Clamp(c, 0, 255); }
}
break;
default:
// Unimplemented color encoding.
// TODO(google): Is this the correct error handling?
*endpoint_high_rgba = *endpoint_low_rgba = {{ 0, 0, 0, 0 }};
}
}
} // namespace astc_codec

View File

@@ -0,0 +1,90 @@
// 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.
#ifndef ASTC_CODEC_DECODER_ENDPOINT_CODEC_H_
#define ASTC_CODEC_DECODER_ENDPOINT_CODEC_H_
#include "src/decoder/physical_astc_block.h"
#include "src/decoder/types.h"
#include <array>
#include <vector>
namespace astc_codec {
// We use a special distinction for encode modes used to pass to the
// EncodeColorsForMode function below. The reason is that some of the color
// modes have sub-modes (like blue-contract) that change whether or not it is
// useful to encode an endpoint pair using one mode versus another. To avoid
// this problem, we approach the problem of encoding by specifying some
// high-level encoding modes. These eventually choose one of the low level
// ColorEndpointModes from Section C.2.14 when used in EncodeColorsForMode.
enum class EndpointEncodingMode {
kDirectLuma,
kDirectLumaAlpha,
kBaseScaleRGB,
kBaseScaleRGBA,
kDirectRGB,
kDirectRGBA
};
// Returns the number of values in the encoded endpoint pair after encoding
// to a specific high-level encoding mode.
constexpr int NumValuesForEncodingMode(EndpointEncodingMode mode) {
return
mode == EndpointEncodingMode::kDirectLuma ? 2 :
mode == EndpointEncodingMode::kDirectLumaAlpha ? 4 :
mode == EndpointEncodingMode::kBaseScaleRGB ? 4 :
mode == EndpointEncodingMode::kBaseScaleRGBA ? 6 :
mode == EndpointEncodingMode::kDirectRGB ? 6 : 8;
}
// Fills |vals| with the quantized endpoint colors values defined in the ASTC
// specification. The values are quantized to the range [0, max_value]. These
// quantization limits can be obtained by querying the associated functions in
// integer_sequence_codec. The returned |astc_mode| will be the ASTC mode used
// to encode the resulting sequence.
//
// The |encoding_mode| is used to determine the way that we encode the values.
// Each encoding mode is used to determine which ASTC mode best corresponds
// to the pair of endpoints. It is a necessary hint to the encoding function
// in order to process the endpoints. Each encoding mode gurantees a certain
// number of values generated per endpoints.
//
// The return value will be true if the endpoints have been switched in order to
// reap the most benefit from the way the hardware decodes the given mode. In
// this case, the associated weights that interpolate this color must also be
// switched. In other words, for each w, it should change to 64 - w.
bool EncodeColorsForMode(
const RgbaColor& endpoint_low_rgba, const RgbaColor& endpoint_high_rgba,
int max_value, EndpointEncodingMode encoding_mode,
ColorEndpointMode* astc_mode, std::vector<int>* vals);
// Decodes the color values quantized to the range [0, max_value] into RGBA
// endpoints for the given mode. This function is the inverse of
// EncodeColorsForMode -- see that function for details. This function should
// work on all LDR endpoint modes, but no HDR modes.
void DecodeColorsForMode(const std::vector<int>& vals,
int max_value, ColorEndpointMode mode,
RgbaColor* endpoint_low_rgba,
RgbaColor* endpoint_high_rgba);
// Returns true if the quantized |vals| in the range [0, max_value] use the
// 'blue_contract' modification during decoding for the given |mode|.
bool UsesBlueContract(int max_value, ColorEndpointMode mode,
const std::vector<int>& vals);
} // namespace astc_codec
#endif // ASTC_CODEC_DECODER_ENDPOINT_CODEC_H_

View File

@@ -0,0 +1,162 @@
// 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/footprint.h"
#include "src/base/string_utils.h"
#include <map>
#include <string>
#include <utility>
#include <vector>
namespace astc_codec {
namespace {
// Encodes the width and height into an integer so that we can use a switch
// statement instead of a costly lookup map.
constexpr int EncodeDims(int width, int height) {
return (width << 16) | height;
}
} // namespace
base::Optional<FootprintType>
Footprint::GetValidFootprintForDimensions(int width, int height) {
switch (EncodeDims(width, height)) {
case EncodeDims(4, 4): return FootprintType::k4x4;
case EncodeDims(5, 4): return FootprintType::k5x4;
case EncodeDims(5, 5): return FootprintType::k5x5;
case EncodeDims(6, 5): return FootprintType::k6x5;
case EncodeDims(6, 6): return FootprintType::k6x6;
case EncodeDims(8, 5): return FootprintType::k8x5;
case EncodeDims(8, 6): return FootprintType::k8x6;
case EncodeDims(8, 8): return FootprintType::k8x8;
case EncodeDims(10, 5): return FootprintType::k10x5;
case EncodeDims(10, 6): return FootprintType::k10x6;
case EncodeDims(10, 8): return FootprintType::k10x8;
case EncodeDims(10, 10): return FootprintType::k10x10;
case EncodeDims(12, 10): return FootprintType::k12x10;
case EncodeDims(12, 12): return FootprintType::k12x12;
default: return {};
}
}
int Footprint::GetWidthForFootprint(FootprintType footprint) {
switch (footprint) {
case FootprintType::k4x4: return 4;
case FootprintType::k5x4: return 5;
case FootprintType::k5x5: return 5;
case FootprintType::k6x5: return 6;
case FootprintType::k6x6: return 6;
case FootprintType::k8x5: return 8;
case FootprintType::k8x6: return 8;
case FootprintType::k10x5: return 10;
case FootprintType::k10x6: return 10;
case FootprintType::k8x8: return 8;
case FootprintType::k10x8: return 10;
case FootprintType::k10x10: return 10;
case FootprintType::k12x10: return 12;
case FootprintType::k12x12: return 12;
default:
assert(false);
return -1;
}
}
int Footprint::GetHeightForFootprint(FootprintType footprint) {
switch (footprint) {
case FootprintType::k4x4: return 4;
case FootprintType::k5x4: return 4;
case FootprintType::k5x5: return 5;
case FootprintType::k6x5: return 5;
case FootprintType::k6x6: return 6;
case FootprintType::k8x5: return 5;
case FootprintType::k8x6: return 6;
case FootprintType::k10x5: return 5;
case FootprintType::k10x6: return 6;
case FootprintType::k8x8: return 8;
case FootprintType::k10x8: return 8;
case FootprintType::k10x10: return 10;
case FootprintType::k12x10: return 10;
case FootprintType::k12x12: return 12;
default:
assert(false);
return -1;
}
}
Footprint::Footprint(FootprintType footprint)
: footprint_(footprint), width_(GetWidthForFootprint(footprint)),
height_(GetHeightForFootprint(footprint)) { }
////////////////////////////////////////////////////////////////////////////////
base::Optional<Footprint> Footprint::Parse(const char* footprint_string) {
assert(footprint_string && footprint_string[0] != '\0');
std::vector<std::string> dimension_strings;
base::Split(footprint_string, "x", [&dimension_strings](std::string&& s) {
dimension_strings.push_back(std::move(s));
});
if (dimension_strings.size() != 2) {
assert(false && "Invalid format for footprint");
return {};
}
const int width = base::ParseInt32(dimension_strings[0].c_str(), 0);
const int height = base::ParseInt32(dimension_strings[1].c_str(), 0);
assert(width > 0 && height > 0 && "Invalid width or height.");
return FromDimensions(width, height);
}
base::Optional<Footprint> Footprint::FromDimensions(int width, int height) {
base::Optional<FootprintType> valid_footprint =
GetValidFootprintForDimensions(width, height);
if (valid_footprint) {
return Footprint(valid_footprint.value());
} else {
return {};
}
}
// Returns a Footprint for the given FootprintType.
base::Optional<Footprint> Footprint::FromFootprintType(FootprintType type) {
if (type >= FootprintType::k4x4 && type < FootprintType::kCount) {
return Footprint(type);
} else {
return {};
}
}
size_t Footprint::StorageRequirements(int width, int height) const {
const int blocks_wide = (width + width_ - 1) / width_;
const int blocks_high = (height + height_ - 1) / height_;
constexpr size_t kASTCBlockSizeInBytes = 16;
return blocks_wide * blocks_high * kASTCBlockSizeInBytes;
}
// Returns bits/pixel for a given footprint.
float Footprint::Bitrate() const {
const int kASTCBlockBitCount = 128;
const int footprint_pixel_count = width_ * height_;
return static_cast<float>(kASTCBlockBitCount) /
static_cast<float>(footprint_pixel_count);
}
} // namespace astc_codec

View File

@@ -0,0 +1,106 @@
// 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.
#ifndef ASTC_CODEC_DECODER_FOOTPRINT_H_
#define ASTC_CODEC_DECODER_FOOTPRINT_H_
#include "include/astc-codec/astc-codec.h"
#include "src/base/optional.h"
#include <cstddef>
namespace astc_codec {
// An ASTC texture can be encoded with varying choices in block size. A set of
// predefined block sizes are specified in the ASTC specification. These are
// referred to in the literature as "footprints" available to an encoder when
// constructing an ASTC bitstream. This class provides various utility functions
// for interacting with these footprints.
class Footprint {
public:
Footprint() = delete;
Footprint(const Footprint& footprint) = default;
// Return the footprint type.
FootprintType Type() const { return footprint_; }
// Return logical descriptions of the dimensions.
int Width() const { return width_; }
int Height() const { return height_; }
// Returns the number of pixels for a block with this footprint.
int NumPixels() const { return width_ * height_; }
// Returns the number of bytes needed to store an ASTC encoded image with the
// given width and height.
size_t StorageRequirements(int width, int height) const;
// Returns the number of bits used per pixel.
float Bitrate() const;
static constexpr int NumValidFootprints() {
return static_cast<int>(FootprintType::kCount);
}
bool operator==(const Footprint& other) const {
return footprint_ == other.footprint_;
}
// These are the valid and available ASTC footprints.
static Footprint Get4x4() { return Footprint(FootprintType::k4x4); }
static Footprint Get5x4() { return Footprint(FootprintType::k5x4); }
static Footprint Get5x5() { return Footprint(FootprintType::k5x5); }
static Footprint Get6x5() { return Footprint(FootprintType::k6x5); }
static Footprint Get6x6() { return Footprint(FootprintType::k6x6); }
static Footprint Get8x5() { return Footprint(FootprintType::k8x5); }
static Footprint Get8x6() { return Footprint(FootprintType::k8x6); }
static Footprint Get8x8() { return Footprint(FootprintType::k8x8); }
static Footprint Get10x5() { return Footprint(FootprintType::k10x5); }
static Footprint Get10x6() { return Footprint(FootprintType::k10x6); }
static Footprint Get10x8() { return Footprint(FootprintType::k10x8); }
static Footprint Get10x10() { return Footprint(FootprintType::k10x10); }
static Footprint Get12x10() { return Footprint(FootprintType::k12x10); }
static Footprint Get12x12() { return Footprint(FootprintType::k12x12); }
// Constructs a footprint from a string of the form "NxM", or no value if
// width and height are not a valid footprint.
static base::Optional<Footprint> Parse(const char* footprint_string);
// Returns a footprint corresponding to a block of the given width and height,
// or no value if it does not.
static base::Optional<Footprint> FromDimensions(int width, int height);
// Returns a Footprint for the given FootprintType.
static base::Optional<Footprint> FromFootprintType(FootprintType type);
private:
// The only constructor.
explicit Footprint(FootprintType footprint);
// Returns the valid footprint for the width and height if possible.
static base::Optional<FootprintType> GetValidFootprintForDimensions(
int width, int height);
// Returns the associated dimension for the given valid footprint.
static int GetWidthForFootprint(FootprintType footprint);
static int GetHeightForFootprint(FootprintType footprint);
FootprintType footprint_;
int width_;
int height_;
};
} // namespace astc_codec
#endif // ASTC_CODEC_DECODER_FOOTPRINT_H_

View File

@@ -0,0 +1,574 @@
// 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/integer_sequence_codec.h"
#include "src/base/math_utils.h"
#include "src/base/utils.h"
#include <algorithm>
#include <iostream>
namespace astc_codec {
namespace {
// Tables of trit and quint encodings generated by the implementation in
// http://cs/aosp-master/external/skia/src/utils/SkTextureCompressor_ASTC.cpp
//
// These tables are used to decode the blocks of values encoded using the ASTC
// integer sequence encoding. The theory is that five trits (values that can
// take any number in the range [0, 2]) can take on a total of 3^5 = 243 total
// values, which can be stored in eight bits. These eight bits are used to
// decode the five trits based on the ASTC specification in Section C.2.12.
// For simplicity, we have stored a look-up table here so that we don't need
// to implement the decoding logic. Similarly, seven bits are used to decode
// three quints (since 5^3 = 125 < 128).
static const std::array<int, 5> kTritEncodings[256] = {
{{ 0, 0, 0, 0, 0 }}, {{ 1, 0, 0, 0, 0 }}, {{ 2, 0, 0, 0, 0 }},
{{ 0, 0, 2, 0, 0 }}, {{ 0, 1, 0, 0, 0 }}, {{ 1, 1, 0, 0, 0 }},
{{ 2, 1, 0, 0, 0 }}, {{ 1, 0, 2, 0, 0 }}, {{ 0, 2, 0, 0, 0 }},
{{ 1, 2, 0, 0, 0 }}, {{ 2, 2, 0, 0, 0 }}, {{ 2, 0, 2, 0, 0 }},
{{ 0, 2, 2, 0, 0 }}, {{ 1, 2, 2, 0, 0 }}, {{ 2, 2, 2, 0, 0 }},
{{ 2, 0, 2, 0, 0 }}, {{ 0, 0, 1, 0, 0 }}, {{ 1, 0, 1, 0, 0 }},
{{ 2, 0, 1, 0, 0 }}, {{ 0, 1, 2, 0, 0 }}, {{ 0, 1, 1, 0, 0 }},
{{ 1, 1, 1, 0, 0 }}, {{ 2, 1, 1, 0, 0 }}, {{ 1, 1, 2, 0, 0 }},
{{ 0, 2, 1, 0, 0 }}, {{ 1, 2, 1, 0, 0 }}, {{ 2, 2, 1, 0, 0 }},
{{ 2, 1, 2, 0, 0 }}, {{ 0, 0, 0, 2, 2 }}, {{ 1, 0, 0, 2, 2 }},
{{ 2, 0, 0, 2, 2 }}, {{ 0, 0, 2, 2, 2 }}, {{ 0, 0, 0, 1, 0 }},
{{ 1, 0, 0, 1, 0 }}, {{ 2, 0, 0, 1, 0 }}, {{ 0, 0, 2, 1, 0 }},
{{ 0, 1, 0, 1, 0 }}, {{ 1, 1, 0, 1, 0 }}, {{ 2, 1, 0, 1, 0 }},
{{ 1, 0, 2, 1, 0 }}, {{ 0, 2, 0, 1, 0 }}, {{ 1, 2, 0, 1, 0 }},
{{ 2, 2, 0, 1, 0 }}, {{ 2, 0, 2, 1, 0 }}, {{ 0, 2, 2, 1, 0 }},
{{ 1, 2, 2, 1, 0 }}, {{ 2, 2, 2, 1, 0 }}, {{ 2, 0, 2, 1, 0 }},
{{ 0, 0, 1, 1, 0 }}, {{ 1, 0, 1, 1, 0 }}, {{ 2, 0, 1, 1, 0 }},
{{ 0, 1, 2, 1, 0 }}, {{ 0, 1, 1, 1, 0 }}, {{ 1, 1, 1, 1, 0 }},
{{ 2, 1, 1, 1, 0 }}, {{ 1, 1, 2, 1, 0 }}, {{ 0, 2, 1, 1, 0 }},
{{ 1, 2, 1, 1, 0 }}, {{ 2, 2, 1, 1, 0 }}, {{ 2, 1, 2, 1, 0 }},
{{ 0, 1, 0, 2, 2 }}, {{ 1, 1, 0, 2, 2 }}, {{ 2, 1, 0, 2, 2 }},
{{ 1, 0, 2, 2, 2 }}, {{ 0, 0, 0, 2, 0 }}, {{ 1, 0, 0, 2, 0 }},
{{ 2, 0, 0, 2, 0 }}, {{ 0, 0, 2, 2, 0 }}, {{ 0, 1, 0, 2, 0 }},
{{ 1, 1, 0, 2, 0 }}, {{ 2, 1, 0, 2, 0 }}, {{ 1, 0, 2, 2, 0 }},
{{ 0, 2, 0, 2, 0 }}, {{ 1, 2, 0, 2, 0 }}, {{ 2, 2, 0, 2, 0 }},
{{ 2, 0, 2, 2, 0 }}, {{ 0, 2, 2, 2, 0 }}, {{ 1, 2, 2, 2, 0 }},
{{ 2, 2, 2, 2, 0 }}, {{ 2, 0, 2, 2, 0 }}, {{ 0, 0, 1, 2, 0 }},
{{ 1, 0, 1, 2, 0 }}, {{ 2, 0, 1, 2, 0 }}, {{ 0, 1, 2, 2, 0 }},
{{ 0, 1, 1, 2, 0 }}, {{ 1, 1, 1, 2, 0 }}, {{ 2, 1, 1, 2, 0 }},
{{ 1, 1, 2, 2, 0 }}, {{ 0, 2, 1, 2, 0 }}, {{ 1, 2, 1, 2, 0 }},
{{ 2, 2, 1, 2, 0 }}, {{ 2, 1, 2, 2, 0 }}, {{ 0, 2, 0, 2, 2 }},
{{ 1, 2, 0, 2, 2 }}, {{ 2, 2, 0, 2, 2 }}, {{ 2, 0, 2, 2, 2 }},
{{ 0, 0, 0, 0, 2 }}, {{ 1, 0, 0, 0, 2 }}, {{ 2, 0, 0, 0, 2 }},
{{ 0, 0, 2, 0, 2 }}, {{ 0, 1, 0, 0, 2 }}, {{ 1, 1, 0, 0, 2 }},
{{ 2, 1, 0, 0, 2 }}, {{ 1, 0, 2, 0, 2 }}, {{ 0, 2, 0, 0, 2 }},
{{ 1, 2, 0, 0, 2 }}, {{ 2, 2, 0, 0, 2 }}, {{ 2, 0, 2, 0, 2 }},
{{ 0, 2, 2, 0, 2 }}, {{ 1, 2, 2, 0, 2 }}, {{ 2, 2, 2, 0, 2 }},
{{ 2, 0, 2, 0, 2 }}, {{ 0, 0, 1, 0, 2 }}, {{ 1, 0, 1, 0, 2 }},
{{ 2, 0, 1, 0, 2 }}, {{ 0, 1, 2, 0, 2 }}, {{ 0, 1, 1, 0, 2 }},
{{ 1, 1, 1, 0, 2 }}, {{ 2, 1, 1, 0, 2 }}, {{ 1, 1, 2, 0, 2 }},
{{ 0, 2, 1, 0, 2 }}, {{ 1, 2, 1, 0, 2 }}, {{ 2, 2, 1, 0, 2 }},
{{ 2, 1, 2, 0, 2 }}, {{ 0, 2, 2, 2, 2 }}, {{ 1, 2, 2, 2, 2 }},
{{ 2, 2, 2, 2, 2 }}, {{ 2, 0, 2, 2, 2 }}, {{ 0, 0, 0, 0, 1 }},
{{ 1, 0, 0, 0, 1 }}, {{ 2, 0, 0, 0, 1 }}, {{ 0, 0, 2, 0, 1 }},
{{ 0, 1, 0, 0, 1 }}, {{ 1, 1, 0, 0, 1 }}, {{ 2, 1, 0, 0, 1 }},
{{ 1, 0, 2, 0, 1 }}, {{ 0, 2, 0, 0, 1 }}, {{ 1, 2, 0, 0, 1 }},
{{ 2, 2, 0, 0, 1 }}, {{ 2, 0, 2, 0, 1 }}, {{ 0, 2, 2, 0, 1 }},
{{ 1, 2, 2, 0, 1 }}, {{ 2, 2, 2, 0, 1 }}, {{ 2, 0, 2, 0, 1 }},
{{ 0, 0, 1, 0, 1 }}, {{ 1, 0, 1, 0, 1 }}, {{ 2, 0, 1, 0, 1 }},
{{ 0, 1, 2, 0, 1 }}, {{ 0, 1, 1, 0, 1 }}, {{ 1, 1, 1, 0, 1 }},
{{ 2, 1, 1, 0, 1 }}, {{ 1, 1, 2, 0, 1 }}, {{ 0, 2, 1, 0, 1 }},
{{ 1, 2, 1, 0, 1 }}, {{ 2, 2, 1, 0, 1 }}, {{ 2, 1, 2, 0, 1 }},
{{ 0, 0, 1, 2, 2 }}, {{ 1, 0, 1, 2, 2 }}, {{ 2, 0, 1, 2, 2 }},
{{ 0, 1, 2, 2, 2 }}, {{ 0, 0, 0, 1, 1 }}, {{ 1, 0, 0, 1, 1 }},
{{ 2, 0, 0, 1, 1 }}, {{ 0, 0, 2, 1, 1 }}, {{ 0, 1, 0, 1, 1 }},
{{ 1, 1, 0, 1, 1 }}, {{ 2, 1, 0, 1, 1 }}, {{ 1, 0, 2, 1, 1 }},
{{ 0, 2, 0, 1, 1 }}, {{ 1, 2, 0, 1, 1 }}, {{ 2, 2, 0, 1, 1 }},
{{ 2, 0, 2, 1, 1 }}, {{ 0, 2, 2, 1, 1 }}, {{ 1, 2, 2, 1, 1 }},
{{ 2, 2, 2, 1, 1 }}, {{ 2, 0, 2, 1, 1 }}, {{ 0, 0, 1, 1, 1 }},
{{ 1, 0, 1, 1, 1 }}, {{ 2, 0, 1, 1, 1 }}, {{ 0, 1, 2, 1, 1 }},
{{ 0, 1, 1, 1, 1 }}, {{ 1, 1, 1, 1, 1 }}, {{ 2, 1, 1, 1, 1 }},
{{ 1, 1, 2, 1, 1 }}, {{ 0, 2, 1, 1, 1 }}, {{ 1, 2, 1, 1, 1 }},
{{ 2, 2, 1, 1, 1 }}, {{ 2, 1, 2, 1, 1 }}, {{ 0, 1, 1, 2, 2 }},
{{ 1, 1, 1, 2, 2 }}, {{ 2, 1, 1, 2, 2 }}, {{ 1, 1, 2, 2, 2 }},
{{ 0, 0, 0, 2, 1 }}, {{ 1, 0, 0, 2, 1 }}, {{ 2, 0, 0, 2, 1 }},
{{ 0, 0, 2, 2, 1 }}, {{ 0, 1, 0, 2, 1 }}, {{ 1, 1, 0, 2, 1 }},
{{ 2, 1, 0, 2, 1 }}, {{ 1, 0, 2, 2, 1 }}, {{ 0, 2, 0, 2, 1 }},
{{ 1, 2, 0, 2, 1 }}, {{ 2, 2, 0, 2, 1 }}, {{ 2, 0, 2, 2, 1 }},
{{ 0, 2, 2, 2, 1 }}, {{ 1, 2, 2, 2, 1 }}, {{ 2, 2, 2, 2, 1 }},
{{ 2, 0, 2, 2, 1 }}, {{ 0, 0, 1, 2, 1 }}, {{ 1, 0, 1, 2, 1 }},
{{ 2, 0, 1, 2, 1 }}, {{ 0, 1, 2, 2, 1 }}, {{ 0, 1, 1, 2, 1 }},
{{ 1, 1, 1, 2, 1 }}, {{ 2, 1, 1, 2, 1 }}, {{ 1, 1, 2, 2, 1 }},
{{ 0, 2, 1, 2, 1 }}, {{ 1, 2, 1, 2, 1 }}, {{ 2, 2, 1, 2, 1 }},
{{ 2, 1, 2, 2, 1 }}, {{ 0, 2, 1, 2, 2 }}, {{ 1, 2, 1, 2, 2 }},
{{ 2, 2, 1, 2, 2 }}, {{ 2, 1, 2, 2, 2 }}, {{ 0, 0, 0, 1, 2 }},
{{ 1, 0, 0, 1, 2 }}, {{ 2, 0, 0, 1, 2 }}, {{ 0, 0, 2, 1, 2 }},
{{ 0, 1, 0, 1, 2 }}, {{ 1, 1, 0, 1, 2 }}, {{ 2, 1, 0, 1, 2 }},
{{ 1, 0, 2, 1, 2 }}, {{ 0, 2, 0, 1, 2 }}, {{ 1, 2, 0, 1, 2 }},
{{ 2, 2, 0, 1, 2 }}, {{ 2, 0, 2, 1, 2 }}, {{ 0, 2, 2, 1, 2 }},
{{ 1, 2, 2, 1, 2 }}, {{ 2, 2, 2, 1, 2 }}, {{ 2, 0, 2, 1, 2 }},
{{ 0, 0, 1, 1, 2 }}, {{ 1, 0, 1, 1, 2 }}, {{ 2, 0, 1, 1, 2 }},
{{ 0, 1, 2, 1, 2 }}, {{ 0, 1, 1, 1, 2 }}, {{ 1, 1, 1, 1, 2 }},
{{ 2, 1, 1, 1, 2 }}, {{ 1, 1, 2, 1, 2 }}, {{ 0, 2, 1, 1, 2 }},
{{ 1, 2, 1, 1, 2 }}, {{ 2, 2, 1, 1, 2 }}, {{ 2, 1, 2, 1, 2 }},
{{ 0, 2, 2, 2, 2 }}, {{ 1, 2, 2, 2, 2 }}, {{ 2, 2, 2, 2, 2 }},
{{ 2, 1, 2, 2, 2 }}
};
static const std::array<int, 3> kQuintEncodings[128] = {
{{ 0, 0, 0 }}, {{ 1, 0, 0 }}, {{ 2, 0, 0 }}, {{ 3, 0, 0 }}, {{ 4, 0, 0 }},
{{ 0, 4, 0 }}, {{ 4, 4, 0 }}, {{ 4, 4, 4 }}, {{ 0, 1, 0 }}, {{ 1, 1, 0 }},
{{ 2, 1, 0 }}, {{ 3, 1, 0 }}, {{ 4, 1, 0 }}, {{ 1, 4, 0 }}, {{ 4, 4, 1 }},
{{ 4, 4, 4 }}, {{ 0, 2, 0 }}, {{ 1, 2, 0 }}, {{ 2, 2, 0 }}, {{ 3, 2, 0 }},
{{ 4, 2, 0 }}, {{ 2, 4, 0 }}, {{ 4, 4, 2 }}, {{ 4, 4, 4 }}, {{ 0, 3, 0 }},
{{ 1, 3, 0 }}, {{ 2, 3, 0 }}, {{ 3, 3, 0 }}, {{ 4, 3, 0 }}, {{ 3, 4, 0 }},
{{ 4, 4, 3 }}, {{ 4, 4, 4 }}, {{ 0, 0, 1 }}, {{ 1, 0, 1 }}, {{ 2, 0, 1 }},
{{ 3, 0, 1 }}, {{ 4, 0, 1 }}, {{ 0, 4, 1 }}, {{ 4, 0, 4 }}, {{ 0, 4, 4 }},
{{ 0, 1, 1 }}, {{ 1, 1, 1 }}, {{ 2, 1, 1 }}, {{ 3, 1, 1 }}, {{ 4, 1, 1 }},
{{ 1, 4, 1 }}, {{ 4, 1, 4 }}, {{ 1, 4, 4 }}, {{ 0, 2, 1 }}, {{ 1, 2, 1 }},
{{ 2, 2, 1 }}, {{ 3, 2, 1 }}, {{ 4, 2, 1 }}, {{ 2, 4, 1 }}, {{ 4, 2, 4 }},
{{ 2, 4, 4 }}, {{ 0, 3, 1 }}, {{ 1, 3, 1 }}, {{ 2, 3, 1 }}, {{ 3, 3, 1 }},
{{ 4, 3, 1 }}, {{ 3, 4, 1 }}, {{ 4, 3, 4 }}, {{ 3, 4, 4 }}, {{ 0, 0, 2 }},
{{ 1, 0, 2 }}, {{ 2, 0, 2 }}, {{ 3, 0, 2 }}, {{ 4, 0, 2 }}, {{ 0, 4, 2 }},
{{ 2, 0, 4 }}, {{ 3, 0, 4 }}, {{ 0, 1, 2 }}, {{ 1, 1, 2 }}, {{ 2, 1, 2 }},
{{ 3, 1, 2 }}, {{ 4, 1, 2 }}, {{ 1, 4, 2 }}, {{ 2, 1, 4 }}, {{ 3, 1, 4 }},
{{ 0, 2, 2 }}, {{ 1, 2, 2 }}, {{ 2, 2, 2 }}, {{ 3, 2, 2 }}, {{ 4, 2, 2 }},
{{ 2, 4, 2 }}, {{ 2, 2, 4 }}, {{ 3, 2, 4 }}, {{ 0, 3, 2 }}, {{ 1, 3, 2 }},
{{ 2, 3, 2 }}, {{ 3, 3, 2 }}, {{ 4, 3, 2 }}, {{ 3, 4, 2 }}, {{ 2, 3, 4 }},
{{ 3, 3, 4 }}, {{ 0, 0, 3 }}, {{ 1, 0, 3 }}, {{ 2, 0, 3 }}, {{ 3, 0, 3 }},
{{ 4, 0, 3 }}, {{ 0, 4, 3 }}, {{ 0, 0, 4 }}, {{ 1, 0, 4 }}, {{ 0, 1, 3 }},
{{ 1, 1, 3 }}, {{ 2, 1, 3 }}, {{ 3, 1, 3 }}, {{ 4, 1, 3 }}, {{ 1, 4, 3 }},
{{ 0, 1, 4 }}, {{ 1, 1, 4 }}, {{ 0, 2, 3 }}, {{ 1, 2, 3 }}, {{ 2, 2, 3 }},
{{ 3, 2, 3 }}, {{ 4, 2, 3 }}, {{ 2, 4, 3 }}, {{ 0, 2, 4 }}, {{ 1, 2, 4 }},
{{ 0, 3, 3 }}, {{ 1, 3, 3 }}, {{ 2, 3, 3 }}, {{ 3, 3, 3 }}, {{ 4, 3, 3 }},
{{ 3, 4, 3 }}, {{ 0, 3, 4 }}, {{ 1, 3, 4 }}
};
// A cached table containing the max ranges for values encoded using ASTC's
// Bounded Integer Sequence Encoding. These are the numbers between 1 and 255
// that can be represented exactly as a number in the ranges
// [0, 2^k), [0, 3 * 2^k), and [0, 5 * 2^k).
static const std::array<int, kNumPossibleRanges> kMaxRanges = []() {
std::array<int, kNumPossibleRanges> ranges;
// Initialize the table that we need for determining value encodings.
auto next_max_range = ranges.begin();
auto add_val = [&next_max_range](int val) {
if (val <= 0 || (1 << kLog2MaxRangeForBits) <= val) {
return;
}
*(next_max_range++) = val;
};
for (int i = 0; i <= kLog2MaxRangeForBits; ++i) {
add_val(3 * (1 << i) - 1);
add_val(5 * (1 << i) - 1);
add_val((1 << i) - 1);
}
assert(std::distance(next_max_range, ranges.end()) == 0);
std::sort(ranges.begin(), ranges.end());
return ranges;
}();
// Returns true if x == 0 or if x is a power of two. This function is only used
// in the GetCountsForRange function, where we need to have it return true
// on zero since we can have single trit/quint ISE encodings according to
// Table C.2.7.
template<typename T,
typename std::enable_if<std::is_integral<T>::value, T>::type = 0>
inline constexpr bool IsPow2(T x) { return (x & (x - 1)) == 0; }
// For the ISE block encoding, these arrays determine how many bits are
// used after each value to store the interleaved quint/trit block.
const int kInterleavedQuintBits[3] = { 3, 2, 2 };
const int kInterleavedTritBits[5] = { 2, 2, 1, 2, 1 };
// Some template meta programming to get around the fact that MSVC
// will not allow (ValRange == 5) ? 3 : 5 as a template parameter
template<int ValRange>
struct DecodeBlockSize {
enum { value = (ValRange == 5 ? 3 : 5) };
};
// Decodes either a trit or quint block using the BISE (Bounded Integer Sequence
// Encoding) defined in Section C.2.12 of the ASTC specification. ValRange is
// expected to be either 3 or 5 depending on whether or not we're encoding trits
// or quints respectively. In other words, it is the remaining factor in whether
// the passed blocks contain encoded values of the form 3*2^k or 5*2^k.
template<int ValRange>
std::array<int, /* kNumVals = */ DecodeBlockSize<ValRange>::value> DecodeISEBlock(
uint64_t block_bits, int num_bits) {
static_assert(ValRange == 3 || ValRange == 5,
"We only know about trits and quints");
// We either have three quints or five trits
constexpr const int kNumVals = (ValRange == 5) ? 3 : 5;
// Depending on whether or not we're using quints or trits will determine
// the positions of the interleaved bits in the encoded block.
constexpr const int* const kInterleavedBits =
(ValRange == 5) ? kInterleavedQuintBits : kInterleavedTritBits;
// Set up the bits for reading
base::BitStream<base::UInt128> block_bit_src(block_bits, sizeof(block_bits) * 8);
// Decode the block
std::array<int, kNumVals> m;
uint64_t encoded = 0;
uint32_t encoded_bits_read = 0;
for (int i = 0; i < kNumVals; ++i) {
{
uint64_t bits = 0;
const bool result = block_bit_src.GetBits(num_bits, &bits);
assert(result);
(void)result;
m[i] = static_cast<int>(bits);
}
uint64_t encoded_bits;
{
const bool result = block_bit_src.GetBits(kInterleavedBits[i], &encoded_bits);
assert(result);
(void)result;
}
encoded |= encoded_bits << encoded_bits_read;
encoded_bits_read += kInterleavedBits[i];
}
// Make sure that our encoded trit/quint doesn't exceed its bounds
assert(ValRange != 3 || encoded < 256);
assert(ValRange != 5 || encoded < 128);
const int* const kEncodings = (ValRange == 5) ?
kQuintEncodings[encoded].data() : kTritEncodings[encoded].data();
std::array<int, kNumVals> result;
for (int i = 0; i < kNumVals; ++i) {
assert(m[i] < 1 << num_bits);
result[i] = kEncodings[i] << num_bits | m[i];
}
return result;
}
// Encode a single trit or quint block using the BISE (Bounded Integer Sequence
// Encoding) defined in Section C.2.12 of the ASTC specification. ValRange is
// expected to be either 3 or 5 depending on whether or not we're encoding trits
// or quints respectively. In other words, it is the remaining factor in whether
// the passed blocks contain encoded values of the form 3*2^k or 5*2^k.
template <int ValRange>
void EncodeISEBlock(const std::vector<int>& vals, int bits_per_val,
base::BitStream<base::UInt128>* bit_sink) {
static_assert(ValRange == 3 || ValRange == 5,
"We only know about trits and quints");
// We either have three quints or five trits
constexpr const int kNumVals = (ValRange == 5) ? 3 : 5;
// Three quints in seven bits or five trits in eight bits
constexpr const int kNumEncodedBitsPerBlock = (ValRange == 5) ? 7 : 8;
// Depending on whether or not we're using quints or trits will determine
// the positions of the interleaved bits in the encoding
constexpr const int* const kInterleavedBits =
(ValRange == 5) ? kInterleavedQuintBits : kInterleavedTritBits;
// ISE blocks can only have up to a specific number of values...
assert(vals.size() <= kNumVals);
// Split up into bits and non bits. Non bits are used to find the quint/trit
// encoding that we need.
std::array<int, kNumVals> non_bits = {{ 0 }};
std::array<int, kNumVals> bits = {{ 0 }};
for (size_t i = 0; i < vals.size(); ++i) {
bits[i] = vals[i] & ((1 << bits_per_val) - 1);
non_bits[i] = vals[i] >> bits_per_val;
assert(non_bits[i] < ValRange);
}
// We only need to add as many bits as necessary, so let's limit it based
// on the computation described in Section C.2.22 of the ASTC specification
const int total_num_bits =
((vals.size() * kNumEncodedBitsPerBlock + kNumVals - 1) / kNumVals)
+ vals.size() * bits_per_val;
int bits_added = 0;
// The number of bits used for the quint/trit encoding is necessary to know
// in order to properly select the encoding we need to represent.
int num_encoded_bits = 0;
for (int i = 0; i < kNumVals; ++i) {
bits_added += bits_per_val;
if (bits_added >= total_num_bits) {
break;
}
num_encoded_bits += kInterleavedBits[i];
bits_added += kInterleavedBits[i];
if (bits_added >= total_num_bits) {
break;
}
}
bits_added = 0;
assert(num_encoded_bits <= kNumEncodedBitsPerBlock);
// TODO(google): The faster way to do this would be to construct trees out
// of the quint/trit encoding patterns, or just invert the decoding logic.
// Here we go from the end backwards because it makes our tests are more
// deterministic.
int non_bit_encoding = -1;
for (int j = (1 << num_encoded_bits) - 1; j >= 0; --j) {
bool matches = true;
// We don't need to match all trits here, just the ones that correspond
// to the values that we passed in
for (size_t i = 0; i < kNumVals; ++i) {
if ((ValRange == 5 && kQuintEncodings[j][i] != non_bits[i]) ||
(ValRange == 3 && kTritEncodings[j][i] != non_bits[i])) {
matches = false;
break;
}
}
if (matches) {
non_bit_encoding = j;
break;
}
}
assert(non_bit_encoding >= 0);
// Now pack the bits into the block
for (size_t i = 0; i < vals.size(); ++i) {
// First add the base bits for this value
if (bits_added + bits_per_val <= total_num_bits) {
bit_sink->PutBits(bits[i], bits_per_val);
bits_added += bits_per_val;
}
// Now add the interleaved bits from the quint/trit
int num_int_bits = kInterleavedBits[i];
int int_bits = non_bit_encoding & ((1 << num_int_bits) - 1);
if (bits_added + num_int_bits <= total_num_bits) {
bit_sink->PutBits(int_bits, num_int_bits);
bits_added += num_int_bits;
non_bit_encoding >>= num_int_bits;
}
}
}
inline void CHECK_COUNTS(int trits, int quints) {
assert(trits == 0 || quints == 0); // Either trits or quints
assert(trits == 0 || trits == 1); // At most one trit
assert(quints == 0 || quints == 1); // At most one quint
(void)trits; (void)quints;
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
std::array<int, kNumPossibleRanges>::const_iterator ISERangeBegin() {
return kMaxRanges.cbegin();
}
std::array<int, kNumPossibleRanges>::const_iterator ISERangeEnd() {
return kMaxRanges.cend();
}
void IntegerSequenceCodec::GetCountsForRange(
int range, int* const trits, int* const quints, int* const bits) {
// Make sure the passed pointers are valid
assert(trits != nullptr);
assert(quints != nullptr);
assert(bits != nullptr);
// These are generally errors -- there should never be any ASTC values
// outside of this range
UTILS_RELEASE_ASSERT(range > 0);
UTILS_RELEASE_ASSERT(range < 1 << kLog2MaxRangeForBits);
*bits = 0;
*trits = 0;
*quints = 0;
// Search through the numbers of the form 2^n, 3 * 2^n and 5 * 2^n
const int max_vals_for_range =
*std::lower_bound(kMaxRanges.begin(), kMaxRanges.end(), range) + 1;
// Make sure we found something
assert(max_vals_for_range > 1);
// Find out what kind of range it is
if ((max_vals_for_range % 3 == 0) && IsPow2(max_vals_for_range / 3)) {
*bits = base::Log2Floor(max_vals_for_range / 3);
*trits = 1;
*quints = 0;
} else if ((max_vals_for_range % 5 == 0) && IsPow2(max_vals_for_range / 5)) {
*bits = base::Log2Floor(max_vals_for_range / 5);
*trits = 0;
*quints = 1;
} else if (IsPow2(max_vals_for_range)) {
*bits = base::Log2Floor(max_vals_for_range);
*trits = 0;
*quints = 0;
}
// If we set any of these values then we're done.
if ((*bits | *trits | *quints) != 0) {
CHECK_COUNTS(*trits, *quints);
}
}
// Returns the overall bit count for a range of val_count values encoded
// using the specified number of trits, quints and straight bits (respectively)
int IntegerSequenceCodec::GetBitCount(int num_vals,
int trits, int quints, int bits) {
CHECK_COUNTS(trits, quints);
// See section C.2.22 for the formula used here.
const int trit_bit_count = ((num_vals * 8 * trits) + 4) / 5;
const int quint_bit_count = ((num_vals * 7 * quints) + 2) / 3;
const int base_bit_count = num_vals * bits;
return trit_bit_count + quint_bit_count + base_bit_count;
}
IntegerSequenceCodec::IntegerSequenceCodec(int range) {
int trits, quints, bits;
GetCountsForRange(range, &trits, &quints, &bits);
InitializeWithCounts(trits, quints, bits);
}
IntegerSequenceCodec::IntegerSequenceCodec(
int trits, int quints, int bits) {
InitializeWithCounts(trits, quints, bits);
}
void IntegerSequenceCodec::InitializeWithCounts(
int trits, int quints, int bits) {
CHECK_COUNTS(trits, quints);
if (trits > 0) {
encoding_ = EncodingMode::kTritEncoding;
} else if (quints > 0) {
encoding_ = EncodingMode::kQuintEncoding;
} else {
encoding_ = EncodingMode::kBitEncoding;
}
bits_ = bits;
}
int IntegerSequenceCodec::NumValsPerBlock() const {
const std::array<int, 3> kNumValsByEncoding = {{ 5, 3, 1 }};
return kNumValsByEncoding[static_cast<int>(encoding_)];
}
int IntegerSequenceCodec::EncodedBlockSize() const {
const std::array<int, 3> kExtraBlockSizeByEncoding = {{ 8, 7, 0 }};
const int num_vals = NumValsPerBlock();
return kExtraBlockSizeByEncoding[static_cast<int>(encoding_)]
+ num_vals * bits_;
}
std::vector<int> IntegerSequenceDecoder::Decode(
int num_vals, base::BitStream<base::UInt128> *bit_src) const {
int trits = (encoding_ == kTritEncoding)? 1 : 0;
int quints = (encoding_ == kQuintEncoding)? 1 : 0;
const int total_num_bits = GetBitCount(num_vals, trits, quints, bits_);
const int bits_per_block = EncodedBlockSize();
assert(bits_per_block < 64);
int bits_left = total_num_bits;
std::vector<int> result;
while (bits_left > 0) {
uint64_t block_bits;
{
const bool result0 = bit_src->GetBits(std::min(bits_left, bits_per_block), &block_bits);
assert(result0);
(void)result0;
}
switch (encoding_) {
case kTritEncoding: {
auto trit_vals = DecodeISEBlock<3>(block_bits, bits_);
result.insert(result.end(), trit_vals.begin(), trit_vals.end());
}
break;
case kQuintEncoding: {
auto quint_vals = DecodeISEBlock<5>(block_bits, bits_);
result.insert(result.end(), quint_vals.begin(), quint_vals.end());
}
break;
case kBitEncoding:
result.push_back(static_cast<int>(block_bits));
break;
}
bits_left -= bits_per_block;
}
// Resize result to only contain as many values as requested
assert(result.size() >= static_cast<size_t>(num_vals));
result.resize(num_vals);
// Encoded all the values
return result;
}
void IntegerSequenceEncoder::Encode(base::BitStream<base::UInt128>* bit_sink) const {
// Go through all of the values and chop them up into blocks. The properties
// of the trit and quint encodings mean that if we need to encode fewer values
// in a block than the number of values encoded in the block then we need to
// consider the last few values to be zero.
auto next_val = vals_.begin();
while (next_val != vals_.end()) {
switch (encoding_) {
case kTritEncoding: {
std::vector<int> trit_vals;
for (int i = 0; i < 5; ++i) {
if (next_val != vals_.end()) {
trit_vals.push_back(*next_val);
++next_val;
}
}
EncodeISEBlock<3>(trit_vals, bits_, bit_sink);
}
break;
case kQuintEncoding: {
std::vector<int> quint_vals;
for (int i = 0; i < 3; ++i) {
if (next_val != vals_.end()) {
quint_vals.push_back(*next_val);
++next_val;
}
}
EncodeISEBlock<5>(quint_vals, bits_, bit_sink);
}
break;
case kBitEncoding: {
bit_sink->PutBits(*next_val, EncodedBlockSize());
++next_val;
}
break;
}
}
}
} // namespace astc_codec

View File

@@ -0,0 +1,169 @@
// 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.
#ifndef ASTC_CODEC_DECODER_INTEGER_SEQUENCE_CODEC_H_
#define ASTC_CODEC_DECODER_INTEGER_SEQUENCE_CODEC_H_
#include "src/base/bit_stream.h"
#include "src/base/uint128.h"
#include <array>
#include <string>
#include <vector>
namespace astc_codec {
// The maximum number of bits that we would need to encode an ISE value. The
// ASTC specification does not give a maximum number, however unquantized color
// values have a maximum range of 255, meaning that we can't feasibly have more
// than eight bits per value.
constexpr int kLog2MaxRangeForBits = 8;
// Ranges can take any of the the forms 2^k, 3*2^k, or 5*2^k for k up to
// kLog2MaxRangeForBits. Hence we have three types of ranges. Since the
// maximum encoded value is 255, k won't go larger than 8. We don't have quints
// that accompany [6, 8]-bits, as (5 * 2^6 = 320 > 255) and we don't have trits
// that accompany [7, 8]-bits, as (3 * 2^7 = 384 > 255). But we do have trits
// and quints that accompany no bits. Hence we have a total of
// 3 * kLog2MaxRangeForBits - 3 - 2 + 2 total ranges.
constexpr int kNumPossibleRanges = 3 * kLog2MaxRangeForBits - 3;
// Returns an iterator through the available ASTC ranges.
std::array<int, kNumPossibleRanges>::const_iterator ISERangeBegin();
std::array<int, kNumPossibleRanges>::const_iterator ISERangeEnd();
// Base class for ASTC integer sequence encoders and decoders. These codecs
// operate on sequences of integers and produce bit patterns that pack the
// integers based on the encoding scheme specified in the ASTC specification
// Section C.2.12. The resulting bit pattern is a sequence of encoded blocks.
// All blocks in a sequence are one of the following encodings:
//
// (1 -- bit encoding) one encoded value of the form 2^k
// (2 -- trit encoding) five encoded values of the form 3*2^k
// (3 -- quint encoding) three encoded values of the form 5*2^k
//
// The layouts of each block are designed such that the blocks can be truncated
// during encoding in order to support variable length input sequences (i.e. a
// sequence of values that are encoded using trit encoded blocks does not
// need to have a multiple-of-five length).
class IntegerSequenceCodec {
public:
// Returns the number of trits, quints, and bits needed to encode values in
// [0, range]. This is used to determine the layout of ISE encoded bit
// streams. The returned array holds the number of trits, quints, and bits
// respectively. range is expected to be within the interval [1, 5242879]
static void GetCountsForRange(int range, int* trits, int* quints, int* bits);
// Returns the number of bits needed to encode the given number of values with
// respect to the number of trits, quints, and bits specified in ise_counts
// (in that order). It is expected that either trits or quints can be
// nonzero, but not both, and neither can be larger than one. Anything else is
// undefined.
static int GetBitCount(int num_vals, int trits, int quints, int bits);
// Convenience function that returns the number of bits needed to encoded
// num_vals within the range [0, range] (inclusive).
static inline int GetBitCountForRange(int num_vals, int range) {
int trits, quints, bits;
GetCountsForRange(range, &trits, &quints, &bits);
return GetBitCount(num_vals, trits, quints, bits);
}
protected:
explicit IntegerSequenceCodec(int range);
IntegerSequenceCodec(int trits, int quints, int bits);
// The encoding mode -- since having trits and quints are mutually exclusive,
// we can store the encoding we decide on in this enum.
enum EncodingMode {
kTritEncoding = 0,
kQuintEncoding,
kBitEncoding,
};
EncodingMode encoding_;
int bits_;
// Returns the number of values stored in a single ISE block. Since quints and
// trits are packed three/five to a bit pattern (respectively), each sequence
// is chunked into blocks in order to encode it. For only bit-encodings, the
// block size is one.
int NumValsPerBlock() const;
// Returns the size of a single ISE block in bits (see NumValsPerBlock).
int EncodedBlockSize() const;
private:
// Determines the encoding mode.
void InitializeWithCounts(int trits, int quints, int bits);
};
// The integer sequence decoder. The decoder only remembers the given encoding
// but each invocation of Decode operates independently on the input bits.
class IntegerSequenceDecoder : public IntegerSequenceCodec {
public:
// Creates a decoder that decodes values within [0, range] (inclusive).
explicit IntegerSequenceDecoder(int range)
: IntegerSequenceCodec(range) { }
// Creates a decoder based on the number of trits, quints, and bits expected
// in the bit stream passed to Decode.
IntegerSequenceDecoder(int trits, int quints, int bits)
: IntegerSequenceCodec(trits, quints, bits) { }
// Decodes num_vals from the bit_src. The number of bits read is dependent
// on the number of bits required to encode num_vals based on the calculation
// provided in Section C.2.22 of the ASTC specification. The return value
// always contains exactly num_vals.
std::vector<int> Decode(int num_vals,
base::BitStream<base::UInt128>* bit_src) const;
};
// The integer sequence encoder. The encoder accepts values one by one and
// places them into a temporary array that it holds. When needed the user
// may call Encode to produce an encoded bit stream of the associated values.
class IntegerSequenceEncoder : public IntegerSequenceCodec {
public:
// Creates an encoder that encodes values within [0, range] (inclusive).
explicit IntegerSequenceEncoder(int range)
: IntegerSequenceCodec(range) { }
// Creates an encoder based on the number of trits, quints, and bits for
// the bit stream produced by Encode.
IntegerSequenceEncoder(int trits, int quints, int bits)
: IntegerSequenceCodec(trits, quints, bits) { }
// Adds a value to the encoding sequence.
void AddValue(int val) {
// Make sure it's within bounds
assert(encoding_ != EncodingMode::kTritEncoding || val < 3 * (1 << bits_));
assert(encoding_ != EncodingMode::kQuintEncoding || val < 5 * (1 << bits_));
assert(encoding_ != EncodingMode::kBitEncoding || val < (1 << bits_));
vals_.push_back(val);
}
// Writes the encoding for vals_ to the bit_sink. Multiple calls to Encode
// will produce the same result.
void Encode(base::BitStream<base::UInt128>* bit_sink) const;
// Removes all of the previously added values to the encoder.
void Reset() { vals_.clear(); }
private:
std::vector<int> vals_;
};
} // namespace astc_codec
#endif // ASTC_CODEC_DECODER_INTEGER_SEQUENCE_CODEC_H_

View File

@@ -0,0 +1,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() !=
size_t(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() == uint32_t(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() == uint32_t(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

View File

@@ -0,0 +1,128 @@
// 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.
#ifndef ASTC_CODEC_DECODER_INTERMEDIATE_ASTC_BLOCK_H_
#define ASTC_CODEC_DECODER_INTERMEDIATE_ASTC_BLOCK_H_
#include "src/base/optional.h"
#include "src/base/uint128.h"
#include "src/decoder/physical_astc_block.h"
#include <array>
#include <vector>
namespace astc_codec {
// From Table C.2.7 -- These are the only valid ranges that weight
// values can take.
constexpr std::array<int, 12> kValidWeightRanges =
{{ 1, 2, 3, 4, 5, 7, 9, 11, 15, 19, 23, 31 }};
// Void extent data are all the ASTC blocks that are labeled for having a
// constant color. In the ASTC spec, some of these blocks may optionally
// have "void extent coordinates" that describe how far in texture space
// the constant color should span. If these coordinates are not valid,
// then the coordinates are all set to a fully saturated bit mask
// ((1 << 13) - 1) and the block is treated as a singular constant color.
// We call both types of these blocks "void extent" to remove confusion
// in our code.
struct VoidExtentData {
uint16_t r;
uint16_t g;
uint16_t b;
uint16_t a;
std::array<uint16_t, 4> coords;
};
// Intermediate endpoint data. Really this is just an endpoint mode
// and a couple of values that represent the data used to decode the
// RGB values from the color endpoint mode.
struct IntermediateEndpointData {
ColorEndpointMode mode;
std::vector<int> colors;
};
// This is an unpacked physical ASTC block, but it does not have enough
// information to be worked with logically. It is simply a container of
// all of the unpacked ASTC information. It is used as a staging area
// for the information that is later Pack()'d into a PhysicalASTCBlock.
struct IntermediateBlockData {
int weight_grid_dim_x;
int weight_grid_dim_y;
int weight_range;
// Quantized, non-interpolated weights
std::vector<int> weights;
// The 10-bit partition ID if we need one
base::Optional<int> partition_id;
// The dual-plane channel in [0, 3] if it exists.
base::Optional<int> dual_plane_channel;
// The quantized/encoded endpoint values for this block. The range of each
// endpoint value is specified by |endpoint_range|, if it exists. If not, the
// range can be queried by calling EndpointRangeForBlock
std::vector<IntermediateEndpointData> endpoints;
// The range [0, endpoint_range] that any one endpoint value can take. Users
// should not write to this value themselves. If it is empty at the time
// someone calls Pack(), it will be automatically inferred. Otherwise, it is
// set by Unpack() based on what the underlying encoding specified.
base::Optional<int> endpoint_range;
};
// Returns the maximum value that a given endpoint value can take according to
// the other settings in the block. Ignores the |endpoint_range| member
// variable. Returns negative values on error:
// -1 : Too many bits required to store weight grid
// -2 : There are too few bits allocated for color endpoint data according to
// C.2.24 in the ASTC spec
int EndpointRangeForBlock(const IntermediateBlockData& data);
inline int EndpointRangeForBlock(const VoidExtentData& data);
// Unpacks the physical ASTC block into the intermediate block. Returns false
// if the physical block is an error encoded block, or if the physical block
// is a void extent block. On error the contents of ib are undefined.
base::Optional<IntermediateBlockData> UnpackIntermediateBlock(
const PhysicalASTCBlock& pb);
// Unpacks the physical ASTC block into a void extent block. Returns false
// if the physical block is an error encoded block, or if the physical block
// is an intermediate block. On error the contents of ib are undefined.
base::Optional<VoidExtentData> UnpackVoidExtent(const PhysicalASTCBlock& pb);
// Packs the given intermediate block into a physical block. Returns an error
// string if the provided values in the intermediate block emit an illegal ASTC
// encoding. In this case the results in the physical block are undefined.
base::Optional<std::string> Pack(const IntermediateBlockData& data,
base::UInt128* pb);
// Packs the given void extent block into a physical block. Returns an error
// string if the provided values in the void extent block emit an illegal ASTC
// encoding. In this case the results in the physical block are undefined.
base::Optional<std::string> Pack(const VoidExtentData& data, base::UInt128* pb);
////////////////////////////////////////////////////////////////////////////////
//
// Impl
inline int EndpointRangeForBlock(const VoidExtentData&) {
// Void extent blocks use 16-bit ARGB definitions
return (1 << 16) - 1;
}
} // namespace astc_codec
#endif // ASTC_CODEC_DECODER_INTERMEDIATE_ASTC_BLOCK_H_

View File

@@ -0,0 +1,262 @@
// 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/logical_astc_block.h"
#include "src/decoder/endpoint_codec.h"
#include "src/decoder/footprint.h"
#include "src/decoder/integer_sequence_codec.h"
#include "src/decoder/quantization.h"
#include "src/decoder/weight_infill.h"
namespace astc_codec {
namespace {
Partition GenerateSinglePartition(Footprint footprint) {
return Partition { footprint, /* num_parts = */ 1, /* partition_id = */ 0,
std::vector<int>(footprint.NumPixels(), 0) };
}
static std::vector<EndpointPair> DecodeEndpoints(const IntermediateBlockData& block) {
const int endpoint_range = block.endpoint_range
? block.endpoint_range.value()
: EndpointRangeForBlock(block);
assert(endpoint_range > 0);
std::vector<EndpointPair> endpoints;
for (const auto& eps : block.endpoints) {
RgbaColor decmp_one_rgba, decmp_two_rgba;
DecodeColorsForMode(eps.colors, endpoint_range, eps.mode,
&decmp_one_rgba, &decmp_two_rgba);
endpoints.emplace_back(decmp_one_rgba, decmp_two_rgba);
}
return endpoints;
}
static std::vector<EndpointPair> DecodeEndpoints(const VoidExtentData& block) {
EndpointPair eps;
eps.first[0] = eps.second[0] = (block.r * 255) / 65535;
eps.first[1] = eps.second[1] = (block.g * 255) / 65535;
eps.first[2] = eps.second[2] = (block.b * 255) / 65535;
eps.first[3] = eps.second[3] = (block.a * 255) / 65535;
std::vector<EndpointPair> endpoints;
endpoints.emplace_back(eps);
return endpoints;
}
Partition ComputePartition(const Footprint& footprint,
const IntermediateBlockData& block) {
if (block.partition_id) {
const int part_id = block.partition_id.value();
const size_t num_parts = block.endpoints.size();
return GetASTCPartition(footprint, num_parts, part_id);
} else {
return GenerateSinglePartition(footprint);
}
}
Partition ComputePartition(const Footprint& footprint, const VoidExtentData&) {
return GenerateSinglePartition(footprint);
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
LogicalASTCBlock::LogicalASTCBlock(const Footprint& footprint)
: endpoints_(1),
weights_(footprint.NumPixels(), 0),
partition_(GenerateSinglePartition(footprint)) { }
LogicalASTCBlock::LogicalASTCBlock(const Footprint& footprint,
const IntermediateBlockData& block)
: endpoints_(DecodeEndpoints(block)),
partition_(ComputePartition(footprint, block)) {
CalculateWeights(footprint, block);
}
LogicalASTCBlock::LogicalASTCBlock(const Footprint& footprint,
const VoidExtentData& block)
: endpoints_(DecodeEndpoints(block)),
partition_(ComputePartition(footprint, block)) {
CalculateWeights(footprint, block);
}
void LogicalASTCBlock::CalculateWeights(const Footprint& footprint,
const IntermediateBlockData& block) {
const int grid_size_x = block.weight_grid_dim_x;
const int grid_size_y = block.weight_grid_dim_y;
const int weight_grid_size = grid_size_x * grid_size_y;
// Either we have a dual plane and we have twice as many weights as
// specified or we don't
assert(block.dual_plane_channel
? block.weights.size() == size_t(weight_grid_size * 2)
: block.weights.size() == size_t(weight_grid_size));
std::vector<int> unquantized;
unquantized.reserve(weight_grid_size);
// According to C.2.16, if we have dual-plane weights, then we have two
// weights per texel -- one adjacent to the other. Hence, we have to skip
// some when we decode the separate weight values.
const int weight_frequency = (block.dual_plane_channel) ? 2 : 1;
const int weight_range = block.weight_range;
for (int i = 0; i < weight_grid_size; ++i) {
const int weight = block.weights[i * weight_frequency];
unquantized.push_back(UnquantizeWeightFromRange(weight, weight_range));
}
weights_ = InfillWeights(unquantized, footprint, grid_size_x, grid_size_y);
if (block.dual_plane_channel) {
SetDualPlaneChannel(block.dual_plane_channel.value());
for (int i = 0; i < weight_grid_size; ++i) {
const int weight = block.weights[i * weight_frequency + 1];
unquantized[i] = UnquantizeWeightFromRange(weight, weight_range);
}
dual_plane_->weights =
InfillWeights(unquantized, footprint, grid_size_x, grid_size_y);
}
}
void LogicalASTCBlock::CalculateWeights(const Footprint& footprint,
const VoidExtentData&) {
weights_ = std::vector<int>(footprint.NumPixels(), 0);
}
void LogicalASTCBlock::SetWeightAt(int x, int y, int weight) {
assert(weight >= 0);
assert(weight <= 64);
weights_.at(y * GetFootprint().Width() + x) = weight;
}
int LogicalASTCBlock::WeightAt(int x, int y) const {
return weights_.at(y * GetFootprint().Width() + x);
}
void LogicalASTCBlock::SetDualPlaneWeightAt(int channel, int x, int y,
int weight) {
assert(weight >= 0);
assert(weight <= 64);
// If it's not a dual plane, then this has no logical meaning
assert(IsDualPlane());
// If it is a dual plane and the passed channel matches the query, then we
// return the specialized weights
if (dual_plane_->channel == channel) {
dual_plane_->weights.at(y * GetFootprint().Width() + x) = weight;
} else {
// If the channel is not the special channel, then return the general weight
SetWeightAt(x, y, weight);
}
}
int LogicalASTCBlock::DualPlaneWeightAt(int channel, int x, int y) const {
// If it's not a dual plane, then we just return the weight for all channels
if (!IsDualPlane()) {
// TODO(google): Log warning, Requesting dual-plane channel for a non
// dual-plane block!
return WeightAt(x, y);
}
// If it is a dual plane and the passed channel matches the query, then we
// return the specialized weights
if (dual_plane_->channel == channel) {
return dual_plane_->weights.at(y * GetFootprint().Width() + x);
}
// If the channel is not the special channel, then return the general weight
return WeightAt(x, y);
}
void LogicalASTCBlock::SetDualPlaneChannel(int channel) {
if (channel < 0) {
dual_plane_.clear();
} else if (dual_plane_) {
dual_plane_->channel = channel;
} else {
dual_plane_ = DualPlaneData {channel, weights_};
}
}
RgbaColor LogicalASTCBlock::ColorAt(int x, int y) const {
const auto footprint = GetFootprint();
assert(x >= 0); assert(x < footprint.Width());
assert(y >= 0); assert(y < footprint.Height());
const int texel_idx = y * footprint.Width() + x;
const int part = partition_.assignment[texel_idx];
const auto& endpoints = endpoints_[part];
RgbaColor result;
for (int channel = 0; channel < 4; ++channel) {
const int weight = (dual_plane_ && dual_plane_->channel == channel)
? dual_plane_->weights[texel_idx]
: weights_[texel_idx];
const int p0 = endpoints.first[channel];
const int p1 = endpoints.second[channel];
assert(p0 >= 0); assert(p0 < 256);
assert(p1 >= 0); assert(p1 < 256);
// According to C.2.19
const int c0 = (p0 << 8) | p0;
const int c1 = (p1 << 8) | p1;
const int c = (c0 * (64 - weight) + c1 * weight + 32) / 64;
// TODO(google): Handle conversion to sRGB or FP16 per C.2.19.
const int quantized = ((c * 255) + 32767) / 65536;
assert(quantized < 256);
result[channel] = quantized;
}
return result;
}
void LogicalASTCBlock::SetPartition(const Partition& p) {
assert(p.footprint == partition_.footprint &&
"New partitions may not be for a different footprint");
partition_ = p;
endpoints_.resize(p.num_parts);
}
void LogicalASTCBlock::SetEndpoints(const EndpointPair& eps, int subset) {
assert(subset < partition_.num_parts);
assert(size_t(subset) < endpoints_.size());
endpoints_[subset] = eps;
}
base::Optional<LogicalASTCBlock> UnpackLogicalBlock(
const Footprint& footprint, const PhysicalASTCBlock& pb) {
if (pb.IsVoidExtent()) {
base::Optional<VoidExtentData> ve = UnpackVoidExtent(pb);
if (!ve) {
return {};
}
return LogicalASTCBlock(footprint, ve.value());
} else {
base::Optional<IntermediateBlockData> ib = UnpackIntermediateBlock(pb);
if (!ib) {
return {};
}
return LogicalASTCBlock(footprint, ib.value());
}
}
} // namespace astc_codec

View File

@@ -0,0 +1,127 @@
// 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.
#ifndef ASTC_CODEC_DECODER_LOGICAL_ASTC_BLOCK_H_
#define ASTC_CODEC_DECODER_LOGICAL_ASTC_BLOCK_H_
#include "src/base/optional.h"
#include "src/decoder/footprint.h"
#include "src/decoder/intermediate_astc_block.h"
#include "src/decoder/partition.h"
#include "src/decoder/physical_astc_block.h"
#include <array>
#include <utility>
#include <vector>
namespace astc_codec {
// A logical ASTC block holds the endpoints, indices, and partition information
// of a compressed block. These values generally do not adhere to any
// quality-for-bitrate-imposed limits and are solely logical entities for
// determining the best representation of a given block.
class LogicalASTCBlock {
public:
LogicalASTCBlock(const LogicalASTCBlock&) = default;
LogicalASTCBlock(LogicalASTCBlock&&) = default;
// Unpack an intermediate block into a logical one.
LogicalASTCBlock(const Footprint& footprint,
const IntermediateBlockData& block);
// Unpack a void extent intermediate block into a logical one.
LogicalASTCBlock(const Footprint& footprint, const VoidExtentData& block);
// Create a new, empty ASTC block
explicit LogicalASTCBlock(const Footprint& footprint);
// Returns the footprint associated with this block. The footprint is defined
// via the partition, because the partition definition is dependent on the
// footprint.
const Footprint& GetFootprint() const { return partition_.footprint; }
// Returns the unquantized and infilled weight in the range [0, 64] for the
// given texel location. Assumes that the block is a single-plane block,
// meaning that weights are used equally across all channels.
void SetWeightAt(int x, int y, int weight);
int WeightAt(int x, int y) const;
// Returns the unquantized and infilled weight in the range [0, 64] for the
// given channel at the given texel location. If the block does not have a
// dual-plane channel then the reference-returning version will fail, as it
// cannot return a reference to a value that (potentially) doesn't exist.
void SetDualPlaneWeightAt(int channel, int x, int y, int weight);
int DualPlaneWeightAt(int channel, int x, int y) const;
// Returns the color as it would be in the given pixel coordinates of the
// block. Fails if the coordinates are outside of the range of the block
// footprint
RgbaColor ColorAt(int x, int y) const;
// Sets the current partition for the block. |p|'s footprint must match the
// return value of GetFootprint() or else this call will fail.
void SetPartition(const Partition& p);
// Sets the endpoints for the given subset.
void SetEndpoints(const EndpointPair& eps, int subset);
void SetEndpoints(const Endpoint& ep1, const Endpoint& ep2, int subset) {
SetEndpoints(std::make_pair(ep1, ep2), subset);
}
// Sets the dual plane channel for the block. Value must be within the range
// [0, 3]. If a negative value is passed, then the dual-plane data for the
// block is removed, and the block is treated as a single-plane block.
void SetDualPlaneChannel(int channel);
bool IsDualPlane() const { return dual_plane_.hasValue(); }
private:
// A block may have up to four endpoint pairs.
std::vector<EndpointPair> endpoints_;
// Weights are stored as values in the interval [0, 64].
std::vector<int> weights_;
// The partition information for this block. This determines the
// appropriate subsets that each pixel should belong to.
Partition partition_;
// Dual plane data holds both the channel and the weights that describe
// the dual plane data for the given block. If a block has a dual plane, then
// we need to know both the channel and the weights associated with it.
struct DualPlaneData {
int channel;
std::vector<int> weights;
};
// The dual-plane data is optional from a logical representation of the block.
base::Optional<DualPlaneData> dual_plane_;
// Calculates the unquantized and interpolated weights from the encoded weight
// values and possibly dual-plane weights specified in the passed ASTC block.
void CalculateWeights(const Footprint& footprint,
const IntermediateBlockData& block);
// Calculates the weights for a VoidExtentBlock.
void CalculateWeights(const Footprint& footprint,
const VoidExtentData& block);
};
// Unpacks the physical ASTC block into a logical block. Returns false if the
// physical block is an error encoded block.
base::Optional<LogicalASTCBlock> UnpackLogicalBlock(
const Footprint& footprint, const PhysicalASTCBlock& pb);
} // namespace astc_codec
#endif // ASTC_CODEC_DECODER_LOGICAL_ASTC_BLOCK_H_

View File

@@ -0,0 +1,601 @@
// 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/partition.h"
#include "src/base/bottom_n.h"
#include "src/base/utils.h"
#include "src/decoder/footprint.h"
#include <algorithm>
#include <array>
#include <limits>
#include <memory>
#include <numeric>
#include <queue>
#include <set>
#include <unordered_set>
#include <utility>
namespace astc_codec {
namespace {
// The maximum number of partitions supported by ASTC is four.
constexpr int kMaxNumSubsets = 4;
// Partition selection function based on the ASTC specification.
// See section C.2.21
int SelectASTCPartition(int seed, int x, int y, int z, int partitioncount,
int num_pixels) {
if (partitioncount <= 1) {
return 0;
}
if (num_pixels < 31) {
x <<= 1;
y <<= 1;
z <<= 1;
}
seed += (partitioncount - 1) * 1024;
uint32_t rnum = seed;
rnum ^= rnum >> 15;
rnum -= rnum << 17;
rnum += rnum << 7;
rnum += rnum << 4;
rnum ^= rnum >> 5;
rnum += rnum << 16;
rnum ^= rnum >> 7;
rnum ^= rnum >> 3;
rnum ^= rnum << 6;
rnum ^= rnum >> 17;
uint8_t seed1 = rnum & 0xF;
uint8_t seed2 = (rnum >> 4) & 0xF;
uint8_t seed3 = (rnum >> 8) & 0xF;
uint8_t seed4 = (rnum >> 12) & 0xF;
uint8_t seed5 = (rnum >> 16) & 0xF;
uint8_t seed6 = (rnum >> 20) & 0xF;
uint8_t seed7 = (rnum >> 24) & 0xF;
uint8_t seed8 = (rnum >> 28) & 0xF;
uint8_t seed9 = (rnum >> 18) & 0xF;
uint8_t seed10 = (rnum >> 22) & 0xF;
uint8_t seed11 = (rnum >> 26) & 0xF;
uint8_t seed12 = ((rnum >> 30) | (rnum << 2)) & 0xF;
seed1 *= seed1;
seed2 *= seed2;
seed3 *= seed3;
seed4 *= seed4;
seed5 *= seed5;
seed6 *= seed6;
seed7 *= seed7;
seed8 *= seed8;
seed9 *= seed9;
seed10 *= seed10;
seed11 *= seed11;
seed12 *= seed12;
int sh1, sh2, sh3;
if (seed & 1) {
sh1 = (seed & 2 ? 4 : 5);
sh2 = (partitioncount == 3 ? 6 : 5);
} else {
sh1 = (partitioncount == 3 ? 6 : 5);
sh2 = (seed & 2 ? 4 : 5);
}
sh3 = (seed & 0x10) ? sh1 : sh2;
seed1 >>= sh1;
seed2 >>= sh2;
seed3 >>= sh1;
seed4 >>= sh2;
seed5 >>= sh1;
seed6 >>= sh2;
seed7 >>= sh1;
seed8 >>= sh2;
seed9 >>= sh3;
seed10 >>= sh3;
seed11 >>= sh3;
seed12 >>= sh3;
int a = seed1 * x + seed2 * y + seed11 * z + (rnum >> 14);
int b = seed3 * x + seed4 * y + seed12 * z + (rnum >> 10);
int c = seed5 * x + seed6 * y + seed9 * z + (rnum >> 06);
int d = seed7 * x + seed8 * y + seed10 * z + (rnum >> 02);
a &= 0x3F;
b &= 0x3F;
c &= 0x3F;
d &= 0x3F;
if (partitioncount <= 3) {
d = 0;
}
if (partitioncount <= 2) {
c = 0;
}
if (a >= b && a >= c && a >= d) {
return 0;
} else if (b >= c && b >= d) {
return 1;
} else if (c >= d) {
return 2;
} else {
return 3;
}
}
// A partition hash that we can pass to containers like std::unordered_set
struct PartitionHasher {
size_t operator()(const Partition& part) const {
// The issue here is that if we have two different partitions, A and B, then
// their hash should be equal if A and B are equal. We define the distance
// between A and B using PartitionMetric, but internally that finds a 1-1
// mapping from labels in A to labels in B.
//
// With that in mind, when we define a hash for partitions, we need to find
// a 1-1 mapping to a 'universal' labeling scheme. Here we define that as
// the heuristic: the first encountered label will be 0, the second will be
// 1, etc. This creates a unique 1-1 mapping scheme from any partition.
//
// Note, we can't use this heuristic for the PartitionMetric, as it will
// generate very large discrepancies between similar labellings (for example
// 000...001 vs 011...111). We are just looking for a boolean distinction
// whether or not two partitions are different and don't care how different
// they are.
std::array<int, kMaxNumSubsets> mapping {{ -1, -1, -1, -1 }};
int next_subset = 0;
for (int subset : part.assignment) {
if (mapping[subset] < 0) {
mapping[subset] = next_subset++;
}
}
assert(next_subset <= kMaxNumSubsets);
// The return value will be the hash of the assignment according to this
// mapping
const size_t seed0 = 0;
return std::accumulate(part.assignment.begin(), part.assignment.end(), seed0,
[&mapping](size_t seed, const int& subset) {
std::hash<size_t> hasher;
const int s = mapping[subset];
return hasher(seed) ^ hasher(static_cast<size_t>(s));
});
}
};
// Construct a VP-Tree of partitions. Since our PartitionMetric satisfies
// the triangle inequality, we can use this general higher-dimensional space
// partitioning tree to organize our partitions.
//
// TODO(google): !SPEED! Right now this tree stores an actual linked
// structure of pointers which is likely very slow during construction and
// very not cache-coherent during traversal, so it'd probably be good to
// switch to a flattened binary tree structure if performance becomes an
// issue.
class PartitionTree {
public:
// Unclear what it means to have an uninitialized tree, so delete default
// constructors, but allow the tree to be moved
PartitionTree() = delete;
PartitionTree(const PartitionTree&) = delete;
PartitionTree(PartitionTree&& t) = default;
// Generate a PartitionTree from iterators over |Partition|s
template<typename Itr>
PartitionTree(Itr begin, Itr end) : parts_(begin, end) {
std::vector<int> part_indices(parts_.size());
std::iota(part_indices.begin(), part_indices.end(), 0);
root_ = std::unique_ptr<PartitionTreeNode>(
new PartitionTreeNode(parts_, part_indices));
}
// Search for the k-nearest partitions that are closest to part based on
// the result of PartitionMetric
void Search(const Partition& part, int k,
std::vector<const Partition*>* const results,
std::vector<int>* const distances) const {
ResultHeap heap(k);
SearchNode(root_, part, &heap);
results->clear();
if (nullptr != distances) {
distances->clear();
}
std::vector<ResultNode> search_results = heap.Pop();
for (const auto& result : search_results) {
results->push_back(&parts_[result.part_idx]);
if (nullptr != distances) {
distances->push_back(result.distance);
}
}
assert(results->size() == size_t(k));
}
private:
// Heap elements to be stored while searching the tree. The two relevant
// pieces of information are the partition index and it's distance from the
// queried partition.
struct ResultNode {
int part_idx;
int distance;
// Heap based on distance from query point.
bool operator<(const ResultNode& other) const {
return distance < other.distance;
}
};
using ResultHeap = base::BottomN<ResultNode>;
struct PartitionTreeNode {
int part_idx;
int split_dist;
std::unique_ptr<PartitionTreeNode> left;
std::unique_ptr<PartitionTreeNode> right;
PartitionTreeNode(const std::vector<Partition> &parts,
const std::vector<int> &part_indices)
: split_dist(-1) {
assert(part_indices.size() > 0);
right.reset(nullptr);
left.reset(nullptr);
// Store the first node as our vantage point
part_idx = part_indices[0];
const Partition& vantage_point = parts[part_indices[0]];
// Calculate the distances of the remaining nodes against the vantage
// point.
std::vector<std::pair<int, int>> part_dists;
for (size_t i = 1; i < part_indices.size(); ++i) {
const int idx = part_indices[i];
const int dist = PartitionMetric(vantage_point, parts[idx]);
if (dist > 0) {
part_dists.push_back(std::make_pair(idx, dist));
}
}
// If there are no more different parts, then this is a leaf node
if (part_dists.empty()) {
return;
}
struct OrderBySecond {
typedef std::pair<int, int> PairType;
bool operator()(const PairType& lhs, const PairType& rhs) {
return lhs.second < rhs.second;
}
};
// We want to partition the set such that the points are ordered
// based on their distances from the vantage point. We can do this
// using the partial sort of nth element.
std::nth_element(
part_dists.begin(), part_dists.begin() + part_dists.size() / 2,
part_dists.end(), OrderBySecond());
// Once that's done, our split position is in the middle
const auto split_iter = part_dists.begin() + part_dists.size() / 2;
split_dist = split_iter->second;
// Recurse down the right and left sub-trees with the indices of the
// parts that are farther and closer respectively
std::vector<int> right_indices;
for (auto itr = split_iter; itr != part_dists.end(); ++itr) {
right_indices.push_back(itr->first);
}
if (!right_indices.empty()) {
right.reset(new PartitionTreeNode(parts, right_indices));
}
std::vector<int> left_indices;
for (auto itr = part_dists.begin(); itr != split_iter; ++itr) {
left_indices.push_back(itr->first);
}
if (!left_indices.empty()) {
left.reset(new PartitionTreeNode(parts, left_indices));
}
}
};
void SearchNode(const std::unique_ptr<PartitionTreeNode>& node,
const Partition& p, ResultHeap* const heap) const {
if (nullptr == node) {
return;
}
// Calculate distance against current node
const int dist = PartitionMetric(parts_[node->part_idx], p);
// Push it onto the heap and remove the top-most nodes to maintain
// closest k indices.
ResultNode result;
result.part_idx = node->part_idx;
result.distance = dist;
heap->Push(result);
// If the split distance is uninitialized, it means we have no children.
if (node->split_dist < 0) {
assert(nullptr == node->left);
assert(nullptr == node->right);
return;
}
// Next we need to check the left and right trees if their distance
// is closer/farther than the farthest element on the heap
const int tau = heap->Top().distance;
if (dist + tau < node->split_dist || dist - tau < node->split_dist) {
SearchNode(node->left, p, heap);
}
if (dist + tau > node->split_dist || dist - tau > node->split_dist) {
SearchNode(node->right, p, heap);
}
}
std::vector<Partition> parts_;
std::unique_ptr<PartitionTreeNode> root_;
};
// A helper function that generates all of the partitions for each number of
// subsets in ASTC blocks and stores them in a PartitionTree for fast retrieval.
const int kNumASTCPartitionIDBits = 10;
PartitionTree GenerateASTCPartitionTree(Footprint footprint) {
std::unordered_set<Partition, PartitionHasher> parts;
for (int num_parts = 2; num_parts <= kMaxNumSubsets; ++num_parts) {
for (int id = 0; id < (1 << kNumASTCPartitionIDBits); ++id) {
Partition part = GetASTCPartition(footprint, num_parts, id);
// Make sure we're not using a degenerate partition assignment that wastes
// an endpoint pair...
bool valid_part = true;
for (int i = 0; i < num_parts; ++i) {
if (std::find(part.assignment.begin(), part.assignment.end(), i) ==
part.assignment.end()) {
valid_part = false;
break;
}
}
if (valid_part) {
parts.insert(std::move(part));
}
}
}
return PartitionTree(parts.begin(), parts.end());
}
// To avoid needing any fancy boilerplate for mapping from a width, height
// tuple, we can define a simple encoding for the block mode:
constexpr int EncodeDims(int width, int height) {
return (width << 16) | height;
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
int PartitionMetric(const Partition& a, const Partition& b) {
// Make sure that one partition is at least a subset of the other...
UTILS_RELEASE_ASSERT(a.footprint == b.footprint);
// Make sure that the number of parts is within our limits. ASTC has a maximum
// of four subsets per block according to the specification.
UTILS_RELEASE_ASSERT(a.num_parts <= kMaxNumSubsets);
UTILS_RELEASE_ASSERT(b.num_parts <= kMaxNumSubsets);
const int w = a.footprint.Width();
const int h = b.footprint.Height();
struct PairCount {
int a;
int b;
int count;
// Comparison needed for sort below.
bool operator>(const PairCount& other) const {
return count > other.count;
}
};
// Since we need to find the smallest mapping from labels in A to labels in B,
// we need to store each label pair in a structure that can later be sorted.
// The maximum number of subsets in an ASTC block is four, meaning that
// between the two partitions, we can have up to sixteen different pairs.
std::array<PairCount, 16> pair_counts;
for (int y = 0; y < 4; ++y) {
for (int x = 0; x < 4; ++x) {
const int idx = y * 4 + x;
pair_counts[idx].a = x;
pair_counts[idx].b = y;
pair_counts[idx].count = 0;
}
}
// Count how many times we see each pair of assigned values (order matters!)
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
const int idx = y * w + x;
const int a_val = a.assignment[idx];
const int b_val = b.assignment[idx];
assert(a_val >= 0);
assert(b_val >= 0);
assert(a_val < 4);
assert(b_val < 4);
++(pair_counts[b_val * 4 + a_val].count);
}
}
// Sort the pairs in descending order based on their count
std::sort(pair_counts.begin(), pair_counts.end(), std::greater<PairCount>());
// Now assign pairs one by one until we have no more pairs to assign. Once
// a value from A is assigned to a value in B, it can no longer be reassigned,
// so we can keep track of this in a matrix. Similarly, to keep the assignment
// one-to-one, once a value in B has been assigned to, it cannot be assigned
// to again.
std::array<std::array<bool, kMaxNumSubsets>, kMaxNumSubsets> assigned { };
int pixels_matched = 0;
for (const auto& pair_count : pair_counts) {
bool is_assigned = false;
for (int i = 0; i < kMaxNumSubsets; ++i) {
is_assigned |= assigned.at(pair_count.a).at(i);
is_assigned |= assigned.at(i).at(pair_count.b);
}
if (!is_assigned) {
assigned.at(pair_count.a).at(pair_count.b) = true;
pixels_matched += pair_count.count;
}
}
// The difference is the number of pixels that had an assignment versus the
// total number of pixels.
return w * h - pixels_matched;
}
// Generates the partition assignment for the given block attributes.
Partition GetASTCPartition(const Footprint& footprint, int num_parts,
int partition_id) {
// Partitions must have at least one subset but may have at most four
assert(num_parts >= 0);
assert(num_parts <= kMaxNumSubsets);
// Partition ID can be no more than 10 bits.
assert(partition_id >= 0);
assert(partition_id < 1 << 10);
Partition part = {footprint, num_parts, partition_id, /* assignment = */ {}};
part.assignment.reserve(footprint.NumPixels());
// Maintain column-major order so that we match all of the image processing
// algorithms that depend on this class.
for (int y = 0; y < footprint.Height(); ++y) {
for (int x = 0; x < footprint.Width(); ++x) {
const int p = SelectASTCPartition(partition_id, x, y, 0, num_parts,
footprint.NumPixels());
part.assignment.push_back(p);
}
}
return part;
}
const std::vector<const Partition*> FindKClosestASTCPartitions(
const Partition& candidate, int k) {
const int encoded_dims = EncodeDims(candidate.footprint.Width(),
candidate.footprint.Height());
int index = 0;
switch (encoded_dims) {
case EncodeDims(4, 4): index = 0; break;
case EncodeDims(5, 4): index = 1; break;
case EncodeDims(5, 5): index = 2; break;
case EncodeDims(6, 5): index = 3; break;
case EncodeDims(6, 6): index = 4; break;
case EncodeDims(8, 5): index = 5; break;
case EncodeDims(8, 6): index = 6; break;
case EncodeDims(8, 8): index = 7; break;
case EncodeDims(10, 5): index = 8; break;
case EncodeDims(10, 6): index = 9; break;
case EncodeDims(10, 8): index = 10; break;
case EncodeDims(10, 10): index = 11; break;
case EncodeDims(12, 10): index = 12; break;
case EncodeDims(12, 12): index = 13; break;
default:
assert(false && "Unknown footprint dimensions. This should have been caught sooner.");
break;
}
static const auto* const kASTCPartitionTrees =
new std::array<PartitionTree, Footprint::NumValidFootprints()> {{
GenerateASTCPartitionTree(Footprint::Get4x4()),
GenerateASTCPartitionTree(Footprint::Get5x4()),
GenerateASTCPartitionTree(Footprint::Get5x5()),
GenerateASTCPartitionTree(Footprint::Get6x5()),
GenerateASTCPartitionTree(Footprint::Get6x6()),
GenerateASTCPartitionTree(Footprint::Get8x5()),
GenerateASTCPartitionTree(Footprint::Get8x6()),
GenerateASTCPartitionTree(Footprint::Get8x8()),
GenerateASTCPartitionTree(Footprint::Get10x5()),
GenerateASTCPartitionTree(Footprint::Get10x6()),
GenerateASTCPartitionTree(Footprint::Get10x8()),
GenerateASTCPartitionTree(Footprint::Get10x10()),
GenerateASTCPartitionTree(Footprint::Get12x10()),
GenerateASTCPartitionTree(Footprint::Get12x12()),
}};
const PartitionTree& parts_vptree = kASTCPartitionTrees->at(index);
std::vector<const Partition*> results;
parts_vptree.Search(candidate, k, &results, nullptr);
return results;
}
// Returns the valid ASTC partition that is closest to the candidate based on
// the PartitionMetric defined above.
const Partition& FindClosestASTCPartition(const Partition& candidate) {
// Given a candidate, the closest valid partition will likely not be an exact
// match. Consider all of the texels for which this valid partition differs
// with the candidate.
//
// If the valid partition has more subsets than the candidate, then all of the
// highest subset will be included in the mismatched texels. Since the number
// of possible partitions with increasing subsets grows exponentially, the
// chance that a valid partition with fewer subsets appears within the first
// few closest partitions is relatively high. Empirically, we can usually find
// a partition with at most |candidate.num_parts| number of subsets within the
// first four closest partitions.
constexpr int kSearchItems = 4;
const std::vector<const Partition*> results =
FindKClosestASTCPartitions(candidate, kSearchItems);
// Optimistically, look for result with the same number of subsets.
for (const auto& result : results) {
if (result->num_parts == candidate.num_parts) {
return *result;
}
}
// If all else fails, then at least find the result with fewer subsets than
// we asked for.
for (const auto& result : results) {
if (result->num_parts < candidate.num_parts) {
return *result;
}
}
assert(false &&
"Could not find partition with acceptable number of subsets!");
return *(results[0]);
}
} // namespace astc_codec

View File

@@ -0,0 +1,97 @@
// 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.
#ifndef ASTC_CODEC_DECODER_PARTITION_H_
#define ASTC_CODEC_DECODER_PARTITION_H_
#include "src/base/optional.h"
#include "src/decoder/footprint.h"
#include <vector>
namespace astc_codec {
struct Partition;
// Determines the "difference" between any two partitions of the same size.
// This metric attempts to find the best one to one mapping from the labels in
// partition a against the labels in partition b. Once that mapping is found, it
// returns the number of pixels that are mismatched between the two. Each
// partition is expected to start in the upper left corner of the block and
// proceed in raster-scan order. Two partitions are equal if the mapping is
// bijective. This metric is a metric in the mathematical sense. In other words
// it has the following properties:
//
// 1) PartitionMetric(a, b) >= 0
// 2) PartitionMetric(a, b) == PartitionMetric(b, a)
// 3) PartitionMetric(a, b) == 0 iff a == b
// 4) PartitionMetric(a, b) + PartitionMetric(b, c) >= PartitionMetric(a, c)
//
// Throws an error if one partition's footprint is not equal to the other.
int PartitionMetric(const Partition& a, const Partition& b);
// A partition is a way to divide up an ASTC block into disjoint subsets such
// that each subset uses a different set of endpoints. This is used to increase
// the compression quality of blocks. One way to store such a partition is to
// assign an ID to use with a predetermined decoding method. Here we store the
// logical representation of partitions by keeping a per-pixel label. All pixels
// that share a label belong to the same subset.
struct Partition {
// The footprint width and height of this partition. This determines the size
// of the assignment array.
Footprint footprint;
// The number of subsets in this partition. The values in the partition
// assignment fall within the range [0, num_parts). The maximum number of
// parts supported is four.
int num_parts;
// The 10-bit partition ID as stored in bits 13-22 of multi-part ASTC blocks.
// (See Section C.2.9) If there is no guarantee that this partition is a valid
// ASTC partition, this should be set to absl::nullopt.
base::Optional<int> partition_id;
// A value in the range [0, num_parts) corresponding to the label for
// the given texel (x, y) in [0, footprint_width) x [0, footprint_height)
// using a raster-order layout.
std::vector<int> assignment;
// Returns true only if their "distance" is zero, i.e. if they have compatible
// subset assignments.
bool operator==(const Partition& other) const {
return PartitionMetric(*this, other) == 0;
}
};
// Generates the ASTC partition assignment for the given block attributes.
Partition GetASTCPartition(const Footprint& footprint, int num_parts,
int partition_id);
// Returns the |k| valid ASTC partitions that are closest to the candidate based
// on the PartitionMetric defined above.
const std::vector<const Partition*> FindKClosestASTCPartitions(
const Partition& candidate, int k);
// Returns the valid ASTC partition closest to the candidate with at most as
// many subsets as the |candidate|. Note: this is not a deterministic function,
// as the underlying valid partitions are sorted using a hash map and a distance
// function whose range is the natural numbers. The chances that two or more
// partitions are equally 'closest' is possible, in which case this function
// makes no guarantees about which one it will return. For more control, use
// FindKClosestASTCPartitions above.
const Partition& FindClosestASTCPartition(const Partition& candidate);
} // namespace astc_codec
#endif // ASTC_CODEC_DECODER_PARTITION_H_

View File

@@ -0,0 +1,761 @@
// 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

View File

@@ -0,0 +1,128 @@
// 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.
#ifndef ASTC_CODEC_DECODER_PHYSICAL_ASTC_BLOCK_H_
#define ASTC_CODEC_DECODER_PHYSICAL_ASTC_BLOCK_H_
// The logic in this file is based on the ASTC specification, which can be
// found here:
// https://www.opengl.org/registry/specs/KHR/texture_compression_astc_hdr.txt
#include "src/base/optional.h"
#include "src/base/uint128.h"
#include "src/decoder/types.h"
#include <string>
namespace astc_codec {
// A PhysicalASTCBlock contains all 128 bits and the logic for decoding the
// various internals of an ASTC block.
class PhysicalASTCBlock {
public:
// The physical size in bytes of an ASTC block
static const size_t kSizeInBytes = 16;
// Initializes an ASTC block based on the encoded string.
explicit PhysicalASTCBlock(const std::string& encoded_block);
explicit PhysicalASTCBlock(const base::UInt128 astc_block);
// Returns the 128 bits of this ASTC block.
base::UInt128 GetBlockBits() const { return astc_bits_; }
// Weights are stored in a grid that may not have the same dimensions
// as the block dimensions. This allows us to see what the physical
// dimensions are of the grid.
base::Optional<std::array<int, 2>> WeightGridDims() const;
// The weight range is the maximum value a weight can take in the
// weight grid.
base::Optional<int> WeightRange() const;
// Returns true if the block encoding specifies a void-extent block. This
// kind of block stores a single color to be used for every pixel in the
// block.
bool IsVoidExtent() const;
// Returns the values (min_s, max_s, min_t, max_t) as defined in the void
// extent block as the range of texture coordinates for which this block is
// defined. (See Section C.2.23)
base::Optional<std::array<int, 4>> VoidExtentCoords() const;
// Returns true if the block contains two separate weight grids. One used
// for the channel returned by DualPlaneChannel() and one used by the other
// channels.
bool IsDualPlane() const;
// Returns the channel used as the "dual plane". The return value is only
// meaningful if IsDualPlane() returns true...
base::Optional<int> DualPlaneChannel() const;
// Returns a reason that the encoding doesn't adhere to the specification.
// If the encoding is legal, then this returns a nullptr. This allows us to
// still use code of the form:
//
// if (IsIllegalEncoding()) {
// ... error ...
// }
// ... no error ...
//
// However, it also helps with debugging since we can find problems with
// encodings a lot faster.
base::Optional<std::string> IsIllegalEncoding() const;
// Returns the number of weight bits present in this block.
base::Optional<int> NumWeightBits() const;
// Returns the starting position within the range [0, 127] of the
// weight data within the block.
base::Optional<int> WeightStartBit() const;
// Returns the number of endpoint pairs used in this block.
base::Optional<int> NumPartitions() const;
// Returns the seed used to determine the partition for a given
// (x, y) coordinate within the block. Determined using the
// block size and the function as described in the specification.
base::Optional<int> PartitionID() const;
// Returns the color endpoint mode for the given partition index.
base::Optional<ColorEndpointMode> GetEndpointMode(int partition) const;
// Returns the starting position within the range [0, 127] of the
// color data within the block.
base::Optional<int> ColorStartBit() const;
// Returns the number of integers used to represent the color endpoints.
base::Optional<int> NumColorValues() const;
// Returns the number of bits used to represent the color endpoints.
base::Optional<int> NumColorBits() const;
// Returns the maximum value that each of the encoded integers used to
// represent the color endpoints can take.
base::Optional<int> ColorValuesRange() const;
private:
const base::UInt128 astc_bits_;
// The logic to return the number of color bits and the color values range
// is very similar, so it's probably best to abstract it away into its own
// function.
void GetColorValuesInfo(int* color_bits, int* color_range) const;
};
} // namespace astc_codec
#endif // ASTC_CODEC_DECODER_PHYSICAL_ASTC_BLOCK_H_

View File

@@ -0,0 +1,462 @@
// 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/quantization.h"
#include "src/base/math_utils.h"
#include <algorithm>
#include <array>
#include <cassert>
#include <map>
#include <memory>
#include <vector>
namespace astc_codec {
namespace {
// Trit unquantization procedure as described in Section C.2.13
int GetUnquantizedTritValue(int trit, int bits, int range) {
int a = (bits & 1) ? 0x1FF : 0;
int b = 0, c = 0;
switch (range) {
case 5: {
b = 0;
c = 204;
}
break;
case 11: {
int x = (bits >> 1) & 0x1;
b = (x << 1) | (x << 2) | (x << 4) | (x << 8);
c = 93;
}
break;
case 23: {
int x = (bits >> 1) & 0x3;
b = x | (x << 2) | (x << 7);
c = 44;
}
break;
case 47: {
int x = (bits >> 1) & 0x7;
b = x | (x << 6);
c = 22;
}
break;
case 95: {
int x = (bits >> 1) & 0xF;
b = (x >> 2) | (x << 5);
c = 11;
}
break;
case 191: {
int x = (bits >> 1) & 0x1F;
b = (x >> 4) | (x << 4);
c = 5;
}
break;
default:
assert(false && "Illegal trit encoding");
break;
}
int t = trit * c + b;
t ^= a;
t = (a & 0x80) | (t >> 2);
return t;
}
// Quint unquantization procedure as described in Section C.2.13
int GetUnquantizedQuintValue(int quint, int bits, int range) {
int a = (bits & 1) ? 0x1FF : 0;
int b = 0, c = 0;
switch (range) {
case 9: {
b = 0;
c = 113;
}
break;
case 19: {
int x = (bits >> 1) & 0x1;
b = (x << 2) | (x << 3) | (x << 8);
c = 54;
}
break;
case 39: {
int x = (bits >> 1) & 0x3;
b = (x >> 1) | (x << 1) | (x << 7);
c = 26;
}
break;
case 79: {
int x = (bits >> 1) & 0x7;
b = (x >> 1) | (x << 6);
c = 13;
}
break;
case 159: {
int x = (bits >> 1) & 0xF;
b = (x >> 3) | (x << 5);
c = 6;
}
break;
default:
assert(false && "Illegal quint encoding");
break;
}
int t = quint * c + b;
t ^= a;
t = (a & 0x80) | (t >> 2);
return t;
}
// Trit unquantization procedure as described in Section C.2.17. In the code
// below, the variables a, b, and c correspond to the columns A, B, and C in
// the specification.
int GetUnquantizedTritWeight(int trit, int bits, int range) {
int a = (bits & 1) ? 0x7F : 0;
int b = 0, c = 0;
switch (range) {
case 2:
return (std::array<int, 3> {{ 0, 32, 63 }})[trit];
case 5:
c = 50;
b = 0;
break;
case 11: {
c = 23;
b = (bits >> 1) & 1;
b |= (b << 2) | (b << 6);
}
break;
case 23: {
c = 11;
b = (bits >> 1) & 0x3;
b |= (b << 5);
}
break;
default:
assert(false && "Illegal trit encoding");
break;
}
int t = trit * c + b;
t ^= a;
t = (a & 0x20) | (t >> 2);
return t;
}
// Quint unquantization procedure as described in Section C.2.17. In the code
// below, the variables a, b, and c correspond to the columns A, B, and C in
// the specification.
int GetUnquantizedQuintWeight(int quint, int bits, int range) {
int a = (bits & 1) ? 0x7F : 0;
int b = 0, c = 0;
switch (range) {
case 4:
return (std::array<int, 5> {{ 0, 16, 32, 47, 63 }})[quint];
case 9:
c = 28;
b = 0;
break;
case 19: {
c = 13;
b = (bits >> 1) & 0x1;
b = (b << 1) | (b << 6);
}
break;
default:
assert(false && "Illegal quint encoding");
break;
}
int t = quint * c + b;
t ^= a;
t = (a & 0x20) | (t >> 2);
return t;
}
// A Quantization map allows us to convert to/from values that are quantized
// according to the ASTC spec.
class QuantizationMap {
public:
int Quantize(size_t x) const {
return x < quantization_map_.size() ? quantization_map_.at(x) : 0;
}
int Unquantize(size_t x) const {
return x < unquantization_map_.size() ? unquantization_map_.at(x) : 0;
}
protected:
QuantizationMap() { }
std::vector<int> quantization_map_;
std::vector<int> unquantization_map_;
void GenerateQuantizationMap() {
assert(unquantization_map_.size() > 1);
quantization_map_.clear();
// TODO(google) For weights, we don't need quantization values all the
// way up to 256, but it doesn't hurt -- just wastes memory, but the code
// is much cleaner this way
for (int i = 0; i < 256; ++i) {
int best_idx = 0;
int best_idx_score = 256;
int idx = 0;
for (int unquantized_val : unquantization_map_) {
const int diff = i - unquantized_val;
const int idx_score = diff * diff;
if (idx_score < best_idx_score) {
best_idx = idx;
best_idx_score = idx_score;
}
idx++;
}
quantization_map_.push_back(best_idx);
}
}
};
template<int (*UnquantizationFunc)(int, int, int)>
class TritQuantizationMap : public QuantizationMap {
public:
explicit TritQuantizationMap(int range) : QuantizationMap() {
assert((range + 1) % 3 == 0);
const int num_bits_pow_2 = (range + 1) / 3;
const int num_bits =
num_bits_pow_2 == 0 ? 0 : base::Log2Floor(num_bits_pow_2);
for (int trit = 0; trit < 3; ++trit) {
for (int bits = 0; bits < (1 << num_bits); ++bits) {
unquantization_map_.push_back(UnquantizationFunc(trit, bits, range));
}
}
GenerateQuantizationMap();
}
};
template<int (*UnquantizationFunc)(int, int, int)>
class QuintQuantizationMap : public QuantizationMap {
public:
explicit QuintQuantizationMap(int range) : QuantizationMap() {
assert((range + 1) % 5 == 0);
const int num_bits_pow_2 = (range + 1) / 5;
const int num_bits =
num_bits_pow_2 == 0 ? 0 : base::Log2Floor(num_bits_pow_2);
for (int quint = 0; quint < 5; ++quint) {
for (int bits = 0; bits < (1 << num_bits); ++bits) {
unquantization_map_.push_back(UnquantizationFunc(quint, bits, range));
}
}
GenerateQuantizationMap();
}
};
template<int TotalUnquantizedBits>
class BitQuantizationMap : public QuantizationMap {
public:
explicit BitQuantizationMap<TotalUnquantizedBits>(int range)
: QuantizationMap() {
// Make sure that if we're using bits then we have a positive power of two.
assert(base::CountOnes(range + 1) == 1);
const int num_bits = base::Log2Floor(range + 1);
for (int bits = 0; bits <= range; ++bits) {
// Need to replicate bits until we fill up the bits
size_t unquantized = bits;
int num_unquantized_bits = num_bits;
while (num_unquantized_bits < TotalUnquantizedBits) {
const int num_dst_bits_to_shift_up =
std::min(num_bits, TotalUnquantizedBits - num_unquantized_bits);
const int num_src_bits_to_shift_down =
num_bits - num_dst_bits_to_shift_up;
unquantized <<= num_dst_bits_to_shift_up;
unquantized |= bits >> num_src_bits_to_shift_down;
num_unquantized_bits += num_dst_bits_to_shift_up;
}
assert(num_unquantized_bits == TotalUnquantizedBits);
unquantization_map_.push_back(unquantized);
// Fill half of the quantization map with the previous value for bits
// and the other half with the current value for bits
if (bits > 0) {
const size_t prev_unquant = unquantization_map_.at(bits - 1);
while (quantization_map_.size() <= (prev_unquant + unquantized) / 2) {
quantization_map_.push_back(bits - 1);
}
}
while (quantization_map_.size() <= unquantized) {
quantization_map_.push_back(bits);
}
}
assert(quantization_map_.size() == 1 << TotalUnquantizedBits);
}
};
using QMap = std::shared_ptr<QuantizationMap>;
// Returns the quantization map for quantizing color values in [0, 255] with the
// smallest range that can accommodate |r|
static const QuantizationMap* GetQuantMapForValueRange(int r) {
// Endpoint values can be quantized using bits, trits, or quints. Here we
// store the quantization maps for each of the ranges that are supported by
// such an encoding. That way we can choose the proper quantization procedure
// based on the range of values rather than by having complicated switches and
// logic. We must use a std::map here instead of a std::unordered_map because
// of the assumption made in std::upper_bound about the iterators being from a
// poset.
static const auto* const kASTCEndpointQuantization = new std::map<int, QMap> {
{ 5, QMap(new TritQuantizationMap<GetUnquantizedTritValue>(5)) },
{ 7, QMap(new BitQuantizationMap<8>(7)) },
{ 9, QMap(new QuintQuantizationMap<GetUnquantizedQuintValue>(9)) },
{ 11, QMap(new TritQuantizationMap<GetUnquantizedTritValue>(11)) },
{ 15, QMap(new BitQuantizationMap<8>(15)) },
{ 19, QMap(new QuintQuantizationMap<GetUnquantizedQuintValue>(19)) },
{ 23, QMap(new TritQuantizationMap<GetUnquantizedTritValue>(23)) },
{ 31, QMap(new BitQuantizationMap<8>(31)) },
{ 39, QMap(new QuintQuantizationMap<GetUnquantizedQuintValue>(39)) },
{ 47, QMap(new TritQuantizationMap<GetUnquantizedTritValue>(47)) },
{ 63, QMap(new BitQuantizationMap<8>(63)) },
{ 79, QMap(new QuintQuantizationMap<GetUnquantizedQuintValue>(79)) },
{ 95, QMap(new TritQuantizationMap<GetUnquantizedTritValue>(95)) },
{ 127, QMap(new BitQuantizationMap<8>(127)) },
{ 159, QMap(new QuintQuantizationMap<GetUnquantizedQuintValue>(159)) },
{ 191, QMap(new TritQuantizationMap<GetUnquantizedTritValue>(191)) },
{ 255, QMap(new BitQuantizationMap<8>(255)) },
};
assert(r < 256);
auto itr = kASTCEndpointQuantization->upper_bound(r);
if (itr != kASTCEndpointQuantization->begin()) {
return (--itr)->second.get();
}
return nullptr;
}
// Returns the quantization map for weight values in [0, 63] with the smallest
// range that can accommodate |r|
static const QuantizationMap* GetQuantMapForWeightRange(int r) {
// Similar to endpoint quantization, weights can also be stored using trits,
// quints, or bits. Here we store the quantization maps for each of the ranges
// that are supported by such an encoding.
static const auto* const kASTCWeightQuantization = new std::map<int, QMap> {
{ 1, QMap(new BitQuantizationMap<6>(1)) },
{ 2, QMap(new TritQuantizationMap<GetUnquantizedTritWeight>(2)) },
{ 3, QMap(new BitQuantizationMap<6>(3)) },
{ 4, QMap(new QuintQuantizationMap<GetUnquantizedQuintWeight>(4)) },
{ 5, QMap(new TritQuantizationMap<GetUnquantizedTritWeight>(5)) },
{ 7, QMap(new BitQuantizationMap<6>(7)) },
{ 9, QMap(new QuintQuantizationMap<GetUnquantizedQuintWeight>(9)) },
{ 11, QMap(new TritQuantizationMap<GetUnquantizedTritWeight>(11)) },
{ 15, QMap(new BitQuantizationMap<6>(15)) },
{ 19, QMap(new QuintQuantizationMap<GetUnquantizedQuintWeight>(19)) },
{ 23, QMap(new TritQuantizationMap<GetUnquantizedTritWeight>(23)) },
{ 31, QMap(new BitQuantizationMap<6>(31)) },
};
assert(r < 32);
auto itr = kASTCWeightQuantization->upper_bound(r);
if (itr != kASTCWeightQuantization->begin()) {
return (--itr)->second.get();
}
return nullptr;
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
int QuantizeCEValueToRange(int value, int range_max_value) {
assert(range_max_value >= kEndpointRangeMinValue);
assert(range_max_value <= 255);
assert(value >= 0);
assert(value <= 255);
const QuantizationMap* map = GetQuantMapForValueRange(range_max_value);
return map ? map->Quantize(value) : 0;
}
int UnquantizeCEValueFromRange(int value, int range_max_value) {
assert(range_max_value >= kEndpointRangeMinValue);
assert(range_max_value <= 255);
assert(value >= 0);
assert(value <= range_max_value);
const QuantizationMap* map = GetQuantMapForValueRange(range_max_value);
return map ? map->Unquantize(value) : 0;
}
int QuantizeWeightToRange(int weight, int range_max_value) {
assert(range_max_value >= 1);
assert(range_max_value <= kWeightRangeMaxValue);
assert(weight >= 0);
assert(weight <= 64);
// The quantization maps that define weight unquantization expect values in
// the range [0, 64), but the specification quantizes them to the range
// [0, 64] according to C.2.17. This is a slight hack similar to the one in
// the unquantization procedure to return the passed in unquantized value to
// [0, 64) prior to running it through the quantization procedure.
if (weight > 33) {
weight -= 1;
}
const QuantizationMap* map = GetQuantMapForWeightRange(range_max_value);
return map ? map->Quantize(weight) : 0;
}
int UnquantizeWeightFromRange(int weight, int range_max_value) {
assert(range_max_value >= 1);
assert(range_max_value <= kWeightRangeMaxValue);
assert(weight >= 0);
assert(weight <= range_max_value);
const QuantizationMap* map = GetQuantMapForWeightRange(range_max_value);
int dq = map ? map->Unquantize(weight) : 0;
// Quantized weights are returned in the range [0, 64), but they should be
// returned in the range [0, 64], so according to C.2.17 we need to add one
// to the result.
assert(dq < 64);
if (dq > 32) {
dq += 1;
}
return dq;
}
} // namespace astc_codec

View File

@@ -0,0 +1,65 @@
// 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.
#ifndef ASTC_CODEC_DECODER_QUANTIZATION_H_
#define ASTC_CODEC_DECODER_QUANTIZATION_H_
////////////////////////////////////////////////////////////////////////////////
//
// ASTC Quantization procedures.
//
// The values stored in ASTC blocks tend to be stored in a range much more
// restricted than the logical range used. For example, sometimes weights are
// stored in the range from [0, 3] but are used in the range [0, 64]. The
// process of translating a value to or from this range is known as quantization
// and dequantization. The ranges to which these values can be (de)quantized
// are defined by ISERange[Begin|End]() in integer_sequence_codec.h
namespace astc_codec {
// The minimum possible range for a pair of endpoints. If endpoints are
// quantized to something smaller than this, then it would constitute an
// illegal ASTC encoding.
constexpr int kEndpointRangeMinValue = 5;
// The maximum possible range for a weight value. If weights are quantized to
// something larger than this, then it would constitute an illegal ASTC
// encoding.
constexpr int kWeightRangeMaxValue = 31;
// Quantizes a value in the range [0, 255] to [0, |range|]. The quantized values
// have no correlation to the input values, and there should be no implicit
// assumptions made about their ordering. Valid values of |range_max_value| are
// in the interval [5, 255]
int QuantizeCEValueToRange(int value, int range_max_value);
// Unquantizes a value in the range [0, |range|] to [0, 255]. Performs the
// inverse procedure of QuantizeValueToRange. Valid values of |range_max_value|
// are in the interval [5, 255]
int UnquantizeCEValueFromRange(int value, int range_max_value);
// Quantizes a weight in the range [0, 64] to [0, |range_max_value|]. The
// quantized values have no correlation to the input values, and there should
// be no implicit assumptions made about their ordering. Valid values of
// |range_max_value| are in the interval [1, 31]
int QuantizeWeightToRange(int weight, int range_max_value);
// Unquantizes a weight in the range [0, |range_max_value|] to [0, 64]. Performs
// the inverse procedure of QuantizeWeightToRange. Valid values of
// |range_max_value| are in the interval [1, 31]
int UnquantizeWeightFromRange(int weight, int range_max_value);
} // namespace astc_codec
#endif // ASTC_CODEC_DECODER_QUANTIZATION_H_

View File

@@ -0,0 +1,36 @@
// 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 fuzzing wrapper to help with fuzz testing.
#include "src/decoder/codec.h"
#include <benchmark/benchmark.h>
#include <vector>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
std::string error;
std::unique_ptr<astc_codec::ASTCFile> file =
astc_codec::ASTCFile::LoadFromMemory(reinterpret_cast<const char*>(data),
size, &error);
if (file) {
std::vector<uint8_t> out_buffer(file->GetWidth() * file->GetHeight() * 4);
bool result = astc_codec::DecompressToImage(
*file, out_buffer.data(), out_buffer.size(), file->GetWidth() * 4);
benchmark::DoNotOptimize(result);
}
return 0;
}

View File

@@ -0,0 +1,181 @@
// 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/codec.h"
#include "include/astc-codec/astc-codec.h"
#include "src/decoder/test/image_utils.h"
#include <gtest/gtest.h>
#include <string>
namespace astc_codec {
static void PrintTo(FootprintType footprint, std::ostream* os) {
switch (footprint) {
case FootprintType::k4x4: *os << "FootprintType::k4x4"; break;
case FootprintType::k5x4: *os << "FootprintType::k5x4"; break;
case FootprintType::k5x5: *os << "FootprintType::k5x5"; break;
case FootprintType::k6x5: *os << "FootprintType::k6x5"; break;
case FootprintType::k6x6: *os << "FootprintType::k6x6"; break;
case FootprintType::k8x5: *os << "FootprintType::k8x5"; break;
case FootprintType::k8x6: *os << "FootprintType::k8x6"; break;
case FootprintType::k10x5: *os << "FootprintType::k10x5"; break;
case FootprintType::k10x6: *os << "FootprintType::k10x6"; break;
case FootprintType::k8x8: *os << "FootprintType::k8x8"; break;
case FootprintType::k10x8: *os << "FootprintType::k10x8"; break;
case FootprintType::k10x10: *os << "FootprintType::k10x10"; break;
case FootprintType::k12x10: *os << "FootprintType::k12x10"; break;
case FootprintType::k12x12: *os << "FootprintType::k12x12"; break;
default:
*os << "<Unexpected FootprintType "
<< static_cast<uint32_t>(footprint) << ">";
}
}
namespace {
using ::testing::TestWithParam;
using ::testing::ValuesIn;
ImageBuffer LoadGoldenImageWithAlpha(std::string basename) {
const std::string filename =
std::string("src/decoder/testdata/") + basename + ".bmp";
ImageBuffer result;
LoadGoldenBmp(filename, &result);
EXPECT_EQ(result.BytesPerPixel(), 4);
return result;
}
struct ImageTestParams {
std::string image_name;
FootprintType footprint;
size_t width;
size_t height;
};
static void PrintTo(const ImageTestParams& params, std::ostream* os) {
*os << "ImageTestParams(" << params.image_name << ", " << params.width << "x"
<< params.height << ", ";
PrintTo(params.footprint, os);
*os << ")";
}
TEST(CodecTest, InvalidInput) {
const size_t valid_width = 16;
const size_t valid_height = 16;
const size_t valid_stride = valid_width * 4;
const std::vector<uint8_t> data(256);
std::vector<uint8_t> output(valid_width * valid_height * 4);
// Invalid footprint.
EXPECT_FALSE(ASTCDecompressToRGBA(
data.data(), data.size(), valid_width, valid_height,
FootprintType::kCount, output.data(), output.size(), valid_stride));
// Fail for 0 width or height.
EXPECT_FALSE(ASTCDecompressToRGBA(data.data(), data.size(), 0, valid_height,
FootprintType::k4x4, output.data(),
output.size(), valid_stride));
EXPECT_FALSE(ASTCDecompressToRGBA(data.data(), data.size(), valid_width, 0,
FootprintType::k4x4, output.data(),
output.size(), valid_stride));
// Fail for data size that's not a multiple of block size.
EXPECT_FALSE(ASTCDecompressToRGBA(
data.data(), data.size() - 1, valid_width, valid_height,
FootprintType::k4x4, output.data(), output.size(), valid_stride));
// Fail for data size that doesn't match the block count.
EXPECT_FALSE(ASTCDecompressToRGBA(
data.data(), data.size() - 16, valid_width, valid_height,
FootprintType::k4x4, output.data(), output.size(), valid_stride));
// Fail for invalid stride.
EXPECT_FALSE(ASTCDecompressToRGBA(
data.data(), data.size(), valid_width, valid_height, FootprintType::k4x4,
output.data(), output.size(), valid_stride - 1));
// Fail for invalid output size.
EXPECT_FALSE(ASTCDecompressToRGBA(
data.data(), data.size(), valid_width, valid_height, FootprintType::k4x4,
output.data(), output.size() - 1, valid_stride));
}
class CodecTest : public TestWithParam<ImageTestParams> {};
TEST_P(CodecTest, PublicAPI) {
const auto& params = GetParam();
const std::string astc = LoadASTCFile(params.image_name);
ImageBuffer our_decoded_image;
our_decoded_image.Allocate(params.width, params.height, 4);
ASSERT_TRUE(ASTCDecompressToRGBA(
reinterpret_cast<const uint8_t*>(astc.data()), astc.size(), params.width,
params.height, params.footprint, our_decoded_image.Data().data(),
our_decoded_image.DataSize(), our_decoded_image.Stride()));
// Check that the decoded image is *very* similar to the library decoding
// of an ASTC texture. They may not be exact due to differences in how we
// convert a 16-bit float to an 8-bit integer.
ImageBuffer decoded_image = LoadGoldenImageWithAlpha(params.image_name);
CompareSumOfSquaredDifferences(decoded_image, our_decoded_image, 1.0);
}
TEST_P(CodecTest, DecompressToImage) {
const auto& params = GetParam();
std::string error;
std::unique_ptr<ASTCFile> image_file = ASTCFile::LoadFile(
std::string("src/decoder/testdata/") + params.image_name + ".astc",
&error);
ASSERT_TRUE(image_file) << "Failed to load " << params.image_name << ": "
<< error;
ASSERT_TRUE(image_file->GetFootprint());
EXPECT_EQ(params.footprint, image_file->GetFootprint().value().Type());
ImageBuffer our_decoded_image;
our_decoded_image.Allocate(image_file->GetWidth(), image_file->GetHeight(),
4);
ASSERT_TRUE(DecompressToImage(*image_file, our_decoded_image.Data().data(),
our_decoded_image.DataSize(),
our_decoded_image.Stride()));
// Check that the decoded image is *very* similar to the library decoding
// of an ASTC texture. They may not be exact due to differences in how we
// convert a 16-bit float to an 8-bit integer.
ImageBuffer decoded_image = LoadGoldenImageWithAlpha(params.image_name);
CompareSumOfSquaredDifferences(decoded_image, our_decoded_image, 1.0);
}
// Test to make sure that reading out color values from blocks in a real-world
// image isn't terribly wrong, either.
std::vector<ImageTestParams> GetTransparentImageTestParams() {
return {
// image_name astc footprint width height
{ "atlas_small_4x4", FootprintType::k4x4, 256, 256 },
{ "atlas_small_5x5", FootprintType::k5x5, 256, 256 },
{ "atlas_small_6x6", FootprintType::k6x6, 256, 256 },
{ "atlas_small_8x8", FootprintType::k8x8, 256, 256 },
};
}
INSTANTIATE_TEST_CASE_P(Transparent, CodecTest,
ValuesIn(GetTransparentImageTestParams()));
} // namespace
} // namespace astc_codec

View File

@@ -0,0 +1,463 @@
// 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/endpoint_codec.h"
#include "src/decoder/intermediate_astc_block.h"
#include "src/decoder/test/image_utils.h"
#include <random>
#include <string>
#include <utility>
#include <vector>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
namespace astc_codec {
namespace {
using ::testing::AllOf;
using ::testing::AnyOf;
using ::testing::Each;
using ::testing::Eq;
using ::testing::Ge;
using ::testing::Le;
using ::testing::Ne;
using ::testing::Pointwise;
using ::testing::SizeIs;
using ::testing::Pair;
constexpr std::array<EndpointEncodingMode, 6> kEndpointEncodingModes = {{
EndpointEncodingMode::kDirectLuma,
EndpointEncodingMode::kDirectLumaAlpha,
EndpointEncodingMode::kBaseScaleRGB,
EndpointEncodingMode::kBaseScaleRGBA,
EndpointEncodingMode::kDirectRGB,
EndpointEncodingMode::kDirectRGBA }};
const std::array<std::pair<RgbaColor, RgbaColor>, 3> kBlueContractPairs = {{
std::make_pair(RgbaColor{{ 22, 18, 30, 59 }},
RgbaColor{{ 162, 148, 155, 59 }}),
std::make_pair(RgbaColor{{ 22, 30, 27, 36 }},
RgbaColor{{ 228, 221, 207, 36 }}),
std::make_pair(RgbaColor{{ 54, 60, 55, 255 }},
RgbaColor{{ 23, 30, 27, 255 }})
}};
// Used to directly initialize std::pairs of colors with initializer lists
// e.g. MakeColors({{ r, g, b, a }}, {{ r, g, b, a }});
std::pair<RgbaColor, RgbaColor> MakeColors(RgbaColor&& a, RgbaColor&& b) {
return std::make_pair(a, b);
}
// Returns |high| and |low| as they would be decoded using the quantization
// factor |quant| for the ColorEndpointMode |mode|.
std::pair<RgbaColor, RgbaColor> TestColors(
RgbaColor low, RgbaColor high, int quant, EndpointEncodingMode mode) {
ColorEndpointMode astc_mode;
std::vector<int> encoded;
const bool needs_swap =
EncodeColorsForMode(low, high, quant, mode, &astc_mode, &encoded);
RgbaColor decoded_low, decoded_high;
DecodeColorsForMode(encoded, quant, astc_mode, &decoded_low, &decoded_high);
if (needs_swap) {
return std::make_pair(decoded_high, decoded_low);
} else {
return std::make_pair(decoded_low, decoded_high);
}
}
// Returns true if the argument tuple entries only differ by at most x.
MATCHER_P(IsCloseTo, x, "") {
const auto& a = ::testing::get<0>(arg);
const auto& b = ::testing::get<1>(arg);
return (a > b) ? ((a - b) <= x) : ((b - a) <= x);
}
// Test to make sure that the range of values that we get as they are
// quantized remains within what we pass as |quant|.
TEST(EndpointCodecTest, QuantRanges) {
const RgbaColor low {{ 0, 0, 0, 0 }};
const RgbaColor high {{ 255, 255, 255, 255 }};
std::vector<int> result;
for (const auto& mode : kEndpointEncodingModes) {
for (int i = 5; i < 256; ++i) {
ColorEndpointMode astc_mode;
const bool needs_swap =
EncodeColorsForMode(low, high, i, mode, &astc_mode, &result);
EXPECT_EQ(result.size(), NumValuesForEncodingMode(mode)) << i;
EXPECT_EQ(result.size(), NumColorValuesForEndpointMode(astc_mode)) << i;
// ASTC mode shouldn't use base/offset when endpoints are so far apart.
EXPECT_THAT(astc_mode, Ne(ColorEndpointMode::kLDRRGBBaseOffset));
EXPECT_THAT(astc_mode, Ne(ColorEndpointMode::kLDRRGBABaseOffset));
EXPECT_THAT(result, Each(AllOf(Ge(0), Le(i))))
<< "Mode: " << static_cast<int>(mode);
// We don't care if we need to swap the weights in this test
EXPECT_TRUE(needs_swap || !needs_swap);
}
}
}
// Test to make sure that each mode that directly encodes colors can effectively
// encode both black and white
TEST(EndpointCodecTest, ExtremeDirectEncodings) {
const RgbaColor kWhite {{ 255, 255, 255, 255 }};
const RgbaColor kBlack {{ 0, 0, 0, 255 }};
std::vector<int> encoded;
for (const auto& mode : kEndpointEncodingModes) {
for (int i = 5; i < 256; ++i) {
const auto expected = std::make_pair(kWhite, kBlack);
EXPECT_EQ(TestColors(kWhite, kBlack, i, mode), expected)
<< "Range: " << i << ", Mode: " << static_cast<int>(mode);
}
}
}
// According to the spec, this is used for colors close to gray. The values
// chosen here were according to the spec.
TEST(EndpointCodecTest, UsesBlueContract) {
std::vector<int> vals = { 132, 127, 116, 112, 183, 180, 31, 22 };
EXPECT_TRUE(UsesBlueContract(255, ColorEndpointMode::kLDRRGBDirect, vals));
EXPECT_TRUE(UsesBlueContract(255, ColorEndpointMode::kLDRRGBADirect, vals));
// For the offset modes the only way to trigger the blue contract mode is if
// we force the subtraction in the decoding procedure (See section C.2.14 of
// the spec), so we need to set the 7th bit to 1 for all of the odd-numbered
// values
vals[1] &= 0xBF;
vals[3] &= 0xBF;
vals[5] &= 0xBF;
vals[7] &= 0xBF;
EXPECT_FALSE(
UsesBlueContract(255, ColorEndpointMode::kLDRRGBBaseOffset, vals));
EXPECT_FALSE(
UsesBlueContract(255, ColorEndpointMode::kLDRRGBABaseOffset, vals));
vals[1] |= 0x40;
vals[3] |= 0x40;
vals[5] |= 0x40;
vals[7] |= 0x40;
EXPECT_TRUE(
UsesBlueContract(255, ColorEndpointMode::kLDRRGBBaseOffset, vals));
EXPECT_TRUE(
UsesBlueContract(255, ColorEndpointMode::kLDRRGBABaseOffset, vals));
// All other LDR endpoint modes should return no blue contract
for (int max_val : { 255, 127, 11 }) {
for (auto mode : { ColorEndpointMode::kLDRLumaDirect,
ColorEndpointMode::kLDRLumaBaseOffset,
ColorEndpointMode::kLDRLumaAlphaDirect,
ColorEndpointMode::kLDRLumaAlphaBaseOffset,
ColorEndpointMode::kLDRRGBBaseScale,
ColorEndpointMode::kLDRRGBBaseScaleTwoA }) {
EXPECT_FALSE(UsesBlueContract(max_val, mode, vals));
}
}
}
// Make sure that encoding and decoding for the direct luminance mode works.
TEST(EndpointCodecTest, LumaDirect) {
const auto mode = EndpointEncodingMode::kDirectLuma;
// With a 255 quantizer, all greys should be exact.
for (int i = 0; i < 255; ++i) {
for (int j = 0; j < 255; ++j) {
EXPECT_EQ(TestColors({{ i, i, i, 255 }}, {{ j, j, j, 255 }}, 255, mode),
MakeColors({{ i, i, i, 255 }}, {{ j, j, j, 255 }}));
}
}
// If we have almost grey, then they should encode to grey.
EXPECT_EQ(TestColors({{ 247, 248, 246, 255 }}, {{ 2, 3, 1, 255 }}, 255, mode),
MakeColors({{ 247, 247, 247, 255 }}, {{ 2, 2, 2, 255 }}));
EXPECT_EQ(TestColors({{ 80, 80, 50, 255 }}, {{ 99, 255, 6, 255 }}, 255, mode),
MakeColors({{ 70, 70, 70, 255 }}, {{ 120, 120, 120, 255 }}));
// If we have almost greys and a really small quantizer, it should be white
// and black (literally).
EXPECT_EQ(TestColors({{ 247, 248, 246, 255 }}, {{ 2, 3, 1, 255 }}, 15, mode),
MakeColors({{ 255, 255, 255, 255 }}, {{ 0, 0, 0, 255 }}));
// The average of 64, 127, and 192 is 127.666..., so it should round to
// 130 instead of 125.
EXPECT_EQ(TestColors({{ 64, 127, 192, 255 }}, {{ 0, 0, 0, 255 }}, 63, mode),
MakeColors({{ 130, 130, 130, 255 }}, {{ 0, 0, 0, 255 }}));
// If we have almost grey, then they should encode to grey -- similar to
// direct encoding since the encoded colors differ by < 63.
EXPECT_EQ(TestColors({{ 80, 80, 50, 255 }}, {{ 99, 255, 6, 255 }}, 255, mode),
MakeColors({{ 70, 70, 70, 255 }}, {{ 120, 120, 120, 255 }}));
// Low precision colors should still encode pretty well with base/offset.
EXPECT_EQ(TestColors({{ 35, 36, 38, 255 }}, {{ 42, 43, 40, 255 }}, 47, mode),
MakeColors({{ 38, 38, 38, 255 }}, {{ 43, 43, 43, 255 }}));
EXPECT_EQ(TestColors({{ 39, 42, 40, 255 }}, {{ 18, 20, 21, 255 }}, 39, mode),
MakeColors({{ 39, 39, 39, 255 }}, {{ 19, 19, 19, 255 }}));
}
// Test encoding and decoding for the base-offset luminance mode.
TEST(EndpointCodecTest, LumaAlphaDirect) {
const auto mode = EndpointEncodingMode::kDirectLumaAlpha;
// With a 255 quantizer, all greys should be exact.
for (int i = 0; i < 255; ++i) {
for (int j = 0; j < 255; ++j) {
EXPECT_EQ(TestColors({{ i, i, i, j }}, {{ j, j, j, i }}, 255, mode),
MakeColors({{ i, i, i, j }}, {{ j, j, j, i }}));
}
}
// If we have almost grey, then they should encode to grey.
EXPECT_EQ(TestColors({{ 247, 248, 246, 250 }}, {{ 2, 3, 1, 172 }}, 255, mode),
MakeColors({{ 247, 247, 247, 250 }}, {{ 2, 2, 2, 172 }}));
EXPECT_EQ(TestColors({{ 80, 80, 50, 0 }}, {{ 99, 255, 6, 255 }}, 255, mode),
MakeColors({{ 70, 70, 70, 0 }}, {{ 120, 120, 120, 255 }}));
// If we have almost greys and a really small quantizer, it should be white
// and black (literally).
EXPECT_EQ(TestColors({{ 247, 248, 246, 253 }}, {{ 2, 3, 1, 3 }}, 15, mode),
MakeColors({{ 255, 255, 255, 255 }}, {{ 0, 0, 0, 0 }}));
// The average of 64, 127, and 192 is 127.666..., so it should round to
// 130 instead of 125. The alpha in this case is independent.
EXPECT_EQ(TestColors({{ 64, 127, 192, 127 }}, {{ 0, 0, 0, 20 }}, 63, mode),
MakeColors({{ 130, 130, 130, 125 }}, {{ 0, 0, 0, 20 }}));
}
// Test encoding for the direct RGB mode.
TEST(EndpointCodecTest, RGBDirect) {
const auto mode = EndpointEncodingMode::kDirectRGB;
// Colors should be encoded exactly with a 255 quantizer.
std::mt19937 random(0xdeadbeef);
std::uniform_int_distribution<int> byte_distribution(0, 255);
for (int i = 0; i < 100; ++i) {
RgbaColor low, high;
for (auto& x : high) { x = byte_distribution(random); }
for (auto& x : low) { x = byte_distribution(random); }
high[3] = low[3] = 255; // RGB Direct mode has opaque alpha.
EXPECT_EQ(TestColors(low, high, 255, mode), std::make_pair(low, high))
<< "Random iter: " << i;
}
// For each of the following tests, order of endpoints shouldn't have any
// bearing on the quantization properties, so we should be able to switch
// endpoints as we see fit and have them generate the same flipped encoded
// pairs.
EXPECT_EQ(TestColors({{ 64, 127, 192, 255 }}, {{ 0, 0, 0, 255 }}, 63, mode),
MakeColors({{ 65, 125, 190, 255 }}, {{ 0, 0, 0, 255 }}));
EXPECT_EQ(TestColors({{ 0, 0, 0, 255 }}, {{ 64, 127, 192, 255 }}, 63, mode),
MakeColors({{ 0, 0, 0, 255 }}, {{ 65, 125, 190, 255 }}));
EXPECT_EQ(TestColors({{ 1, 2, 94, 255 }}, {{ 168, 255, 13, 255 }}, 7, mode),
MakeColors({{ 0, 0, 109, 255 }}, {{ 182, 255, 0, 255 }}));
// Colors close to grey will likely need a blue contract.
EXPECT_EQ(TestColors(kBlueContractPairs[0].first,
kBlueContractPairs[0].second, 31, mode),
MakeColors({{ 24, 20, 33, 255 }}, {{ 160, 148, 156, 255 }}));
EXPECT_EQ(TestColors(kBlueContractPairs[0].second,
kBlueContractPairs[0].first, 31, mode),
MakeColors({{ 160, 148, 156, 255 }}, {{ 24, 20, 33, 255 }}));
EXPECT_EQ(TestColors(kBlueContractPairs[1].first,
kBlueContractPairs[1].second, 7, mode),
MakeColors({{ 18, 36, 36, 255 }}, {{ 237, 219, 219, 255 }}));
EXPECT_EQ(TestColors(kBlueContractPairs[1].second,
kBlueContractPairs[1].first, 7, mode),
MakeColors({{ 237, 219, 219, 255 }}, {{ 18, 36, 36, 255 }}));
// Colors close to grey (and each other) will likely need a blue contract AND
// use the offset mode for encoding
EXPECT_EQ(TestColors(kBlueContractPairs[2].first,
kBlueContractPairs[2].second, 31, mode),
MakeColors({{ 53, 59, 53, 255 }}, {{ 24, 30, 26, 255 }}));
EXPECT_EQ(TestColors(kBlueContractPairs[2].second,
kBlueContractPairs[2].first, 31, mode),
MakeColors({{ 24, 30, 26, 255 }}, {{ 53, 59, 53, 255 }}));
// Colors close to each other, but not to grey will likely only use the offset
// mode and not the blue-contract modes.
EXPECT_EQ(TestColors({{ 22, 148, 30, 59 }}, {{ 162, 18, 155, 59 }}, 31, mode),
MakeColors({{ 24, 148, 33, 255 }}, {{ 165, 16, 156, 255 }}));
EXPECT_EQ(TestColors({{ 162, 18, 155, 59 }}, {{ 22, 148, 30, 59 }}, 31, mode),
MakeColors({{ 165, 16, 156, 255 }}, {{ 24, 148, 33, 255 }}));
}
// Make sure that certain endpoint pairs result in the blue-contract path as
// we'd expect, such that we can make sure that we're hitting all of the encode
// paths.
TEST(EndpointCodecTest, RGBDirectMakesBlueContract) {
constexpr int kEndpointRange = 31;
for (const auto& endpoint_pair : kBlueContractPairs) {
ColorEndpointMode astc_mode;
std::vector<int> vals;
bool needs_swap = EncodeColorsForMode(
endpoint_pair.first, endpoint_pair.second,
kEndpointRange, EndpointEncodingMode::kDirectRGB, &astc_mode, &vals);
(void)(needs_swap); // Don't really care.
EXPECT_TRUE(UsesBlueContract(kEndpointRange, astc_mode, vals));
}
}
// Make sure that encoding and decoding for the RGB base-scale mode works.
TEST(EndpointCodecTest, RGBBaseScale) {
const auto mode = EndpointEncodingMode::kBaseScaleRGB;
const auto close_to = [](RgbaColor c, int x) {
return Pointwise(IsCloseTo(x), c);
};
// Identical colors should be encoded with a 255 scale factor. Since ASTC
// decodes the scaled color by doing (x * s) >> 8, the decoded color will be
// multiplied by 255/256. This might cause rounding errors sometimes, so we
// check that every channel only deviates by 1.
std::mt19937 random(0xdeadbeef);
std::uniform_int_distribution<int> byte_distribution(0, 255);
for (int i = 0; i < 100; ++i) {
RgbaColor color{{byte_distribution(random), byte_distribution(random),
byte_distribution(random), 255}};
const auto test_result = TestColors(color, color, 255, mode);
EXPECT_THAT(test_result, Pair(close_to(color, 1), close_to(color, 1)));
}
// Make sure that if we want to scale by e.g. 1/4 then we can do that exactly:
const RgbaColor low = {{ 20, 4, 40, 255 }};
const RgbaColor high = {{ 80, 16, 160, 255 }};
EXPECT_THAT(TestColors(low, high, 255, mode),
Pair(close_to(low, 0), close_to(high, 0)));
// And if we quantize it, then we get roughly the same thing. The scale factor
// should be representable with most quantization levels. The problem is that
// if we're off on the 'high' color, then we will be off on the 'low' color.
EXPECT_THAT(TestColors(low, high, 127, mode),
Pair(close_to(low, 1), close_to(high, 1)));
EXPECT_THAT(TestColors(low, high, 63, mode),
Pair(close_to(low, 1), close_to(high, 2)));
EXPECT_THAT(TestColors(low, high, 31, mode),
Pair(close_to(low, 1), close_to(high, 4)));
EXPECT_THAT(TestColors(low, high, 15, mode),
Pair(close_to(low, 2), close_to(high, 8)));
}
// Make sure that encoding and decoding for the RGB base-offset mode works.
// Since we don't have a decoder, this is currently only a test that should work
// based on reasoning about what's written in the spec.
// TODO(krajcevski): Write an encoder.
TEST(EndpointCodecTest, RGBBaseOffset) {
const auto test_colors = [](const RgbaColor& low, const RgbaColor& high) {
const RgbaColor diff = {{ high[0] - low[0], high[1] - low[1],
high[2] - low[2], high[3] - low[3] }};
std::vector<int> vals;
for (int i = 0; i < 3; ++i) {
// If the base is "large", then it grabs it's most significant bit from
// the offset value. Hence, we need to save it here.
const bool is_large = low[i] >= 128;
vals.push_back((low[i] * 2) & 0xFF);
vals.push_back(diff[i] * 2);
// Give the "large" bases their bits back.
if (is_large) {
vals.back() |= 0x80;
}
}
RgbaColor dec_low, dec_high;
DecodeColorsForMode(vals, 255, ColorEndpointMode::kLDRRGBBaseOffset,
&dec_low, &dec_high);
EXPECT_THAT(std::make_pair(dec_low, dec_high), Pair(Eq(low), Eq(high)));
};
// Test the "direct encoding" path.
test_colors({{ 80, 16, 112, 255 }}, {{ 87, 18, 132, 255 }});
test_colors({{ 80, 74, 82, 255 }}, {{ 90, 92, 110, 255 }});
test_colors({{ 0, 0, 0, 255 }}, {{ 2, 2, 2, 255 }});
// Identical endpoints should always encode exactly, provided they satisfy the
// requirements for the base encoding.
std::mt19937 random(0xdeadbeef);
std::uniform_int_distribution<int> byte_distribution(0, 255);
for (int i = 0; i < 100; ++i) {
RgbaColor color{{byte_distribution(random), byte_distribution(random),
byte_distribution(random), 255}};
if ((color[0] | color[1] | color[2]) & 1) {
continue;
}
test_colors(color, color);
}
// TODO(google): Test the "blue contract" path.
}
// Make sure that we can decode colors that are given to us straight out of the
// ASTC codec.
TEST(EndpointCodecTest, DecodeCheckerboard) {
const RgbaColor kWhite {{ 255, 255, 255, 255 }};
const RgbaColor kBlack {{ 0, 0, 0, 255 }};
const std::string astc = LoadASTCFile("checkerboard");
for (int i = 0; i < astc.size(); i += 16) {
base::UInt128 block;
memcpy(&block, &astc[i], sizeof(block));
const auto intermediate = UnpackIntermediateBlock(PhysicalASTCBlock(block));
ASSERT_TRUE(intermediate) << "Block is void extent???";
const auto block_data = &intermediate.value();
ASSERT_THAT(block_data->endpoints, SizeIs(Eq(1)));
const int color_range = EndpointRangeForBlock(*block_data);
const auto& endpoints = block_data->endpoints[0];
RgbaColor low, high;
DecodeColorsForMode(endpoints.colors, color_range, endpoints.mode,
&low, &high);
// Expect that the endpoints are black and white, but either order.
EXPECT_THAT(std::make_pair(low, high),
AnyOf(
Pair(Eq(kWhite), Eq(kBlack)),
Pair(Eq(kBlack), Eq(kWhite))));
}
}
} // namespace
} // namespace astc_codec

View File

@@ -0,0 +1,97 @@
// 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/footprint.h"
#include <array>
#include <tuple>
#include <vector>
#include <gtest/gtest.h>
namespace astc_codec {
namespace {
TEST(FootprintTest, ParseAstcFootprintString) {
using ASTCTestPair = std::pair<std::string, Footprint>;
const std::array<ASTCTestPair, Footprint::NumValidFootprints()>
valid_footprints {{
std::make_pair("4x4", Footprint::Get4x4()),
std::make_pair("5x4", Footprint::Get5x4()),
std::make_pair("5x5", Footprint::Get5x5()),
std::make_pair("6x5", Footprint::Get6x5()),
std::make_pair("6x6", Footprint::Get6x6()),
std::make_pair("8x5", Footprint::Get8x5()),
std::make_pair("8x6", Footprint::Get8x6()),
std::make_pair("8x8", Footprint::Get8x8()),
std::make_pair("10x5", Footprint::Get10x5()),
std::make_pair("10x6", Footprint::Get10x6()),
std::make_pair("10x8", Footprint::Get10x8()),
std::make_pair("10x10", Footprint::Get10x10()),
std::make_pair("12x10", Footprint::Get12x10()),
std::make_pair("12x12", Footprint::Get12x12())
}};
for (const auto& test : valid_footprints) {
base::Optional<Footprint> footprint = Footprint::Parse(test.first.c_str());
EXPECT_TRUE(footprint);
EXPECT_EQ(test.second, footprint.value());
}
EXPECT_DEBUG_DEATH(EXPECT_FALSE(Footprint::Parse("")), "");
EXPECT_DEBUG_DEATH(EXPECT_FALSE(Footprint::Parse("3")), "");
EXPECT_DEBUG_DEATH(EXPECT_FALSE(Footprint::Parse("x")), "");
// Validly formed but out-of-bounds dimensions do not assert, otherwise
// malformed ASTC files could crash the decoder in debug builds.
EXPECT_FALSE(Footprint::Parse("9999999999x10"));
EXPECT_DEBUG_DEATH(EXPECT_FALSE(Footprint::Parse("ax8")), "");
EXPECT_DEBUG_DEATH(EXPECT_FALSE(Footprint::Parse("2x3x4")), "");
EXPECT_DEBUG_DEATH(EXPECT_FALSE(Footprint::Parse("-3x4")), "");
EXPECT_FALSE(Footprint::Parse("10x4"));
}
TEST(FootprintTest, Bitrates) {
EXPECT_NEAR(Footprint::Get4x4().Bitrate(), 8.f, 0.01f);
EXPECT_NEAR(Footprint::Get5x4().Bitrate(), 6.4f, 0.01f);
EXPECT_NEAR(Footprint::Get5x5().Bitrate(), 5.12f, 0.01f);
EXPECT_NEAR(Footprint::Get6x5().Bitrate(), 4.27f, 0.01f);
EXPECT_NEAR(Footprint::Get6x6().Bitrate(), 3.56f, 0.01f);
EXPECT_NEAR(Footprint::Get8x5().Bitrate(), 3.20f, 0.01f);
EXPECT_NEAR(Footprint::Get8x6().Bitrate(), 2.67f, 0.01f);
EXPECT_NEAR(Footprint::Get8x8().Bitrate(), 2.00f, 0.01f);
EXPECT_NEAR(Footprint::Get10x5().Bitrate(), 2.56f, 0.01f);
EXPECT_NEAR(Footprint::Get10x6().Bitrate(), 2.13f, 0.01f);
EXPECT_NEAR(Footprint::Get10x8().Bitrate(), 1.60f, 0.01f);
EXPECT_NEAR(Footprint::Get10x10().Bitrate(), 1.28f, 0.01f);
EXPECT_NEAR(Footprint::Get12x10().Bitrate(), 1.07f, 0.01f);
EXPECT_NEAR(Footprint::Get12x12().Bitrate(), 0.89f, 0.01f);
}
TEST(FootprintTest, StorageRequirements) {
auto footprint = Footprint::Get10x8();
EXPECT_EQ(footprint.Width(), 10);
EXPECT_EQ(footprint.Height(), 8);
// If we have 8x8 blocks, then we have 64*16 = 1024 bytes.
EXPECT_EQ(footprint.StorageRequirements(80, 64), 1024);
// If our block is a little smaller this still counts because we need to
// cover a partial block with a fully encoded one.
EXPECT_EQ(footprint.StorageRequirements(79, 63), 1024);
}
} // namespace
} // namespace astc_codec

View File

@@ -0,0 +1,217 @@
// 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 <gtest/gtest.h>
#include <fstream>
#include <vector>
static constexpr size_t kMaxVectorOutput = 128;
class ImageBuffer {
public:
static constexpr size_t Align = 4;
void Allocate(size_t width, size_t height, size_t bytes_per_pixel) {
width_ = width;
height_ = height;
bytes_per_pixel_ = bytes_per_pixel;
stride_ = AlignBytes(width * bytes_per_pixel);
data_.resize(stride_ * height);
}
uint8_t* operator()(size_t x, size_t y) {
assert(x < width_ && y < height_);
return &data_[y * Stride() + x * bytes_per_pixel_];
}
size_t Stride() const { return stride_; }
size_t BytesPerPixel() const { return bytes_per_pixel_; }
std::vector<uint8_t>& Data() { return data_; }
const std::vector<uint8_t>& Data() const { return data_; }
size_t DataSize() const { return data_.size(); }
private:
size_t AlignBytes(size_t bytes) const {
return (bytes + (Align - 1)) / Align * Align;
}
size_t width_ = 0;
size_t height_ = 0;
size_t stride_ = 0;
size_t bytes_per_pixel_ = 0;
std::vector<uint8_t> data_;
};
namespace std {
static void PrintTo(const vector<uint8_t>& vec, ostream* os) {
ios::fmtflags origFlags(os->flags());
*os << '{';
size_t count = 0;
for (vector<uint8_t>::const_iterator it = vec.begin(); it != vec.end();
++it, ++count) {
if (count > 0) {
*os << ", ";
}
if (count == kMaxVectorOutput) {
*os << "... ";
break;
}
if ((count % 16) == 0) {
*os << "\n";
}
if (*it == 0) {
*os << " ";
} else {
*os << "0x" << std::hex << std::uppercase << std::setw(2)
<< std::setfill('0') << int(*it) << std::dec;
}
}
*os << '}';
os->flags(origFlags);
}
} // namespace std
static inline std::string LoadFile(const std::string& path) {
std::ifstream is(path, std::ios::binary);
EXPECT_TRUE(is) << "Failed to load file " << path;
if (!is) {
return "";
}
std::ostringstream ss;
ss << is.rdbuf();
return ss.str();
}
static inline std::string LoadASTCFile(const std::string& basename) {
const std::string filename =
std::string("src/decoder/testdata/") + basename + ".astc";
std::string result = LoadFile(filename);
// Don't parse the header here, we already know what kind of astc encoding it
// is.
if (result.size() < 16) {
return "";
} else {
return result.substr(16);
}
}
static inline void LoadGoldenBmp(const std::string& path, ImageBuffer* result) {
constexpr size_t kBmpHeaderSize = 54;
SCOPED_TRACE(testing::Message() << "LoadGoldenBmp " << path);
const std::string data = LoadFile(path);
ASSERT_FALSE(data.empty()) << "Failed to open golden image: " << path;
ASSERT_GE(data.size(), kBmpHeaderSize);
ASSERT_EQ('B', data[0]);
ASSERT_EQ('M', data[1]);
uint32_t dataPos = *reinterpret_cast<const uint32_t*>(&data[0x0A]);
uint32_t imageSize = *reinterpret_cast<const uint32_t*>(&data[0x22]);
const uint16_t bitsPerPixel = *reinterpret_cast<const uint16_t*>(&data[0x1C]);
int width = *reinterpret_cast<const int*>(&data[0x12]);
int height = *reinterpret_cast<const int*>(&data[0x16]);
SCOPED_TRACE(testing::Message()
<< "dataPos=" << dataPos << ", imageSize=" << imageSize
<< ", bitsPerPixel=" << bitsPerPixel << ", width=" << width
<< ", height=" << height);
if (height < 0) {
height = -height;
}
if (imageSize == 0) {
imageSize = width * height * 3;
}
if (dataPos < kBmpHeaderSize) {
dataPos = kBmpHeaderSize;
}
ASSERT_TRUE(bitsPerPixel == 24 || bitsPerPixel == 32)
<< "BMP bits per pixel mismatch, expected 24 or 32";
result->Allocate(width, height, bitsPerPixel == 24 ? 3 : 4);
ASSERT_LE(imageSize, result->DataSize());
std::vector<uint8_t>& resultData = result->Data();
const size_t stride = result->Stride();
// Copy the data row-by-row to make sure that stride is right.
for (size_t row = 0; row < static_cast<size_t>(height); ++row) {
memcpy(&resultData[row * stride], &data[dataPos + row * stride],
width * bitsPerPixel / 8);
}
if (bitsPerPixel == 32) {
// Swizzle the data from ABGR to ARGB.
for (size_t row = 0; row < static_cast<size_t>(height); ++row) {
uint8_t* rowData = resultData.data() + row * stride;
for (size_t i = 3; i < stride; i += 4) {
const uint8_t b = rowData[i - 3];
rowData[i - 3] = rowData[i - 1];
rowData[i - 1] = b;
}
}
} else {
// Swizzle the data from BGR to RGB.
for (size_t row = 0; row < static_cast<size_t>(height); ++row) {
uint8_t* rowData = resultData.data() + row * stride;
for (size_t i = 2; i < stride; i += 3) {
const uint8_t tmp = rowData[i - 2];
rowData[i - 2] = rowData[i];
rowData[i] = tmp;
}
}
}
}
static inline void CompareSumOfSquaredDifferences(const ImageBuffer& golden,
const ImageBuffer& image,
double threshold) {
ASSERT_EQ(golden.DataSize(), image.DataSize());
ASSERT_EQ(golden.Stride(), image.Stride());
ASSERT_EQ(golden.BytesPerPixel(), image.BytesPerPixel());
const std::vector<uint8_t>& image_data = image.Data();
const std::vector<uint8_t>& golden_data = golden.Data();
double sum = 0.0;
for (size_t i = 0; i < image_data.size(); ++i) {
const double diff = static_cast<double>(image_data[i]) - golden_data[i];
sum += diff * diff;
}
EXPECT_LE(sum, threshold * image_data.size())
<< "Per pixel " << (sum / image_data.size())
<< ", expected <= " << threshold;
if (sum > threshold * image_data.size()) {
// Fall back to comparison which will dump first chunk of vector.
EXPECT_EQ(golden_data, image_data);
}
}

View File

@@ -0,0 +1,336 @@
// 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/integer_sequence_codec.h"
#include "src/base/uint128.h"
#include <random>
#include <string>
#include <vector>
#include <gtest/gtest.h>
using astc_codec::base::UInt128;
using astc_codec::base::BitStream;
using astc_codec::IntegerSequenceCodec;
using astc_codec::IntegerSequenceEncoder;
using astc_codec::IntegerSequenceDecoder;
namespace {
// Make sure that the counts returned for a specific range match what's
// expected. In particular, make sure that it fits with Table C.2.7
TEST(ASTCIntegerSequenceCodecTest, TestGetCountsForRange) {
std::array<int, 3> kExpectedCounts[31] = {
{{ 0, 0, 1 }}, // 1
{{ 1, 0, 0 }}, // 2
{{ 0, 0, 2 }}, // 3
{{ 0, 1, 0 }}, // 4
{{ 1, 0, 1 }}, // 5
{{ 0, 0, 3 }}, // 6
{{ 0, 0, 3 }}, // 7
{{ 0, 1, 1 }}, // 8
{{ 0, 1, 1 }}, // 9
{{ 1, 0, 2 }}, // 10
{{ 1, 0, 2 }}, // 11
{{ 0, 0, 4 }}, // 12
{{ 0, 0, 4 }}, // 13
{{ 0, 0, 4 }}, // 14
{{ 0, 0, 4 }}, // 15
{{ 0, 1, 2 }}, // 16
{{ 0, 1, 2 }}, // 17
{{ 0, 1, 2 }}, // 18
{{ 0, 1, 2 }}, // 19
{{ 1, 0, 3 }}, // 20
{{ 1, 0, 3 }}, // 21
{{ 1, 0, 3 }}, // 22
{{ 1, 0, 3 }}, // 23
{{ 0, 0, 5 }}, // 24
{{ 0, 0, 5 }}, // 25
{{ 0, 0, 5 }}, // 26
{{ 0, 0, 5 }}, // 27
{{ 0, 0, 5 }}, // 28
{{ 0, 0, 5 }}, // 29
{{ 0, 0, 5 }}, // 30
{{ 0, 0, 5 }}, // 31
};
int t, q, b;
for (int i = 1; i < 32; ++i) {
IntegerSequenceCodec::GetCountsForRange(i, &t, &q, &b);
EXPECT_EQ(t, kExpectedCounts[i - 1][0]);
EXPECT_EQ(q, kExpectedCounts[i - 1][1]);
EXPECT_EQ(b, kExpectedCounts[i - 1][2]);
}
ASSERT_DEATH(IntegerSequenceCodec::GetCountsForRange(0, &t, &q, &b), "");
ASSERT_DEATH(IntegerSequenceCodec::GetCountsForRange(256, &t, &q, &b), "");
IntegerSequenceCodec::GetCountsForRange(1, &t, &q, &b);
EXPECT_EQ(t, 0);
EXPECT_EQ(q, 0);
EXPECT_EQ(b, 1);
}
// Test to make sure that we're calculating the number of bits needed to
// encode a given number of values based on the range of the values.
TEST(ASTCIntegerSequenceCodecTest, TestNumBitsForCounts) {
int trits = 0;
int quints = 0;
int bits = 0;
// A range of one should have single bits, so n 1-bit values should be n bits.
trits = 0;
quints = 0;
bits = 1;
for (int i = 0; i < 64; ++i) {
EXPECT_EQ(IntegerSequenceCodec::GetBitCount(i, trits, quints, bits), i);
EXPECT_EQ(IntegerSequenceCodec::GetBitCountForRange(i, 1), i);
}
// Similarly, N two-bit values should be 2n bits...
trits = 0;
quints = 0;
bits = 2;
for (int i = 0; i < 64; ++i) {
int bit_counts = IntegerSequenceCodec::GetBitCount(i, trits, quints, bits);
EXPECT_EQ(bit_counts, 2 * i);
EXPECT_EQ(IntegerSequenceCodec::GetBitCountForRange(i, 3), 2 * i);
}
// Trits are a bit more complicated -- there are five trits in a block, so
// if we encode 15 values with 3 bits each in trits, we'd get three blocks,
// each with eight bits of trits.
trits = 1;
quints = 0;
bits = 3;
EXPECT_EQ(IntegerSequenceCodec::GetBitCount(15, trits, quints, bits),
8 * 3 + 15 * 3);
EXPECT_EQ(IntegerSequenceCodec::GetBitCountForRange(15, 23),
IntegerSequenceCodec::GetBitCount(15, trits, quints, bits));
// However, if instead we encode 13 values, we don't need to use the remaining
// two values, so we only need bits as they will be encoded. As it turns out,
// this means we can avoid three bits in the final block (one for the high
// order trit encoding and two for one of the values), resulting in 47 bits.
trits = 1;
quints = 0;
bits = 2;
EXPECT_EQ(IntegerSequenceCodec::GetBitCount(13, trits, quints, bits), 47);
EXPECT_EQ(IntegerSequenceCodec::GetBitCountForRange(13, 11),
IntegerSequenceCodec::GetBitCount(13, trits, quints, bits));
// Quints have a similar property -- if we encode six values using a quint and
// four bits, then we have two quint blocks each with three values and a seven
// bit encoded quint triplet...
trits = 0;
quints = 1;
bits = 4;
EXPECT_EQ(IntegerSequenceCodec::GetBitCount(6, trits, quints, bits),
7 * 2 + 6 * 4);
EXPECT_EQ(IntegerSequenceCodec::GetBitCountForRange(6, 79),
IntegerSequenceCodec::GetBitCount(6, trits, quints, bits));
// If we have fewer values than blocks we can again avoid about 2 + nbits
// bits...
trits = 0;
quints = 1;
bits = 3;
EXPECT_EQ(IntegerSequenceCodec::GetBitCount(7, trits, quints, bits),
/* first two quint blocks */ 7 * 2 +
/* first two blocks of bits */ 6 * 3 +
/* last quint block without the high order four bits */ 3 +
/* last block with one set of three bits */ 3);
}
// Tests that the encoder knows how to encode values of the form 5*2^k.
TEST(ASTCIntegerSequenceCodecTest, TestQuintCodec) {
// In this case, k = 4
// Setup bit src/sink
BitStream<UInt128> bit_sink;
const int kValueRange = 79;
IntegerSequenceEncoder enc(kValueRange);
enc.AddValue(3);
enc.AddValue(79);
enc.AddValue(37);
enc.Encode(&bit_sink);
// quint: 1000101 m0: 0011 m1: 1111 m2: 0101
// 100 0100 0111 1101 0010
// interleaved 10m200m1101m0
// should be 100 1010 0111 1101 0011 = 0x4A7D3
EXPECT_EQ(bit_sink.Bits(), 19);
uint64_t encoded = 0;
bit_sink.GetBits(19, &encoded);
EXPECT_EQ(encoded, 0x4A7D3);
// Now check that decoding it works as well
BitStream<UInt128> bit_src(encoded, 19);
IntegerSequenceDecoder dec(kValueRange);
auto decoded_vals = dec.Decode(3, &bit_src);
ASSERT_EQ(decoded_vals.size(), 3);
EXPECT_EQ(decoded_vals[0], 3);
EXPECT_EQ(decoded_vals[1], 79);
EXPECT_EQ(decoded_vals[2], 37);
}
// Tests that the encoder knows how to encode values of the form 3*2^k.
TEST(ASTCIntegerSequenceCodecTest, TestTritCodec) {
uint64_t encoded = 0;
// Setup bit src/sink
BitStream<UInt128> bit_sink(encoded, 0);
const int kValueRange = 11;
IntegerSequenceEncoder enc(kValueRange);
enc.AddValue(7);
enc.AddValue(5);
enc.AddValue(3);
enc.AddValue(6);
enc.AddValue(10);
enc.Encode(&bit_sink);
EXPECT_EQ(bit_sink.Bits(), 18);
bit_sink.GetBits(18, &encoded);
EXPECT_EQ(encoded, 0x37357);
// Now check that decoding it works as well
BitStream<UInt128> bit_src(encoded, 19);
IntegerSequenceDecoder dec(kValueRange);
auto decoded_vals = dec.Decode(5, &bit_src);
ASSERT_EQ(decoded_vals.size(), 5);
EXPECT_EQ(decoded_vals[0], 7);
EXPECT_EQ(decoded_vals[1], 5);
EXPECT_EQ(decoded_vals[2], 3);
EXPECT_EQ(decoded_vals[3], 6);
EXPECT_EQ(decoded_vals[4], 10);
}
// Test a specific quint encoding/decoding. This test makes sure that the way we
// encode and decode integer sequences matches what we should expect out of the
// reference ASTC encoder.
TEST(ASTCIntegerSequenceCodecTest, TestDecodeThenEncode) {
std::vector<int> vals = {{ 16, 18, 17, 4, 7, 14, 10, 0 }};
const uint64_t kValEncoding = 0x2b9c83dc;
BitStream<UInt128> bit_src(kValEncoding, 64);
IntegerSequenceDecoder dec(19);
auto decoded_vals = dec.Decode(8, &bit_src);
ASSERT_EQ(decoded_vals.size(), vals.size());
for (size_t i = 0; i < decoded_vals.size(); ++i) {
EXPECT_EQ(decoded_vals[i], vals[i]);
}
// Setup bit src/sink
BitStream<UInt128> bit_sink;
IntegerSequenceEncoder enc(19);
for (const auto& v : vals) {
enc.AddValue(v);
}
enc.Encode(&bit_sink);
EXPECT_EQ(bit_sink.Bits(), 35);
uint64_t encoded = 0;
EXPECT_TRUE(bit_sink.GetBits(35, &encoded));
EXPECT_EQ(encoded, kValEncoding)
<< std::hex << encoded << " -- " << kValEncoding;
}
// Same as the previous test, except it uses a trit encoding rather than a
// quint encoding.
TEST(ASTCIntegerSequenceCodecTest, TestDecodeThenEncodeTrits) {
std::vector<int> vals = {{ 6, 0, 0, 2, 0, 0, 0, 0, 8, 0, 0, 0, 0, 8, 8, 0 }};
const uint64_t kValEncoding = 0x0004c0100001006ULL;
BitStream<UInt128> bit_src(kValEncoding, 64);
IntegerSequenceDecoder dec(11);
auto decoded_vals = dec.Decode(vals.size(), &bit_src);
ASSERT_EQ(decoded_vals.size(), vals.size());
for (size_t i = 0; i < decoded_vals.size(); ++i) {
EXPECT_EQ(decoded_vals[i], vals[i]);
}
// Setup bit src/sink
BitStream<UInt128> bit_sink;
IntegerSequenceEncoder enc(11);
for (const auto& v : vals) {
enc.AddValue(v);
}
enc.Encode(&bit_sink);
EXPECT_EQ(bit_sink.Bits(), 58);
uint64_t encoded = 0;
EXPECT_TRUE(bit_sink.GetBits(58, &encoded));
EXPECT_EQ(encoded, kValEncoding)
<< std::hex << encoded << " -- " << kValEncoding;
}
// Generate a random sequence of integer codings with different ranges to test
// the reciprocability of our codec (encoded sequences should be able to
// decoded)
TEST(ASTCIntegerSequenceCodecTest, TestRandomReciprocation) {
std::mt19937 mt(0xbad7357);
std::uniform_int_distribution<int> rand(0, 255);
for (int test = 0; test < 1600; ++test) {
// Generate a random number of values and a random range
int num_vals = 4 + rand(mt) % 44; // Up to 48 weights in a grid
int range = 1 + rand(mt) % 63;
// If this produces a bit pattern larger than our buffer, then ignore
// it... we already know what our bounds are for the integer sequences
int num_bits = IntegerSequenceCodec::GetBitCountForRange(num_vals, range);
if (num_bits >= 64) {
continue;
}
std::vector<int> generated_vals(num_vals);
for (auto& val : generated_vals) {
val = rand(mt) % (range + 1);
}
// Encode the values using the
BitStream<UInt128> bit_sink;
// Add them to the encoder
IntegerSequenceEncoder enc(range);
for (int v : generated_vals) {
enc.AddValue(v);
}
enc.Encode(&bit_sink);
uint64_t encoded = 0;
bit_sink.GetBits(bit_sink.Bits(), &encoded);
ASSERT_GE(encoded, 0);
EXPECT_LT(encoded, 1ULL << num_bits);
BitStream<UInt128> bit_src(encoded, 64);
IntegerSequenceDecoder dec(range);
auto decoded_vals = dec.Decode(num_vals, &bit_src);
ASSERT_EQ(decoded_vals.size(), generated_vals.size());
for (size_t i = 0; i < decoded_vals.size(); ++i) {
EXPECT_EQ(decoded_vals[i], generated_vals[i]);
}
}
}
} // namespace

View File

@@ -0,0 +1,454 @@
// 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/test/image_utils.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <string>
namespace astc_codec {
namespace {
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::HasSubstr;
using ::testing::SizeIs;
using ::testing::TestWithParam;
using ::testing::ValuesIn;
// Test to make sure that unpacking an error block returns false.
TEST(IntermediateASTCBlockTest, TestUnpackError) {
const PhysicalASTCBlock kErrorBlock(base::UInt128(0));
EXPECT_FALSE(UnpackVoidExtent(kErrorBlock));
EXPECT_FALSE(UnpackIntermediateBlock(kErrorBlock));
}
// Test to make sure that if we don't populate our weight data in the
// intermediate block than the resulting color range should error due to the
// mismatch.
TEST(IntermediateASTCBlockTest, TestEndpointRangeErrorOnNotSettingWeights) {
IntermediateBlockData data;
data.weight_range = 15;
for (auto& ep : data.endpoints) {
ep.mode = ColorEndpointMode::kLDRRGBDirect;
}
data.weight_grid_dim_x = 6;
data.weight_grid_dim_y = 6;
EXPECT_EQ(-1, EndpointRangeForBlock(data));
base::UInt128 dummy;
auto err_str = Pack(data, &dummy);
EXPECT_TRUE(err_str.hasValue());
EXPECT_THAT(err_str.value(), HasSubstr("Incorrect number of weights"));
}
// Test to make sure that if we run out of bits, then we should say so.
TEST(IntermediateASTCBlockTest, TestEndpointRangeErrorOnNotEnoughBits) {
IntermediateBlockData data;
data.weight_range = 1;
data.partition_id = 0;
data.endpoints.resize(3);
for (auto& ep : data.endpoints) {
ep.mode = ColorEndpointMode::kLDRRGBDirect;
}
data.weight_grid_dim_x = 8;
data.weight_grid_dim_y = 8;
EXPECT_EQ(-2, EndpointRangeForBlock(data));
// Resize the weights to get past the error that they do not match the grid
// dimensions.
data.weights.resize(64);
base::UInt128 dummy;
auto err_str = Pack(data, &dummy);
EXPECT_TRUE(err_str.hasValue());
EXPECT_THAT(err_str.value(), HasSubstr("illegal color range"));
}
// Test to make sure that as we increase the number of weights, we decrease the
// allowable range of colors
TEST(IntermediateASTCBlockTest, TestEndpointRangeForBlock) {
IntermediateBlockData data;
data.weight_range = 2;
data.endpoints.resize(2);
data.dual_plane_channel.clear();
for (auto& ep : data.endpoints) {
ep.mode = ColorEndpointMode::kLDRRGBDirect;
}
// Weight params control how many weights are present in a block
struct WeightParams {
int width;
int height;
// We should sort based on number of weights for these params
int NumWeights() const { return width * height; }
bool operator<(const WeightParams& other) const {
return NumWeights() < other.NumWeights();
}
};
std::vector<WeightParams> weight_params;
for (int y = 2; y < 8; ++y) {
for (int x = 2; x < 8; ++x) {
weight_params.emplace_back(WeightParams{x, y});
}
}
// Sort weights from fewest to largest such that the allowable color range
// should be monotonically decreasing
std::sort(weight_params.begin(), weight_params.end());
// Keep track of the largest available color range and measure that it
// decreases as we add more weights to our block
int last_color_range = 255;
for (const auto& params : weight_params) {
data.weight_grid_dim_x = params.width;
data.weight_grid_dim_y = params.height;
const int color_range = EndpointRangeForBlock(data);
EXPECT_LE(color_range, last_color_range);
last_color_range = std::min(color_range, last_color_range);
}
// Make sure that we actually changed it at some point.
EXPECT_LT(last_color_range, 255);
}
// Test to make sure that unpacking an legitimate ASTC block returns the encoded
// values that we expect.
TEST(IntermediateASTCBlockTest, TestUnpackNonVoidExtentBlock) {
PhysicalASTCBlock blk(0x0000000001FE000173ULL);
auto b = UnpackIntermediateBlock(blk);
ASSERT_TRUE(b);
const auto& data = b.value();
EXPECT_EQ(data.weight_grid_dim_x, 6);
EXPECT_EQ(data.weight_grid_dim_y, 5);
EXPECT_EQ(data.weight_range, 7);
EXPECT_FALSE(data.partition_id);
EXPECT_FALSE(data.dual_plane_channel);
ASSERT_EQ(data.weights.size(), 30);
for (auto weight : data.weights) {
EXPECT_EQ(weight, 0);
}
ASSERT_EQ(data.endpoints.size(), 1);
for (const auto& ep_data : data.endpoints) {
EXPECT_EQ(ep_data.mode, ColorEndpointMode::kLDRLumaDirect);
ASSERT_EQ(ep_data.colors.size(), 2);
EXPECT_EQ(ep_data.colors[0], 0);
EXPECT_EQ(ep_data.colors[1], 255);
}
}
// Make sure that we can pack blocks that aren't void extent blocks. (In other
// words, can we actually deal with intermediate ASTC data).
TEST(IntermediateASTCBlockTest, TestPackNonVoidExtentBlock) {
IntermediateBlockData data;
data.weight_grid_dim_x = 6;
data.weight_grid_dim_y = 5;
data.weight_range = 7;
data.partition_id = {};
data.dual_plane_channel = {};
data.weights.resize(30);
for (auto& weight : data.weights) {
weight = 0;
}
data.endpoints.resize(1);
for (auto& ep_data : data.endpoints) {
ep_data.mode = ColorEndpointMode::kLDRLumaDirect;
ep_data.colors.resize(2);
ep_data.colors[0] = 0;
ep_data.colors[1] = 255;
}
base::UInt128 packed;
auto error_str = Pack(data, &packed);
ASSERT_FALSE(error_str) << (error_str ? error_str.value() : std::string(""));
EXPECT_EQ(packed, 0x0000000001FE000173ULL);
}
// Make sure that we can unpack void extent blocks
TEST(IntermediateASTCBlockTest, TestUnpackVoidExtentBlock) {
PhysicalASTCBlock void_extent_block(0xFFFFFFFFFFFFFDFCULL);
auto b = UnpackVoidExtent(void_extent_block);
ASSERT_TRUE(b);
const auto& data = b.value();
EXPECT_EQ(data.r, 0);
EXPECT_EQ(data.g, 0);
EXPECT_EQ(data.b, 0);
EXPECT_EQ(data.a, 0);
for (const auto& coord : data.coords) {
EXPECT_EQ(coord, (1 << 13) - 1);
}
base::UInt128 more_interesting(0xdeadbeefdeadbeefULL, 0xFFF8003FFE000DFCULL);
b = UnpackVoidExtent(PhysicalASTCBlock(more_interesting));
ASSERT_TRUE(b);
const auto& other_data = b.value();
EXPECT_EQ(other_data.r, 0xbeef);
EXPECT_EQ(other_data.g, 0xdead);
EXPECT_EQ(other_data.b, 0xbeef);
EXPECT_EQ(other_data.a, 0xdead);
EXPECT_EQ(other_data.coords[0], 0);
EXPECT_EQ(other_data.coords[1], 8191);
EXPECT_EQ(other_data.coords[2], 0);
EXPECT_EQ(other_data.coords[3], 8191);
}
// Make sure that we can pack void extent blocks and void extent data.
TEST(IntermediateASTCBlockTest, TestPackVoidExtentBlock) {
VoidExtentData data;
data.r = 0;
data.g = 0;
data.b = 0;
data.a = 0;
for (auto& coord : data.coords) {
coord = (1 << 13) - 1;
}
base::UInt128 packed;
auto error_str = Pack(data, &packed);
ASSERT_FALSE(error_str) << (error_str ? error_str.value() : std::string(""));
EXPECT_EQ(packed, 0xFFFFFFFFFFFFFDFCULL);
data.r = 0xbeef;
data.g = 0xdead;
data.b = 0xbeef;
data.a = 0xdead;
data.coords[0] = 0;
data.coords[1] = 8191;
data.coords[2] = 0;
data.coords[3] = 8191;
error_str = Pack(data, &packed);
ASSERT_FALSE(error_str) << (error_str ? error_str.value() : std::string(""));
EXPECT_EQ(packed,
base::UInt128(0xdeadbeefdeadbeefULL, 0xFFF8003FFE000DFCULL));
}
// Make sure that the color endpoint mode is properly repacked. This test case
// was created as a bug during testing.
TEST(IntermediateASTCBlockTest, TestPackUnpackWithSameCEM) {
base::UInt128 orig(0xe8e8eaea20000980ULL, 0x20000200cb73f045ULL);
auto b = UnpackIntermediateBlock(PhysicalASTCBlock(orig));
ASSERT_TRUE(b);
base::UInt128 repacked;
auto err_str = Pack(b.value(), &repacked);
ASSERT_FALSE(err_str) << (err_str ? err_str.value() : std::string(""));
EXPECT_EQ(repacked, orig);
// Test case #2
orig = base::UInt128(0x3300c30700cb01c5ULL, 0x0573907b8c0f6879ULL);
b = UnpackIntermediateBlock(PhysicalASTCBlock(orig));
ASSERT_TRUE(b);
err_str = Pack(b.value(), &repacked);
ASSERT_FALSE(err_str) << (err_str ? err_str.value() : std::string(""));
EXPECT_EQ(repacked, orig);
}
// Test that we can encode/decode a block that uses a very large gap
// between weight and endpoint data.
TEST(IntermediateASTCBlockTest, TestPackingWithLargeGap) {
// We can construct this block by doing the following:
// -- choose a block mode that only gives 24 weight bits
// -- choose the smallest endpoint mode: grayscale direct
// -- make sure there are no partitions
const base::UInt128 orig(0xBEDEAD0000000000ULL, 0x0000000001FE032EULL);
const auto b = UnpackIntermediateBlock(PhysicalASTCBlock(orig));
ASSERT_TRUE(b);
const auto& data = b.value();
EXPECT_EQ(data.weight_grid_dim_x, 2);
EXPECT_EQ(data.weight_grid_dim_y, 3);
EXPECT_EQ(data.weight_range, 15);
EXPECT_FALSE(data.partition_id);
EXPECT_FALSE(data.dual_plane_channel);
ASSERT_EQ(data.endpoints.size(), 1);
EXPECT_EQ(data.endpoints.at(0).mode, ColorEndpointMode::kLDRLumaDirect);
ASSERT_EQ(data.endpoints.at(0).colors.size(), 2);
EXPECT_EQ(data.endpoints.at(0).colors.at(0), 255);
EXPECT_EQ(data.endpoints.at(0).colors.at(1), 0);
// Now encode it again
base::UInt128 repacked;
const auto err_str = Pack(b.value(), &repacked);
EXPECT_EQ(orig, repacked) << (err_str ? err_str.value() : std::string(""));
}
// Take a block that is encoded using direct luma with full byte values and see
// if we properly set the endpoint range.
TEST(IntermediateASTCBlockTest, TestEndpointRange) {
PhysicalASTCBlock blk(0x0000000001FE000173ULL);
EXPECT_TRUE(blk.ColorValuesRange().hasValue());
EXPECT_EQ(blk.ColorValuesRange().valueOr(0), 255);
auto b = UnpackIntermediateBlock(blk);
ASSERT_TRUE(b);
const auto& data = b.value();
ASSERT_THAT(data.endpoints, SizeIs(1));
EXPECT_THAT(data.endpoints[0].mode, Eq(ColorEndpointMode::kLDRLumaDirect));
EXPECT_THAT(data.endpoints[0].colors, ElementsAre(0, 255));
EXPECT_TRUE(data.endpoint_range.hasValue());
EXPECT_EQ(data.endpoint_range.valueOr(0), 255);
}
struct ImageTestParams {
std::string image_name;
int checkered_dim;
};
static void PrintTo(const ImageTestParams& params, std::ostream* os) {
*os << "ImageTestParams(" << params.image_name << ")";
}
class IntermediateASTCBlockTest : public TestWithParam<ImageTestParams> { };
// Test whether or not a real-world ASTC implementation can be unpacked and
// then repacked into the same implementation. In conjunction with the other
// tests, we make sure that we can recreate ASTC blocks that we have previously
// unpacked.
TEST_P(IntermediateASTCBlockTest, TestPackUnpack) {
const auto& params = GetParam();
const int astc_dim = 8;
const int img_dim = params.checkered_dim * astc_dim;
const std::string astc = LoadASTCFile(params.image_name);
// Make sure that unpacking and repacking all of the blocks works...
const int kNumASTCBlocks = (img_dim / astc_dim) * (img_dim / astc_dim);
for (int i = 0; i < kNumASTCBlocks; ++i) {
base::UInt128 block_bits;
memcpy(&block_bits, astc.data() + PhysicalASTCBlock::kSizeInBytes * i,
PhysicalASTCBlock::kSizeInBytes);
const PhysicalASTCBlock block(block_bits);
base::UInt128 repacked;
if (block.IsVoidExtent()) {
auto b = UnpackVoidExtent(block);
ASSERT_TRUE(b);
auto err_str = Pack(b.value(), &repacked);
ASSERT_FALSE(err_str) << (err_str ? err_str.value() : std::string(""));
} else {
auto b = UnpackIntermediateBlock(block);
ASSERT_TRUE(b);
// Check to see that we properly set the endpoint range when we decoded
// the block.
auto& block_data = b.value();
EXPECT_EQ(block_data.endpoint_range, block.ColorValuesRange());
// Reset the endpoint range here to see if we correctly reconstruct it
// below
block_data.endpoint_range = {};
auto err_str = Pack(b.value(), &repacked);
ASSERT_FALSE(err_str) << (err_str ? err_str.value() : std::string(""));
}
// You would expect the following line to be enough:
// EXPECT_EQ(repacked, block.GetBlockBits())
// ... except that the ASTC encoder makes some interesting decisions
// about how to encode the same logical bits. One example is that
// sometimes if all partitions share an endpoint mode, the encoded
// block will not use the shared CEM mode, and rather list each
// partition's mode explicitly. For that reason, we just need to make as
// close of an approximation as possible that we decode to the same
// physical values.
PhysicalASTCBlock pb(repacked);
ASSERT_FALSE(pb.IsIllegalEncoding());
base::UInt128 pb_color_mask =
(base::UInt128(1) << pb.NumColorBits().value()) - 1;
base::UInt128 pb_color_bits =
pb.GetBlockBits() >> pb.ColorStartBit().value();
pb_color_bits &= pb_color_mask;
base::UInt128 b_color_mask =
(base::UInt128(1) << pb.NumColorBits().value()) - 1;
base::UInt128 b_color_bits =
block.GetBlockBits() >> block.ColorStartBit().value();
b_color_bits &= b_color_mask;
EXPECT_EQ(pb_color_mask, b_color_mask);
EXPECT_EQ(pb_color_bits, b_color_bits);
EXPECT_EQ(pb.IsVoidExtent(), block.IsVoidExtent());
EXPECT_EQ(pb.VoidExtentCoords(), block.VoidExtentCoords());
EXPECT_EQ(pb.WeightGridDims(), block.WeightGridDims());
EXPECT_EQ(pb.WeightRange(), block.WeightRange());
EXPECT_EQ(pb.NumWeightBits(), block.NumWeightBits());
EXPECT_EQ(pb.WeightStartBit(), block.WeightStartBit());
EXPECT_EQ(pb.IsDualPlane(), block.IsDualPlane());
EXPECT_EQ(pb.DualPlaneChannel(), block.DualPlaneChannel());
EXPECT_EQ(pb.NumPartitions(), block.NumPartitions());
EXPECT_EQ(pb.PartitionID(), block.PartitionID());
EXPECT_EQ(pb.NumColorValues(), block.NumColorValues());
EXPECT_EQ(pb.ColorValuesRange(), block.ColorValuesRange());
for (int j = 0; j < pb.NumPartitions().valueOr(0); ++j) {
EXPECT_EQ(pb.GetEndpointMode(j), block.GetEndpointMode(j));
}
}
}
std::vector<ImageTestParams> GetImageTestParams() {
return {
// image_name checkered_dim
{ "checkered_4", 4 },
{ "checkered_5", 5 },
{ "checkered_6", 6 },
{ "checkered_7", 7 },
{ "checkered_8", 8 },
{ "checkered_9", 9 },
{ "checkered_10", 10 },
{ "checkered_11", 11 },
{ "checkered_12", 12 },
};
}
INSTANTIATE_TEST_CASE_P(Checkered, IntermediateASTCBlockTest,
ValuesIn(GetImageTestParams()));
} // namespace
} // namespace astc_codec

View File

@@ -0,0 +1,273 @@
// 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/logical_astc_block.h"
#include "src/decoder/test/image_utils.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <fstream>
#include <string>
namespace astc_codec {
namespace {
using ::testing::Eq;
using ::testing::ElementsAre;
using ::testing::TestWithParam;
using ::testing::ValuesIn;
ImageBuffer LoadGoldenImageWithAlpha(std::string basename) {
const std::string filename = std::string("src/decoder/testdata/") + basename + ".bmp";
ImageBuffer result;
LoadGoldenBmp(filename, &result);
EXPECT_EQ(result.BytesPerPixel(), 4);
return result;
}
ImageBuffer LoadGoldenImage(std::string basename) {
const std::string filename = std::string("src/decoder/testdata/") + basename + ".bmp";
ImageBuffer result;
LoadGoldenBmp(filename, &result);
EXPECT_EQ(result.BytesPerPixel(), 3);
return result;
}
struct ImageTestParams {
std::string image_name;
bool has_alpha;
Footprint footprint;
int width;
int height;
};
static void PrintTo(const ImageTestParams& params, std::ostream* os) {
*os << "ImageTestParams(" << params.image_name << ", "
<< params.width << "x" << params.height << ", "
<< (params.has_alpha ? "RGBA" : "RGB") << ", "
<< "footprint " << params.footprint.Width() << "x"
<< params.footprint.Height() << ")";
}
class LogicalASTCBlockTest : public TestWithParam<ImageTestParams> { };
// Test to make sure that reading out color values from blocks is not
// terribly wrong. To do so, we compress an image and then decompress it
// using our logical blocks and the library. The difference between the
// decoded images should be minimal.
TEST_P(LogicalASTCBlockTest, ImageWithFootprint) {
const auto& params = GetParam();
const std::string astc = LoadASTCFile(params.image_name);
ImageBuffer our_decoded_image;
our_decoded_image.Allocate(params.width, params.height, params.has_alpha ? 4 : 3);
const int block_width = params.footprint.Width();
const int block_height = params.footprint.Height();
base::UInt128 block;
for (int i = 0; i < astc.size(); i += 16) {
const int block_index = i / 16;
const int blocks_wide =
(params.width + block_width - 1) / block_width;
const int block_x = block_index % blocks_wide;
const int block_y = block_index / blocks_wide;
memcpy(&block, astc.data() + i, sizeof(block));
PhysicalASTCBlock physical_block(block);
if (physical_block.IsVoidExtent()) {
auto ve = UnpackVoidExtent(physical_block);
ASSERT_TRUE(ve) << "ASTC encoder produced invalid block!";
} else {
auto ib = UnpackIntermediateBlock(physical_block);
ASSERT_TRUE(ib) << "ASTC encoder produced invalid block!";
}
// Make sure that the library doesn't produce incorrect ASTC blocks.
// This is covered in more depth in other tests in
// intermediate_astc_block_test and physical_astc_block_test
auto lb = UnpackLogicalBlock(params.footprint, physical_block);
ASSERT_TRUE(lb) << "ASTC encoder produced invalid block!";
LogicalASTCBlock logical_block = lb.value();
const size_t color_size = params.has_alpha ? 4 : 3;
for (int y = 0; y < block_height; ++y) {
for (int x = 0; x < block_width; ++x) {
const int px = block_width * block_x + x;
const int py = block_height * block_y + y;
// Skip out of bounds.
if (px >= params.width || py >= params.height) {
continue;
}
uint8_t* pixel = our_decoded_image(px, py);
const RgbaColor decoded_color = logical_block.ColorAt(x, y);
ASSERT_LE(color_size, decoded_color.size());
for (int c = 0; c < color_size; ++c) {
// All of the pixels should also be 8-bit values.
ASSERT_GE(decoded_color[c], 0);
ASSERT_LT(decoded_color[c], 256);
pixel[c] = decoded_color[c];
}
}
}
}
// Check that the decoded image is *very* similar to the library decoding
// of an ASTC texture. They may not be exact due to differences in how we
// convert a 16-bit float to an 8-bit integer.
ImageBuffer decoded_image = params.has_alpha ? LoadGoldenImageWithAlpha(params.image_name) : LoadGoldenImage(params.image_name);
CompareSumOfSquaredDifferences(decoded_image, our_decoded_image, 1.0);
}
// Test to make sure that a simple gradient image can be compressed and decoded
// by our logical block representation. This should work with every footprint.
std::vector<ImageTestParams> GetSyntheticImageTestParams() {
return {
// image_name alpha astc footprint width height
{ "footprint_4x4", false, Footprint::Get4x4(), 32, 32 },
{ "footprint_5x4", false, Footprint::Get5x4(), 32, 32 },
{ "footprint_5x5", false, Footprint::Get5x5(), 32, 32 },
{ "footprint_6x5", false, Footprint::Get6x5(), 32, 32 },
{ "footprint_6x6", false, Footprint::Get6x6(), 32, 32 },
{ "footprint_8x5", false, Footprint::Get8x5(), 32, 32 },
{ "footprint_8x6", false, Footprint::Get8x6(), 32, 32 },
{ "footprint_10x5", false, Footprint::Get10x5(), 32, 32 },
{ "footprint_10x6", false, Footprint::Get10x6(), 32, 32 },
{ "footprint_8x8", false, Footprint::Get8x8(), 32, 32 },
{ "footprint_10x8", false, Footprint::Get10x8(), 32, 32 },
{ "footprint_10x10", false, Footprint::Get10x10(), 32, 32 },
{ "footprint_12x10", false, Footprint::Get12x10(), 32, 32 },
{ "footprint_12x12", false, Footprint::Get12x12(), 32, 32 },
};
}
INSTANTIATE_TEST_CASE_P(Synthetic, LogicalASTCBlockTest,
ValuesIn(GetSyntheticImageTestParams()));
// Test to make sure that reading out color values from blocks in a real-world
// image isn't terribly wrong, either.
std::vector<ImageTestParams> GetRealWorldImageTestParams() {
return {
// image_name alpha astc footprint width height
{ "rgb_4x4", false, Footprint::Get4x4(), 224, 288 },
{ "rgb_6x6", false, Footprint::Get6x6(), 224, 288 },
{ "rgb_8x8", false, Footprint::Get8x8(), 224, 288 },
{ "rgb_12x12", false, Footprint::Get12x12(), 224, 288 },
{ "rgb_5x4", false, Footprint::Get5x4(), 224, 288 }
};
}
INSTANTIATE_TEST_CASE_P(RealWorld, LogicalASTCBlockTest,
ValuesIn(GetRealWorldImageTestParams()));
// Test to make sure that reading out color values from blocks in a real-world
// image isn't terribly wrong, either.
std::vector<ImageTestParams> GetTransparentImageTestParams() {
return {
// image_name alpha astc footprint width height
{ "atlas_small_4x4", true, Footprint::Get4x4(), 256, 256 },
{ "atlas_small_5x5", true, Footprint::Get5x5(), 256, 256 },
{ "atlas_small_6x6", true, Footprint::Get6x6(), 256, 256 },
{ "atlas_small_8x8", true, Footprint::Get8x8(), 256, 256 },
};
}
INSTANTIATE_TEST_CASE_P(Transparent, LogicalASTCBlockTest,
ValuesIn(GetTransparentImageTestParams()));
// Test to make sure that if we set our endpoints then it's reflected in our
// color selection
TEST(LogicalASTCBlockTest, SetEndpoints) {
LogicalASTCBlock logical_block(Footprint::Get8x8());
// Setup a weight checkerboard
for (int j = 0; j < 8; ++j) {
for (int i = 0; i < 8; ++i) {
if (((i ^ j) & 1) == 1) {
logical_block.SetWeightAt(i, j, 0);
} else {
logical_block.SetWeightAt(i, j, 64);
}
}
}
// Now set the colors to something ridiculous
logical_block.SetEndpoints({{ 123, 45, 67, 89 }}, {{ 101, 121, 31, 41 }}, 0);
// For each pixel, we expect it to mirror the endpoints in a checkerboard
// pattern
for (int j = 0; j < 8; ++j) {
for (int i = 0; i < 8; ++i) {
if (((i ^ j) & 1) == 1) {
EXPECT_THAT(logical_block.ColorAt(i, j), ElementsAre(123, 45, 67, 89));
} else {
EXPECT_THAT(logical_block.ColorAt(i, j), ElementsAre(101, 121, 31, 41));
}
}
}
}
// Test whether or not setting weight values under different circumstances is
// supported and reflected in the query functions.
TEST(LogicalASTCBlockTest, SetWeightVals) {
LogicalASTCBlock logical_block(Footprint::Get4x4());
EXPECT_THAT(logical_block.GetFootprint(), Eq(Footprint::Get4x4()));
// Not a dual plane by default
EXPECT_FALSE(logical_block.IsDualPlane());
logical_block.SetWeightAt(2, 3, 2);
// Set the dual plane
logical_block.SetDualPlaneChannel(0);
EXPECT_TRUE(logical_block.IsDualPlane());
// This shouldn't have reset our weight
const LogicalASTCBlock other_block = logical_block;
EXPECT_THAT(other_block.WeightAt(2, 3), Eq(2));
EXPECT_THAT(other_block.DualPlaneWeightAt(0, 2, 3), Eq(2));
// If we set the dual plane weight, it shouldn't change the original weight
// value or the other channels
logical_block.SetDualPlaneWeightAt(0, 2, 3, 1);
EXPECT_THAT(logical_block.WeightAt(2, 3), Eq(2));
EXPECT_THAT(logical_block.DualPlaneWeightAt(0, 2, 3), Eq(1));
for (int i = 1; i < 4; ++i) {
EXPECT_THAT(logical_block.DualPlaneWeightAt(i, 2, 3), Eq(2));
}
// Remove the dual plane
logical_block.SetDualPlaneChannel(-1);
EXPECT_FALSE(logical_block.IsDualPlane());
// Now the original dual plane weight should be reset back to the others. Note
// that we have to call DualPlaneWeightAt from a const logical block since
// returning a reference to a weight that doesn't exist is illegal.
const LogicalASTCBlock other_block2 = logical_block;
EXPECT_THAT(logical_block.WeightAt(2, 3), Eq(2));
for (int i = 0; i < 4; ++i) {
EXPECT_EQ(logical_block.WeightAt(2, 3),
other_block2.DualPlaneWeightAt(i, 2, 3));
}
}
} // namespace
} // namespace astc_codec

View File

@@ -0,0 +1,263 @@
// 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/partition.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <array>
#include <random>
#include <string>
#include <vector>
namespace {
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::Le;
using ::testing::Not;
using astc_codec::Footprint;
using astc_codec::Partition;
using astc_codec::PartitionMetric;
using astc_codec::GetASTCPartition;
using astc_codec::FindClosestASTCPartition;
// Test to make sure that a simple difference between two partitions where
// most of the values are the same returns what we expect.
TEST(PartitionTest, TestSimplePartitionMetric) {
Partition a = {Footprint::Get6x6(), /* num_parts = */ 2,
/* partition_id = */ {}, /* assignment = */ {}};
Partition b = a;
a.assignment = {
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1,
};
b.assignment = {
1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
};
const int dist = PartitionMetric(a, b);
EXPECT_EQ(dist, 2);
}
// Test to make sure that if one partition is a subset of another that we still
// return the proper difference against the subset of the larger one.
TEST(PartitionDeathTest, TestPartitionMetric) {
Partition a = {Footprint::Get4x4(), /* num_parts = */ 2,
/* partition_id = */ {}, /* assignment = */ {}};
Partition b = {Footprint::Get6x6(), /* num_parts = */ 2,
/* partition_id = */ {}, /* assignment = */ {}};
a.assignment = {{
1, 1, 1, 1,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 1,
}};
b.assignment = {{
1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0,
0, 1, 0, 0, 1, 0,
0, 0, 1, 1, 0, 0,
}};
EXPECT_DEATH(PartitionMetric(a, b), "");
}
// Test to make sure that even if we have different numbers of subsets for each
// partition, that the returned value is what we'd expect.
TEST(PartitionTest, TestDiffPartsPartitionMetric) {
Partition a = {Footprint::Get4x4(), /* num_parts = */ 2,
/* partition_id = */ {}, /* assignment = */ {}};
Partition b = {Footprint::Get4x4(), /* num_parts = */ 3,
/* partition_id = */ {}, /* assignment = */ {}};
a.assignment = {{
2, 2, 2, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 1,
}};
b.assignment = {{
1, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0
}};
const int dist = PartitionMetric(a, b);
EXPECT_EQ(dist, 3);
}
// An additional sanity check test that makes sure that we're not always mapping
// zero to zero in our tests.
TEST(PartitionTest, TestDiffMappingPartitionMetric) {
Partition a = {Footprint::Get4x4(), /* num_parts = */ 2,
/* partition_id = */ {}, /* assignment = */ {}};
Partition b = {Footprint::Get4x4(), /* num_parts = */ 3,
/* partition_id = */ {}, /* assignment = */ {}};
a.assignment = {{
0, 1, 2, 2,
2, 2, 2, 2,
2, 2, 2, 2,
2, 2, 2, 2,
}};
b.assignment = {{
1, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
}};
const int dist = PartitionMetric(a, b);
EXPECT_EQ(dist, 1);
}
// Finally, if we grab an ASTC partition and modify it a tad, the closest
// partition should still be the same ASTC partition.
TEST(PartitionTest, TestFindingASTCPartition) {
const Partition astc = GetASTCPartition(Footprint::Get12x12(), 3, 0x3CB);
Partition almost_astc = astc;
almost_astc.assignment[0]++;
const Partition& closest_astc = FindClosestASTCPartition(almost_astc);
EXPECT_EQ(astc, closest_astc);
}
// Test a partition that was obtained from the reference ASTC encoder. We should
// be able to match it exactly
TEST(PartitionTest, TestSpecificPartition) {
const Partition astc = GetASTCPartition(Footprint::Get10x6(), 3, 557);
EXPECT_THAT(astc.assignment, ElementsAreArray(std::array<int, 60> {{
0, 0, 0, 0, 1, 1, 1, 2, 2, 2,
0, 0, 0, 0, 1, 1, 1, 2, 2, 2,
0, 0, 0, 0, 1, 1, 1, 2, 2, 2,
0, 0, 0, 0, 1, 1, 1, 2, 2, 2,
0, 0, 0, 0, 1, 1, 1, 2, 2, 2,
0, 0, 0, 0, 1, 1, 1, 2, 2, 2 }}));
}
// Make sure that when we match against this specific partition, it'll return a
// partition with the same number of subsets
TEST(PartitionTest, EstimatedPartitionSubsets) {
Partition partition = {
/* footprint = */ Footprint::Get6x6(),
/* num_parts = */ 2,
/* partition_id = */ {},
/* assignment = */ {
0, 0, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1
}};
const Partition astc = FindClosestASTCPartition(partition);
EXPECT_THAT(astc.num_parts, Eq(partition.num_parts));
}
// Make sure that regardless of what partition we match against, it'll return a
// partition with at most a fewer number of subsets
TEST(PartitionTest, EstimatedPartitionFewerSubsets) {
std::mt19937 random(0xdeadbeef);
auto randUniform = [&random](int max) {
std::uniform_int_distribution<> dist(0, max - 1);
return dist(random);
};
constexpr int kNumFootprints = Footprint::NumValidFootprints();
const auto kFootprints = std::array<Footprint, kNumFootprints> {{
Footprint::Get4x4(),
Footprint::Get5x4(),
Footprint::Get5x5(),
Footprint::Get6x5(),
Footprint::Get6x6(),
Footprint::Get8x5(),
Footprint::Get8x6(),
Footprint::Get8x8(),
Footprint::Get10x5(),
Footprint::Get10x6(),
Footprint::Get10x8(),
Footprint::Get10x10(),
Footprint::Get12x10(),
Footprint::Get12x12()
}};
constexpr int kNumTests = 200;
for (int i = 0; i < kNumTests; ++i) {
const auto& footprint = kFootprints[randUniform(kNumFootprints)];
const int num_parts = 2 + randUniform(3);
Partition partition = {
footprint,
num_parts,
/* partition_id = */ {},
/* assignment = */ std::vector<int>(footprint.NumPixels(), 0)};
for (auto& p : partition.assignment) {
p = randUniform(num_parts);
}
const Partition astc = FindClosestASTCPartition(partition);
EXPECT_THAT(astc.num_parts, Le(partition.num_parts))
<< "Test #" << i << ": "
<< "Selected partition with ID " << astc.partition_id.value();
}
}
// Make sure that we generate unique partitions that are close to the
// candidates.
TEST(PartitionTest, UniquePartitionResults) {
Partition partition = {
/* footprint = */ Footprint::Get6x6(),
/* num_parts = */ 2,
/* partition_id = */ {},
/* assignment = */ {
0, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1
}};
const auto parts = FindKClosestASTCPartitions(partition, 2);
EXPECT_THAT(*parts[0], Not(Eq(*parts[1])));
}
// TODO(google): Verify somehow that the assignment generated from
// GetASTCPartition actually matches what's in the spec. The selection
// function was more or less copy/pasted though so it's unclear how to
// measure that against e.g. the ASTC encoder.
} // namespace

View File

@@ -0,0 +1,361 @@
// 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/uint128.h"
#include <gtest/gtest.h>
#include <string>
#include <vector>
using astc_codec::PhysicalASTCBlock;
using astc_codec::ColorEndpointMode;
using astc_codec::base::UInt128;
namespace {
static const PhysicalASTCBlock kErrorBlock(UInt128(0));
// Test to make sure that each of the constructors work and that
// they produce the same block encodings, since the ASTC blocks
// are little-endian
TEST(PhysicalASTCBlockTest, TestConstructors) {
// Little-endian reading of bytes
PhysicalASTCBlock blk1(0x0000000001FE000173ULL);
PhysicalASTCBlock blk2(
std::string("\x73\x01\x00\xFE\x01\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16));
EXPECT_EQ(blk1.GetBlockBits(), blk2.GetBlockBits());
}
// Test to see if we properly decode the maximum value that a weight
// can take in an ASTC block based on the block mode encoding. We test
// against a valid case and various error cases
TEST(PhysicalASTCBlockTest, TestWeightRange) {
PhysicalASTCBlock blk1(0x0000000001FE000173ULL);
auto weight_range = blk1.WeightRange();
ASSERT_TRUE(weight_range);
EXPECT_EQ(weight_range.value(), 7);
// If we flip the high bit then we should have a range of 31,
// although then we have too many bits and this should error.
PhysicalASTCBlock blk2(0x0000000001FE000373ULL);
EXPECT_FALSE(blk2.WeightRange());
// One bit per weight -- range of 1
PhysicalASTCBlock non_shared_cem(0x4000000000800D44ULL);
weight_range = non_shared_cem.WeightRange();
ASSERT_TRUE(weight_range);
EXPECT_EQ(weight_range.value(), 1);
// Error blocks have no weight range
EXPECT_FALSE(kErrorBlock.WeightRange());
}
// Test to see if we properly decode the weight grid width and height
// in an ASTC block based on the block mode encoding. We test against
// a valid case and various error cases
TEST(PhysicalASTCBlockTest, TestWeightDims) {
PhysicalASTCBlock blk1(0x0000000001FE000173ULL);
auto weight_dims = blk1.WeightGridDims();
EXPECT_TRUE(weight_dims);
EXPECT_EQ(weight_dims.value()[0], 6);
EXPECT_EQ(weight_dims.value()[1], 5);
// If we flip the high bit then we should have a range of 31,
// although then we have too many bits for the weight grid
// and this should error.
PhysicalASTCBlock blk2(0x0000000001FE000373ULL);
EXPECT_FALSE(blk2.WeightGridDims());
EXPECT_EQ(blk2.IsIllegalEncoding().value(),
"Too many bits required for weight grid");
// Dual plane block with 3x5 weight dims
PhysicalASTCBlock blk3(0x0000000001FE0005FFULL);
weight_dims = blk3.WeightGridDims();
ASSERT_TRUE(weight_dims);
EXPECT_EQ(weight_dims->at(0), 3);
EXPECT_EQ(weight_dims->at(1), 5);
// Error blocks shouldn't have any weight dims
EXPECT_FALSE(kErrorBlock.WeightGridDims());
PhysicalASTCBlock non_shared_cem(0x4000000000800D44ULL);
weight_dims = non_shared_cem.WeightGridDims();
ASSERT_TRUE(weight_dims);
EXPECT_EQ(weight_dims->at(0), 8);
EXPECT_EQ(weight_dims->at(1), 8);
}
// Test to see whether or not the presence of a dual-plane bit
// is decoded properly. Error encodings are tested to *not* return
// that they have dual planes.
TEST(PhysicalASTCBlockTest, TestDualPlane) {
PhysicalASTCBlock blk1(0x0000000001FE000173ULL);
EXPECT_FALSE(blk1.IsDualPlane());
EXPECT_FALSE(kErrorBlock.IsDualPlane());
// If we flip the dual plane bit, we will have too many bits
// for the weight grid and this should error
PhysicalASTCBlock blk2(0x0000000001FE000573ULL);
EXPECT_FALSE(blk2.IsDualPlane());
EXPECT_FALSE(blk2.WeightGridDims());
EXPECT_EQ(blk2.IsIllegalEncoding().value(),
"Too many bits required for weight grid");
// A dual plane with 3x5 weight grid should be supported
PhysicalASTCBlock blk3(0x0000000001FE0005FFULL);
EXPECT_TRUE(blk3.IsDualPlane());
// If we use the wrong block mode, then a valid block
// shouldn't have any dual plane
PhysicalASTCBlock blk4(0x0000000001FE000108ULL);
EXPECT_FALSE(blk4.IsDualPlane());
EXPECT_FALSE(blk4.IsIllegalEncoding());
}
// Make sure that we properly calculate the number of bits used to encode
// the weight grid. Given error encodings or void extent blocks, this number
// should be zero
TEST(PhysicalASTCBlockTest, TestNumWeightBits) {
// 6x5 single-plane weight grid with 3-bit weights
// should have 90 bits for the weights.
PhysicalASTCBlock blk1(0x0000000001FE000173ULL);
EXPECT_EQ(90, blk1.NumWeightBits());
// Error block has no weight bits
EXPECT_FALSE(kErrorBlock.NumWeightBits());
// Void extent blocks have no weight bits
EXPECT_FALSE(PhysicalASTCBlock(0xFFF8003FFE000DFCULL).NumWeightBits());
// If we flip the dual plane bit, we will have too many bits
// for the weight grid and this should error and return no bits
PhysicalASTCBlock blk2(0x0000000001FE000573ULL);
EXPECT_FALSE(blk2.NumWeightBits());
// 3x5 dual-plane weight grid with 3-bit weights
// should have 90 bits for the weights.
PhysicalASTCBlock blk3(0x0000000001FE0005FFULL);
EXPECT_EQ(90, blk3.NumWeightBits());
}
// Test to make sure that our weight bits start where we expect them to.
// In other words, make sure that the calculation based on the block mode for
// where the weight bits start is accurate.
TEST(PhysicalASTCBlockTest, TestStartWeightBit) {
EXPECT_EQ(PhysicalASTCBlock(0x4000000000800D44ULL).WeightStartBit(), 64);
// Error blocks have no weight start bit
EXPECT_FALSE(kErrorBlock.WeightStartBit());
// Void extent blocks have no weight start bit
EXPECT_FALSE(PhysicalASTCBlock(0xFFF8003FFE000DFCULL).WeightStartBit());
}
// Test to make sure that we catch various different reasons for error encoding
// of ASTC blocks, but also that certain encodings aren't errors.
TEST(PhysicalASTCBlockTest, TestErrorBlocks) {
// Various valid block modes
EXPECT_FALSE(PhysicalASTCBlock(0x0000000001FE000173ULL).IsIllegalEncoding());
EXPECT_FALSE(PhysicalASTCBlock(0x0000000001FE0005FFULL).IsIllegalEncoding());
EXPECT_FALSE(PhysicalASTCBlock(0x0000000001FE000108ULL).IsIllegalEncoding());
// This is an error because it uses an invalid block mode
EXPECT_EQ(kErrorBlock.IsIllegalEncoding().value(), "Reserved block mode");
// This is an error because we have too many weight bits
PhysicalASTCBlock err_blk(0x0000000001FE000573ULL);
EXPECT_EQ(err_blk.IsIllegalEncoding().value(),
"Too many bits required for weight grid");
// This is an error because we have too many weights
PhysicalASTCBlock err_blk2 = PhysicalASTCBlock(0x0000000001FE0005A8ULL);
EXPECT_EQ(err_blk2.IsIllegalEncoding().value(), "Too many weights specified");
PhysicalASTCBlock err_blk3 = PhysicalASTCBlock(0x0000000001FE000588ULL);
EXPECT_EQ(err_blk3.IsIllegalEncoding().value(), "Too many weights specified");
// This is an error because we have too few weights
PhysicalASTCBlock err_blk4 = PhysicalASTCBlock(0x0000000001FE00002ULL);
EXPECT_EQ(err_blk4.IsIllegalEncoding().value(),
"Too few bits required for weight grid");
// Four partitions, dual plane -- should be error
// 2x2 weight grid, 3 bits per weight
PhysicalASTCBlock dual_plane_four_parts(0x000000000000001D1FULL);
EXPECT_FALSE(dual_plane_four_parts.NumPartitions());
EXPECT_EQ(dual_plane_four_parts.IsIllegalEncoding().value(),
"Both four partitions and dual plane specified");
}
// Test to make sure that we properly identify and can manipulate void-extent
// blocks. These are ASTC blocks that only define a single color for the entire
// block.
TEST(PhysicalASTCBlockTest, TestVoidExtentBlocks) {
// Various valid block modes that aren't void extent blocks
EXPECT_FALSE(PhysicalASTCBlock(0x0000000001FE000173ULL).IsVoidExtent());
EXPECT_FALSE(PhysicalASTCBlock(0x0000000001FE0005FFULL).IsVoidExtent());
EXPECT_FALSE(PhysicalASTCBlock(0x0000000001FE000108ULL).IsVoidExtent());
// Error block is not a void extent block
EXPECT_FALSE(kErrorBlock.IsVoidExtent());
// Void extent block is void extent block...
UInt128 void_extent_encoding(0, 0xFFF8003FFE000DFCULL);
EXPECT_FALSE(PhysicalASTCBlock(void_extent_encoding).IsIllegalEncoding());
EXPECT_TRUE(PhysicalASTCBlock(void_extent_encoding).IsVoidExtent());
// If we modify the high 64 bits it shouldn't change anything
void_extent_encoding |= UInt128(0xdeadbeefdeadbeef, 0);
EXPECT_FALSE(PhysicalASTCBlock(void_extent_encoding).IsIllegalEncoding());
EXPECT_TRUE(PhysicalASTCBlock(void_extent_encoding).IsVoidExtent());
}
TEST(PhysicalASTCBlockTest, TestVoidExtentCoordinates) {
// The void extent block should have texture coordinates from 0-8191
auto coords = PhysicalASTCBlock(0xFFF8003FFE000DFCULL).VoidExtentCoords();
EXPECT_EQ(coords->at(0), 0);
EXPECT_EQ(coords->at(1), 8191);
EXPECT_EQ(coords->at(2), 0);
EXPECT_EQ(coords->at(3), 8191);
// If we set the coords to all 1's then it's still a void extent
// block, but there aren't any void extent coords.
EXPECT_FALSE(PhysicalASTCBlock(0xFFFFFFFFFFFFFDFCULL).IsIllegalEncoding());
EXPECT_TRUE(PhysicalASTCBlock(0xFFFFFFFFFFFFFDFCULL).IsVoidExtent());
EXPECT_FALSE(PhysicalASTCBlock(0xFFFFFFFFFFFFFDFCULL).VoidExtentCoords());
// If we set the void extent coords to something where the coords are
// >= each other, then the encoding is illegal.
EXPECT_TRUE(PhysicalASTCBlock(0x0008004002001DFCULL).IsIllegalEncoding());
EXPECT_TRUE(PhysicalASTCBlock(0x0007FFC001FFFDFCULL).IsIllegalEncoding());
}
// Test to see if we can properly identify the number of partitions in a block
// In particular -- we need to make sure we properly identify single and
// multi-partition blocks, but also that void extent and error blocks don't
// return valid numbers of partitions
TEST(PhysicalASTCBlockTest, TestNumPartitions) {
// Various valid block modes, but all single partition
EXPECT_EQ(PhysicalASTCBlock(0x0000000001FE000173ULL).NumPartitions(), 1);
EXPECT_EQ(PhysicalASTCBlock(0x0000000001FE0005FFULL).NumPartitions(), 1);
EXPECT_EQ(PhysicalASTCBlock(0x0000000001FE000108ULL).NumPartitions(), 1);
// Two to four partitions don't have enough bits for color.
EXPECT_FALSE(PhysicalASTCBlock(0x000000000000000973ULL).NumPartitions());
EXPECT_FALSE(PhysicalASTCBlock(0x000000000000001173ULL).NumPartitions());
EXPECT_FALSE(PhysicalASTCBlock(0x000000000000001973ULL).NumPartitions());
// Test against having more than one partition
PhysicalASTCBlock non_shared_cem(0x4000000000800D44ULL);
EXPECT_EQ(non_shared_cem.NumPartitions(), 2);
}
// Test the color endpoint modes specified for how the endpoints are encoded.
// In particular, test that shared color endpoint modes work for multi-partition
// blocks and that non-shared color endpoint modes also work.
TEST(PhysicalASTCBlockTest, TestColorEndpointModes) {
// Four partitions -- one shared CEM
const auto blk1 = PhysicalASTCBlock(0x000000000000001961ULL);
for (int i = 0; i < 4; ++i) {
EXPECT_EQ(blk1.GetEndpointMode(i), ColorEndpointMode::kLDRLumaDirect);
}
// Void extent blocks have no endpoint modes
EXPECT_FALSE(PhysicalASTCBlock(0xFFF8003FFE000DFCULL).GetEndpointMode(0));
// Test out of range partitions
EXPECT_FALSE(PhysicalASTCBlock(0x0000000001FE000173ULL).GetEndpointMode(1));
EXPECT_FALSE(PhysicalASTCBlock(0x0000000001FE000173ULL).GetEndpointMode(-1));
EXPECT_FALSE(PhysicalASTCBlock(0x0000000001FE000173ULL).GetEndpointMode(100));
// Error blocks have no endpoint modes
EXPECT_FALSE(kErrorBlock.GetEndpointMode(0));
// Test non-shared CEMs
PhysicalASTCBlock non_shared_cem(0x4000000000800D44ULL);
EXPECT_EQ(non_shared_cem.GetEndpointMode(0),
ColorEndpointMode::kLDRLumaDirect);
EXPECT_EQ(non_shared_cem.GetEndpointMode(1),
ColorEndpointMode::kLDRLumaBaseOffset);
}
// Make sure that if we have more than one partition then we have proper
// partition IDs (these determine which pixels correspond to which partition)
TEST(PhysicalASTCBlockTest, TestPartitionID) {
// Valid partitions
EXPECT_EQ(PhysicalASTCBlock(0x4000000000FFED44ULL).PartitionID(), 0x3FF);
EXPECT_EQ(PhysicalASTCBlock(0x4000000000AAAD44ULL).PartitionID(), 0x155);
// Error blocks have no partition IDs
EXPECT_FALSE(kErrorBlock.PartitionID());
// Void extent blocks have no endpoint modes
EXPECT_FALSE(PhysicalASTCBlock(0xFFF8003FFE000DFCULL).PartitionID());
}
// Make sure that we're properly attributing the number of bits associated with
// the encoded color values.
TEST(PhysicalASTCBlockTest, TestNumColorBits) {
// If we're using a direct luma channel, then the number of color bits is 16
EXPECT_EQ(PhysicalASTCBlock(0x0000000001FE000173ULL).NumColorValues(), 2);
EXPECT_EQ(PhysicalASTCBlock(0x0000000001FE000173ULL).NumColorBits(), 16);
// Error blocks have nothing
EXPECT_FALSE(kErrorBlock.NumColorValues());
EXPECT_FALSE(kErrorBlock.NumColorBits());
// Void extent blocks have four color values and 64 bits of color
EXPECT_EQ(PhysicalASTCBlock(0xFFF8003FFE000DFCULL).NumColorValues(), 4);
EXPECT_EQ(PhysicalASTCBlock(0xFFF8003FFE000DFCULL).NumColorBits(), 64);
}
// Make sure that we're properly decoding the range of values that each of the
// encoded color values can take
TEST(PhysicalASTCBlockTest, TestColorValuesRange) {
// If we're using a direct luma channel, then we use two color values up to
// a full byte each.
EXPECT_EQ(PhysicalASTCBlock(0x0000000001FE000173ULL).ColorValuesRange(), 255);
// Error blocks have nothing
EXPECT_FALSE(kErrorBlock.ColorValuesRange());
// Void extent blocks have four color values and 64 bits of color, so the
// color range for each is sixteen bits.
EXPECT_EQ(PhysicalASTCBlock(0xFFF8003FFE000DFCULL).ColorValuesRange(),
(1 << 16) - 1);
}
// Test that we know where the color data starts. This is different mostly
// depending on whether or not the block is single-partition or void extent.
TEST(PhysicalASTCBlockTest, TestColorStartBits) {
// Void extent blocks start at bit 64
EXPECT_EQ(PhysicalASTCBlock(0xFFF8003FFE000DFCULL).ColorStartBit(), 64);
// Error blocks don't start anywhere
EXPECT_FALSE(kErrorBlock.ColorStartBit());
// Single partition blocks start at bit 17
EXPECT_EQ(PhysicalASTCBlock(0x0000000001FE000173ULL).ColorStartBit(), 17);
EXPECT_EQ(PhysicalASTCBlock(0x0000000001FE0005FFULL).ColorStartBit(), 17);
EXPECT_EQ(PhysicalASTCBlock(0x0000000001FE000108ULL).ColorStartBit(), 17);
// Multi-partition blocks start at bit 29
EXPECT_EQ(PhysicalASTCBlock(0x4000000000FFED44ULL).ColorStartBit(), 29);
EXPECT_EQ(PhysicalASTCBlock(0x4000000000AAAD44ULL).ColorStartBit(), 29);
}
} // namespace

View File

@@ -0,0 +1,288 @@
// 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/quantization.h"
#include "src/decoder/integer_sequence_codec.h"
#include <gtest/gtest.h>
#include <functional>
#include <string>
#include <vector>
namespace astc_codec {
namespace {
// Make sure that we never exceed the maximum range that we pass in.
TEST(QuantizationTest, TestQuantizeMaxRange) {
for (int i = kEndpointRangeMinValue; i < 256; ++i) {
EXPECT_LE(QuantizeCEValueToRange(255, i), i);
}
for (int i = 1; i < kWeightRangeMaxValue; ++i) {
EXPECT_LE(QuantizeWeightToRange(64, i), i);
}
}
// Make sure that whenever we unquantize and requantize a value we get back
// what we started with.
TEST(QuantizationTest, TestReversibility) {
for (auto itr = ISERangeBegin(); itr != ISERangeEnd(); itr++) {
const int range = *itr;
if (range <= kWeightRangeMaxValue) {
for (int j = 0; j <= range; ++j) {
const int q = UnquantizeWeightFromRange(j, range);
EXPECT_EQ(QuantizeWeightToRange(q, range), j);
}
}
if (range >= kEndpointRangeMinValue) {
for (int j = 0; j <= range; ++j) {
const int q = UnquantizeCEValueFromRange(j, range);
EXPECT_EQ(QuantizeCEValueToRange(q, range), j);
}
}
}
}
// Make sure that whenever we quantize a non-maximal value it gets sent to the
// proper range
TEST(QuantizationTest, TestQuantizationRange) {
for (auto itr = ISERangeBegin(); itr != ISERangeEnd(); itr++) {
const int range = *itr;
if (range >= kEndpointRangeMinValue) {
EXPECT_LE(QuantizeCEValueToRange(0, range), range);
EXPECT_LE(QuantizeCEValueToRange(4, range), range);
EXPECT_LE(QuantizeCEValueToRange(15, range), range);
EXPECT_LE(QuantizeCEValueToRange(22, range), range);
EXPECT_LE(QuantizeCEValueToRange(66, range), range);
EXPECT_LE(QuantizeCEValueToRange(91, range), range);
EXPECT_LE(QuantizeCEValueToRange(126, range), range);
}
if (range <= kWeightRangeMaxValue) {
EXPECT_LE(QuantizeWeightToRange(0, range), range);
EXPECT_LE(QuantizeWeightToRange(4, range), range);
EXPECT_LE(QuantizeWeightToRange(15, range), range);
EXPECT_LE(QuantizeWeightToRange(22, range), range);
}
}
}
// Make sure that whenever we unquantize a value it remains within [0, 255]
TEST(QuantizationTest, TestUnquantizationRange) {
EXPECT_LT(UnquantizeCEValueFromRange(2, 7), 256);
EXPECT_LT(UnquantizeCEValueFromRange(7, 7), 256);
EXPECT_LT(UnquantizeCEValueFromRange(39, 63), 256);
EXPECT_LT(UnquantizeCEValueFromRange(66, 79), 256);
EXPECT_LT(UnquantizeCEValueFromRange(91, 191), 256);
EXPECT_LT(UnquantizeCEValueFromRange(126, 255), 256);
EXPECT_LT(UnquantizeCEValueFromRange(255, 255), 256);
EXPECT_LE(UnquantizeWeightFromRange(0, 1), 64);
EXPECT_LE(UnquantizeWeightFromRange(2, 7), 64);
EXPECT_LE(UnquantizeWeightFromRange(7, 7), 64);
EXPECT_LE(UnquantizeWeightFromRange(29, 31), 64);
}
// When we quantize a value, it should use the largest quantization range that
// does not exceed the desired range.
TEST(QuantizationTest, TestUpperBoundRanges) {
auto expected_range_itr = ISERangeBegin();
for (int desired_range = 1; desired_range < 256; ++desired_range) {
if (desired_range == *(expected_range_itr + 1)) {
++expected_range_itr;
}
const int expected_range = *expected_range_itr;
ASSERT_LE(expected_range, desired_range);
if (desired_range >= kEndpointRangeMinValue) {
EXPECT_EQ(QuantizeCEValueToRange(0, desired_range),
QuantizeCEValueToRange(0, expected_range));
EXPECT_EQ(QuantizeCEValueToRange(208, desired_range),
QuantizeCEValueToRange(208, expected_range));
EXPECT_EQ(QuantizeCEValueToRange(173, desired_range),
QuantizeCEValueToRange(173, expected_range));
EXPECT_EQ(QuantizeCEValueToRange(13, desired_range),
QuantizeCEValueToRange(13, expected_range));
EXPECT_EQ(QuantizeCEValueToRange(255, desired_range),
QuantizeCEValueToRange(255, expected_range));
}
if (desired_range <= kWeightRangeMaxValue) {
EXPECT_EQ(QuantizeWeightToRange(0, desired_range),
QuantizeWeightToRange(0, expected_range));
EXPECT_EQ(QuantizeWeightToRange(63, desired_range),
QuantizeWeightToRange(63, expected_range));
EXPECT_EQ(QuantizeWeightToRange(12, desired_range),
QuantizeWeightToRange(12, expected_range));
EXPECT_EQ(QuantizeWeightToRange(23, desired_range),
QuantizeWeightToRange(23, expected_range));
}
}
// Make sure that we covered all the possible ranges
ASSERT_EQ(std::next(expected_range_itr), ISERangeEnd());
}
// Make sure that quantizing to the largest range is the identity function.
TEST(QuantizationTest, TestIdentity) {
for (int i = 0; i < 256; ++i) {
EXPECT_EQ(QuantizeCEValueToRange(i, 255), i);
}
// Note: This doesn't apply to weights since there's a weird hack to convert
// values from [0, 31] to [0, 64].
}
// Make sure that bit quantization is monotonic with respect to the input,
// since quantizing and dequantizing bits is a matter of truncation and bit
// replication
TEST(QuantizationTest, TestMonotonicBitPacking) {
for (int num_bits = 3; num_bits < 8; ++num_bits) {
const int range = (1 << num_bits) - 1;
int last_quant_val = -1;
for (int i = 0; i < 256; ++i) {
const int quant_val = QuantizeCEValueToRange(i, range);
EXPECT_LE(last_quant_val, quant_val);
last_quant_val = quant_val;
}
// Also expect the last quantization val to be equal to the range
EXPECT_EQ(last_quant_val, range);
if (range <= kWeightRangeMaxValue) {
last_quant_val = -1;
for (int i = 0; i <= 64; ++i) {
const int quant_val = QuantizeWeightToRange(i, range);
EXPECT_LE(last_quant_val, quant_val);
last_quant_val = quant_val;
}
EXPECT_EQ(last_quant_val, range);
}
}
}
// Make sure that bit quantization reflects that quantized values below the bit
// replication threshold get mapped to zero
TEST(QuantizationTest, TestSmallBitPacking) {
for (int num_bits = 1; num_bits <= 8; ++num_bits) {
const int range = (1 << num_bits) - 1;
// The largest number that should map to zero is one less than half of the
// smallest representation w.r.t. range. For example: if we have a range
// of 7, it means that we have 3 total bits abc for quantized values. If we
// unquantize to 8 bits, it means that our resulting value will be abcabcab.
// Hence, we map 000 to 0 and 001 to 0b00100100 = 36. The earliest value
// that should not map to zero with three bits is therefore 0b00001111 = 15.
// This ends up being (1 << (8 - 3 - 1)) - 1. We don't use 0b00011111 = 31
// because this would "round up" to 1 during quantization. This value is not
// necessarily the largest, but it is the largest that we can *guarantee*
// should map to zero.
if (range >= kEndpointRangeMinValue) {
constexpr int cev_bits = 8;
const int half_max_quant_bits = std::max(0, cev_bits - num_bits - 1);
const int largest_cev_to_zero = (1 << half_max_quant_bits) - 1;
EXPECT_EQ(QuantizeCEValueToRange(largest_cev_to_zero, range), 0)
<< " Largest CEV to zero: " << largest_cev_to_zero
<< " Range: " << range;
}
if (range <= kWeightRangeMaxValue) {
constexpr int weight_bits = 6;
const int half_max_quant_bits = std::max(0, weight_bits - num_bits - 1);
const int largest_weight_to_zero = (1 << half_max_quant_bits) - 1;
EXPECT_EQ(QuantizeWeightToRange(largest_weight_to_zero, range), 0)
<< " Largest weight to zero: " << largest_weight_to_zero
<< " Range: " << range;
}
}
}
// Test specific quint and trit weight encodings with values that were obtained
// using the reference ASTC codec.
TEST(QuantizationTest, TestSpecificQuintTritPackings) {
std::vector<int> vals = { 4, 6, 4, 6, 7, 5, 7, 5 };
std::vector<int> quantized;
// Test a quint packing
std::transform(
vals.begin(), vals.end(), std::back_inserter(quantized),
std::bind(UnquantizeWeightFromRange, std::placeholders::_1, 9));
const std::vector<int> quintExpected = {14, 21, 14, 21, 43, 50, 43, 50 };
EXPECT_EQ(quantized, quintExpected);
// Test a trit packing
std::transform(
vals.begin(), vals.end(), quantized.begin(),
std::bind(UnquantizeWeightFromRange, std::placeholders::_1, 11));
const std::vector<int> tritExpected = { 5, 23, 5, 23, 41, 59, 41, 59 };
EXPECT_EQ(quantized, tritExpected);
}
// Make sure that we properly die when we pass in values below the minimum
// allowed ranges for our quantization intervals.
TEST(QuantizationDeathTest, TestInvalidMinRange) {
for (int i = 0; i < kEndpointRangeMinValue; ++i) {
EXPECT_DEBUG_DEATH(QuantizeCEValueToRange(0, i), "");
EXPECT_DEBUG_DEATH(UnquantizeCEValueFromRange(0, i), "");
}
EXPECT_DEBUG_DEATH(QuantizeWeightToRange(0, 0), "");
EXPECT_DEBUG_DEATH(UnquantizeWeightFromRange(0, 0), "");
}
// Make sure that we properly die when we pass in bogus values.
TEST(QuantizationDeathTest, TestOutOfRange) {
EXPECT_DEBUG_DEATH(QuantizeCEValueToRange(-1, 10), "");
EXPECT_DEBUG_DEATH(QuantizeCEValueToRange(256, 7), "");
EXPECT_DEBUG_DEATH(QuantizeCEValueToRange(10000, 17), "");
EXPECT_DEBUG_DEATH(UnquantizeCEValueFromRange(-1, 10), "");
EXPECT_DEBUG_DEATH(UnquantizeCEValueFromRange(8, 7), "");
EXPECT_DEBUG_DEATH(UnquantizeCEValueFromRange(-1000, 17), "");
EXPECT_DEBUG_DEATH(QuantizeCEValueToRange(0, -7), "");
EXPECT_DEBUG_DEATH(UnquantizeCEValueFromRange(0, -17), "");
EXPECT_DEBUG_DEATH(QuantizeCEValueToRange(0, 257), "");
EXPECT_DEBUG_DEATH(UnquantizeCEValueFromRange(0, 256), "");
EXPECT_DEBUG_DEATH(QuantizeWeightToRange(-1, 10), "");
EXPECT_DEBUG_DEATH(QuantizeWeightToRange(256, 7), "");
EXPECT_DEBUG_DEATH(QuantizeWeightToRange(10000, 17), "");
EXPECT_DEBUG_DEATH(UnquantizeWeightFromRange(-1, 10), "");
EXPECT_DEBUG_DEATH(UnquantizeWeightFromRange(8, 7), "");
EXPECT_DEBUG_DEATH(UnquantizeWeightFromRange(-1000, 17), "");
EXPECT_DEBUG_DEATH(QuantizeWeightToRange(0, -7), "");
EXPECT_DEBUG_DEATH(UnquantizeWeightFromRange(0, -17), "");
EXPECT_DEBUG_DEATH(QuantizeWeightToRange(0, 32), "");
EXPECT_DEBUG_DEATH(UnquantizeWeightFromRange(0, 64), "");
}
} // namespace
} // namespace astc_codec

View File

@@ -0,0 +1,69 @@
// 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/weight_infill.h"
#include "src/decoder/footprint.h"
#include <gtest/gtest.h>
#include <vector>
namespace astc_codec {
namespace {
// Make sure that the physical size of the bit representations for certain
// dimensions of weight grids matches our expectations
TEST(ASTCWeightInfillTest, TestGetBitCount) {
// Bit encodings
EXPECT_EQ(32, CountBitsForWeights(4, 4, 3));
EXPECT_EQ(48, CountBitsForWeights(4, 4, 7));
EXPECT_EQ(24, CountBitsForWeights(2, 4, 7));
EXPECT_EQ(8, CountBitsForWeights(2, 4, 1));
// Trit encodings
EXPECT_EQ(32, CountBitsForWeights(4, 5, 2));
EXPECT_EQ(26, CountBitsForWeights(4, 4, 2));
EXPECT_EQ(52, CountBitsForWeights(4, 5, 5));
EXPECT_EQ(42, CountBitsForWeights(4, 4, 5));
// Quint encodings
EXPECT_EQ(21, CountBitsForWeights(3, 3, 4));
EXPECT_EQ(38, CountBitsForWeights(4, 4, 4));
EXPECT_EQ(49, CountBitsForWeights(3, 7, 4));
EXPECT_EQ(52, CountBitsForWeights(4, 3, 19));
EXPECT_EQ(70, CountBitsForWeights(4, 4, 19));
}
// Make sure that we bilerp our weights properly
TEST(ASTCWeightInfillTest, TestInfillBilerp) {
std::vector<int> weights = InfillWeights(
{{ 1, 3, 5, 3, 5, 7, 5, 7, 9 }}, Footprint::Get5x5(), 3, 3);
std::vector<int> expected_weights = {
1, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 7,
4, 5, 6, 7, 8,
5, 6, 7, 8, 9 };
ASSERT_EQ(weights.size(), expected_weights.size());
for (int i = 0; i < weights.size(); ++i) {
EXPECT_EQ(weights[i], expected_weights[i]);
}
}
} // namespace
} // namespace astc_codec

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Some files were not shown because too many files have changed in this diff Show More