mirror of
https://github.com/libjxl/libjxl.git
synced 2026-10-28 10:15:22 +08:00
Update JPEG-XL with latest changes.
This includes all changes up to 2019-12-27 17:38:53 +0100.
This commit is contained in:
commit
ff09371267
582 files changed
+112377
No files matched your search
@@ -0,0 +1,4 @@
|
||||
BasedOnStyle: Google
|
||||
IncludeCategories:
|
||||
- Regex: '^<hwy/'
|
||||
Priority: 2
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
# Disabled checks:
|
||||
# - google-readability-todo: We don't use the google TODO format.
|
||||
#
|
||||
# - modernize-deprecated-headers: We don't use std:: versions of the standard
|
||||
# types and functions like size_t or printf, so we should include <stdio.h>
|
||||
# instead <cstdio>.
|
||||
# - modernize-return-braced-init-list: this often doesn't improve readability.
|
||||
# - modernize-use-auto: is too aggressive towards using auto.
|
||||
# - modernize-use-default-member-init: with a mix of constructors and default
|
||||
# member initialization this can be confusing if enforced.
|
||||
# - modernize-use-using: typedefs are ok.
|
||||
#
|
||||
# - readability-else-after-return: It doesn't always improve readability.
|
||||
# - readability-static-accessed-through-instance
|
||||
# It is often more useful and readable to access a constant of a passed
|
||||
# variable (like d.N) instead of using the type of the variable that could be
|
||||
# long and complex.
|
||||
|
||||
Checks: >-
|
||||
clang-*,
|
||||
-clang-diagnostic-unused-command-line-argument,
|
||||
google-*,
|
||||
modernize-*,
|
||||
performance-*,
|
||||
readability-*,
|
||||
-google-readability-todo,
|
||||
-modernize-deprecated-headers,
|
||||
-modernize-return-braced-init-list,
|
||||
-modernize-use-auto,
|
||||
-modernize-use-default-member-init,
|
||||
-modernize-use-using,
|
||||
-readability-else-after-return,
|
||||
-readability-static-accessed-through-instance,
|
||||
|
||||
|
||||
WarningsAsErrors: >-
|
||||
clang-*,
|
||||
clang-analyzer-*,
|
||||
-clang-diagnostic-unused-command-line-argument,
|
||||
google-build-using-namespace,
|
||||
google-explicit-constructor,
|
||||
google-readability-namespace-comments
|
||||
|
||||
# We are only interested in the headers from this projects, excluding
|
||||
# third_party/ and build/.
|
||||
HeaderFilterRegex: '^.*/(jxl|fuif|tools)/.*\.h$'
|
||||
|
||||
CheckOptions:
|
||||
- key: readability-braces-around-statements.ShortStatementLines
|
||||
value: '2'
|
||||
- key: google-readability-braces-around-statements.ShortStatementLines
|
||||
value: '2'
|
||||
- key: readability-implicit-bool-conversion.AllowPointerConditions
|
||||
value: '1'
|
||||
- key: readability-implicit-bool-conversion.AllowIntegerConditions
|
||||
value: '1'
|
||||
@@ -0,0 +1,7 @@
|
||||
# Build output directories
|
||||
/build
|
||||
/build*
|
||||
/docker/*.log
|
||||
|
||||
# The downloaded corpora files for benchmark.
|
||||
/third_party/corpora
|
||||
+548
@@ -0,0 +1,548 @@
|
||||
# Copyright (c) the JPEG XL Project
|
||||
#
|
||||
# Use of this source code is governed by an MIT-style
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
# We define only two stages for development. The "build" stage only compiles the
|
||||
# code and doesn't run any target code. The "test" stage runs the code compiled
|
||||
# by the previous stage. Stages form dependencies in a block, all the build
|
||||
# targets in one stage need to be completed before any of the next stage can
|
||||
# start.
|
||||
stages:
|
||||
- build
|
||||
- test
|
||||
- deploy
|
||||
|
||||
variables:
|
||||
# We need to fetch the third_party dependencies together with the code unless
|
||||
# otherwise noted.
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
|
||||
# A template for running in the generic cloud builders. These are tagged with
|
||||
# "linux" and run on shared VMs.
|
||||
.linux_builder_template: &linux_builder_template
|
||||
image: &jpegxl-builder gcr.io/jpegxl/jpegxl-builder@sha256:b4744eed15a00e88d6faf54798c8d8bca8b58a581bfdda374cfa8c668ec6c1b2
|
||||
tags:
|
||||
- linux
|
||||
# By default all the workflows run on master and on request. This can be
|
||||
# override by users of this template.
|
||||
only:
|
||||
- master
|
||||
- tags
|
||||
- schedules
|
||||
# Retry on system failures. This can be typically caused by the runner or VM
|
||||
# being evicted.
|
||||
retry:
|
||||
max: 2
|
||||
when:
|
||||
- unknown_failure
|
||||
- api_failure
|
||||
- stuck_or_timeout_failure
|
||||
- runner_system_failure
|
||||
|
||||
# A template for building on x86_64 images on the cloud builders.
|
||||
.linux_x86_64_template: &linux_x86_64_template
|
||||
<<: *linux_builder_template
|
||||
variables:
|
||||
CC: clang-6.0
|
||||
CXX: clang++-6.0
|
||||
|
||||
.linux_i686_template: &linux_i686_template
|
||||
<<: *linux_builder_template
|
||||
variables:
|
||||
CC: clang-6.0
|
||||
CXX: clang++-6.0
|
||||
# This makes cross-compiling for i686 the default when calling ci.sh.
|
||||
BUILD_TARGET: i686-linux-gnu
|
||||
BUILD_DIR: build
|
||||
|
||||
# A template for building on aarch64 images on the cloud builders. These may not
|
||||
# run on actual aarch64 hardware.
|
||||
.linux_aarch64_template: &linux_aarch64_template
|
||||
<<: *linux_builder_template
|
||||
variables:
|
||||
CC: clang-6.0
|
||||
CXX: clang++-6.0
|
||||
# This makes cross-compiling for aarch64 the default when calling ci.sh.
|
||||
BUILD_TARGET: aarch64-linux-gnu
|
||||
BUILD_DIR: build
|
||||
# We need to fetch the third_party dependencies together with the code.
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
|
||||
# A template for cross-compiling for arm-linux-gnueabihf.
|
||||
.linux_armhf_template: &linux_armhf_template
|
||||
<<: *linux_builder_template
|
||||
variables:
|
||||
CC: clang-6.0
|
||||
CXX: clang++-6.0
|
||||
# This makes cross-compiling for armhf the default when calling ci.sh.
|
||||
BUILD_TARGET: arm-linux-gnueabihf
|
||||
BUILD_DIR: build
|
||||
# We need to fetch the third_party dependencies together with the code.
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
|
||||
# Common artifacts for the build stage
|
||||
.default_build_paths: &default_build_paths
|
||||
- build/libjpegxl.so*
|
||||
- build/tools/cjpegxl
|
||||
- build/tools/djpegxl
|
||||
- build/tools/benchmark_xl
|
||||
- build/tools/decode_and_encode
|
||||
- build/tools/comparison_viewer/compare_codecs
|
||||
- build/tools/comparison_viewer/compare_images
|
||||
- build/tools/viewer/viewer
|
||||
# All the test binaries in the build/ directory and .cmake files used by
|
||||
# ctest, compressed into this file together to save space when uploading.
|
||||
- build/tests.tar.xz
|
||||
# The coverage data produced at compile time.
|
||||
- build/gcno.tar.xz
|
||||
|
||||
.linux_x86_64_build_template: &linux_x86_64_build_template
|
||||
<<: *linux_x86_64_template
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
|
||||
expire_in: 1 week
|
||||
paths: *default_build_paths
|
||||
|
||||
.linux_i686_build_template: &linux_i686_build_template
|
||||
<<: *linux_i686_template
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
|
||||
expire_in: 1 week
|
||||
paths: *default_build_paths
|
||||
|
||||
.linux_aarch64_build_template: &linux_aarch64_build_template
|
||||
<<: *linux_aarch64_template
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
|
||||
expire_in: 1 week
|
||||
paths: *default_build_paths
|
||||
|
||||
.linux_armhf_build_template: &linux_armhf_build_template
|
||||
<<: *linux_armhf_template
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
|
||||
expire_in: 1 week
|
||||
paths: *default_build_paths
|
||||
|
||||
# The default Release build runs always.
|
||||
build:x86_64:clang:release:
|
||||
<<: *linux_x86_64_build_template
|
||||
stage: build
|
||||
only:
|
||||
- master
|
||||
- merge_requests
|
||||
- schedules
|
||||
- tags
|
||||
script:
|
||||
- SKIP_TEST=1 PACK_TEST=1 ./ci.sh release -DCMAKE_INSTALL_PREFIX=`pwd`/prefix
|
||||
# Test that the package can be installed.
|
||||
- ninja -C build install
|
||||
|
||||
test:x86_64:clang:release:
|
||||
<<: *linux_x86_64_template
|
||||
stage: test
|
||||
only:
|
||||
- master
|
||||
- merge_requests
|
||||
- schedules
|
||||
- tags
|
||||
dependencies:
|
||||
- build:x86_64:clang:release
|
||||
script:
|
||||
- ./ci.sh test
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: none
|
||||
|
||||
# x86_84 test coverage build
|
||||
build:x86_64:clang:coverage:
|
||||
<<: *linux_x86_64_build_template
|
||||
stage: build
|
||||
only:
|
||||
- master
|
||||
- schedules
|
||||
- tags
|
||||
script:
|
||||
- SKIP_TEST=1 PACK_TEST=1 ./ci.sh coverage
|
||||
|
||||
test:x86_64:clang:coverage:
|
||||
<<: *linux_x86_64_template
|
||||
stage: test
|
||||
only:
|
||||
- master
|
||||
- schedules
|
||||
- tags
|
||||
dependencies:
|
||||
- build:x86_64:clang:coverage
|
||||
script:
|
||||
- ./ci.sh test
|
||||
- ./ci.sh coverage_report
|
||||
variables:
|
||||
CC: clang-6.0
|
||||
CXX: clang++-6.0
|
||||
# Headers from submodules might be needed when generating the HTML reports.
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
|
||||
expire_in: 1 year
|
||||
paths:
|
||||
- build/coverage.txt
|
||||
- build/coverage*.html
|
||||
- build/coverage.xml
|
||||
|
||||
# Updates the "Pages" section in GitLab with the latest coverage report from
|
||||
# master.
|
||||
pages:
|
||||
<<: *linux_x86_64_template
|
||||
stage: deploy
|
||||
only:
|
||||
- master
|
||||
dependencies:
|
||||
- test:x86_64:clang:coverage
|
||||
script:
|
||||
- mkdir public
|
||||
- mv build/coverage* public/
|
||||
- mv public/coverage.html public/index.html
|
||||
- sed -E "s,GCC Code Coverage Report,Coverage Report for <a href='${CI_PROJECT_URL}/commit/${CI_COMMIT_SHA}'>${CI_COMMIT_SHORT_SHA}</a>," -i public/index.html
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
|
||||
|
||||
# x86_64 Scalar-only release runs only on master and on request.
|
||||
build:x86_64:clang:scalar:
|
||||
<<: *linux_x86_64_build_template
|
||||
stage: build
|
||||
only:
|
||||
- master
|
||||
- merge_requests
|
||||
- schedules
|
||||
- tags
|
||||
script:
|
||||
- SKIP_TEST=1 PACK_TEST=1 CMAKE_CXX_FLAGS="-DHWY_STATIC_TARGETS=0 -DHWY_RUNTIME_TARGETS=0" ./ci.sh release
|
||||
|
||||
test:x86_64:clang:scalar:
|
||||
<<: *linux_x86_64_template
|
||||
stage: test
|
||||
dependencies:
|
||||
- build:x86_64:clang:scalar
|
||||
script:
|
||||
- ./ci.sh test
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: none
|
||||
|
||||
# i686 (x86 32-bit) builders
|
||||
build:i686:clang:release:
|
||||
<<: *linux_i686_build_template
|
||||
stage: build
|
||||
only:
|
||||
- master
|
||||
- merge_requests
|
||||
- schedules
|
||||
- tags
|
||||
script:
|
||||
# AVX2 on 32-bit binaries hits clang compiler bugs.
|
||||
- CMAKE_FLAGS="-DHWY_DISABLE_AVX2" SKIP_TEST=1 PACK_TEST=1
|
||||
./ci.sh release -DJPEGXL_ENABLE_TCMALLOC=OFF -DJPEGXL_ENABLE_FUZZERS=OFF
|
||||
|
||||
test:i686:clang:release:
|
||||
<<: *linux_i686_template
|
||||
stage: test
|
||||
dependencies:
|
||||
- build:i686:clang:release
|
||||
script:
|
||||
- ./ci.sh test
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: none
|
||||
|
||||
# aarch64 release runs only on master and on request.
|
||||
build:aarch64:clang:release:
|
||||
<<: *linux_aarch64_build_template
|
||||
stage: build
|
||||
only:
|
||||
- master
|
||||
- merge_requests
|
||||
- schedules
|
||||
- tags
|
||||
- web
|
||||
script:
|
||||
- CMAKE_CXX_FLAGS="-DJXL_DISABLE_SLOW_TESTS" SKIP_TEST=1 PACK_TEST=1
|
||||
./ci.sh release
|
||||
|
||||
test:aarch64:clang:release:
|
||||
<<: *linux_aarch64_template
|
||||
stage: test
|
||||
dependencies:
|
||||
- build:aarch64:clang:release
|
||||
script:
|
||||
# LeakSanitizer doesn't work when running under qemu-arm.
|
||||
- ASAN_OPTIONS=detect_leaks=0 ./ci.sh test
|
||||
|
||||
# arm release
|
||||
build:armhf:clang:release:
|
||||
<<: *linux_armhf_build_template
|
||||
stage: build
|
||||
only:
|
||||
- master
|
||||
- merge_requests
|
||||
- schedules
|
||||
- tags
|
||||
- web
|
||||
script:
|
||||
- CMAKE_CXX_FLAGS="-DJXL_DISABLE_SLOW_TESTS" SKIP_TEST=1 PACK_TEST=1
|
||||
./ci.sh release
|
||||
|
||||
test:armhf:clang:release:
|
||||
<<: *linux_armhf_template
|
||||
stage: test
|
||||
dependencies:
|
||||
- build:armhf:clang:release
|
||||
script:
|
||||
- ./ci.sh test
|
||||
|
||||
# arm debug
|
||||
build:armhf:clang:debug:
|
||||
<<: *linux_armhf_build_template
|
||||
stage: build
|
||||
only:
|
||||
- merge_requests
|
||||
script:
|
||||
- SKIP_TEST=1 ./ci.sh debug
|
||||
|
||||
# "debug" build enabled for merge_requests. This is here to check that
|
||||
# debug-only statements still compile.
|
||||
build:x86_64:clang:debug:
|
||||
<<: *linux_x86_64_build_template
|
||||
stage: build
|
||||
only:
|
||||
- merge_requests
|
||||
script:
|
||||
- SKIP_TEST=1 ./ci.sh debug
|
||||
|
||||
# "asan", "tsan" and "msan" builds only run on master, tags and when requested
|
||||
# from the web. Only supported in x86_64 for now.
|
||||
build:x86_64:clang:asan:
|
||||
<<: *linux_x86_64_build_template
|
||||
stage: build
|
||||
script:
|
||||
- SKIP_TEST=1 PACK_TEST=1 ./ci.sh asan
|
||||
|
||||
test:x86_64:clang:asan:
|
||||
<<: *linux_x86_64_template
|
||||
stage: test
|
||||
dependencies:
|
||||
- build:x86_64:clang:asan
|
||||
script:
|
||||
- ./ci.sh test
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: none
|
||||
|
||||
# aarch64 asan build and test.
|
||||
build:aarch64:clang:asan:
|
||||
<<: *linux_aarch64_build_template
|
||||
stage: build
|
||||
script:
|
||||
- SKIP_TEST=1 PACK_TEST=1 ./ci.sh asan
|
||||
|
||||
test:aarch64:clang:asan:
|
||||
<<: *linux_aarch64_template
|
||||
stage: test
|
||||
dependencies:
|
||||
- build:aarch64:clang:asan
|
||||
# Disable asan test on arm since they timeout.
|
||||
only:
|
||||
- tags
|
||||
script:
|
||||
- ./ci.sh test
|
||||
|
||||
build:x86_64:clang:tsan:
|
||||
<<: *linux_x86_64_build_template
|
||||
stage: build
|
||||
script:
|
||||
- SKIP_TEST=1 PACK_TEST=1 ./ci.sh tsan
|
||||
|
||||
test:x86_64:clang:tsan:
|
||||
<<: *linux_x86_64_template
|
||||
stage: test
|
||||
dependencies:
|
||||
- build:x86_64:clang:tsan
|
||||
script:
|
||||
- ./ci.sh test
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: none
|
||||
|
||||
build:x86_64:clang:msan:
|
||||
<<: *linux_x86_64_build_template
|
||||
stage: build
|
||||
script:
|
||||
- SKIP_TEST=1 PACK_TEST=1 ./ci.sh msan
|
||||
|
||||
test:x86_64:clang:msan:
|
||||
<<: *linux_x86_64_template
|
||||
stage: test
|
||||
dependencies:
|
||||
- build:x86_64:clang:msan
|
||||
script:
|
||||
- ./ci.sh test
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: none
|
||||
|
||||
# Merge request linter checks.
|
||||
build:lint:
|
||||
<<: *linux_x86_64_template
|
||||
stage: build
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: none
|
||||
only:
|
||||
- merge_requests
|
||||
script:
|
||||
- LINT_OUTPUT=clang-format.patch ./ci.sh lint
|
||||
allow_failure: true
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
|
||||
expire_in: 1 month
|
||||
when: on_failure
|
||||
paths:
|
||||
- clang-format.patch
|
||||
|
||||
# Most clang-tidy checks are currently informational only since these are
|
||||
# currently broken, however several are marked as errors.
|
||||
build:tidy:
|
||||
<<: *linux_x86_64_template
|
||||
stage: build
|
||||
only:
|
||||
- merge_requests
|
||||
script:
|
||||
- CMAKE_BUILD_TYPE="Release" ./ci.sh tidy
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
|
||||
expire_in: 1 month
|
||||
when: always
|
||||
paths:
|
||||
- build/clang-tidy.txt
|
||||
|
||||
# Faster benchmark over a smaller set of images.
|
||||
.benchmark_x86_64_template: &benchmark_x86_64_template
|
||||
<<: *linux_x86_64_template
|
||||
stage: test
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: none
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
|
||||
expire_in: 1 year
|
||||
paths:
|
||||
- build/benchmark_results/
|
||||
|
||||
test:fast_benchmark:release:
|
||||
<<: *benchmark_x86_64_template
|
||||
only:
|
||||
- master
|
||||
- merge_requests
|
||||
- schedules
|
||||
- tags
|
||||
dependencies:
|
||||
- build:x86_64:clang:release
|
||||
script:
|
||||
- STORE_IMAGES=0 ./ci.sh fast_benchmark
|
||||
|
||||
test:fast_benchmark:asan:
|
||||
<<: *benchmark_x86_64_template
|
||||
dependencies:
|
||||
- build:x86_64:clang:asan
|
||||
script:
|
||||
- STORE_IMAGES=0 ./ci.sh fast_benchmark
|
||||
|
||||
test:fast_benchmark.msan:
|
||||
<<: *benchmark_x86_64_template
|
||||
dependencies:
|
||||
- build:x86_64:clang:msan
|
||||
script:
|
||||
- STORE_IMAGES=0 ./ci.sh fast_benchmark
|
||||
|
||||
# This template runs on actual aarch64 hardware.
|
||||
.benchmark_aarch64_template: &benchmark_aarch64_template
|
||||
<<: *linux_builder_template
|
||||
image: gcr.io/jpegxl/jpegxl-builder-run-aarch64@sha256:4ff7fe5e3e1d19a8c4604b72a41916173d83ec95d335f7fabd599ad92c438df2
|
||||
stage: test
|
||||
tags:
|
||||
- aarch64
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: none
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
|
||||
expire_in: 1 year
|
||||
paths:
|
||||
- build/benchmark_results/
|
||||
|
||||
test:aarch64:fast_benchmark:release:
|
||||
<<: *benchmark_aarch64_template
|
||||
dependencies:
|
||||
- build:aarch64:clang:release
|
||||
script:
|
||||
# Bind to the big CPUs only.
|
||||
- ./ci.sh cpuset "${RUNNER_CPU_BIG}"
|
||||
- STORE_IMAGES=0 ./ci.sh fast_benchmark
|
||||
|
||||
# Benchmark test that runs separately on the big and little CPUs of the runner.
|
||||
test:aarch64:arm_benchmark:release:
|
||||
<<: *benchmark_aarch64_template
|
||||
only:
|
||||
- web
|
||||
- master
|
||||
dependencies:
|
||||
- build:aarch64:clang:release
|
||||
script:
|
||||
- ./ci.sh arm_benchmark
|
||||
|
||||
# Benchmark ToT on nightly builds. These are scheduled at midnight UTC.
|
||||
test:benchmark:release:
|
||||
image: *jpegxl-builder
|
||||
stage: test
|
||||
only:
|
||||
- schedules
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: none
|
||||
dependencies:
|
||||
- build:x86_64:clang:release
|
||||
script:
|
||||
- STORE_IMAGES=0 ./ci.sh benchmark
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
|
||||
expire_in: 1 year
|
||||
paths:
|
||||
- build/benchmark_results/
|
||||
|
||||
build:tidy:all:
|
||||
<<: *linux_x86_64_template
|
||||
stage: build
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- CMAKE_BUILD_TYPE="Release" ./ci.sh tidy all
|
||||
allow_failure: true
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
|
||||
expire_in: 1 year
|
||||
when: always
|
||||
paths:
|
||||
- build/clang-tidy.txt
|
||||
|
||||
# Emscripten (WASM) build.
|
||||
build:ems:all:
|
||||
<<: *linux_x86_64_template
|
||||
stage: build
|
||||
variables:
|
||||
CC: emcc
|
||||
CXX: em++
|
||||
EMSDK: /opt/emsdk
|
||||
EM_CONFIG: /opt/emsdk/.emscripten
|
||||
CMAKE_TOOLCHAIN_FILE: /opt/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
|
||||
expire_in: 1 week
|
||||
paths:
|
||||
- build/jpegxl_emcc.*
|
||||
script:
|
||||
- source "${EMSDK}/emsdk_env.sh"
|
||||
- SKIP_TEST=1 ./ci.sh release
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
[submodule "third_party/brotli"]
|
||||
path = third_party/brotli
|
||||
url = https://github.com/google/brotli
|
||||
[submodule "third_party/lodepng"]
|
||||
path = third_party/lodepng
|
||||
url = https://github.com/lvandeve/lodepng
|
||||
[submodule "third_party/lcms"]
|
||||
path = third_party/lcms
|
||||
url = https://github.com/mm2/Little-CMS
|
||||
[submodule "third_party/googletest"]
|
||||
path = third_party/googletest
|
||||
url = https://github.com/google/googletest
|
||||
[submodule "third_party/gperftools"]
|
||||
path = third_party/gperftools
|
||||
url = https://github.com/gperftools/gperftools
|
||||
[submodule "third_party/brunsli"]
|
||||
path = third_party/brunsli
|
||||
url = https://github.com/google/brunsli.git
|
||||
[submodule "third_party/sjpeg"]
|
||||
path = third_party/sjpeg
|
||||
url = https://github.com/webmproject/sjpeg.git
|
||||
[submodule "third_party/skcms"]
|
||||
path = third_party/skcms
|
||||
url = https://skia.googlesource.com/skcms
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
# Copyright (c) the JPEG XL Project
|
||||
#
|
||||
# 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.
|
||||
|
||||
# Ubuntu bionic ships with cmake 3.10.
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
# Set compilers to default to Clang is not set.
|
||||
if ("$ENV{CC}" STREQUAL "")
|
||||
set(ENV{CC} clang)
|
||||
endif()
|
||||
if ("$ENV{CXX}" STREQUAL "")
|
||||
set(ENV{CXX} clang++)
|
||||
endif()
|
||||
|
||||
# Honor VISIBILITY_INLINES_HIDDEN on all types of targets.
|
||||
if(POLICY CMP0063)
|
||||
cmake_policy(SET CMP0063 NEW)
|
||||
endif()
|
||||
# Pass CMAKE_EXE_LINKER_FLAGS to CC and CXX compilers when testing if they work.
|
||||
if(POLICY CMP0065)
|
||||
cmake_policy(SET CMP0065 NEW)
|
||||
endif()
|
||||
|
||||
project(JPEGXL LANGUAGES C CXX)
|
||||
|
||||
include(CheckCXXSourceCompiles)
|
||||
check_cxx_source_compiles(
|
||||
"int main() {
|
||||
#if !defined(__EMSCRIPTEN__)
|
||||
static_assert(false, \"__EMSCRIPTEN__ is not defined\");
|
||||
#endif
|
||||
return 0;
|
||||
}"
|
||||
JPEGXL_EMSCRIPTEN
|
||||
)
|
||||
if(JPEGXL_EMSCRIPTEN)
|
||||
message("-- Compiler is EMSCRIPTEN.")
|
||||
set(JPEGXL_NOT_EMSCRIPTEN false)
|
||||
set(CMAKE_STATIC_LIBRARY_SUFFIX ".bc")
|
||||
set(CMAKE_EXECUTABLE_SUFFIX ".html")
|
||||
add_definitions(-DPROFILER_ENABLED=0)
|
||||
else()
|
||||
message("-- Compiler is not EMSCRIPTEN.")
|
||||
set(JPEGXL_NOT_EMSCRIPTEN true)
|
||||
endif()
|
||||
|
||||
### Project build options:
|
||||
if(NOT APPLE AND NOT ${CMAKE_SYSTEM_PROCESSOR} MATCHES "aarch64"
|
||||
AND NOT ${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm")
|
||||
# Enabled by default except on arm64 and Apple builds.
|
||||
set(ENABLE_FUZZERS_DEFAULT true)
|
||||
endif()
|
||||
if(NOT APPLE AND NOT JPEGXL_EMSCRIPTEN
|
||||
AND NOT ${CMAKE_SYSTEM_PROCESSOR} MATCHES "aarch64"
|
||||
AND NOT ${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm")
|
||||
# Enabled by default except on arm64 and Apple builds.
|
||||
set(ENABLE_TCMALLOC_DEFAULT true)
|
||||
endif()
|
||||
|
||||
set(JPEGXL_ENABLE_FUZZERS ${ENABLE_FUZZERS_DEFAULT} CACHE BOOL
|
||||
"Build JPEGXL fuzzer targets")
|
||||
set(JPEGXL_ENABLE_BENCHMARK ${JPEGXL_NOT_EMSCRIPTEN} CACHE BOOL
|
||||
"Build JPEGXL benchmark tools")
|
||||
set(JPEGXL_ENABLE_SJPEG true CACHE BOOL
|
||||
"Build JPEGXL with support for encoding with sjpeg")
|
||||
set(JPEGXL_ENABLE_SKCMS false CACHE BOOL
|
||||
"Where possible, use skcms instead of lcms2")
|
||||
set(JPEGXL_ENABLE_VIEWERS ${JPEGXL_NOT_EMSCRIPTEN} CACHE BOOL
|
||||
"Build JPEGXL viewer tools for evaluation")
|
||||
set(JPEGXL_ENABLE_TCMALLOC ${ENABLE_TCMALLOC_DEFAULT} CACHE BOOL
|
||||
"Build JPEGXL using gperftools (tcmalloc) allocator.")
|
||||
set(JPEGXL_ENABLE_PLUGINS true CACHE BOOL
|
||||
"Build third-party plugings to support JPEG XL in other applications.")
|
||||
set(JPEGXL_ENABLE_COVERAGE false CACHE BOOL
|
||||
"Enable code coverage tracking for libjpegxl. This also enables debug and disables optimizations.")
|
||||
|
||||
# Require clang for building.
|
||||
if(NOT ${CMAKE_C_COMPILER_ID} MATCHES "Clang" OR
|
||||
NOT ${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
|
||||
message(FATAL_ERROR "${CMAKE_CXX_COMPILER_ID} compiler is not supported.\n"
|
||||
"Use clang instead:\n CC=clang CXX=clang++ cmake ..")
|
||||
endif()
|
||||
if (${CMAKE_C_COMPILER_VERSION} VERSION_LESS 6 OR
|
||||
${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 6)
|
||||
message(FATAL_ERROR
|
||||
"Minimum Clang version required is Clang 6, please update.")
|
||||
endif()
|
||||
|
||||
# CMAKE_EXPORT_COMPILE_COMMANDS is used to generate the compilation database
|
||||
# used by clang-tidy.
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# Global compiler flags for all targets here and in subdirectories.
|
||||
add_definitions(
|
||||
# Avoid changing the binary based on the current time and date.
|
||||
-D__DATE__="redacted"
|
||||
-D__TIMESTAMP__="redacted"
|
||||
-D__TIME__="redacted"
|
||||
)
|
||||
|
||||
# In CMake before 3.12 it is problematic to pass repeated flags like -Xclang.
|
||||
# For this reason we place them in CMAKE_CXX_FLAGS instead.
|
||||
# See https://gitlab.kitware.com/cmake/cmake/issues/15826
|
||||
|
||||
if (NOT "${JPEGXL_EMSCRIPTEN}")
|
||||
|
||||
# Pretty colorful messages within reasonable limits.
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
|
||||
-Xclang -ferror-limit -Xclang 19 -Xclang -fmessage-length -Xclang 0 \
|
||||
-Xclang -fdiagnostics-show-option -fcolor-diagnostics")
|
||||
|
||||
# Machine flags.
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
|
||||
-Xclang -mrelax-all \
|
||||
-Xclang -mrelocation-model -Xclang pic \
|
||||
-Xclang -pic-level -Xclang 2 \
|
||||
-Xclang -mconstructor-aliases \
|
||||
-Xclang -mpie-copy-relocations \
|
||||
-Xclang -munwind-tables")
|
||||
|
||||
if(WIN32)
|
||||
# Not supported by clang-cl, but frame pointers are default on Windows
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer")
|
||||
endif()
|
||||
|
||||
# CPU flags
|
||||
if(${CMAKE_SYSTEM_PROCESSOR} MATCHES "x86_64")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
|
||||
-mavx2 \
|
||||
-mfma \
|
||||
-Xclang -mprefer-vector-width=128 \
|
||||
-Xclang -target-cpu -Xclang haswell \
|
||||
-Xclang -target-feature -Xclang +avx2")
|
||||
elseif(${CMAKE_SYSTEM_PROCESSOR} MATCHES "AMD64")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX2")
|
||||
elseif(${CMAKE_SYSTEM_PROCESSOR} MATCHES "aarch64")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
|
||||
-Xclang -mprefer-vector-width=128 \
|
||||
-Xclang -target-cpu -Xclang cortex-a72")
|
||||
elseif(${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
|
||||
-mcpu=cortex-a7 -mfpu=neon-vfpv4")
|
||||
endif()
|
||||
|
||||
endif() # JPEGXL_EMSCRIPTEN
|
||||
|
||||
# Force build with optimizations in release mode.
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
|
||||
|
||||
add_compile_options(
|
||||
# Ignore this to allow redefining __DATE__ and others.
|
||||
-Wno-builtin-macro-redefined
|
||||
|
||||
# Global warning settings.
|
||||
-Wall
|
||||
)
|
||||
|
||||
if (NOT WIN32)
|
||||
add_compile_options(-Werror)
|
||||
endif ()
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED YES)
|
||||
|
||||
add_subdirectory(third_party)
|
||||
|
||||
set(THREADS_PREFER_PTHREAD_FLAG YES)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
# Enable tests regardless of where are they defined.
|
||||
enable_testing()
|
||||
include(CTest)
|
||||
|
||||
# The jpegxl library definition. This also defines the internal list of flags we
|
||||
# use in our project (library, tests and tools): JPEGXL_INTERNAL_FLAGS.
|
||||
include(jpegxl.cmake)
|
||||
|
||||
if (NOT "${JPEGXL_EMSCRIPTEN}")
|
||||
|
||||
# Other libraries outside the core jpegxl library.
|
||||
include(jpegxl_extras.cmake)
|
||||
include(jpegxl_threads.cmake)
|
||||
|
||||
# Plugins for third-party software
|
||||
if (${JPEGXL_ENABLE_PLUGINS})
|
||||
add_subdirectory(plugins)
|
||||
endif ()
|
||||
|
||||
# Binary tools
|
||||
add_subdirectory(tools)
|
||||
|
||||
# Unittests
|
||||
cmake_policy(SET CMP0057 NEW) # https://gitlab.kitware.com/cmake/cmake/issues/18198
|
||||
include(GoogleTest)
|
||||
|
||||
# Tests for the jpegxl library.
|
||||
include(jpegxltests.cmake)
|
||||
|
||||
else() # JPEGXL_EMSCRIPTEN
|
||||
|
||||
# WASM API facade.
|
||||
add_executable(jpegxl_emcc jxl/jpegxl_emcc.cc)
|
||||
target_link_libraries(jpegxl_emcc jpegxl-static)
|
||||
set_target_properties(jpegxl_emcc PROPERTIES LINK_FLAGS "\
|
||||
-O3\
|
||||
-s TOTAL_MEMORY=256MB\
|
||||
-s EXPORTED_FUNCTIONS='[\
|
||||
\"_decompress\",\
|
||||
\"_free\",\
|
||||
\"_freeImage\",\
|
||||
\"_getImageWidth\",\
|
||||
\"_getImageHeight\",\
|
||||
\"_getImagePixels\",\
|
||||
\"_malloc\"\
|
||||
]'\
|
||||
")
|
||||
|
||||
endif () # JPEGXL_EMSCRIPTEN
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# How to Contribute
|
||||
|
||||
We are currently unable to accept patches to this project, but we'd very much
|
||||
appreciate if you open a Gitlab issue for any bug reports/feature requests.
|
||||
We will be happy to investigate and hopefully fix any issues.
|
||||
|
||||
# Community Guidelines
|
||||
|
||||
This project follows
|
||||
[Google's Open Source Community Guidelines](https://opensource.google.com/conduct/).
|
||||
@@ -0,0 +1,23 @@
|
||||
# This files lists individuals who made significant contributions to the JPEG XL
|
||||
# code base, such as design, adding features, performing experiments, ...
|
||||
# Small changes such as a small bugfix or fixing spelling errors are not
|
||||
# included. If you'd like to be included in this file thanks to a significant
|
||||
# contribution, feel free to send a pull request changing this file.
|
||||
Alex Deymo
|
||||
Alexander Rhatushnyak
|
||||
Evgenii Kliuchnikov
|
||||
Iulia-Maria Comșa
|
||||
Jan Wassenberg
|
||||
Jon Sneyers
|
||||
Jyrki Alakuijala
|
||||
Krzysztof Potempa
|
||||
Lode Vandevenne
|
||||
Luca Versari
|
||||
Martin Bruse
|
||||
Moritz Firsching
|
||||
Renata Khasanova
|
||||
Robert Obryk
|
||||
Sami Boukortt
|
||||
Sebastian Gomez-Gonzalez
|
||||
Thomas Fischbacher
|
||||
Zoltan Szabadka
|
||||
@@ -0,0 +1,203 @@
|
||||
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
## Disclaimer
|
||||
|
||||
OSX builds have "best effort" support, i.e. build might not work at all, some
|
||||
tests may fail and some sub-projects are excluded from build.
|
||||
|
||||
This manual outlines OSX specific setup. For general building and testing
|
||||
instructions see "[README](README.md)" and
|
||||
"[Building and Testing changes](doc/building_and_testing.md)".
|
||||
|
||||
## Dependencies
|
||||
|
||||
[Homebrew](https://brew.sh/) is a popular package manager that can be used for
|
||||
installing dependencies.
|
||||
|
||||
Make sure that `brew doctor` does not report serious problems and up-to-date
|
||||
version of XCode is installed.
|
||||
|
||||
Installing (actually, building) `clang` might take a couple hours.
|
||||
|
||||
```shell
|
||||
brew install llvm
|
||||
```
|
||||
|
||||
```shell
|
||||
brew install clang-format coreutils cmake giflib libjpeg ninja parallel
|
||||
```
|
||||
|
||||
If `git-clang-format` command is not accessible, run
|
||||
|
||||
```shell
|
||||
brew link --overwrite clang-format
|
||||
```
|
||||
|
||||
Before building the project check that `which clang` is
|
||||
`/usr/local/opt/llvm/bin/clang`, not the one provided by XCode. If not, update
|
||||
`PATH` environment variable.
|
||||
|
||||
Also, `export CMAKE_PREFIX_PATH=/usr/local/opt/giflib` might be necessary for
|
||||
correct include paths resolving.
|
||||
@@ -0,0 +1,83 @@
|
||||
# JPEG XL reference implementation
|
||||
|
||||
This repository contains a reference implementation of JPEG XL (encoder and
|
||||
decoder). As [previously announced](https://jpeg.org/items/20190803_press.html),
|
||||
it is available under a royalty-free and open source license (Apache 2).
|
||||
|
||||
**NOTE**
|
||||
|
||||
JPEG XL is in the final stages of standardization, but minor changes to the
|
||||
codestream are still likely. WARNING: until further notice, do not depend on
|
||||
future decoders being able to decode the output of a current encoder.
|
||||
|
||||
## Checking out the code
|
||||
|
||||
This repository uses git submodules to handle some third party dependencies
|
||||
under `third_party/`. To check out these dependencies as well clone the
|
||||
repository with `--recursive`:
|
||||
|
||||
```shell
|
||||
git clone git@gitlab.com:wg1/jpeg-xl.git --recursive
|
||||
```
|
||||
|
||||
If you didn't check out recursively, and after any update run the following
|
||||
command to check out the git submodules.
|
||||
|
||||
```shell
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
## Minimum build dependencies
|
||||
|
||||
Apart from the dependencies in third_party, some of the tools use external
|
||||
dependencies that need to be installed in your system first. For a Debian/Ubuntu
|
||||
based Linux distribution install:
|
||||
|
||||
```shell
|
||||
sudo apt install cmake clang-6.0 g++-8 qtbase5-dev libqt5x11extras5-dev \
|
||||
extra-cmake-modules libgif-dev libjpeg-dev ninja-build
|
||||
```
|
||||
|
||||
For developing changes in JPEG XL, take a look at the
|
||||
[Building and Testing changes](doc/building_and_testing.md) guide.
|
||||
|
||||
Make sure your default "clang" compiler is at least version 6 running
|
||||
|
||||
```bash
|
||||
clang --version
|
||||
```
|
||||
|
||||
If it still shows an old version despite having a clang-6.0 installed, you need
|
||||
to update the default clang compiler. In Debian-based systems run:
|
||||
|
||||
```bash
|
||||
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-6.0 100
|
||||
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-6.0 100
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
The project builds with cmake. We currently support Linux (tested with Debian).
|
||||
To build the "release" version, you can use the following helper command:
|
||||
|
||||
```shell
|
||||
./ci.sh release
|
||||
```
|
||||
|
||||
This will build and test the project, and leave the binaries in the `build/`
|
||||
directory. Check out the `tools` subdirectory for command-line tools that
|
||||
interact with the library. You can set the environment variable SKIP_TEST=1 to
|
||||
skip the test stage.
|
||||
|
||||
There are other build versions with more debug information useful when
|
||||
developing. You can read more about it in the
|
||||
[Building and Testing changes](doc/building_and_testing.md) guide.
|
||||
|
||||
## Documentation
|
||||
|
||||
* [Developing in GitLab](doc/developing_in_gitlab.md)
|
||||
* [XL Overview](doc/xl_overview.md)
|
||||
* [Building and Testing changes](doc/building_and_testing.md)
|
||||
* [Software Contribution Guidelines](doc/guidelines.md)
|
||||
* [JPEG XL committee draft](https://arxiv.org/abs/1908.03565)
|
||||
* [Introductory paper](https://www.spiedigitallibrary.org/proceedings/Download?fullDOI=10.1117%2F12.2529237)
|
||||
@@ -0,0 +1,956 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) the JPEG XL Project
|
||||
#
|
||||
# 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.
|
||||
|
||||
# Continuous integration helper module. This module is meant to be called from
|
||||
# the .gitlab-ci.yml file during the continuous integration build, as well as
|
||||
# from the command line for developers.
|
||||
|
||||
set -eu
|
||||
|
||||
OS=`uname -s`
|
||||
|
||||
MYDIR=$(dirname $(realpath "$0"))
|
||||
|
||||
### Environment parameters:
|
||||
CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-RelWithDebInfo}
|
||||
CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH:-}
|
||||
SKIP_TEST="${SKIP_TEST:-0}"
|
||||
BUILD_TARGET="${BUILD_TARGET:-}"
|
||||
if [[ -n "${BUILD_TARGET}" ]]; then
|
||||
BUILD_DIR="${BUILD_DIR:-${MYDIR}/build-${BUILD_TARGET%%-*}}"
|
||||
else
|
||||
BUILD_DIR="${BUILD_DIR:-${MYDIR}/build}"
|
||||
fi
|
||||
# Whether we should post a message in the MR when the build fails.
|
||||
POST_MESSAGE_ON_ERROR="${POST_MESSAGE_ON_ERROR:-1}"
|
||||
|
||||
# Convenience flag to pass both CMAKE_C_FLAGS and CMAKE_CXX_FLAGS
|
||||
CMAKE_FLAGS=${CMAKE_FLAGS:-}
|
||||
CMAKE_C_FLAGS=${CMAKE_C_FLAGS:-${CMAKE_FLAGS}}
|
||||
CMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS:-${CMAKE_FLAGS}}
|
||||
CMAKE_EXE_LINKER_FLAGS=${CMAKE_EXE_LINKER_FLAGS:-}
|
||||
CMAKE_MODULE_LINKER_FLAGS=${CMAKE_MODULE_LINKER_FLAGS:-}
|
||||
CMAKE_SHARED_LINKER_FLAGS=${CMAKE_SHARED_LINKER_FLAGS:-}
|
||||
CMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE:-}
|
||||
|
||||
# Version inferred from the CI variables.
|
||||
CI_COMMIT_SHA=${CI_COMMIT_SHA:-}
|
||||
JPEGXL_VERSION=${JPEGXL_VERSION:-${CI_COMMIT_SHA:0:8}}
|
||||
|
||||
# Benchmark parameters
|
||||
STORE_IMAGES=${STORE_IMAGES:-1}
|
||||
BENCHMARK_CORPORA="${MYDIR}/third_party/corpora"
|
||||
|
||||
# Local flags passed to sanitizers.
|
||||
UBSAN_FLAGS=(
|
||||
-fsanitize=alignment
|
||||
-fsanitize=bool
|
||||
-fsanitize=bounds
|
||||
-fsanitize=builtin
|
||||
-fsanitize=enum
|
||||
-fsanitize=float-cast-overflow
|
||||
-fsanitize=float-divide-by-zero
|
||||
-fsanitize=integer-divide-by-zero
|
||||
-fsanitize=null
|
||||
-fsanitize=object-size
|
||||
-fsanitize=pointer-overflow
|
||||
-fsanitize=return
|
||||
-fsanitize=returns-nonnull-attribute
|
||||
-fsanitize=shift-base
|
||||
-fsanitize=shift-exponent
|
||||
-fsanitize=unreachable
|
||||
-fsanitize=vla-bound
|
||||
|
||||
-fno-sanitize-recover=undefined
|
||||
# Brunsli uses unaligned accesses to uint32_t, so alignment is just a warning.
|
||||
-fsanitize-recover=alignment
|
||||
)
|
||||
# -fsanitize=function doesn't work on aarch64 and arm.
|
||||
if [[ "${BUILD_TARGET%%-*}" != "aarch64" &&
|
||||
"${BUILD_TARGET%%-*}" != "arm" ]]; then
|
||||
UBSAN_FLAGS+=(
|
||||
-fsanitize=function
|
||||
)
|
||||
fi
|
||||
if [[ "${BUILD_TARGET%%-*}" != "arm" ]]; then
|
||||
UBSAN_FLAGS+=(
|
||||
-fsanitize=signed-integer-overflow
|
||||
)
|
||||
fi
|
||||
|
||||
CLANG_TIDY_BIN=$(which clang-tidy-6.0 clang-tidy-7 clang-tidy-8 | head -n 1)
|
||||
# Default to "cat" if "colordiff" is not installed or if stdout is not a tty.
|
||||
if [[ -t 1 ]]; then
|
||||
COLORDIFF_BIN=$(which colordiff cat | head -n 1)
|
||||
else
|
||||
COLORDIFF_BIN="cat"
|
||||
fi
|
||||
|
||||
CLANG_VERSION="${CLANG_VERSION:-}"
|
||||
# Detect the clang version suffix and store it in CLANG_VERSION. For example,
|
||||
# "6.0" for clang 6 or "7" for clang 7.
|
||||
detect_clang_version() {
|
||||
if [[ -n "${CLANG_VERSION}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
local clang_version=$("${CC:-clang}" --version | head -n1)
|
||||
local llvm_tag
|
||||
case "${clang_version}" in
|
||||
"clang version 6."*)
|
||||
CLANG_VERSION="6.0"
|
||||
;;
|
||||
"clang version 7."*)
|
||||
CLANG_VERSION="7"
|
||||
;;
|
||||
"clang version 8."*)
|
||||
CLANG_VERSION="8"
|
||||
;;
|
||||
"clang version 9."*)
|
||||
CLANG_VERSION="9"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown clang version: ${clang_version}" >&2
|
||||
return 1
|
||||
esac
|
||||
}
|
||||
|
||||
# Temporary files cleanup hooks.
|
||||
CLEANUP_FILES=()
|
||||
cleanup() {
|
||||
if [[ ${#CLEANUP_FILES[@]} -ne 0 ]]; then
|
||||
rm -fr "${CLEANUP_FILES[@]}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Executed on exit.
|
||||
on_exit() {
|
||||
local retcode="$1"
|
||||
# Always cleanup the CLEANUP_FILES.
|
||||
cleanup
|
||||
|
||||
# Post a message in the MR when requested with POST_MESSAGE_ON_ERROR but only
|
||||
# if the run failed and we are not running from a MR pipeline.
|
||||
if [[ ${retcode} -ne 0 && -n "${CI_BUILD_NAME:-}" &&
|
||||
-n "${POST_MESSAGE_ON_ERROR}" && -z "${CI_MERGE_REQUEST_ID:-}" &&
|
||||
"${CI_BUILD_REF_NAME}" = "master" ]]; then
|
||||
load_mr_vars_from_commit
|
||||
{ set +xeu; } 2>/dev/null
|
||||
local message="**Run ${CI_BUILD_NAME} @ ${CI_COMMIT_SHORT_SHA} failed.**
|
||||
|
||||
Check the output of the job at ${CI_JOB_URL:-} to see if this was your problem.
|
||||
If it was, please rollback this change or fix the problem ASAP, broken builds
|
||||
slow down development. Check if the error already existed in the previous build
|
||||
as well.
|
||||
|
||||
Pipeline: ${CI_PIPELINE_URL}
|
||||
|
||||
Previous build commit: ${CI_COMMIT_BEFORE_SHA}
|
||||
"
|
||||
cmd_post_mr_comment "${message}"
|
||||
fi
|
||||
}
|
||||
|
||||
trap 'retcode=$?; { set +x; } 2>/dev/null; on_exit ${retcode}' INT TERM EXIT
|
||||
|
||||
|
||||
# These variables are populated when calling merge_request_commits().
|
||||
|
||||
# The current hash at the top of the current branch or merge request branch (if
|
||||
# running from a merge request pipeline).
|
||||
MR_HEAD_SHA=""
|
||||
# The common ancestor between the current commit and the tracked branch, such
|
||||
# as master. This includes a list
|
||||
MR_ANCESTOR_SHA=""
|
||||
|
||||
# Populate MR_HEAD_SHA and MR_ANCESTOR_SHA.
|
||||
merge_request_commits() {
|
||||
{ set +x; } 2>/dev/null
|
||||
# CI_BUILD_REF is the reference currently being build in the CI workflow.
|
||||
MR_HEAD_SHA=$(git -C "${MYDIR}" rev-parse -q "${CI_BUILD_REF:-HEAD}")
|
||||
if [[ -z "${CI_MERGE_REQUEST_IID:-}" ]]; then
|
||||
# We are in a local branch, not a merge request.
|
||||
MR_ANCESTOR_SHA=$(git -C "${MYDIR}" rev-parse -q HEAD@{upstream} || true)
|
||||
else
|
||||
# Merge request pipeline in CI. In this case the upstream is called "origin"
|
||||
# but it refers to the forked project that's the source of the merge
|
||||
# request. We need to get the target of the merge request, for which we need
|
||||
# to query that repository using our CI_JOB_TOKEN.
|
||||
echo "machine gitlab.com login gitlab-ci-token password ${CI_JOB_TOKEN}" \
|
||||
>> "${HOME}/.netrc"
|
||||
git -C "${MYDIR}" fetch "${CI_MERGE_REQUEST_PROJECT_URL}" \
|
||||
"${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}"
|
||||
MR_ANCESTOR_SHA=$(git -C "${MYDIR}" rev-parse -q FETCH_HEAD)
|
||||
fi
|
||||
if [[ -z "${MR_ANCESTOR_SHA}" ]]; then
|
||||
echo "Warning, not tracking any branch, using the last commit in HEAD.">&2
|
||||
# This prints the return value with just HEAD.
|
||||
MR_ANCESTOR_SHA=$(git -C "${MYDIR}" rev-parse -q "${MR_HEAD_SHA}^")
|
||||
else
|
||||
MR_ANCESTOR_SHA=$(git -C "${MYDIR}" merge-base --all \
|
||||
"${MR_ANCESTOR_SHA}" "${MR_HEAD_SHA}")
|
||||
fi
|
||||
set -x
|
||||
}
|
||||
|
||||
# Load the MR iid from the landed commit message when running not from a
|
||||
# merge request workflow. This is useful to post back results at the merge
|
||||
# request when running pipelines from master.
|
||||
load_mr_vars_from_commit() {
|
||||
{ set +x; } 2>/dev/null
|
||||
if [[ -z "${CI_MERGE_REQUEST_IID:-}" ]]; then
|
||||
local mr_iid=$(git rev-list --format=%B --max-count=1 HEAD |
|
||||
grep -F "${CI_PROJECT_URL}/merge_requests" | head -n 1)
|
||||
# mr_iid contains a string like this if it matched:
|
||||
# Part-of: <https://gitlab.com/wg1/jpeg-xlm/merge_requests/123456>
|
||||
if [[ -n "${mr_iid}" ]]; then
|
||||
mr_iid=$(echo "${mr_iid}" |
|
||||
sed -E 's,^.*merge_requests/([0-9]+)>.*$,\1,')
|
||||
CI_MERGE_REQUEST_IID="${mr_iid}"
|
||||
CI_MERGE_REQUEST_PROJECT_ID=${CI_PROJECT_ID}
|
||||
fi
|
||||
fi
|
||||
set -x
|
||||
}
|
||||
|
||||
# Posts a comment to the current merge request.
|
||||
cmd_post_mr_comment() {
|
||||
{ set +x; } 2>/dev/null
|
||||
local comment="$1"
|
||||
if [[ -n "${BOT_TOKEN:-}" && -n "${CI_MERGE_REQUEST_IID:-}" ]]; then
|
||||
local url="${CI_API_V4_URL}/projects/${CI_MERGE_REQUEST_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/notes"
|
||||
curl -X POST -g \
|
||||
-H "PRIVATE-TOKEN: ${BOT_TOKEN}" \
|
||||
--data-urlencode "body=${comment}" \
|
||||
--output /dev/null \
|
||||
"${url}"
|
||||
fi
|
||||
set -x
|
||||
}
|
||||
|
||||
cmake_configure() {
|
||||
local args=(
|
||||
-B"${BUILD_DIR}" -H"${MYDIR}"
|
||||
-DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}"
|
||||
-G Ninja
|
||||
-DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS}"
|
||||
-DCMAKE_C_FLAGS="${CMAKE_C_FLAGS}"
|
||||
-DCMAKE_TOOLCHAIN_FILE="${CMAKE_TOOLCHAIN_FILE}"
|
||||
-DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}"
|
||||
-DCMAKE_MODULE_LINKER_FLAGS="${CMAKE_MODULE_LINKER_FLAGS}"
|
||||
-DCMAKE_SHARED_LINKER_FLAGS="${CMAKE_SHARED_LINKER_FLAGS}"
|
||||
-DCMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}"
|
||||
-DJPEGXL_VERSION="${JPEGXL_VERSION}"
|
||||
)
|
||||
if [[ -n "${BUILD_TARGET}" ]]; then
|
||||
# If set, BUILD_TARGET must be the target triplet such as
|
||||
# x86_64-unknown-linux-gnu.
|
||||
args+=(
|
||||
-DCMAKE_C_COMPILER_TARGET="${BUILD_TARGET}"
|
||||
-DCMAKE_CXX_COMPILER_TARGET="${BUILD_TARGET}"
|
||||
# Only the first element of the target triplet.
|
||||
-DCMAKE_SYSTEM_PROCESSOR="${BUILD_TARGET%%-*}"
|
||||
-DCMAKE_SYSTEM_NAME=Linux
|
||||
# These are needed to make googletest work when cross-compiling.
|
||||
-DCMAKE_CROSSCOMPILING=1
|
||||
-DHAVE_STD_REGEX=0
|
||||
-DHAVE_POSIX_REGEX=0
|
||||
-DHAVE_GNU_POSIX_REGEX=0
|
||||
-DHAVE_STEADY_CLOCK=0
|
||||
-DHAVE_THREAD_SAFETY_ATTRIBUTES=0
|
||||
)
|
||||
# Use pkg-config for the target:
|
||||
local pkg_config=$(which "${BUILD_TARGET}-pkg-config" || true)
|
||||
if [[ -n "${pkg_config}" ]]; then
|
||||
args+=(-DPKG_CONFIG_EXECUTABLE="${pkg_config}")
|
||||
fi
|
||||
fi
|
||||
cmake "${args[@]}" "$@"
|
||||
}
|
||||
|
||||
cmake_build_and_test() {
|
||||
# gtest_discover_tests() runs the test binaries to discover the list of tests
|
||||
# at build time, which fails under qemu.
|
||||
ASAN_OPTIONS=detect_leaks=0 cmake --build "${BUILD_DIR}"
|
||||
# Pack test binaries if requested.
|
||||
if [[ "${PACK_TEST:-}" == "1" ]]; then
|
||||
(cd "${BUILD_DIR}"
|
||||
find -name '*.cmake' -a '!' -path '*CMakeFiles*'
|
||||
find -type d -name tests -a '!' -path '*CMakeFiles*'
|
||||
) | tar -C "${BUILD_DIR}" -cf "${BUILD_DIR}/tests.tar.xz" -T - \
|
||||
--use-compress-program="xz --threads=$(nproc --all || echo 1) -6"
|
||||
du -h "${BUILD_DIR}/tests.tar.xz"
|
||||
# Pack coverage data if also available.
|
||||
touch "${BUILD_DIR}/gcno.sentinel"
|
||||
(cd "${BUILD_DIR}"; echo gcno.sentinel; find -name '*gcno') | \
|
||||
tar -C "${BUILD_DIR}" -cvf "${BUILD_DIR}/gcno.tar.xz" -T - \
|
||||
--use-compress-program="xz --threads=$(nproc --all || echo 1) -6"
|
||||
fi
|
||||
|
||||
if [[ "${SKIP_TEST}" -ne "1" ]]; then
|
||||
(cd "${BUILD_DIR}"
|
||||
export UBSAN_OPTIONS=print_stacktrace=1
|
||||
ctest -j $(nproc --all || echo 1) --output-on-failure)
|
||||
fi
|
||||
}
|
||||
|
||||
# Configure the build to strip unused functions. This considerably reduces the
|
||||
# output size, specially for tests which only use a small part of the whole
|
||||
# library.
|
||||
strip_dead_code() {
|
||||
# Emscripten does tree shaking without any extra flags.
|
||||
if [[ "${CMAKE_TOOLCHAIN_FILE##*/}" == "Emscripten.cmake" ]]; then
|
||||
return 0
|
||||
fi
|
||||
# -ffunction-sections, -fdata-sections and -Wl,--gc-sections effectively
|
||||
# discard all unreachable code, reducing the code size. For this to work, we
|
||||
# need to also pass --no-export-dynamic to prevent it from exporting all the
|
||||
# internal symbols (like functions) making them all reachable and thus not a
|
||||
# candidate for removal.
|
||||
CMAKE_CXX_FLAGS+=" -ffunction-sections -fdata-sections"
|
||||
CMAKE_C_FLAGS+=" -ffunction-sections -fdata-sections"
|
||||
if [[ "${OS}" == "Darwin" ]]; then
|
||||
CMAKE_EXE_LINKER_FLAGS+=" -dead_strip"
|
||||
CMAKE_SHARED_LINKER_FLAGS+=" -dead_strip"
|
||||
else
|
||||
CMAKE_EXE_LINKER_FLAGS+=" -Wl,--gc-sections -Wl,--no-export-dynamic"
|
||||
CMAKE_SHARED_LINKER_FLAGS+=" -Wl,--gc-sections -Wl,--no-export-dynamic"
|
||||
fi
|
||||
}
|
||||
|
||||
### Externally visible commands
|
||||
|
||||
cmd_debug() {
|
||||
CMAKE_BUILD_TYPE="Debug"
|
||||
cmake_configure "$@"
|
||||
cmake_build_and_test
|
||||
}
|
||||
|
||||
cmd_release() {
|
||||
CMAKE_BUILD_TYPE="Release"
|
||||
strip_dead_code
|
||||
cmake_configure "$@"
|
||||
cmake_build_and_test
|
||||
}
|
||||
|
||||
cmd_opt() {
|
||||
CMAKE_BUILD_TYPE="RelWithDebInfo"
|
||||
CMAKE_CXX_FLAGS+=" -DJXL_DEBUG_WARNING -DJXL_DEBUG_ON_ERROR"
|
||||
cmake_configure "$@"
|
||||
cmake_build_and_test
|
||||
}
|
||||
|
||||
cmd_coverage() {
|
||||
cmd_release -DJPEGXL_ENABLE_COVERAGE=ON "$@"
|
||||
|
||||
if [[ "${SKIP_TEST}" -ne "1" ]]; then
|
||||
# If we didn't run the test we also don't print a coverage report.
|
||||
cmd_coverage_report
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_coverage_report() {
|
||||
detect_clang_version
|
||||
LLVM_COV=$(which "llvm-cov-${CLANG_VERSION}")
|
||||
local real_build_dir=$(realpath ${BUILD_DIR})
|
||||
local gcovr_args=(
|
||||
-r "${real_build_dir}"
|
||||
--gcov-executable "${LLVM_COV} gcov"
|
||||
# Only print coverage information for the jxl and fuif directories. The rest
|
||||
# is not part of the code under test.
|
||||
--filter '.*jxl/.*'
|
||||
--exclude '.*_test.cc'
|
||||
--object-directory "${real_build_dir}"
|
||||
)
|
||||
|
||||
(
|
||||
cd "${real_build_dir}"
|
||||
gcovr "${gcovr_args[@]}" --html --html-details \
|
||||
--output="${real_build_dir}/coverage.html"
|
||||
gcovr "${gcovr_args[@]}" --print-summary |
|
||||
tee "${real_build_dir}/coverage.txt"
|
||||
gcovr "${gcovr_args[@]}" --xml --output="${real_build_dir}/coverage.xml"
|
||||
)
|
||||
}
|
||||
|
||||
cmd_test() {
|
||||
# Unpack tests if needed.
|
||||
if [[ -e "${BUILD_DIR}/tests.tar.xz" && ! -d "${BUILD_DIR}/tests" ]]; then
|
||||
tar -C "${BUILD_DIR}" -Jxvf "${BUILD_DIR}/tests.tar.xz"
|
||||
fi
|
||||
if [[ -e "${BUILD_DIR}/gcno.tar.xz" && ! -d "${BUILD_DIR}/gcno.sentinel" ]]; then
|
||||
tar -C "${BUILD_DIR}" -Jxvf "${BUILD_DIR}/gcno.tar.xz"
|
||||
fi
|
||||
(cd "${BUILD_DIR}"
|
||||
export UBSAN_OPTIONS=print_stacktrace=1
|
||||
ctest -j $(nproc --all || echo 1) --output-on-failure)
|
||||
}
|
||||
|
||||
cmd_asan() {
|
||||
detect_clang_version
|
||||
LLVM_SYMBOLIZER=$(which llvm-symbolizer "llvm-symbolizer-${CLANG_VERSION}" | \
|
||||
head -n1)
|
||||
LLVM_SYMBOLIZER="$(realpath "${LLVM_SYMBOLIZER}" || true)"
|
||||
|
||||
CMAKE_C_FLAGS+=" -DJXL_ENABLE_ASSERT=1 -g -DADDRESS_SANITIZER \
|
||||
-fsanitize=address ${UBSAN_FLAGS[@]}"
|
||||
CMAKE_CXX_FLAGS+=" -DJXL_ENABLE_ASSERT=1 -g -DADDRESS_SANITIZER \
|
||||
-fsanitize=address ${UBSAN_FLAGS[@]}"
|
||||
strip_dead_code
|
||||
cmake_configure "$@"
|
||||
export ASAN_SYMBOLIZER_PATH="${LLVM_SYMBOLIZER}"
|
||||
export UBSAN_SYMBOLIZER_PATH="${LLVM_SYMBOLIZER}"
|
||||
cmake_build_and_test
|
||||
}
|
||||
|
||||
cmd_tsan() {
|
||||
local tsan_args=(
|
||||
-DJXL_ENABLE_ASSERT=1
|
||||
-g
|
||||
-DTHREAD_SANITIZER
|
||||
${UBSAN_FLAGS[@]}
|
||||
-fsanitize=thread
|
||||
)
|
||||
CMAKE_C_FLAGS+=" ${tsan_args[@]}"
|
||||
CMAKE_CXX_FLAGS+=" ${tsan_args[@]}"
|
||||
|
||||
CMAKE_BUILD_TYPE="RelWithDebInfo"
|
||||
cmake_configure "$@" -DJPEGXL_ENABLE_TCMALLOC=OFF
|
||||
cmake_build_and_test
|
||||
}
|
||||
|
||||
cmd_msan() {
|
||||
detect_clang_version
|
||||
local msan_prefix="${HOME}/.msan/${CLANG_VERSION}"
|
||||
if [[ ! -d "${msan_prefix}" || -e "${msan_prefix}/lib/libc++abi.a" ]]; then
|
||||
# Install msan libraries for this version if needed or if an older version
|
||||
# with libc++abi was installed.
|
||||
cmd_msan_install
|
||||
fi
|
||||
|
||||
local msan_c_flags=(
|
||||
-fsanitize=memory
|
||||
-fno-omit-frame-pointer
|
||||
-fsanitize-memory-track-origins
|
||||
|
||||
-DJXL_ENABLE_ASSERT=1
|
||||
-g
|
||||
-DMEMORY_SANITIZER
|
||||
|
||||
# Force gtest to not use the cxxbai.
|
||||
-DGTEST_HAS_CXXABI_H_=0
|
||||
)
|
||||
local msan_cxx_flags=(
|
||||
"${msan_c_flags[@]}"
|
||||
|
||||
# Some C++ sources don't use the std at all, so the -stdlib=libc++ is unused
|
||||
# in those cases. Ignore the warning.
|
||||
-Wno-unused-command-line-argument
|
||||
-stdlib=libc++
|
||||
|
||||
# We include the libc++ from the msan directory instead, so we don't want
|
||||
# the std includes.
|
||||
-nostdinc++
|
||||
-cxx-isystem"${msan_prefix}/include/c++/v1"
|
||||
)
|
||||
|
||||
local msan_linker_flags=(
|
||||
-L"${msan_prefix}"/lib
|
||||
-Wl,-rpath -Wl,"${msan_prefix}"/lib/
|
||||
)
|
||||
|
||||
LLVM_SYMBOLIZER=$(which llvm-symbolizer "llvm-symbolizer-${CLANG_VERSION}" | \
|
||||
head -n1)
|
||||
LLVM_SYMBOLIZER="$(realpath "${LLVM_SYMBOLIZER}" || true)"
|
||||
|
||||
CMAKE_C_FLAGS+=" ${msan_c_flags[@]} ${UBSAN_FLAGS[@]}"
|
||||
CMAKE_CXX_FLAGS+=" ${msan_cxx_flags[@]} ${UBSAN_FLAGS[@]}"
|
||||
CMAKE_EXE_LINKER_FLAGS+=" ${msan_linker_flags[@]}"
|
||||
CMAKE_MODULE_LINKER_FLAGS+=" ${msan_linker_flags[@]}"
|
||||
CMAKE_SHARED_LINKER_FLAGS+=" ${msan_linker_flags[@]}"
|
||||
strip_dead_code
|
||||
cmake_configure "$@" \
|
||||
-DCMAKE_CROSSCOMPILING=1 -DRUN_HAVE_STD_REGEX=0 -DRUN_HAVE_POSIX_REGEX=0
|
||||
export MSAN_SYMBOLIZER_PATH="${LLVM_SYMBOLIZER}"
|
||||
export UBSAN_SYMBOLIZER_PATH="${LLVM_SYMBOLIZER}"
|
||||
cmake_build_and_test
|
||||
}
|
||||
|
||||
# Install libc++ libraries compiled with msan in the msan_prefix for the current
|
||||
# compiler version.
|
||||
cmd_msan_install() {
|
||||
local tmpdir=$(mktemp -d)
|
||||
CLEANUP_FILES+=("${tmpdir}")
|
||||
# Detect the llvm to install:
|
||||
export CC="${CC:-clang}"
|
||||
export CXX="${CXX:-clang++}"
|
||||
detect_clang_version
|
||||
local llvm_tag
|
||||
case "${CLANG_VERSION}" in
|
||||
"6.0")
|
||||
llvm_tag="llvmorg-6.0.1"
|
||||
;;
|
||||
"7")
|
||||
llvm_tag="llvmorg-7.0.1"
|
||||
;;
|
||||
"8")
|
||||
llvm_tag="llvmorg-8.0.0"
|
||||
;;
|
||||
"9")
|
||||
llvm_tag="llvmorg-9.0.0"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown clang version: ${CLANG_VERSION}" >&2
|
||||
return 1
|
||||
esac
|
||||
local llvm_targz="${tmpdir}/${llvm_tag}.tar.gz"
|
||||
curl -L --show-error -o "${llvm_targz}" \
|
||||
"https://github.com/llvm/llvm-project/archive/${llvm_tag}.tar.gz"
|
||||
tar -C "${tmpdir}" -zxf "${llvm_targz}"
|
||||
local llvm_root="${tmpdir}/llvm-project-${llvm_tag}"
|
||||
|
||||
local msan_prefix="${HOME}/.msan/${CLANG_VERSION}"
|
||||
rm -rf "${msan_prefix}"
|
||||
|
||||
declare -A CMAKE_EXTRAS
|
||||
CMAKE_EXTRAS[libcxx]="\
|
||||
-DLIBCXX_CXX_ABI=libstdc++ \
|
||||
-DLIBCXX_INSTALL_EXPERIMENTAL_LIBRARY=ON"
|
||||
|
||||
for project in libcxx; do
|
||||
local proj_build="${tmpdir}/build-${project}"
|
||||
local proj_dir="${llvm_root}/${project}"
|
||||
mkdir -p "${proj_build}"
|
||||
cmake -B"${proj_build}" -H"${proj_dir}" \
|
||||
-G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DLLVM_USE_SANITIZER=Memory \
|
||||
-DLLVM_PATH="${llvm_root}/llvm" \
|
||||
-DLLVM_CONFIG_PATH="$(which llvm-config llvm-config-7 llvm-config-6.0 | \
|
||||
head -n1)" \
|
||||
-DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS}" \
|
||||
-DCMAKE_C_FLAGS="${CMAKE_C_FLAGS}" \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}" \
|
||||
-DCMAKE_SHARED_LINKER_FLAGS="${CMAKE_SHARED_LINKER_FLAGS}" \
|
||||
-DCMAKE_INSTALL_PREFIX="${msan_prefix}" \
|
||||
${CMAKE_EXTRAS[${project}]}
|
||||
cmake --build "${proj_build}"
|
||||
ninja -C "${proj_build}" install
|
||||
done
|
||||
}
|
||||
|
||||
cmd_fast_benchmark() {
|
||||
local small_corpus_tar="${BENCHMARK_CORPORA}/jyrki-full.tar"
|
||||
mkdir -p "${BENCHMARK_CORPORA}"
|
||||
curl --show-error -o "${small_corpus_tar}" -z "${small_corpus_tar}" \
|
||||
"https://storage.googleapis.com/artifacts.jpegxl.appspot.com/corpora/jyrki-full.tar"
|
||||
|
||||
local tmpdir=$(mktemp -d)
|
||||
CLEANUP_FILES+=("${tmpdir}")
|
||||
tar -xf "${small_corpus_tar}" -C "${tmpdir}"
|
||||
|
||||
run_benchmark "${tmpdir}" 1048576
|
||||
}
|
||||
|
||||
cmd_benchmark() {
|
||||
local nikon_corpus_tar="${BENCHMARK_CORPORA}/nikon-subset.tar"
|
||||
mkdir -p "${BENCHMARK_CORPORA}"
|
||||
curl --show-error -o "${nikon_corpus_tar}" -z "${nikon_corpus_tar}" \
|
||||
"https://storage.googleapis.com/artifacts.jpegxl.appspot.com/corpora/nikon-subset.tar"
|
||||
|
||||
local tmpdir=$(mktemp -d)
|
||||
CLEANUP_FILES+=("${tmpdir}")
|
||||
tar -xvf "${nikon_corpus_tar}" -C "${tmpdir}"
|
||||
|
||||
local sem_id="jpegxl_benchmark-$$"
|
||||
local nprocs=$(nproc --all || echo 1)
|
||||
images=()
|
||||
local filename
|
||||
while IFS= read -r filename; do
|
||||
# This removes the './'
|
||||
filename="${filename:2}"
|
||||
local mode
|
||||
if [[ "${filename:0:4}" == "srgb" ]]; then
|
||||
mode="RGB_D65_SRG_Rel_SRG"
|
||||
elif [[ "${filename:0:5}" == "adobe" ]]; then
|
||||
mode="RGB_D65_Ado_Rel_Ado"
|
||||
else
|
||||
echo "Unknown image colorspace: ${filename}" >&2
|
||||
exit 1
|
||||
fi
|
||||
png_filename="${filename%.ppm}.png"
|
||||
png_filename=$(echo "${png_filename}" | tr '/' '_')
|
||||
sem --bg --id "${sem_id}" -j"${nprocs}" -- \
|
||||
"${BUILD_DIR}/tools/decode_and_encode" \
|
||||
"${tmpdir}/${filename}" "${mode}" "${tmpdir}/${png_filename}"
|
||||
images+=( "${png_filename}" )
|
||||
done < <(cd "${tmpdir}"; find . -name '*.ppm' -type f)
|
||||
sem --id "${sem_id}" --wait
|
||||
|
||||
# We need about 10 GiB per thread on these images.
|
||||
run_benchmark "${tmpdir}" 10485760
|
||||
}
|
||||
|
||||
get_mem_available() {
|
||||
if [[ "${OS}" == "Darwin" ]]; then
|
||||
echo $(vm_stat | grep -F 'Pages free:' | awk '{print $3 * 4}')
|
||||
else
|
||||
echo $(grep -F MemAvailable: /proc/meminfo | awk '{print $2}')
|
||||
fi
|
||||
}
|
||||
|
||||
run_benchmark() {
|
||||
local src_img_dir="$1"
|
||||
local mem_per_thread="${2:-10485760}"
|
||||
|
||||
local output_dir="${BUILD_DIR}/benchmark_results"
|
||||
mkdir -p "${output_dir}"
|
||||
|
||||
# The memory available at the beginning of the benchmark run in kB. The number
|
||||
# of threads depends on the available memory, and the passed memory per
|
||||
# thread. We also add a 2 GiB of constant memory.
|
||||
local mem_available="$(get_mem_available)"
|
||||
# Check that we actually have a MemAvailable value.
|
||||
[[ -n "${mem_available}" ]]
|
||||
local num_threads=$(( (${mem_available} - 1048576) / ${mem_per_thread} ))
|
||||
if [[ ${num_threads} -le 0 ]]; then
|
||||
num_threads=1
|
||||
fi
|
||||
|
||||
local benchmark_args=(
|
||||
--input "${src_img_dir}/*.png"
|
||||
--codec=jpeg:yuv420:q85,webp:q80,jxl:fast:d1,jxl:fast:d1:downsampling=8,jxl:fast:d4,jxl:fast:d4:downsampling=8,jxl:mg:nl,jxl:mg,jxl:mg:P7,jxl:mg:q80
|
||||
--output_dir "${output_dir}"
|
||||
--noprofiler --show_progress
|
||||
--num_threads="${num_threads}"
|
||||
)
|
||||
if [[ "${STORE_IMAGES}" == "1" ]]; then
|
||||
benchmark_args+=(--save_decompressed --save_compressed)
|
||||
fi
|
||||
"${BUILD_DIR}/tools/benchmark_xl" "${benchmark_args[@]}" | \
|
||||
tee "${output_dir}/results.txt"
|
||||
# Check error code for benckmark_xl command. This will exit if not.
|
||||
[[ "${PIPESTATUS[0]}" == 0 ]]
|
||||
|
||||
if [[ -n "${CI_BUILD_NAME:-}" ]]; then
|
||||
{ set +x; } 2>/dev/null
|
||||
local message="Results for ${CI_BUILD_NAME} @ ${CI_COMMIT_SHORT_SHA} (job ${CI_JOB_URL:-}):
|
||||
|
||||
$(cat "${output_dir}/results.txt")
|
||||
"
|
||||
cmd_post_mr_comment "${message}"
|
||||
set -x
|
||||
fi
|
||||
}
|
||||
|
||||
# Helper function to wait for the CPU temperature to cool down on ARM.
|
||||
wait_for_temp() {
|
||||
{ set +x; } 2>/dev/null
|
||||
local temp_limit=${1:-37000}
|
||||
if [[ -z "${THERMAL_FILE:-}" ]]; then
|
||||
echo "Must define the THERMAL_FILE with the thermal_zoneX/temp file" \
|
||||
"to read the temperature from. This is normally set in the runner." >&2
|
||||
exit 1
|
||||
fi
|
||||
local org_temp=$(cat "${THERMAL_FILE}")
|
||||
if [[ "${org_temp}" -ge "${temp_limit}" ]]; then
|
||||
echo -n "Waiting for temp to get down from ${org_temp}... "
|
||||
fi
|
||||
local temp="${org_temp}"
|
||||
while [[ "${temp}" -ge "${temp_limit}" ]]; do
|
||||
sleep 1
|
||||
temp=$(cat "${THERMAL_FILE}")
|
||||
done
|
||||
if [[ "${org_temp}" -ge "${temp_limit}" ]]; then
|
||||
echo "Done, temp=${temp}"
|
||||
fi
|
||||
set -x
|
||||
}
|
||||
|
||||
# Helper function to set the cpuset restriction of the current process.
|
||||
cmd_cpuset() {
|
||||
local newset="$1"
|
||||
local mycpuset=$(cat /proc/self/cpuset)
|
||||
mycpuset="/dev/cpuset${mycpuset}"
|
||||
# Check that the directory exists:
|
||||
[[ -d "${mycpuset}" ]]
|
||||
echo "${newset}" >"${mycpuset}/cpuset.cpus"
|
||||
}
|
||||
|
||||
# Run benchmarks on ARM for the big and little CPUs.
|
||||
cmd_arm_benchmark() {
|
||||
local benchmarks=(
|
||||
# Lossy options:
|
||||
"--adaptive_reconstruction=1 --distance=1.0 --speed=fast"
|
||||
"--adaptive_reconstruction=0 --distance=1.0 --speed=fast"
|
||||
"--adaptive_reconstruction=1 --distance=8.0 --speed=fast"
|
||||
"--adaptive_reconstruction=0 --distance=8.0 --speed=fast"
|
||||
"--modular-group -Q 90"
|
||||
"--modular-group -Q 50"
|
||||
# Lossless options:
|
||||
"--modular-group"
|
||||
"--modular-group -E 0 -I 0 -A"
|
||||
"--modular-group -P 5 -A"
|
||||
"--modular-group --responsive=1"
|
||||
# Near-lossless options:
|
||||
"--adaptive_reconstruction=0 --distance=0.3 --speed=fast"
|
||||
"--modular-group -N 3 -B 11"
|
||||
"--modular-group -Q 97"
|
||||
)
|
||||
|
||||
local images=(
|
||||
"third_party/testdata/imagecompression.info/flower_foveon.png"
|
||||
)
|
||||
|
||||
local cpu_confs=(
|
||||
"${RUNNER_CPU_LITTLE}"
|
||||
"${RUNNER_CPU_BIG}"
|
||||
# The CPU description is something like 3-7, so these configurations only
|
||||
# take the first CPU of the group.
|
||||
"${RUNNER_CPU_LITTLE%%-*}"
|
||||
"${RUNNER_CPU_BIG%%-*}"
|
||||
)
|
||||
local output_dir="${BUILD_DIR}/benchmark_results"
|
||||
mkdir -p "${output_dir}"
|
||||
local runs_file="${output_dir}/runs.txt"
|
||||
|
||||
if [[ ! -e "${runs_file}" ]]; then
|
||||
echo -e "flags\tsrc_img\tsrc size\tsrc pixels\tcpuset\tenc size (B)\tdec speed (MP/s)" |
|
||||
tee -a "${runs_file}"
|
||||
fi
|
||||
|
||||
local flags
|
||||
local src_img
|
||||
for src_img in "${images[@]}"; do
|
||||
local src_img_hash=$(sha1sum "${src_img}" | cut -f 1 -d ' ')
|
||||
for flags in "${benchmarks[@]}"; do
|
||||
cmd_cpuset "${RUNNER_CPU_ALL}"
|
||||
# Encoding step.
|
||||
local enc_file_hash="$flags || ${src_img} || ${src_img_hash}"
|
||||
enc_file_hash=$(echo "${enc_file_hash}" | sha1sum | cut -f 1 -d ' ')
|
||||
|
||||
local enc_file="${BUILD_DIR}/${enc_file_hash}.bin"
|
||||
if [[ ! -f "${enc_file}" ]]; then
|
||||
"${BUILD_DIR}/tools/cjpegxl" ${flags} "${src_img}" "${enc_file}.tmp"
|
||||
mv "${enc_file}.tmp" "${enc_file}"
|
||||
fi
|
||||
local enc_size=$(stat -c "%s" "${enc_file}")
|
||||
|
||||
for cpu_conf in "${cpu_confs[@]}"; do
|
||||
cmd_cpuset "${cpu_conf}"
|
||||
echo "Decoding with: img=${src_img} cpus=${cpu_conf} enc_flags=${flags}"
|
||||
|
||||
local dec_output
|
||||
wait_for_temp
|
||||
dec_output=$("${BUILD_DIR}/tools/djpegxl" "${enc_file}" \
|
||||
--num_reps=5 2>&1 | grep -F "MP/s")
|
||||
local img_size=$(echo "${dec_output}" | cut -f 1 -d ',')
|
||||
local img_size_x=$(echo "${img_size}" | cut -f 1 -d ' ')
|
||||
local img_size_y=$(echo "${img_size}" | cut -f 3 -d ' ')
|
||||
local img_size_px=$(( ${img_size_x} * ${img_size_y} ))
|
||||
local dec_speed=$(echo "${dec_output}" | cut -f 5 -d ' ')
|
||||
|
||||
# Record entry in a tab-separated file.
|
||||
echo -e "${flags}\t${src_img}\t${img_size}\t${img_size_px}\t${cpu_conf}\t${enc_size}\t${dec_speed}" |
|
||||
tee -a "${runs_file}"
|
||||
done
|
||||
done
|
||||
done
|
||||
cmd_cpuset "${RUNNER_CPU_ALL}"
|
||||
cat "${runs_file}"
|
||||
|
||||
if [[ -n "${CI_BUILD_NAME:-}" ]]; then
|
||||
load_mr_vars_from_commit
|
||||
{ set +x; } 2>/dev/null
|
||||
local message="Results for ${CI_BUILD_NAME} @ ${CI_COMMIT_SHORT_SHA} (job ${CI_JOB_URL:-}):
|
||||
|
||||
\`\`\`
|
||||
$(column -t -s " " "${runs_file}")
|
||||
\`\`\`
|
||||
"
|
||||
cmd_post_mr_comment "${message}"
|
||||
set -x
|
||||
fi
|
||||
}
|
||||
|
||||
# Runs the linter (clang-format) on the pending CLs.
|
||||
cmd_lint() {
|
||||
merge_request_commits
|
||||
# { set +x; } 2>/dev/null
|
||||
local versions=(${1:-6.0 7 8 9})
|
||||
local clang_format_bins=("${versions[@]/#/clang-format-}" clang-format)
|
||||
local tmpdir=$(mktemp -d)
|
||||
CLEANUP_FILES+=("${tmpdir}")
|
||||
|
||||
local installed=()
|
||||
local clang_patch
|
||||
local clang_format
|
||||
for clang_format in "${clang_format_bins[@]}"; do
|
||||
if ! which "${clang_format}" >/dev/null; then
|
||||
continue
|
||||
fi
|
||||
installed+=("${clang_format}")
|
||||
local tmppatch="${tmpdir}/${clang_format}.patch"
|
||||
# We include in this linter all the changes including the uncommited changes
|
||||
# to avoid printing changes already applied.
|
||||
set -x
|
||||
git -C "${MYDIR}" "${clang_format}" --binary "${clang_format}" \
|
||||
--style=file --diff "${MR_ANCESTOR_SHA}" -- >"${tmppatch}"
|
||||
{ set +x; } 2>/dev/null
|
||||
|
||||
if grep -E '^--- ' "${tmppatch}">/dev/null; then
|
||||
if [[ -n "${LINT_OUTPUT:-}" ]]; then
|
||||
cp "${tmppatch}" "${LINT_OUTPUT}"
|
||||
fi
|
||||
clang_patch="${tmppatch}"
|
||||
else
|
||||
echo "clang-format check OK" >&2
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#installed[@]} -eq 0 ]]; then
|
||||
echo "You must install clang-format for \"git clang-format\"" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# clang-format is installed but found problems.
|
||||
echo "clang-format findings:" >&2
|
||||
"${COLORDIFF_BIN}" < "${clang_patch}"
|
||||
|
||||
echo "clang-format found issues in your patches from ${MR_ANCESTOR_SHA}" \
|
||||
"to the current patch. Run \`./ci.sh lint | patch -p1\` from the base" \
|
||||
"directory to apply them." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Runs clang-tidy on the pending CLs. If the "all" argument is passed it runs
|
||||
# clang-tidy over all the source files instead.
|
||||
cmd_tidy() {
|
||||
local what="${1:-}"
|
||||
|
||||
if [[ -z "${CLANG_TIDY_BIN}" ]]; then
|
||||
echo "ERROR: You must install clang-tidy-6.0 or newer to use ci.sh tidy" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local git_args=()
|
||||
if [[ "${what}" == "all" ]]; then
|
||||
git_args=(ls-files)
|
||||
shift
|
||||
else
|
||||
merge_request_commits
|
||||
git_args=(
|
||||
diff-tree --no-commit-id --name-only -r "${MR_ANCESTOR_SHA}"
|
||||
"${MR_HEAD_SHA}"
|
||||
)
|
||||
fi
|
||||
|
||||
# Clang-tidy needs the compilation database generated by cmake.
|
||||
if [[ ! -e "${BUILD_DIR}/compile_commands.json" ]]; then
|
||||
# Generate the build options in debug mode, since we need the debug asserts
|
||||
# enabled for the clang-tidy analyzer to use them.
|
||||
CMAKE_BUILD_TYPE="Debug"
|
||||
cmake_configure
|
||||
fi
|
||||
|
||||
cd "${MYDIR}"
|
||||
local nprocs=$(nproc --all || echo 1)
|
||||
local ret=0
|
||||
if ! parallel -j"${nprocs}" --keep-order -- \
|
||||
"${CLANG_TIDY_BIN}" -p "${BUILD_DIR}" -format-style=file -quiet "$@" {} \
|
||||
< <(git "${git_args[@]}" | grep -E '(\.cc|\.cpp)$') \
|
||||
>"${BUILD_DIR}/clang-tidy.txt"; then
|
||||
ret=1
|
||||
fi
|
||||
{ set +x; } 2>/dev/null
|
||||
echo "Findings statistics:" >&2
|
||||
grep -E ' \[[A-Za-z\.,\-]+\]' -o "${BUILD_DIR}/clang-tidy.txt" | sort \
|
||||
| uniq -c >&2
|
||||
|
||||
if [[ $ret -ne 0 ]]; then
|
||||
cat >&2 <<EOF
|
||||
Errors found, see ${BUILD_DIR}/clang-tidy.txt for details.
|
||||
To automatically fix them, run:
|
||||
|
||||
SKIP_TEST=1 ./ci.sh debug
|
||||
${CLANG_TIDY_BIN} -p ${BUILD_DIR} -fix -format-style=file -quiet $@ \$(git ${git_args[@]} | grep -E '(\.cc|\.cpp)\$')
|
||||
EOF
|
||||
fi
|
||||
|
||||
return ${ret}
|
||||
}
|
||||
|
||||
main() {
|
||||
local cmd="${1:-}"
|
||||
if [[ -z "${cmd}" ]]; then
|
||||
cat >&2 <<EOF
|
||||
Use: $0 CMD
|
||||
|
||||
Where cmd is one of:
|
||||
opt Build and test a Release with symbols build.
|
||||
debug Build and test a Debug build (NDEBUG is not defined).
|
||||
release Build and test a striped Release binary without debug information.
|
||||
asan Build and test an ASan (AddressSanitizer) build.
|
||||
msan Build and test an MSan (MemorySanitizer) build. Needs to have msan
|
||||
c++ libs installed with msan_install first.
|
||||
tsan Build and test a TSan (ThreadSanitizer) build.
|
||||
test Run the tests build by opt, debug, release, asan or msan. Useful when
|
||||
building with SKIP_TEST=1.
|
||||
benchmark Run the benchmark over the default corpus.
|
||||
fast_benchmark Run the benchmark over the small corpus.
|
||||
|
||||
coverage Buils and run tests with coverage support. Runs coverage_report as
|
||||
well.
|
||||
coverage_report Generate HTML, XML and text coverage report after a coverage
|
||||
run.
|
||||
|
||||
lint Run the linter checks on the current commit or merge request.
|
||||
tidy Run clang-tidy on the current commit or merge request.
|
||||
|
||||
msan_install Install the libc++ libraries required to build in msan mode. This
|
||||
needs to be done once.
|
||||
|
||||
You can pass some optional environment variables as well:
|
||||
- BUILD_DIR: The output build directory (by default "$$repo/build")
|
||||
- BUILD_TARGET: The target triplet used when cross-compiling.
|
||||
- CMAKE_FLAGS: Convenience flag to pass both CMAKE_C_FLAGS and CMAKE_CXX_FLAGS.
|
||||
- CMAKE_PREFIX_PATH: Installation prefixes to be searched by the find_package.
|
||||
- LINT_OUTPUT: Path to the output patch from the "lint" command.
|
||||
- SKIP_TEST=1: Skip the test stage.
|
||||
- STORE_IMAGES=0: Makes the benchmark discard the computed images.
|
||||
|
||||
These optional environment variables are forwarded to the cmake call as
|
||||
parameters:
|
||||
- CMAKE_C_FLAGS
|
||||
- CMAKE_CXX_FLAGS
|
||||
- CMAKE_EXE_LINKER_FLAGS
|
||||
- CMAKE_MODULE_LINKER_FLAGS
|
||||
- CMAKE_SHARED_LINKER_FLAGS
|
||||
- CMAKE_TOOLCHAIN_FILE
|
||||
- CMAKE_BUILD_TYPE
|
||||
|
||||
Example:
|
||||
BUILD_DIR=/tmp/build $0 opt
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cmd="cmd_${cmd}"
|
||||
shift
|
||||
set -x
|
||||
"${cmd}" "$@"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,181 @@
|
||||
# Building and Testing
|
||||
|
||||
## Basic building
|
||||
|
||||
If you only want to build the project but not modify it you can run:
|
||||
|
||||
```bash
|
||||
./ci.sh release
|
||||
```
|
||||
|
||||
Running `./ci.sh` with no parameters shows a list of available commands. For
|
||||
example, you can run `opt` for optimized developer builds with symbols or
|
||||
`debug` for debug builds which do not have NDEBUG defined and therefore include
|
||||
more runtime debug information.
|
||||
|
||||
## Sending patches
|
||||
|
||||
Before sending a patch, make sure your patch conforms to the
|
||||
[project guidelines](guidelines.md) and test it following these steps.
|
||||
|
||||
You will need to install a few other dependencies for verifying these patches,
|
||||
which are used by the `./ci.sh` helper command.
|
||||
|
||||
```bash
|
||||
sudo apt install clang-format-6.0 clang-tidy-6.0 curl parallel
|
||||
```
|
||||
|
||||
### Build and unittest checks
|
||||
|
||||
Your patch must build in the "release" and "debug" configurations at least, and
|
||||
pass all the tests. The "opt" configuration will be tested in the Merge Request
|
||||
pipeline which is equivalent to a "release" build with debug information.
|
||||
|
||||
```bash
|
||||
./ci.sh opt
|
||||
```
|
||||
|
||||
All of the tests must pass. If you added new functions, you should also add
|
||||
tests to these functions in the same commit.
|
||||
|
||||
### Cross-compiling
|
||||
|
||||
To compile the code for an architecture different than the one you are running
|
||||
you can pass a
|
||||
[toolchain file](https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html)
|
||||
to cmake if you have one for your target, or you can use the `BUILD_TARGET`
|
||||
environment variable in `./ci.sh`. This assumes that you already have a
|
||||
cross-compiling environment set up and the library dependencies are already
|
||||
installed for the target architecture as well. Alternatively, you can use one of
|
||||
the [jpegxl docker containers](developing_in_docker.md) already configured to
|
||||
cross-compile and run for other architectures.
|
||||
|
||||
For example, to compile for the `aarch64-linux-gnu` target triplet you can run:
|
||||
|
||||
```bash
|
||||
BUILD_TARGET=aarch64-linux-gnu ./ci.sh release
|
||||
```
|
||||
|
||||
### Format checks (lint)
|
||||
|
||||
```bash
|
||||
./ci.sh lint
|
||||
```
|
||||
|
||||
Linter checks will verify that the format of your patch conforms to the project
|
||||
style (see [guidelines](guidelines.md)). For this, we run clang-format only on
|
||||
the lines that were changed by your commits.
|
||||
|
||||
If your local git branch is tracking `origin/master` and you landed a few
|
||||
commits in your branch, running this lint command will check all the changes
|
||||
made from the common ancestor with `origin/master` to the latest changes,
|
||||
including uncommitted changes. The output of the program will show the patch
|
||||
that should be applied to fix your commits. You can apply these changes with the
|
||||
following command from the base directory of the git checkout:
|
||||
|
||||
```bash
|
||||
./ci.sh lint | patch -p1
|
||||
```
|
||||
|
||||
### Programming errors (tidy)
|
||||
|
||||
```bash
|
||||
./ci.sh tidy
|
||||
```
|
||||
|
||||
clang-tidy is a tool to check common programming errors in C++, and other valid
|
||||
C++ constructions that are discouraged by the style guide or otherwise dangerous
|
||||
and may constitute a bug.
|
||||
|
||||
To run clang-tidy on the files changed by your changes you can run `./ci.sh
|
||||
tidy`. Note that this will report all the problems encountered in any file that
|
||||
was modified by one of your commits, not just on the lines that your commits
|
||||
modified.
|
||||
|
||||
## Testing
|
||||
|
||||
All of the `./ci.sh` workflows like `release`, `opt`, etc will run the test
|
||||
after building. It is possible to manually run the tests, for example:
|
||||
|
||||
```bash
|
||||
cd build/
|
||||
ctest -j32 --output-on-failure
|
||||
```
|
||||
|
||||
It is also possible for faster iteration to run a specific test binary directly.
|
||||
googletest binaries support a glob filter to match the name of the test, for
|
||||
example `TransposeTest*` will execute all the test names that start with
|
||||
`TransposeTest`.
|
||||
|
||||
```bash
|
||||
build/dct_test --gtest_filter=TransposeTest.Transpose8
|
||||
|
||||
```
|
||||
|
||||
If you want to run multiple tests from different binaries, you can pass a
|
||||
regular expression filter to ctest. Note that this is a regular expression
|
||||
instead of a glob match like in the `gtest_filter`.
|
||||
|
||||
```bash
|
||||
cd build/
|
||||
ctest -j32 --output-on-failure -R '.*Transpose.*'
|
||||
```
|
||||
|
||||
### Address Sanitizer (asan)
|
||||
|
||||
```bash
|
||||
./ci.sh asan
|
||||
```
|
||||
|
||||
ASan builds allow to check for invalid address usages, such as use-after-free.
|
||||
To perform these checks, as well as other undefined behavior checks we only need
|
||||
to build and run the unittests with ASan enabled which can be easily achieved
|
||||
with the command above. If you want to have the ASan build files separated from
|
||||
your regular `build/` directory to quickly switch between asan and regular
|
||||
builds, you can pass the build directory target as follows:
|
||||
|
||||
```bash
|
||||
BUILD_DIR=build-asan ./ci.sh asan
|
||||
```
|
||||
|
||||
### Memory Sanitizer (msan)
|
||||
|
||||
MSan allows to check for invalid memory accesses at runtime, such as using an
|
||||
uninitialized value which likely means that there is a bug. To run these checks,
|
||||
a specially compiled version of the project and tests is needed.
|
||||
|
||||
For building with MSan, you need to build a version of libc++ with
|
||||
`-fsanitize=memory` so we can link against it from the MSan build. Also, having
|
||||
an `llvm-symbolizer` installed is very helpful to obtain stack traces that
|
||||
include the symbols (functions and line numbers). To install `llvm-symbolizer`
|
||||
on a Debian-based system run:
|
||||
|
||||
```bash
|
||||
sudo apt install llvm # or llvm-6.0, llvm-7, etc for a specific version.
|
||||
```
|
||||
|
||||
To install a version of libc++ compiled with `-fsanitize=memory` you can use the
|
||||
`./ci.sh msan_install` command helper. This will download, compile and install
|
||||
libc++ and libc++abi in the `${HOME}/.msan` directory to be used later.
|
||||
|
||||
After this is set up, you can build the project using the following command:
|
||||
|
||||
```bash
|
||||
./ci.sh msan
|
||||
```
|
||||
|
||||
This command by default uses the `build` directory to store the cmake and object
|
||||
files. If you want to have a separate build directory configured with msan you
|
||||
can for example call:
|
||||
|
||||
```bash
|
||||
BUILD_DIR=build-msan ./ci.sh msan
|
||||
```
|
||||
|
||||
### Benchmark
|
||||
|
||||
To run the benchmark:
|
||||
|
||||
```shell
|
||||
build/tools/benchmark_xl --input "/path/*.png" --codec jxl:d1,jxl:d2,jxl:d4
|
||||
```
|
||||
@@ -0,0 +1,90 @@
|
||||
# Developing in docker
|
||||
|
||||
Docker allows to run software in a packaged container, isolated from your host
|
||||
system. This is useful to run the code in a standard environment instead of
|
||||
dealing with problems caused by different building environments during
|
||||
development.
|
||||
|
||||
Read over the [docker install instructions](https://docs.docker.com/install/) to
|
||||
get docker installed in your computer.
|
||||
|
||||
## Basic building
|
||||
|
||||
The image used by our builders is an ubuntu-bionic image with all the required
|
||||
dependencies and build tools installed. You can pull this image from
|
||||
`gcr.io/jpegxl/jpegxl-builder`.
|
||||
|
||||
```bash
|
||||
sudo docker pull gcr.io/jpegxl/jpegxl-builder
|
||||
```
|
||||
|
||||
## Cross-compiling environments
|
||||
|
||||
We have installed the required cross-compiling tools in the main docker image
|
||||
`jpegxl-builder`. This allows to compile for other architectures such as arm
|
||||
and run the tests emulated under qemu.
|
||||
|
||||
To use the main docker image you can run the following command:
|
||||
```bash
|
||||
sudo docker run -it --rm \
|
||||
--user $(id -u):$(id -g) \
|
||||
-v $HOME/jpegxl:/jpegxl \
|
||||
gcr.io/jpegxl/jpegxl-builder bash
|
||||
```
|
||||
|
||||
This creates and runs a container that will be deleted after you exit from this
|
||||
terminal (`--rm` flag).
|
||||
|
||||
The `-v` flag is to map the directory containing your jpegxl checkout in your
|
||||
host (assumed to be at `$HOME/jpegxl`) to a directory inside the container at
|
||||
/jpegxl. This means that whenever you make changes to the code from your host
|
||||
using your favorite editor they are also changed in the container, since the
|
||||
directory is simply mounted into the container.
|
||||
|
||||
On OSX, the path must be one of those whitelisted/shared with Docker. $HOME
|
||||
(which is a subdirectory of /Users/) is known to work with the factory-default
|
||||
settings of Docker.
|
||||
|
||||
**Quick start:** To compile and run unit tests,
|
||||
|
||||
````bash
|
||||
CC=clang-6.0 CXX=clang++-6.0 ./ci.sh opt
|
||||
```
|
||||
Note that the default "clang" compiler is not installed on the image, hence we
|
||||
specify clang-6.0. If a build/ directory already exists and was configured for
|
||||
a different compiler, cmake will complain. This can be avoided by renaming any
|
||||
existing build/ directory. On OSX, "cannot find name for group ID" can be
|
||||
ignored.
|
||||
|
||||
The docker container already has several `qemu-*-static` binaries (such as
|
||||
`qemu-aarch64-static`) that emulate other architectures on x86_64. These qemu
|
||||
binaries are automatically used when running a foreign architecture program in
|
||||
the container only if you have `binfmt` installed and configured to use the
|
||||
binaries from `/usr/bin/qemu-*-static` in the *host*. This is the default in
|
||||
Ubuntu/Debian. If you have these configured somewhere else, symlink that
|
||||
location to the `/usr/bin/qemu-*-static` inside the docker before running. To
|
||||
install binfmt support in your Ubuntu host run outside the container:
|
||||
|
||||
```bash
|
||||
sudo apt install binfmt-support
|
||||
```
|
||||
|
||||
Then to cross-compile and run unit tests execute the following commands:
|
||||
|
||||
```bash
|
||||
cd /jpegxl
|
||||
export BUILD_TARGET=aarch64-linux-gnu CC=clang-6.0 CXX=clang++-6.0
|
||||
./ci.sh release
|
||||
```
|
||||
|
||||
The `BUILD_TARGET=aarch64-linux-gnu` environment variable tells the `ci.sh`
|
||||
script to cross-compile for that target. This also changes the default
|
||||
`BUILD_DIR` to `build-aarch64` since you never want to mix them with the `build`
|
||||
of your host. You can also explicitly set a `BUILD_DIR` environment variable and
|
||||
that will be used instead. The list of supported BUILD_TARGET values for this
|
||||
container is:
|
||||
|
||||
* *the empty string* (for native x86_64 support)
|
||||
* aarch64-linux-gnu
|
||||
* arm-linux-gnueabihf
|
||||
* i686-linux-gnu
|
||||
@@ -0,0 +1,306 @@
|
||||
# Developing in GitLab
|
||||
|
||||
This document describes the development steps related to handling the git
|
||||
repository.
|
||||
|
||||
## Initial setup
|
||||
|
||||
You need to perform these steps at least once. After that, the "Life of a Merge"
|
||||
section describes the common everyday workflows.
|
||||
|
||||
### Configure your SSH access
|
||||
|
||||
The easiest way to configure access to a private GitLab repo is to use SSH keys.
|
||||
For that you need an SSH private and public key, ideally a strong one. You can
|
||||
use different keys for different sites if you want. In this example, we will
|
||||
create one for using in GitLab only.
|
||||
|
||||
Create the `~/.ssh/id_rsa_gitlab` file executing the following. (Here and
|
||||
elsewhere, {{X}} are placeholders for your email/username)
|
||||
|
||||
```bash
|
||||
ssh-keygen -t rsa -b 4096 -C "{{EMAIL}}" -f ~/.ssh/id_rsa_gitlab
|
||||
```
|
||||
|
||||
Go to your [SSH settings in GitLab](https://gitlab.com/profile/keys) and paste
|
||||
the contents of your *public key* (the one ending in `.pub`), that would be the
|
||||
output of this command:
|
||||
|
||||
```bash
|
||||
cat ~/.ssh/id_rsa_gitlab.pub
|
||||
```
|
||||
|
||||
To use a specific key when SSHing to the gitlab.com domain, you can add this
|
||||
snippet of config to your .ssh/config file executing the following.
|
||||
|
||||
```bash
|
||||
cat >> ~/.ssh/config <<EOF
|
||||
|
||||
Host gitlab.com
|
||||
Hostname gitlab.com
|
||||
IdentityFile ~/.ssh/id_rsa_gitlab
|
||||
IdentitiesOnly yes
|
||||
EOF
|
||||
```
|
||||
|
||||
The `IdentitiesOnly yes` part forces to only use the provided IdentityFile when
|
||||
talking to GitLab.
|
||||
|
||||
### Checkout the JPEG XL code from GitLab
|
||||
|
||||
The JPEG XL code is located in
|
||||
[this repo](https://gitlab.com/wg1/jpeg-xl). Clone it using the SSH
|
||||
version of the URL.
|
||||
|
||||
```bash
|
||||
git clone git@gitlab.com:wg1/jpeg-xl.git --recursive ~/jpeg-xl
|
||||
```
|
||||
|
||||
### Fork your private copy
|
||||
|
||||
The normal developer workflow in GitLab involves creating your own fork of a
|
||||
repository and uploading your own changes there. From your own copy you can
|
||||
request merges *to* the upstream repository directly, there's no need to create
|
||||
a branch in the upstream repository.
|
||||
|
||||
Navigate to the [JPEG XL GitLab repo](https://gitlab.com/wg1/jpeg-xl) and
|
||||
click "Fork" on the top right corner. After creating the Fork you will have an
|
||||
exact copy of the repository which should remain private. Then add this new copy
|
||||
to your existing checkout run:
|
||||
|
||||
```bash
|
||||
cd ~/jpeg-xl
|
||||
git remote set-url --push origin git@gitlab.com:{{USERNAME}}/jpeg-xl.git
|
||||
```
|
||||
|
||||
{{USERNAME}} denotes your GitLab username. This will make the "git push origin"
|
||||
command push to your fork, which then allows you to create merge requests.
|
||||
|
||||
## Life of a Merge
|
||||
|
||||
All the commands here assume you are in a git checkout as setup here.
|
||||
|
||||
### Sync to the latest version
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
```
|
||||
|
||||
The last upstream version is now on `origin/master` and none of your local
|
||||
branches have been modified by this command.
|
||||
|
||||
### Start a new branch
|
||||
|
||||
To start a new change you need a local branch. Each branch will represent a list
|
||||
of individual commits which can then be requested to be merged as a single merge
|
||||
request. So in general one branch is one code review, but each branch can have
|
||||
multiple individual commits in it.
|
||||
|
||||
```bash
|
||||
git checkout origin/master -b mybranch
|
||||
```
|
||||
|
||||
This will create a new branch `mybranch` tracking `origin/master`. A branch can
|
||||
track any remove or local branch, which is used by some tools. Running `git
|
||||
branch -vv` will show all the branches you have have, what are they tracking and
|
||||
how many commits are ahead or behind. If you create a branch without tracking
|
||||
any other, you can add or change the tracking branch of the current branch
|
||||
running `git branch --set-upstream-to=...`.
|
||||
|
||||
### Add changes to your branch
|
||||
|
||||
Follow any of the many online tutorials, for example
|
||||
[The basics](https://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository)
|
||||
chapter from the https://git-scm.com/doc website is a good starting guide.
|
||||
Create, change or delete files and do a git commit with a message.
|
||||
|
||||
The commit message is required. A commit message should follow the 50/72 rule:
|
||||
|
||||
* First line is 50 characters or less.
|
||||
* Then a blank line.
|
||||
* Remaining text should be wrapped at 72 characters.
|
||||
|
||||
The first line should identify your commit, since that's what most tools will
|
||||
show to the user. First lines like "Some fixes" are not useful. Explain what the
|
||||
commit contains and why.
|
||||
|
||||
### Upload your changes for review
|
||||
|
||||
The first step is a local review of your changes to see what will you be sending
|
||||
for review. `gitg` is a nice Gtk UI for reviewing your local changes, or `tig`
|
||||
for similar ncurses console-based interface. Otherwise, from the terminal you
|
||||
can run:
|
||||
|
||||
```bash
|
||||
git branch -vv
|
||||
```
|
||||
|
||||
To show the current status of your local branches. In particular, since your
|
||||
branch is tracking origin/master (as seen in the output) git will tell you that
|
||||
you are one commit ahead of the tracking branch.
|
||||
|
||||
```
|
||||
* mybranch e74ae1a [origin/master: ahead 1] Improved decoding speed by 40%
|
||||
```
|
||||
|
||||
It is a good idea before uploading to sync again with upstream (`git fetch
|
||||
origin`) and then run `git branch -vv` to check whether there are new changes
|
||||
upstream. If that is the case, you will see a "behind" flag in the output:
|
||||
|
||||
```
|
||||
* mybranch e74ae1a [origin/master: ahead 1, behind 2] Improved decoding speed by 40%
|
||||
```
|
||||
|
||||
To sync your changes on top of the latest changes in upstream you need to
|
||||
rebase:
|
||||
|
||||
```bash
|
||||
git rebase
|
||||
```
|
||||
|
||||
This will by default rebase your current branch changes on top of the tracking
|
||||
branch. In this case, this will try to apply the current commit on top of the
|
||||
latest origin/master (which has 2 more commits than the ones we have in our
|
||||
branch) and your branch will now include that. There could be conflicts that you
|
||||
have to deal with. A shortcut to do both fetch and rebase is to run `git pull
|
||||
-r`, where the `-r` stands for "rebase" and will rebase the local commits on top
|
||||
of the remote ones.
|
||||
|
||||
Before uploading a patch, make sure your patch conforms to the
|
||||
[project guidelines](guidelines.md) and it
|
||||
[builds and passes tests](building_and_testing.md).
|
||||
|
||||
Once you are ready to send your branch for review, upload it to *your* fork:
|
||||
|
||||
```bash
|
||||
git push origin mybranch
|
||||
```
|
||||
|
||||
This will push your local branch "mybranch" to a remote in your fork called
|
||||
"mybranch". The name can be anything, but keep in mind that it is public. A link
|
||||
to the URL to create a merge request will be displayed.
|
||||
|
||||
```
|
||||
Enumerating objects: 627, done.
|
||||
Counting objects: 100% (627/627), done.
|
||||
Delta compression using up to 56 threads
|
||||
Compressing objects: 100% (388/388), done.
|
||||
Writing objects: 100% (389/389), 10.71 MiB | 8.34 MiB/s, done.
|
||||
Total 389 (delta 236), reused 0 (delta 0)
|
||||
remote: Resolving deltas: 100% (236/236), completed with 225 local objects.
|
||||
remote:
|
||||
remote: To create a merge request for mybranch, visit:
|
||||
remote: https://gitlab.com/{{USERNAME}}/jpeg-xl/merge_requests/new?merge_request%5Bsource_branch%5D=mybranch
|
||||
remote:
|
||||
To gitlab.com:{{USERNAME}}/jpeg-xl.git
|
||||
* [new branch] mybranch -> mybranch
|
||||
```
|
||||
|
||||
### Updating submodules
|
||||
|
||||
The repository uses submodules for external library dependencies in
|
||||
third_party. Each submodule points to a particular external commit of the
|
||||
external repository by the hash code of that external commit. Just like
|
||||
regular source code files, this hash code is part of the current branch and
|
||||
jpeg xl commit you have checked out.
|
||||
|
||||
When changing branches or when doing `git rebase`, git will unfortunately
|
||||
*not* automatically set those hashes to the ones of the branch or jpeg xl
|
||||
commit you changed to nor set the source files of the third_party submodules
|
||||
to the new state. That is, even though git will have updated the jpeg xl
|
||||
source code files on your disk to the new ones, it will leave the submodule
|
||||
hashes and the files in third_party in your workspace to the ones they were
|
||||
before you changed branches. This will show up in a git diff because this
|
||||
is seen as a change compared to the branch you switched to. The git diff shows
|
||||
the difference in hash codes (as if you are changing to the old ones), it does
|
||||
not show changes in files inside the third_party directory.
|
||||
|
||||
This mismatch can cause at least two problems:
|
||||
|
||||
*) the jpeg xl codebase may not compile due to third_party library version
|
||||
mismatch if e.g. API changed or a submodule was added/removed.
|
||||
|
||||
*) when using `commit -a` your commit, which may be a technical change
|
||||
unrelated to submodule changes, will unintentially contain a change to the
|
||||
submodules hash code, which is undesired unless you actually want to change
|
||||
the version of third_party libraries.
|
||||
|
||||
To resolve this, the submodules must be updated manually with
|
||||
the following command after those actions (at least when the submodules
|
||||
changed):
|
||||
|
||||
```
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
Here, the init flag ensures new modules get added when encessary and the
|
||||
recursive flag is required for the submodules depending on other submodules.
|
||||
|
||||
If you checkout a different branch, you can spot that submodules changed
|
||||
when it shows a message similar to this:
|
||||
|
||||
```
|
||||
M third_party/brotli
|
||||
M third_party/lcms
|
||||
```
|
||||
|
||||
If you do a rebase you may end up in a harder to solve situation, where
|
||||
`git submodule update --init --recursive` itself fails with errors such as:
|
||||
|
||||
```
|
||||
Unable to checkout '35ef5c554d888bef217d449346067de05e269b30' in submodule path 'third_party/brunsli/third_party/brotli'
|
||||
Unable to checkout '35ef5c554d888bef217d449346067de05e269b30' in submodule path 'third_party/brotli'
|
||||
```
|
||||
|
||||
In that case, you can use the force flag:
|
||||
|
||||
```
|
||||
git submodule update --init --recursive --force
|
||||
```
|
||||
|
||||
### Iterating changes in your merge request
|
||||
|
||||
To address reviewer changes you need to amend the local changes in your branch
|
||||
first. Make the changes you need in your commit locally by running `git commit
|
||||
--amend file1 file2 file3 ...` or `git commit --amend -a` to amend all the
|
||||
changes from all the staged files.
|
||||
|
||||
Once you have the new version of the "mybranch" branch to re-upload, you need to
|
||||
force push it to the same branch in your fork. Since you are pushing a different
|
||||
version of the same commit (as opposed to another commit on top of the existing
|
||||
ones), you need to force the operation to replace the old version.
|
||||
|
||||
```bash
|
||||
git push origin mybranch --force
|
||||
```
|
||||
|
||||
The merge request should now be updated with the new changes.
|
||||
|
||||
### Merging your changes
|
||||
|
||||
We use "rebase" as a merge policy, which means that there a no "merge" commits
|
||||
(commits with more than one parent) but instead only a linear history of
|
||||
changes. If other commits landed in the master branch since you last synced you
|
||||
need to `git fetch`, `git rebase` and push again your changes which need to go
|
||||
through the pipeline again to verify that all the tests pass again after
|
||||
including the latests changes. Instead of doing this manually, you can _Assign_
|
||||
the patch to the user @jpegxl-bot which will do this for you. In order for the
|
||||
bot to manage your Merge Request, you need to add it to your fork as a
|
||||
Developer.
|
||||
|
||||
### Trying locally a pending merge request.
|
||||
|
||||
If you want to review in your computer a pending merge request proposed by
|
||||
another user you can fetch the merge request commit with the following command,
|
||||
replacing `NNNN` with the merge request number:
|
||||
|
||||
```bash
|
||||
git fetch origin refs/merge-requests/NNNN/head
|
||||
git checkout FETCH_HEAD
|
||||
```
|
||||
|
||||
The first command will add to your local git repository the remote commit for
|
||||
the pending merge request and store a temporary reference called `FETCH_HEAD`.
|
||||
The second command then checks out that reference. From this point you can
|
||||
review the files in your computer, create a local branch for this FETCH_HEAD or
|
||||
build on top of it.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Guidelines for JPEG-XL software
|
||||
|
||||
The following rules govern experimental software contributed for JPEG XL. Merge
|
||||
requests not complying with these rules will not be accepted by the software
|
||||
coordinators.
|
||||
|
||||
## License
|
||||
|
||||
The contribution shall be licensed under the Apache 2 license, with each file
|
||||
beginning with the following header:
|
||||
|
||||
```
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
```
|
||||
|
||||
## Language
|
||||
|
||||
The software shall be compilable as C++ without custom preprocessors, and be
|
||||
sent in the preferred representation for reading/development (without
|
||||
obfuscation).
|
||||
|
||||
In general, please follow
|
||||
[guidelines on features and style](https://google.github.io/styleguide/cppguide.html)
|
||||
for newly added code.
|
||||
|
||||
In particular:
|
||||
|
||||
* The code shall compile under clang++ (version 6) without any warnings.
|
||||
* Exceptions and RTTI shall not be used.
|
||||
* Features from C++11 may be used if desired, but not C++14 or later.
|
||||
* Integer types shall be fixed-size types from stdint.h (e.g. uint32_t), or size_t, or [u]intptr_t.
|
||||
* NULL shall not be used (use nullptr instead).
|
||||
* The code shall be formatted by running clang-format.
|
||||
* Macro names shall be UPPERCASE_WITH_UNDERSCORES.
|
||||
* External dependencies should be minimized; large dependencies such as Qt or Boost are not allowed.
|
||||
* Headers shall be surrounded by include guards of the form
|
||||
|
||||
```
|
||||
#ifndef FILENAME_H_
|
||||
#define FILENAME_H_
|
||||
#endif // FILENAME_H_
|
||||
|
||||
```
|
||||
|
||||
## Acceptance test
|
||||
|
||||
Before merging, the pipeline shall pass.
|
||||
|
||||
Note that tests can also be run locally from the cmake build/ directory:
|
||||
`ctest -j50 --output-on-failure` (or some other number of parallel tasks)
|
||||
|
||||
The code shall not involve race conditions nor accesses to undefined memory.
|
||||
In particular, before merging results of a core experiment, tests shall be run
|
||||
in three configurations: compiling with -fsanitize=address, -fsanitize=memory
|
||||
and -fsanitize=thread.
|
||||
@@ -0,0 +1,6 @@
|
||||
#### Table M.8 – "Adobe" marker template
|
||||
|
||||
```
|
||||
0xEE, 0x00, 0x0E, 0x41, 0x64, 0x6F, 0x62, 0x65, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x01
|
||||
```
|
||||
|
||||
Binary file not shown.
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) the JPEG XL Project
|
||||
#
|
||||
# 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.
|
||||
|
||||
cat dct_gen.md \
|
||||
is_zero_base.md num_nonzeros_base.md brn_proto.md app0.md icc.md ducky.md \
|
||||
adobe.md stock_counts.md stock_values.md symbol_order.md stock_quant.md \
|
||||
quant.md freq_context.md num_nonzero_context.md nonzero_buckets.md \
|
||||
> all_tables.md
|
||||
@@ -0,0 +1,6 @@
|
||||
#### Table M.4 – APP0 template
|
||||
|
||||
```
|
||||
0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00
|
||||
```
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#### Table M.3 – Protocol Buffer descriptor of top-level structure of losslessly compressed JPEG stream
|
||||
|
||||
```protobuf
|
||||
message Header {
|
||||
optional uint64 width = 1;
|
||||
optional uint64 height = 2;
|
||||
required uint64 version_and_component_count_code = 3;
|
||||
optional uint64 subsampling_code = 4;
|
||||
}
|
||||
|
||||
message Jpeg {
|
||||
required bytes signature = 1;
|
||||
required Header header = 2;
|
||||
optional bytes meta_data = 3;
|
||||
optional bytes jpeg1_internals = 4;
|
||||
optional bytes quant_data = 5;
|
||||
optional bytes histogram_data = 6;
|
||||
optional bytes dc_data = 7;
|
||||
optional bytes ac_data = 8;
|
||||
optional bytes original_jpg = 9;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
#### Electronic Insert I.1 – DCT-II / DCT-III code generator
|
||||
|
||||
```python
|
||||
#######################################################################
|
||||
# DCT-II / DCT-III generator
|
||||
#
|
||||
# Based on:
|
||||
# "A low multiplicative complexity fast recursive DCT-2 algorithm"
|
||||
# by Maxim Vashkevich and Alexander Petrovsky / arXiv / 20 Jul 2012
|
||||
#######################################################################
|
||||
|
||||
import math
|
||||
import sys
|
||||
N = 8
|
||||
|
||||
#######################################################################
|
||||
# Base transforms / generators
|
||||
#######################################################################
|
||||
|
||||
CNTR = 0
|
||||
def makeTmp():
|
||||
global CNTR
|
||||
result = "t{:02d}".format(CNTR)
|
||||
CNTR = CNTR + 1
|
||||
return result
|
||||
|
||||
def makeVar(i):
|
||||
return "i{:02d}".format(i)
|
||||
|
||||
def add(x, y):
|
||||
tmp = makeTmp()
|
||||
print(tmp + " = " + x + " + " + y + ";")
|
||||
return tmp
|
||||
|
||||
def sub(x, y):
|
||||
tmp = makeTmp()
|
||||
print(tmp + " = " + x + " - " + y + ";")
|
||||
return tmp
|
||||
|
||||
def mul(x, c):
|
||||
tmp = makeTmp()
|
||||
print(tmp + " = " + x + " * " + c + ";")
|
||||
return tmp
|
||||
|
||||
# 2.0 * math.cos((a + 0.0) / (b + 0.0) * math.pi)
|
||||
def C2(a, b):
|
||||
return "c_c2_" + str(a) + "_" + str(b)
|
||||
|
||||
# 1.0 / C2(a, b)
|
||||
def iC2(a, b):
|
||||
return "c_ic2_" + str(a) + "_" + str(b)
|
||||
|
||||
#######################################################################
|
||||
# Utilities
|
||||
#######################################################################
|
||||
|
||||
# Generate identity matrix. Usually this matrix is passed to
|
||||
# DCT algorithm to generate "basis" vectors of the transform.
|
||||
def makeVars():
|
||||
return [makeVar(i) for i in range(N)]
|
||||
|
||||
# Split list of variables info halves.
|
||||
def split(x):
|
||||
m = len(x)
|
||||
m2 = m // 2
|
||||
return (x[0 : m2], x[m2 : m])
|
||||
|
||||
# Make a list of variables in a reverse order.
|
||||
def reverse(varz):
|
||||
m = len(varz)
|
||||
result = [0] * m
|
||||
for i in range(m):
|
||||
result[i] = varz[m - 1 - i]
|
||||
return result
|
||||
|
||||
# Apply permutation
|
||||
def permute(x, p):
|
||||
return [x[p[i]] for i in range(len(p))]
|
||||
|
||||
def transposePermutation(p):
|
||||
n = len(p)
|
||||
result = [0] * n
|
||||
for i in range(n):
|
||||
result[p[i]] = i
|
||||
return result
|
||||
|
||||
# See paper. Split even-odd elements.
|
||||
def P(n):
|
||||
if n == 1:
|
||||
return [0]
|
||||
n2 = n // 2
|
||||
return [2 * i for i in range(n2)] + [2 * i + 1 for i in range(n2)]
|
||||
|
||||
# See paper. Interleave first and second half.
|
||||
def Pt(n):
|
||||
return transposePermutation(P(n))
|
||||
|
||||
#######################################################################
|
||||
# Scheme
|
||||
#######################################################################
|
||||
|
||||
def B2(x):
|
||||
n = len(x)
|
||||
n2 = n // 2
|
||||
if n == 1:
|
||||
raise "ooops"
|
||||
(top, bottom) = split(x)
|
||||
bottom = reverse(bottom)
|
||||
t = [add(top[i], bottom[i]) for i in range(n2)]
|
||||
b = [sub(top[i], bottom[i]) for i in range(n2)]
|
||||
return t + b
|
||||
|
||||
def iB2(x):
|
||||
n = len(x)
|
||||
n2 = n // 2
|
||||
if n == 1:
|
||||
raise "ooops"
|
||||
(top, bottom) = split(x)
|
||||
t = [add(top[i], bottom[i]) for i in range(n2)]
|
||||
b = [sub(top[i], bottom[i]) for i in range(n2)]
|
||||
return t + reverse(b)
|
||||
|
||||
def B4(x, rn):
|
||||
n = len(x)
|
||||
n2 = n // 2
|
||||
if n == 1:
|
||||
raise "ooops"
|
||||
(top, bottom) = split(x)
|
||||
rbottom = reverse(bottom)
|
||||
t = [sub(top[i], rbottom[i]) for i in range(n2)]
|
||||
b = [mul(bottom[i], C2(rn, 2 * N)) for i in range(n2)]
|
||||
top = [add(t[i], b[i]) for i in range(n2)]
|
||||
bottom = [sub(t[i], b[i]) for i in range(n2)]
|
||||
return top + bottom
|
||||
|
||||
def iB4(x, rn):
|
||||
n = len(x)
|
||||
n2 = n // 2
|
||||
if n == 1:
|
||||
raise "ooops"
|
||||
(top, bottom) = split(x)
|
||||
t = [add(top[i], bottom[i]) for i in range(n2)]
|
||||
b = [sub(top[i], bottom[i]) for i in range(n2)]
|
||||
bottom = [mul(b[i], iC2(rn, 2 * N)) for i in range(n2)]
|
||||
rbottom = reverse(bottom)
|
||||
top = [add(t[i], rbottom[i]) for i in range(n2)]
|
||||
return top + bottom
|
||||
|
||||
def P4(n):
|
||||
if n == 1:
|
||||
return [0]
|
||||
if n == 2:
|
||||
return [0, 1]
|
||||
n2 = n // 2
|
||||
result = [0] * n
|
||||
tc = 0
|
||||
bc = 0
|
||||
i = 0
|
||||
result[i] = tc; tc = tc + 1; i = i + 1
|
||||
turn = True
|
||||
while i < n - 1:
|
||||
if turn:
|
||||
result[i] = n2 + bc; bc = bc + 1; i = i + 1
|
||||
result[i] = n2 + bc; bc = bc + 1; i = i + 1
|
||||
else:
|
||||
result[i] = tc; tc = tc + 1; i = i + 1
|
||||
result[i] = tc; tc = tc + 1; i = i + 1
|
||||
turn = not turn
|
||||
result[i] = tc; tc = tc + 1; i = i + 1
|
||||
return result
|
||||
|
||||
def iP4(n):
|
||||
return transposePermutation(P4(n))
|
||||
|
||||
def d2n(x):
|
||||
n = len(x)
|
||||
if n == 1:
|
||||
return x
|
||||
y = B2(x)
|
||||
(top, bottom) = split(y)
|
||||
return permute(d2n(top) + d4n(bottom, N // 2), Pt(n))
|
||||
|
||||
def id2n(x):
|
||||
n = len(x)
|
||||
if n == 1:
|
||||
return x
|
||||
(top, bottom) = split(permute(x, P(n)))
|
||||
return iB2(id2n(top) + id4n(bottom, N // 2))
|
||||
|
||||
def d4n(x, rn):
|
||||
n = len(x)
|
||||
if n == 1:
|
||||
return x
|
||||
y = B4(x, rn)
|
||||
(top, bottom) = split(y)
|
||||
rn2 = rn // 2
|
||||
return permute(d4n(top, rn2) + d4n(bottom, N - rn2), P4(n))
|
||||
|
||||
def id4n(x, rn):
|
||||
n = len(x)
|
||||
if n == 1:
|
||||
return x
|
||||
(top, bottom) = split(permute(x, iP4(n)))
|
||||
rn2 = rn // 2
|
||||
y = id4n(top, rn2) + id4n(bottom, N -rn2)
|
||||
return iB4(y, rn)
|
||||
|
||||
#######################################################################
|
||||
# Main.
|
||||
#######################################################################
|
||||
|
||||
def help():
|
||||
print("Usage: %s [N [T]]" % sys.argv[0])
|
||||
print(" N should be the power of 2, default is 8")
|
||||
print(" T is one of {2, 3}, default is 2")
|
||||
sys.exit()
|
||||
|
||||
def parseInt(s):
|
||||
try:
|
||||
return int(s)
|
||||
except ValueError:
|
||||
help()
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 1 or len(sys.argv) > 3: help()
|
||||
if len(sys.argv) >= 2:
|
||||
N = parseInt(sys.argv[1])
|
||||
if (N & (N - 1)) != 0: help()
|
||||
type = 0
|
||||
if len(sys.argv) >= 3:
|
||||
typeOption = sys.argv[2]
|
||||
if len(typeOption) != 1: help()
|
||||
type = "23".index(typeOption)
|
||||
if type == -1: help()
|
||||
if type == 0:
|
||||
vars = d2n(makeVars())
|
||||
else: # type == 1
|
||||
vars = id2n(makeVars())
|
||||
print("Output vector: " + str(vars))
|
||||
```
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#### Table M.7 – "Ducky" marker template
|
||||
|
||||
```
|
||||
0xEC, 0x00, 0x11, 0x44, 0x75, 0x63, 0x6B, 0x79, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00
|
||||
```
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#### Table M.15 – freq_context
|
||||
|
||||
`scheme == 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, 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
|
||||
```
|
||||
|
||||
`scheme == 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,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0
|
||||
```
|
||||
|
||||
`scheme == 2`:
|
||||
```
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1
|
||||
```
|
||||
|
||||
`scheme == 3`:
|
||||
```
|
||||
0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7,
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 2, 2, 2
|
||||
```
|
||||
|
||||
`scheme == 4`:
|
||||
```
|
||||
0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9,
|
||||
9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13,
|
||||
13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15,
|
||||
15, 15, 15, 15, 15, 15, 15, 15, 15, 15
|
||||
```
|
||||
|
||||
`scheme == 5`:
|
||||
```
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 16,
|
||||
17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 24, 24,
|
||||
25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29,
|
||||
29, 29, 30, 30, 30, 30, 31, 31, 31, 31
|
||||
```
|
||||
|
||||
`scheme == 6`:
|
||||
```
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
|
||||
18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
|
||||
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
|
||||
54, 55, 56, 57, 58, 59, 60, 61, 62, 63
|
||||
```
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#### Table M.6 – common ICC profile template
|
||||
|
||||
```
|
||||
0xE2, 0x0C, 0x58, 0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00, 0x01, 0x01, 0x00, 0x00, 0x0C, 0x48, 0x4C, 0x69, 0x6E, 0x6F, 0x02, 0x10, 0x00, 0x00, 0x6D, 0x6E, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5A, 0x20, 0x07, 0xCE, 0x00, 0x02, 0x00, 0x09, 0x00, 0x06, 0x00, 0x31, 0x00, 0x00, 0x61, 0x63, 0x73, 0x70, 0x4D, 0x53, 0x46, 0x54, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x43, 0x20, 0x73, 0x52, 0x47, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0xF6, 0xD6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xD3, 0x2D, 0x48, 0x50, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x33, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x01, 0x84, 0x00, 0x00, 0x00, 0x6C, 0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00, 0x00, 0x14, 0x62, 0x6B, 0x70, 0x74, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x14, 0x72, 0x58, 0x59, 0x5A, 0x00, 0x00, 0x02, 0x18, 0x00, 0x00, 0x00, 0x14, 0x67, 0x58, 0x59, 0x5A, 0x00, 0x00, 0x02, 0x2C, 0x00, 0x00, 0x00, 0x14, 0x62, 0x58, 0x59, 0x5A, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x14, 0x64, 0x6D, 0x6E, 0x64, 0x00, 0x00, 0x02, 0x54, 0x00, 0x00, 0x00, 0x70, 0x64, 0x6D, 0x64, 0x64, 0x00, 0x00, 0x02, 0xC4, 0x00, 0x00, 0x00, 0x88, 0x76, 0x75, 0x65, 0x64, 0x00, 0x00, 0x03, 0x4C, 0x00, 0x00, 0x00, 0x86, 0x76, 0x69, 0x65, 0x77, 0x00, 0x00, 0x03, 0xD4, 0x00, 0x00, 0x00, 0x24, 0x6C, 0x75, 0x6D, 0x69, 0x00, 0x00, 0x03, 0xF8, 0x00, 0x00, 0x00, 0x14, 0x6D, 0x65, 0x61, 0x73, 0x00, 0x00, 0x04, 0x0C, 0x00, 0x00, 0x00, 0x24, 0x74, 0x65, 0x63, 0x68, 0x00, 0x00, 0x04, 0x30, 0x00, 0x00, 0x00, 0x0C, 0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x04, 0x3C, 0x00, 0x00, 0x08, 0x0C, 0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x04, 0x3C, 0x00, 0x00, 0x08, 0x0C, 0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x04, 0x3C, 0x00, 0x00, 0x08, 0x0C, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, 0x43, 0x6F, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x38, 0x20, 0x48, 0x65, 0x77, 0x6C, 0x65, 0x74, 0x74, 0x2D, 0x50, 0x61, 0x63, 0x6B, 0x61, 0x72, 0x64, 0x20, 0x43, 0x6F, 0x6D, 0x70, 0x61, 0x6E, 0x79, 0x00, 0x00, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x73, 0x52, 0x47, 0x42, 0x20, 0x49, 0x45, 0x43, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2D, 0x32, 0x2E, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x73, 0x52, 0x47, 0x42, 0x20, 0x49, 0x45, 0x43, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2D, 0x32, 0x2E, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x59, 0x5A, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF3, 0x51, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x16, 0xCC, 0x58, 0x59, 0x5A, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x59, 0x5A, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6F, 0xA2, 0x00, 0x00, 0x38, 0xF5, 0x00, 0x00, 0x03, 0x90, 0x58, 0x59, 0x5A, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x99, 0x00, 0x00, 0xB7, 0x85, 0x00, 0x00, 0x18, 0xDA, 0x58, 0x59, 0x5A, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0xA0, 0x00, 0x00, 0x0F, 0x84, 0x00, 0x00, 0xB6, 0xCF, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x49, 0x45, 0x43, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x77, 0x77, 0x77, 0x2E, 0x69, 0x65, 0x63, 0x2E, 0x63, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x49, 0x45, 0x43, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x77, 0x77, 0x77, 0x2E, 0x69, 0x65, 0x63, 0x2E, 0x63, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x49, 0x45, 0x43, 0x20, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2D, 0x32, 0x2E, 0x31, 0x20, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74, 0x20, 0x52, 0x47, 0x42, 0x20, 0x63, 0x6F, 0x6C, 0x6F, 0x75, 0x72, 0x20, 0x73, 0x70, 0x61, 0x63, 0x65, 0x20, 0x2D, 0x20, 0x73, 0x52, 0x47, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x49, 0x45, 0x43, 0x20, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2D, 0x32, 0x2E, 0x31, 0x20, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74, 0x20, 0x52, 0x47, 0x42, 0x20, 0x63, 0x6F, 0x6C, 0x6F, 0x75, 0x72, 0x20, 0x73, 0x70, 0x61, 0x63, 0x65, 0x20, 0Line truncated
|
||||
```
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#### Table M.1 – is_zero_base table
|
||||
|
||||
```
|
||||
228, 216, 216, 195, 192, 189, 182, 184, 179, 176, 171, 168, 166, 159,
|
||||
156, 151, 151, 150, 150, 146, 144, 138, 138, 137, 135, 131, 127, 126,
|
||||
124, 123, 124, 123, 122, 121, 118, 117, 114, 115, 116, 116, 115, 115,
|
||||
114, 111, 111, 111, 112, 111, 110, 110, 110, 111, 111, 114, 110, 111,
|
||||
112, 113, 116, 120, 126, 131, 147, 160
|
||||
```
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
settings.json:
|
||||
"markdown-pdf.styles": ["markdown-pdf.css",],
|
||||
"markdown-pdf.format": "Letter",
|
||||
"markdown-pdf.margin.top": "1in",
|
||||
"markdown-pdf.margin.bottom": "1in",
|
||||
"markdown-pdf.margin.left": "1in",
|
||||
"markdown-pdf.margin.right": "1in",
|
||||
"markdown-pdf.stylesRelativePathFile" : true,
|
||||
"markdown-pdf.displayHeaderFooter": false,
|
||||
*/
|
||||
|
||||
body {
|
||||
font-family: "Times";
|
||||
font-size: 10pt;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-family: "Times New Roman";
|
||||
font-size: 10pt;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: Consolas, "Source Code Pro";
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
pre.hljs code > div {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
:not(pre):not(.hljs) > code {
|
||||
color: #4d4d4c;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#### Table M.17 – nonzero_buckets
|
||||
|
||||
```
|
||||
0, 1, 2, 3, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
|
||||
7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9,
|
||||
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10,
|
||||
10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10
|
||||
```
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
#### Table M.16 – num_nonzero_context
|
||||
|
||||
`scheme == 0`:
|
||||
```
|
||||
0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7,
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
|
||||
```
|
||||
|
||||
`scheme == 1`:
|
||||
```
|
||||
0, 2, 2, 4, 4, 4, 6, 6, 6, 6, 8, 8, 8, 8, 8, 8, 10, 10,
|
||||
10, 10, 10, 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
|
||||
12, 12, 12, 12, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
|
||||
14, 14, 14, 14, 14, 14, 14, 14, 14, 14
|
||||
```
|
||||
|
||||
`scheme == 2`:
|
||||
```
|
||||
0, 4, 4, 8, 8, 8, 12, 12, 12, 12, 16, 16, 16, 16, 16, 16, 20, 20,
|
||||
20, 20, 20, 20, 20, 20, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
|
||||
24, 24, 24, 24, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
|
||||
28, 28, 28, 28, 28, 28, 28, 28, 28, 28
|
||||
```
|
||||
|
||||
`scheme == 3`:
|
||||
```
|
||||
0, 8, 8, 16, 16, 16, 24, 24, 24, 24, 32, 32, 32, 32, 32, 32, 40, 40,
|
||||
40, 40, 40, 40, 40, 40, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
|
||||
48, 48, 48, 48, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55,
|
||||
55, 55, 55, 55, 55, 55, 55, 55, 55, 55
|
||||
```
|
||||
|
||||
`scheme == 4`:
|
||||
```
|
||||
0, 16, 16, 32, 32, 32, 48, 48, 48, 48, 64, 64, 64, 64,
|
||||
64, 64, 80, 80, 80, 80, 80, 80, 80, 80, 95, 95, 95, 95,
|
||||
95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 109, 109,
|
||||
109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109,
|
||||
109, 109, 109, 109, 109, 109, 109, 109
|
||||
```
|
||||
|
||||
`scheme == 5`:
|
||||
```
|
||||
0, 32, 32, 64, 64, 64, 96, 96, 96, 96, 127, 127, 127, 127,
|
||||
127, 127, 157, 157, 157, 157, 157, 157, 157, 157, 185, 185, 185, 185,
|
||||
185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 211, 211,
|
||||
211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211,
|
||||
211, 211, 211, 211, 211, 211, 211, 211
|
||||
```
|
||||
|
||||
`scheme == 6`:
|
||||
```
|
||||
0, 64, 64, 127, 127, 127, 188, 188, 188, 188, 246, 246, 246, 246,
|
||||
246, 246, 300, 300, 300, 300, 300, 300, 300, 300, 348, 348, 348, 348,
|
||||
348, 348, 348, 348, 348, 348, 348, 348, 348, 348, 348, 348, 388, 388,
|
||||
388, 388, 388, 388, 388, 388, 388, 388, 388, 388, 388, 388, 388, 388,
|
||||
388, 388, 388, 388, 388, 388, 388, 388
|
||||
```
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
#### Table M.2 – num_nonzeros_base table
|
||||
|
||||
```
|
||||
251, 252, 117, 249, 161, 136, 83, 238, 184, 126, 137, 129, 140, 119,
|
||||
70, 213, 160, 175, 174, 130, 166, 134, 122, 125, 131, 144, 136, 133,
|
||||
139, 123, 79, 216, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
254, 252, 174, 232, 189, 155, 122, 177, 204, 173, 146, 149, 141, 133,
|
||||
103, 109, 167, 187, 168, 142, 154, 147, 125, 139, 144, 138, 138, 153,
|
||||
141, 133, 90, 121, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
251, 240, 197, 176, 184, 177, 114, 89, 194, 165, 153, 161, 158, 136,
|
||||
92, 95, 123, 171, 160, 140, 148, 136, 129, 139, 145, 136, 143, 134,
|
||||
138, 124, 92, 154, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
247, 220, 201, 110, 194, 176, 147, 59, 175, 171, 156, 157, 152, 146,
|
||||
115, 114, 88, 151, 164, 141, 153, 135, 141, 131, 146, 139, 140, 145,
|
||||
138, 137, 112, 184, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
238, 179, 203, 63, 194, 173, 149, 71, 139, 169, 154, 159, 150, 146,
|
||||
117, 143, 78, 122, 152, 137, 149, 138, 138, 133, 134, 142, 142, 142,
|
||||
148, 128, 118, 199, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
227, 127, 200, 44, 192, 170, 148, 100, 102, 161, 156, 153, 148, 149,
|
||||
124, 160, 88, 101, 134, 132, 149, 145, 134, 134, 136, 141, 138, 142,
|
||||
144, 137, 116, 208, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
214, 86, 195, 44, 187, 163, 148, 126, 81, 147, 156, 152, 150, 144,
|
||||
121, 172, 96, 95, 117, 122, 145, 152, 136, 133, 135, 135, 131, 142,
|
||||
141, 135, 114, 217, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
198, 56, 191, 54, 171, 162, 147, 144, 74, 128, 152, 149, 150, 142,
|
||||
119, 177, 101, 100, 106, 111, 135, 154, 136, 137, 136, 132, 133, 142,
|
||||
144, 130, 117, 222, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
176, 40, 189, 73, 147, 159, 148, 152, 79, 106, 147, 149, 151, 139,
|
||||
123, 188, 108, 110, 106, 97, 125, 151, 137, 138, 135, 135, 134, 136,
|
||||
140, 131, 116, 221, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
148, 33, 185, 88, 117, 158, 145, 163, 95, 91, 137, 146, 150, 140,
|
||||
120, 197, 115, 116, 114, 92, 114, 144, 130, 133, 132, 133, 129, 140,
|
||||
138, 130, 111, 224, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
117, 31, 180, 104, 93, 150, 143, 166, 99, 85, 124, 139, 148, 142,
|
||||
118, 201, 105, 120, 120, 90, 107, 135, 127, 130, 131, 131, 132, 140,
|
||||
142, 133, 114, 229, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
87, 35, 170, 110, 78, 141, 144, 176, 106, 90, 112, 132, 143, 138,
|
||||
119, 204, 111, 121, 125, 90, 105, 131, 124, 122, 129, 128, 129, 137,
|
||||
138, 133, 114, 227, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
63, 42, 159, 123, 73, 127, 142, 191, 105, 91, 105, 123, 139, 137,
|
||||
120, 209, 117, 110, 122, 98, 110, 125, 115, 123, 122, 126, 128, 134,
|
||||
141, 129, 113, 229, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
45, 53, 146, 135, 71, 114, 138, 193, 100, 98, 98, 113, 133, 135,
|
||||
118, 222, 113, 111, 139, 103, 107, 126, 111, 119, 121, 122, 127, 135,
|
||||
141, 128, 114, 242, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
33, 60, 132, 138, 75, 100, 134, 203, 112, 99, 98, 105, 126, 131,
|
||||
115, 229, 107, 93, 121, 106, 108, 122, 106, 109, 114, 116, 127, 133,
|
||||
143, 128, 110, 242, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
24, 70, 118, 134, 76, 87, 130, 201, 110, 96, 99, 97, 119, 130,
|
||||
111, 229, 97, 104, 125, 102, 112, 125, 101, 109, 113, 114, 125, 129,
|
||||
142, 127, 112, 241, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
17, 65, 100, 121, 80, 75, 124, 174, 117, 100, 94, 93, 114, 128,
|
||||
110, 216, 103, 94, 113, 122, 118, 126, 113, 108, 105, 108, 122, 128,
|
||||
141, 125, 113, 238, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
12, 70, 82, 132, 78, 65, 118, 155, 136, 103, 97, 89, 106, 124,
|
||||
111, 215, 115, 123, 129, 99, 104, 127, 110, 108, 101, 109, 118, 126,
|
||||
136, 123, 110, 233, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
8, 66, 61, 117, 91, 59, 108, 195, 101, 112, 99, 99, 99, 116,
|
||||
106, 230, 127, 99, 144, 101, 118, 137, 117, 111, 106, 104, 116, 121,
|
||||
134, 122, 110, 223, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
6, 78, 42, 146, 101, 54, 94, 201, 116, 102, 110, 94, 92, 108,
|
||||
103, 214, 108, 111, 127, 102, 121, 132, 120, 121, 95, 98, 110, 121,
|
||||
129, 117, 107, 235, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
5, 93, 29, 145, 102, 52, 77, 216, 108, 115, 108, 102, 89, 97,
|
||||
94, 229, 89, 103, 139, 120, 103, 151, 102, 100, 97, 96, 99, 111,
|
||||
125, 116, 104, 242, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
4, 105, 21, 145, 100, 54, 64, 217, 100, 122, 128, 87, 88, 91,
|
||||
87, 230, 112, 80, 148, 95, 146, 123, 96, 140, 90, 91, 98, 106,
|
||||
122, 111, 100, 249, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
4, 130, 14, 142, 104, 56, 51, 208, 116, 135, 100, 89, 82, 84,
|
||||
75, 239, 85, 85, 122, 125, 94, 144, 151, 136, 92, 97, 104, 109,
|
||||
113, 110, 91, 246, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
3, 126, 9, 172, 105, 57, 39, 219, 95, 120, 118, 96, 93, 75,
|
||||
66, 241, 102, 134, 96, 156, 146, 162, 130, 112, 82, 89, 97, 101,
|
||||
116, 103, 82, 254, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
3, 149, 7, 182, 122, 54, 29, 224, 103, 100, 113, 96, 90, 74,
|
||||
55, 250, 127, 94, 118, 93, 135, 160, 113, 130, 95, 117, 106, 96,
|
||||
111, 97, 77, 242, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
3, 150, 4, 170, 138, 59, 20, 229, 91, 150, 107, 98, 92, 68,
|
||||
48, 245, 113, 64, 114, 111, 134, 127, 102, 104, 85, 118, 103, 107,
|
||||
102, 91, 72, 245, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
3, 171, 3, 165, 137, 62, 14, 211, 96, 127, 132, 121, 95, 62,
|
||||
37, 248, 102, 57, 144, 85, 127, 191, 102, 97, 127, 104, 91, 102,
|
||||
107, 81, 64, 254, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
2, 166, 2, 196, 122, 65, 10, 243, 102, 93, 117, 92, 96, 63,
|
||||
29, 251, 169, 159, 149, 96, 91, 139, 157, 40, 100, 89, 120, 92,
|
||||
109, 79, 58, 247, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
2, 176, 2, 189, 118, 48, 7, 219, 68, 43, 109, 96, 129, 75,
|
||||
19, 254, 2, 3, 185, 6, 102, 127, 127, 127, 1, 131, 83, 99,
|
||||
107, 80, 45, 254, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
1, 205, 2, 208, 64, 89, 4, 223, 29, 169, 29, 123, 118, 76,
|
||||
11, 240, 202, 243, 65, 6, 12, 243, 96, 55, 102, 102, 114, 102,
|
||||
107, 74, 31, 247, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
1, 216, 1, 214, 127, 94, 2, 234, 145, 3, 127, 106, 155, 80,
|
||||
4, 247, 4, 65, 86, 127, 127, 127, 127, 102, 127, 143, 143, 108,
|
||||
113, 80, 16, 216, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
```
|
||||
2, 199, 1, 222, 93, 94, 1, 232, 2, 65, 74, 139, 201, 48,
|
||||
2, 254, 169, 127, 52, 243, 251, 249, 102, 86, 202, 153, 65, 65,
|
||||
146, 69, 8, 238, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 128
|
||||
```
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#### Table M.13 – template quant tables
|
||||
|
||||
`is_luma == true`:
|
||||
```
|
||||
16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60,
|
||||
55, 14, 13, 16, 24, 40, 57, 69, 56, 14, 17, 22, 29, 51, 87,
|
||||
80, 62, 18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55, 64, 81,
|
||||
104, 113, 92, 49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98,
|
||||
112, 100, 103, 99
|
||||
```
|
||||
|
||||
`is_luma == false`:
|
||||
```
|
||||
17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99, 24, 26,
|
||||
56, 99, 99, 99, 99, 99, 47, 66, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 99, 99, 99, 99, 99, 99
|
||||
```
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
#### Table M.9 – stock counts arrays
|
||||
|
||||
`is_ac == 0`, `stock_index == 0`:
|
||||
```
|
||||
0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0
|
||||
```
|
||||
|
||||
`is_ac == 0`, `stock_index == 1`:
|
||||
```
|
||||
0, 0, 1, 5, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0
|
||||
```
|
||||
|
||||
`is_ac == 1`, `stock_index == 0`:
|
||||
```
|
||||
0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 126
|
||||
```
|
||||
|
||||
`is_ac == 1`, `stock_index == 1`:
|
||||
```
|
||||
0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 120
|
||||
```
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#### Table M.12 – stock quant tables
|
||||
|
||||
`is_luma == true`, `stock_index == 0`:
|
||||
```
|
||||
3, 2, 2, 3, 5, 8, 10, 12, 2, 2, 3, 4, 5, 12, 12, 11, 3, 3,
|
||||
3, 5, 8, 11, 14, 11, 3, 3, 4, 6, 10, 17, 16, 12, 4, 4, 7, 11,
|
||||
14, 22, 21, 15, 5, 7, 11, 13, 16, 21, 23, 18, 10, 13, 16, 17, 21, 24,
|
||||
24, 20, 14, 18, 19, 20, 22, 20, 21, 20
|
||||
```
|
||||
|
||||
`is_luma == true`, `stock_index == 1`:
|
||||
```
|
||||
8, 6, 5, 8, 12, 20, 26, 31, 6, 6, 7, 10, 13, 29, 30, 28, 7, 7,
|
||||
8, 12, 20, 29, 35, 28, 7, 9, 11, 15, 26, 44, 40, 31, 9, 11, 19, 28,
|
||||
34, 55, 52, 39, 12, 18, 28, 32, 41, 52, 57, 46, 25, 32, 39, 44, 52, 61,
|
||||
60, 51, 36, 46, 48, 49, 56, 50, 52, 50
|
||||
```
|
||||
|
||||
`is_luma == true`, `stock_index == 2`:
|
||||
```
|
||||
6, 4, 4, 6, 10, 16, 20, 24, 5, 5, 6, 8, 10, 23, 24, 22, 6, 5,
|
||||
6, 10, 16, 23, 28, 22, 6, 7, 9, 12, 20, 35, 32, 25, 7, 9, 15, 22,
|
||||
27, 44, 41, 31, 10, 14, 22, 26, 32, 42, 45, 37, 20, 26, 31, 35, 41, 48,
|
||||
48, 40, 29, 37, 38, 39, 45, 40, 41, 40
|
||||
```
|
||||
|
||||
`is_luma == true`, `stock_index == 3`:
|
||||
```
|
||||
5, 3, 3, 5, 7, 12, 15, 18, 4, 4, 4, 6, 8, 17, 18, 17, 4, 4,
|
||||
5, 7, 12, 17, 21, 17, 4, 5, 7, 9, 15, 26, 24, 19, 5, 7, 11, 17,
|
||||
20, 33, 31, 23, 7, 11, 17, 19, 24, 31, 34, 28, 15, 19, 23, 26, 31, 36,
|
||||
36, 30, 22, 28, 29, 29, 34, 30, 31, 30
|
||||
```
|
||||
|
||||
`is_luma == true`, `stock_index == 4`:
|
||||
```
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
```
|
||||
|
||||
`is_luma == true`, `stock_index == 5`:
|
||||
```
|
||||
2, 1, 1, 2, 2, 4, 5, 6, 1, 1, 1, 2, 3, 6, 6, 6, 1, 1,
|
||||
2, 2, 4, 6, 7, 6, 1, 2, 2, 3, 5, 9, 8, 6, 2, 2, 4, 6,
|
||||
7, 11, 10, 8, 2, 4, 6, 6, 8, 10, 11, 9, 5, 6, 8, 9, 10, 12,
|
||||
12, 10, 7, 9, 10, 10, 11, 10, 10, 10
|
||||
```
|
||||
|
||||
`is_luma == true`, `stock_index == 6`:
|
||||
```
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1,
|
||||
1, 2, 2, 3, 1, 1, 1, 1, 2, 2, 3, 3, 1, 1, 1, 2, 2, 3,
|
||||
3, 3, 1, 1, 2, 2, 3, 3, 3, 3
|
||||
```
|
||||
|
||||
`is_luma == true`, `stock_index == 7`:
|
||||
```
|
||||
10, 7, 6, 10, 14, 24, 31, 37, 7, 7, 8, 11, 16, 35, 36, 33, 8, 8,
|
||||
10, 14, 24, 34, 41, 34, 8, 10, 13, 17, 31, 52, 48, 37, 11, 13, 22, 34,
|
||||
41, 65, 62, 46, 14, 21, 33, 38, 49, 62, 68, 55, 29, 38, 47, 52, 62, 73,
|
||||
72, 61, 43, 55, 57, 59, 67, 60, 62, 59
|
||||
```
|
||||
|
||||
`is_luma == false`, `stock_index == 0`:
|
||||
```
|
||||
9, 9, 9, 12, 11, 12, 24, 13, 13, 24, 50, 33, 28, 33, 50, 50, 50, 50,
|
||||
50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
|
||||
50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
|
||||
50, 50, 50, 50, 50, 50, 50, 50, 50, 50
|
||||
```
|
||||
|
||||
`is_luma == false`, `stock_index == 1`:
|
||||
```
|
||||
3, 4, 5, 9, 20, 20, 20, 20, 4, 4, 5, 13, 20, 20, 20, 20, 5, 5,
|
||||
11, 20, 20, 20, 20, 20, 9, 13, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
|
||||
20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
|
||||
20, 20, 20, 20, 20, 20, 20, 20, 20, 20
|
||||
```
|
||||
|
||||
`is_luma == false`, `stock_index == 2`:
|
||||
```
|
||||
9, 9, 12, 24, 50, 50, 50, 50, 9, 11, 13, 33, 50, 50, 50, 50, 12, 13,
|
||||
28, 50, 50, 50, 50, 50, 24, 33, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
|
||||
50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
|
||||
50, 50, 50, 50, 50, 50, 50, 50, 50, 50
|
||||
```
|
||||
|
||||
`is_luma == false`, `stock_index == 3`:
|
||||
```
|
||||
5, 5, 7, 14, 30, 30, 30, 30, 5, 6, 8, 20, 30, 30, 30, 30, 7, 8,
|
||||
17, 30, 30, 30, 30, 30, 14, 20, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
|
||||
30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
|
||||
30, 30, 30, 30, 30, 30, 30, 30, 30, 30
|
||||
```
|
||||
|
||||
`is_luma == false`, `stock_index == 4`:
|
||||
```
|
||||
7, 7, 10, 19, 40, 40, 40, 40, 7, 8, 10, 26, 40, 40, 40, 40, 10, 10,
|
||||
22, 40, 40, 40, 40, 40, 19, 26, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
|
||||
40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
|
||||
40, 40, 40, 40, 40, 40, 40, 40, 40, 40
|
||||
```
|
||||
|
||||
`is_luma == false`, `stock_index == 5`:
|
||||
```
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
```
|
||||
|
||||
`is_luma == false`, `stock_index == 6`:
|
||||
```
|
||||
2, 2, 2, 5, 10, 10, 10, 10, 2, 2, 3, 7, 10, 10, 10, 10, 2, 3,
|
||||
6, 10, 10, 10, 10, 10, 5, 7, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
|
||||
10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
|
||||
10, 10, 10, 10, 10, 10, 10, 10, 10, 10
|
||||
```
|
||||
|
||||
`is_luma == false`, `stock_index == 7`:
|
||||
```
|
||||
10, 11, 14, 28, 59, 59, 59, 59, 11, 13, 16, 40, 59, 59, 59, 59, 14, 16,
|
||||
34, 59, 59, 59, 59, 59, 28, 40, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
|
||||
59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
|
||||
59, 59, 59, 59, 59, 59, 59, 59, 59, 59
|
||||
```
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
#### Table M.10 – stock values arrays
|
||||
|
||||
`is_ac == 0`, `stock_index == 0`:
|
||||
```
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 256
|
||||
```
|
||||
|
||||
`is_ac == 0`, `stock_index == 1`:
|
||||
```
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 256
|
||||
```
|
||||
|
||||
`is_ac == 1`, `stock_index == 0`:
|
||||
```
|
||||
1, 2, 3, 0, 4, 17, 5, 18, 33, 49, 65, 6, 19, 81,
|
||||
97, 7, 34, 113, 20, 50, 129, 145, 161, 8, 35, 66, 177, 193,
|
||||
21, 82, 209, 240, 36, 51, 98, 114, 130, 9, 10, 22, 23, 24,
|
||||
25, 26, 37, 38, 39, 40, 41, 42, 52, 53, 54, 55, 56, 57,
|
||||
58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87,
|
||||
88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, 116, 117,
|
||||
118, 119, 120, 121, 122, 131, 132, 133, 134, 135, 136, 137, 138, 146,
|
||||
147, 148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165, 166, 167,
|
||||
168, 169, 170, 178, 179, 180, 181, 182, 183, 184, 185, 186, 194, 195,
|
||||
196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214, 215, 216,
|
||||
217, 218, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 241, 242,
|
||||
243, 244, 245, 246, 247, 248, 249, 250, 256
|
||||
```
|
||||
|
||||
`is_ac == 1`, `stock_index == 1`:
|
||||
```
|
||||
0, 1, 2, 3, 17, 4, 5, 33, 49, 6, 18, 65, 81, 7,
|
||||
97, 113, 19, 34, 50, 129, 8, 20, 66, 145, 161, 177, 193, 9,
|
||||
35, 51, 82, 240, 21, 98, 114, 209, 10, 22, 36, 52, 225, 37,
|
||||
241, 23, 24, 25, 26, 38, 39, 40, 41, 42, 53, 54, 55, 56,
|
||||
57, 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86,
|
||||
87, 88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, 116,
|
||||
117, 118, 119, 120, 121, 122, 130, 131, 132, 133, 134, 135, 136, 137,
|
||||
138, 146, 147, 148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165,
|
||||
166, 167, 168, 169, 170, 178, 179, 180, 181, 182, 183, 184, 185, 186,
|
||||
194, 195, 196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214,
|
||||
215, 216, 217, 218, 226, 227, 228, 229, 230, 231, 232, 233, 234, 242,
|
||||
243, 244, 245, 246, 247, 248, 249, 250, 256
|
||||
```
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#### Table M.11 – predefined symbol order
|
||||
|
||||
`is_ac == 0`:
|
||||
```
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
|
||||
```
|
||||
|
||||
`is_ac == 1`:
|
||||
```
|
||||
1, 0, 2, 3, 17, 4, 5, 33, 18, 49, 65, 6, 81, 19,
|
||||
97, 7, 34, 113, 50, 129, 20, 145, 161, 8, 35, 66, 177, 193,
|
||||
21, 82, 209, 240, 36, 51, 98, 114, 9, 130, 10, 22, 52, 225,
|
||||
23, 37, 241, 24, 25, 26, 38, 39, 40, 41, 42, 53, 54, 55,
|
||||
56, 57, 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85,
|
||||
86, 87, 88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115,
|
||||
116, 117, 118, 119, 120, 121, 122, 131, 132, 133, 134, 135, 136, 137,
|
||||
138, 146, 147, 148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165,
|
||||
166, 167, 168, 169, 170, 178, 179, 180, 181, 182, 183, 184, 185, 186,
|
||||
194, 195, 196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214,
|
||||
215, 216, 217, 218, 226, 227, 228, 229, 230, 231, 232, 233, 234, 242,
|
||||
243, 244, 245, 246, 247, 248, 249, 250, 16, 32, 48, 64, 80, 96,
|
||||
112, 128, 144, 160, 176, 192, 208, 11, 12, 13, 14, 15, 27, 28,
|
||||
29, 30, 31, 43, 44, 45, 46, 47, 59, 60, 61, 62, 63, 75,
|
||||
76, 77, 78, 79, 91, 92, 93, 94, 95, 107, 108, 109, 110, 111,
|
||||
123, 124, 125, 126, 127, 139, 140, 141, 142, 143, 155, 156, 157, 158,
|
||||
159, 171, 172, 173, 174, 175, 187, 188, 189, 190, 191, 203, 204, 205,
|
||||
206, 207, 219, 220, 221, 222, 223, 224, 235, 236, 237, 238, 239, 251,
|
||||
252, 253, 254, 255
|
||||
```
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
# XL Overview
|
||||
|
||||
## Requirements
|
||||
|
||||
JPEG XL was designed for two main requirements:
|
||||
|
||||
* high quality: visually lossless at reasonable bitrates;
|
||||
* decoding speed: multithreaded decoding should be able to reach around
|
||||
400 Megapixel/s on large images.
|
||||
|
||||
These goals apply to various types of images, including HDR content, whose
|
||||
support is made possible by full-precision (float32) computations and extensive
|
||||
support of color spaces and transfer functions.
|
||||
|
||||
High performance is achieved by designing the format with careful consideration
|
||||
of memory bandwidth usage and ease of SIMD/GPU implementation.
|
||||
|
||||
The full requirements for JPEG XL are listed in document wg1m82079.
|
||||
|
||||
## General architecture
|
||||
|
||||
The architecture follows the traditional block transform model with improvements
|
||||
in the individual components. For a quick overview, we sketch a "block diagram"
|
||||
of the lossy format decoder in the form of module names in **bold** followed by
|
||||
a brief description. Note that post-processing modules in [brackets] are
|
||||
optional - they are unnecessary or even counterproductive at very high quality
|
||||
settings.
|
||||
|
||||
**Header**: decode metadata (e.g. image dimensions) from compressed fields
|
||||
(smaller than Exp-Golomb thanks to per-field encodings). The compression and
|
||||
small number of required fields enables very compact headers - much smaller than
|
||||
JFIF and HEVC. The container supports multiple images (e.g. animations/bursts)
|
||||
and passes (progressive).
|
||||
|
||||
**Bitstream**: decode transform coefficient residuals using rANS-encoded
|
||||
<#bits,bits> symbols
|
||||
|
||||
**Dequantize**: from adaptive quant map side information, plus chroma from luma
|
||||
|
||||
**DC prediction**: expand DC residuals using adaptive (history-based) predictors
|
||||
|
||||
**Chroma from luma**: restore predicted X from B and Y from B
|
||||
|
||||
**IDCT:** 2x2..32x32, floating-point
|
||||
|
||||
**[Gaborish]**: additional deblocking convolution with 3x3 kernel
|
||||
|
||||
**[Edge preserving filter]**: nonlinear adaptive smoothing controlled by side
|
||||
information
|
||||
|
||||
**[Noise injection]**: add perceptually pleasing noise according to a per-image
|
||||
noise model
|
||||
|
||||
**Color space conversion**: from perceptual opsin XYB to linear RGB
|
||||
|
||||
**[Converting to other color spaces via ICC]**
|
||||
|
||||
The encoder is basically the reverse:
|
||||
|
||||
**Color space conversion**: from linear RGB to perceptual opsin XYB
|
||||
|
||||
**[Noise estimation]**: compute a noise model for the image
|
||||
|
||||
**[Gaborish]**: sharpening to counteract the blurring on the decoder side
|
||||
|
||||
**DCT**: transform sizes communicated via per-block side information
|
||||
|
||||
**Chroma from luma**: find the best multipliers of Y for X and B channels of
|
||||
entire image
|
||||
|
||||
**Adaptive quantization**: iterative search for quant map that yields the best
|
||||
perceived restoration
|
||||
|
||||
**Quantize**: store 16-bit prediction residuals
|
||||
|
||||
**DC prediction**: store residuals (prediction happens in quantized space)
|
||||
|
||||
**Entropy coding**: rANS and context modeling with clustering
|
||||
|
||||
|
||||
# File Structure
|
||||
|
||||
A codestream begins with a `FileHeader` followed by one or more "passes"
|
||||
(= scans: e.g. DC or AC_LF) which are then added together (summing the
|
||||
respective color components in Opsin space) to form the final image. There is no
|
||||
limit to the number of passes, so an encoder could choose to send salient parts
|
||||
first, followed by arbitrary decompositions of the final image (in terms of
|
||||
resolution, bit depth, quality or spatial location).
|
||||
|
||||
Each pass contains groups of AC and DC data. A group is a subset of pixels that
|
||||
can be decoded in parallel. DC groups contain 256x256 DCs (from 2048x2048 input
|
||||
pixels), AC groups cover 256x256 input pixels.
|
||||
|
||||
Each pass starts with a table of contents (sizes of each of their DC+AC
|
||||
groups), which enables parallel decoding and/or the decoding of a subset.
|
||||
However, there is no higher-level TOC of passes, as that would prevent
|
||||
appending additional images and could be too constraining for the encoder.
|
||||
|
||||
|
||||
## Lossless
|
||||
|
||||
JPEG XL supports tools for lossless coding designed by Alexander Rhatushnyak and
|
||||
Jon Sneyers. They are about 60-75% of size of PNG, and smaller than WebP
|
||||
lossless for photos.
|
||||
|
||||
An adaptive predictor computes 4 from the NW, N, NE and W pixels and combines
|
||||
them with weights based on previous errors. The error value is encoded in a
|
||||
bucket chosen based on a heuristic max error. The result is entropy-coded using
|
||||
the ANS encoder.
|
||||
|
||||
## Current Reference Implementation
|
||||
|
||||
### Conventions
|
||||
|
||||
The software is written in C++ and built using CMake 3.6 or later.
|
||||
|
||||
Error handling is done by having functions return values of type `jxl::Status`
|
||||
(a thin wrapper around bool which checks that it is not ignored). A convenience
|
||||
macro named `JXL_RETURN_IF_ERROR` makes this more convenient by automatically
|
||||
forwarding errors, and another macro named `JXL_FAILURE` exits with an error
|
||||
message if reached, with no effect in optimized builds.
|
||||
|
||||
To diagnose the cause of encoder/decoder failures (which often only result in a
|
||||
generic "decode failed" message), build using the following command:
|
||||
|
||||
```bash
|
||||
CMAKE_FLAGS="-DJXL_CRASH_ON_ERROR" ./ci.sh opt
|
||||
```
|
||||
|
||||
In such builds, the first JXL_FAILURE will print a message identifying where the
|
||||
problem is and the program will exit immediately afterwards.
|
||||
|
||||
### Architecture
|
||||
|
||||
Getting back to the earlier block diagram:
|
||||
|
||||
**Header** handling is implemented in `headers.h` and `field*`.
|
||||
|
||||
**Bitstream**: `entropy_coder.h`, `dec_ans_*`.
|
||||
|
||||
**(De)quantize**: `quantizer.h`.
|
||||
|
||||
**DC prediction**: `predictor.h`.
|
||||
|
||||
**Chroma from luma**: `chroma_from_luma.h`
|
||||
|
||||
**(I)DCT**: `dct.h`. Instead of operating directly on blocks of memory, the
|
||||
functions operate on thin wrappers (declared in `block.h`) which can handle
|
||||
blocks spread across multiple image lines.
|
||||
|
||||
**DCT size selection**: `ac_strategy.cc`
|
||||
|
||||
**[Gaborish]**: `gaborish.h`.
|
||||
|
||||
**[Edge preserving filter]**: `epf.h`
|
||||
|
||||
**[Noise injection]**: `noise*` (currently disabled)
|
||||
|
||||
**Color space conversion**: `color_*`, `dec_xyb.h`.
|
||||
|
||||
## Decoder overview
|
||||
|
||||
After decoding headers, the decoder begins processing frames (`dec_frame.cc`).
|
||||
|
||||
For each pass, it will read the DC group table of contents (TOC) and start
|
||||
decoding, dequantizing and restoring color correlation of each DC group
|
||||
(covering 2048x2048 pixels in the input image) in parallel
|
||||
(`compressed_dc.cc`). The DC is split into parts corresponding to each AC group
|
||||
(with 1px of extra border); the AC group TOC is read and each AC group (256x256
|
||||
pixels) is processed in parallel (`dec_group.cc`).
|
||||
|
||||
In each AC group, the decoder reads per-block side information indicating the
|
||||
kind of DCT transform; this is followed by the quantization field. Then, AC
|
||||
coefficients are read, dequantized and have color correlation restored on a
|
||||
tile per tile basis for better locality.
|
||||
|
||||
After all the groups are read, postprocessing is applied: Gaborish smoothing
|
||||
and edge preserving filter, to reduce blocking and other artifacts.
|
||||
|
||||
Finally, the image is converted back from the XYB color space
|
||||
(`dec_xyb.cc`) and saved to the output image (`codec_*.cc`).
|
||||
@@ -0,0 +1,18 @@
|
||||
# Copyright (c) the JPEG XL Project
|
||||
#
|
||||
# Use of this source code is governed by an MIT-style
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
# Build an Ubuntu-based docker image with the installed software needed to
|
||||
# develop and test JPEG XL.
|
||||
|
||||
FROM ubuntu:bionic
|
||||
|
||||
# Set a prompt for when using it locally.
|
||||
ENV PS1="\[\033[01;33m\]\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ "
|
||||
|
||||
COPY scripts /jpegxl_scripts
|
||||
|
||||
RUN /jpegxl_scripts/jpegxl_builder.sh && \
|
||||
rm -rf /jpegxl_scripts
|
||||
@@ -0,0 +1,30 @@
|
||||
# Copyright (c) the JPEG XL Project
|
||||
#
|
||||
# Use of this source code is governed by an MIT-style
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
# Build an Ubuntu-based docker image for aarch64 with the installed software
|
||||
# needed to run JPEG XL. This is only useful when running on actual aarch64
|
||||
# hardware.
|
||||
|
||||
FROM arm64v8/ubuntu:bionic
|
||||
|
||||
# Set a prompt for when using it locally.
|
||||
ENV PS1="\[\033[01;33m\]\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ "
|
||||
|
||||
RUN set -ex; \
|
||||
apt-get update -y; \
|
||||
apt-get install -y \
|
||||
bsdmainutils \
|
||||
cmake \
|
||||
curl \
|
||||
extra-cmake-modules \
|
||||
git \
|
||||
libjpeg8 \
|
||||
libgif7 \
|
||||
libpng16-16 \
|
||||
libqt5x11extras5 \
|
||||
libsdl2-2.0-0 \
|
||||
parallel; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
@@ -0,0 +1,7 @@
|
||||
### Docker container infrastructure for JPEG XL
|
||||
|
||||
This directory contains the requirements to build a docker image for the
|
||||
JPEG XL project builder.
|
||||
|
||||
Docker images need to be created and upload manually. See ./build.sh for
|
||||
details.
|
||||
Executable
+89
@@ -0,0 +1,89 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) the JPEG XL Project
|
||||
#
|
||||
# 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.
|
||||
|
||||
set -eu
|
||||
|
||||
MYDIR=$(dirname $(realpath "$0"))
|
||||
|
||||
declare -a TARGETS
|
||||
|
||||
load_targets() {
|
||||
for f in $(find -maxdepth 1 -name 'Dockerfile.*' | sort); do
|
||||
local target="${f#*Dockerfile.}"
|
||||
TARGETS+=("${target}")
|
||||
done
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat >&2 <<EOF
|
||||
Use: $1 [targets]
|
||||
|
||||
Available targets:
|
||||
* all
|
||||
EOF
|
||||
for target in "${TARGETS[@]}"; do
|
||||
echo " * ${target}" >&2
|
||||
done
|
||||
}
|
||||
|
||||
build_target() {
|
||||
local target="$1"
|
||||
|
||||
local dockerfile="${MYDIR}/Dockerfile.${target}"
|
||||
# JPEG XL builder images are stored in the gcr.io/jpegxl project.
|
||||
local tag="gcr.io/jpegxl/${target}"
|
||||
|
||||
echo "Building ${target}"
|
||||
if ! sudo docker build -t "${tag}" -f "${dockerfile}" "${MYDIR}" >"${target}.log" 2>&1; then
|
||||
echo "${target} failed. See ${target}.log" >&2
|
||||
else
|
||||
echo "Done, to upload image run:" >&2
|
||||
echo " sudo docker push ${tag}"
|
||||
if [[ "${JPEGXL_PUSH:-}" == "1" ]]; then
|
||||
echo "sudo docker push ${tag}" >&2
|
||||
sudo docker push "${tag}"
|
||||
# The RepoDigest is only created after it is pushed.
|
||||
local fulltag=$(sudo docker inspect --format="{{.RepoDigests}}" "${tag}")
|
||||
fulltag="${fulltag#[}"
|
||||
fulltag="${fulltag%]}"
|
||||
echo "Updating .gitlab-ci.yml to ${fulltag}" >&2
|
||||
sed -E "s;${tag}@sha256:[0-9a-f]+;${fulltag};" \
|
||||
-i "${MYDIR}/../.gitlab-ci.yml"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
cd "${MYDIR}"
|
||||
local target="${1:-}"
|
||||
|
||||
load_targets
|
||||
if [[ -z "${target}" ]]; then
|
||||
usage $0
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${target}" == "all" ]]; then
|
||||
for target in "${TARGETS[@]}"; do
|
||||
build_target "${target}"
|
||||
done
|
||||
else
|
||||
for target in "$@"; do
|
||||
build_target "${target}"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) the JPEG XL Project
|
||||
#
|
||||
# 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.
|
||||
|
||||
EMSDK_URL="https://github.com/emscripten-core/emsdk/archive/master.tar.gz"
|
||||
EMSDK_DIR="/opt/emsdk"
|
||||
|
||||
EMSDK_RELEASE="1.39.4" # Alternatively, we can use "latest"
|
||||
|
||||
set -eu -x
|
||||
|
||||
# Temporary files cleanup hooks.
|
||||
CLEANUP_FILES=()
|
||||
cleanup() {
|
||||
if [[ ${#CLEANUP_FILES[@]} -ne 0 ]]; then
|
||||
rm -fr "${CLEANUP_FILES[@]}"
|
||||
fi
|
||||
}
|
||||
trap "{ set +x; } 2>/dev/null; cleanup" INT TERM EXIT
|
||||
|
||||
main() {
|
||||
local workdir=$(mktemp -d --suffix=qemu)
|
||||
CLEANUP_FILES+=("${workdir}")
|
||||
|
||||
local emsdktar="${workdir}/emsdk.tar.gz"
|
||||
curl --output "${emsdktar}" "${EMSDK_URL}" --location
|
||||
mkdir -p "${EMSDK_DIR}"
|
||||
tar -zxf "${emsdktar}" -C "${EMSDK_DIR}" --strip-components=1
|
||||
|
||||
cd "${EMSDK_DIR}"
|
||||
./emsdk install --shallow "${EMSDK_RELEASE}"
|
||||
./emsdk activate --embedded "${EMSDK_RELEASE}"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+225
@@ -0,0 +1,225 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) the JPEG XL Project
|
||||
#
|
||||
# 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.
|
||||
|
||||
# Main entry point for all the Dockerfile for jpegxl-builder. This centralized
|
||||
# file helps sharing code and configuration between Dockerfiles.
|
||||
|
||||
set -eux
|
||||
|
||||
MYDIR=$(dirname $(realpath "$0"))
|
||||
|
||||
# List of Ubuntu arch names supported by the builder (such as "i386").
|
||||
LIST_ARCHS=(
|
||||
amd64
|
||||
i386
|
||||
arm64
|
||||
armhf
|
||||
)
|
||||
|
||||
# List of target triplets supported by the builder.
|
||||
LIST_TARGETS=(
|
||||
x86_64-linux-gnu
|
||||
i686-linux-gnu
|
||||
arm-linux-gnueabihf
|
||||
aarch64-linux-gnu
|
||||
)
|
||||
|
||||
# Setup the apt repositories and supported architectures.
|
||||
setup_apt() {
|
||||
apt-get update -y
|
||||
apt-get install -y gnupg
|
||||
|
||||
# gcc ppa sources.
|
||||
cat >/etc/apt/sources.list.d/gcc.list <<EOF
|
||||
deb [arch=$(echo ${LIST_ARCHS[@]} | tr ' ' ,)] http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic main
|
||||
EOF
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1E9377A2BA9EF27F
|
||||
|
||||
local porl_list=()
|
||||
local main_list=()
|
||||
local ubarch
|
||||
for ubarch in "${LIST_ARCHS[@]}"; do
|
||||
if [[ "${ubarch}" != "amd64" && "${ubarch}" != "i386" ]]; then
|
||||
# other archs are not part of the main mirrors, but available in
|
||||
# ports.ubuntu.com.
|
||||
port_list+=("${ubarch}")
|
||||
else
|
||||
main_list+=("${ubarch}")
|
||||
fi
|
||||
# Add the arch to the system.
|
||||
if [[ "${ubarch}" != "amd64" ]]; then
|
||||
dpkg --add-architecture "${ubarch}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Update the sources.list with the split of supported architectures.
|
||||
local bkplist="/etc/apt/sources.list.bkp"
|
||||
[[ -e "${bkplist}" ]] || \
|
||||
mv /etc/apt/sources.list "${bkplist}"
|
||||
|
||||
local newlist="/etc/apt/sources.list.tmp"
|
||||
rm -f "${newlist}"
|
||||
port_list=$(echo "${port_list[@]}" | tr ' ' ,)
|
||||
if [[ -n "${port_list}" ]]; then
|
||||
local port_url="http://ports.ubuntu.com/ubuntu-ports/"
|
||||
grep -v -E '^#' "${bkplist}" |
|
||||
sed -E "s;^deb (http[^ ]+) (.*)\$;deb [arch=${port_list}] ${port_url} \\2;" \
|
||||
>>"${newlist}"
|
||||
fi
|
||||
|
||||
main_list=$(echo "${main_list[@]}" | tr ' ' ,)
|
||||
grep -v -E '^#' "${bkplist}" |
|
||||
sed -E "s;^deb (http[^ ]+) (.*)\$;deb [arch=${main_list}] \\1 \\2;" \
|
||||
>>"${newlist}"
|
||||
mv "${newlist}" /etc/apt/sources.list
|
||||
}
|
||||
|
||||
install_pkgs() {
|
||||
packages=(
|
||||
# Native compilers
|
||||
clang-6.0 clang-format-6.0 clang-tidy-6.0
|
||||
|
||||
# TODO: Consider adding clang-7 and clang-8 to every builder:
|
||||
# clang-7 clang-format-7 clang-tidy-7
|
||||
# clang-8 clang-format-8 clang-tidy-8
|
||||
|
||||
# Native tools.
|
||||
bsdmainutils
|
||||
cmake
|
||||
curl
|
||||
extra-cmake-modules
|
||||
git
|
||||
llvm
|
||||
nasm
|
||||
ninja-build
|
||||
parallel
|
||||
pkg-config
|
||||
|
||||
# These are used by the ./ci.sh lint in the native builder.
|
||||
clang-format-7
|
||||
clang-format-8
|
||||
|
||||
# For coverage builds
|
||||
gcovr
|
||||
|
||||
# Common libraries.
|
||||
libstdc++-8-dev
|
||||
libgif-dev # See libgif-dev comment below.
|
||||
)
|
||||
|
||||
# Install packages that are arch-dependent.
|
||||
local ubarch
|
||||
for ubarch in "${LIST_ARCHS[@]}"; do
|
||||
packages+=(
|
||||
# Library dependencies. These normally depend on the target architecture
|
||||
# we are compiling for and can't usually be installed for multiple
|
||||
# architectures at the same time.
|
||||
libjpeg-dev:"${ubarch}"
|
||||
libpng-dev:"${ubarch}"
|
||||
libqt5x11extras5-dev:"${ubarch}"
|
||||
|
||||
libstdc++-8-dev:"${ubarch}"
|
||||
qtbase5-dev:"${ubarch}"
|
||||
|
||||
# Cross-compiling tools per arch.
|
||||
libc6-dev-"${ubarch}"-cross
|
||||
libstdc++-8-dev-"${ubarch}"-cross
|
||||
|
||||
# libgif-dev package can't be installed for multiple architectures at
|
||||
# the same time. Instead we unpack it manually later.
|
||||
# libgif-dev:"${ubarch}"
|
||||
)
|
||||
done
|
||||
|
||||
local target
|
||||
for target in "${LIST_TARGETS[@]}"; do
|
||||
# Per target cross-compiling tools.
|
||||
if [[ "${target}" != "x86_64-linux-gnu" ]]; then
|
||||
packages+=(
|
||||
binutils-"${target}"
|
||||
gcc-"${target}"
|
||||
)
|
||||
fi
|
||||
done
|
||||
|
||||
apt install -y "${packages[@]}"
|
||||
}
|
||||
|
||||
# Packages that are manually unpacked for each architecture.
|
||||
UNPACK_PKGS=(
|
||||
libgif-dev
|
||||
libclang-common-6.0-dev
|
||||
)
|
||||
|
||||
# Main script entry point.
|
||||
main() {
|
||||
cd "${MYDIR}"
|
||||
|
||||
# Configure the repositories with the sources for multi-arch cross
|
||||
# compilation.
|
||||
setup_apt
|
||||
apt-get update -y
|
||||
apt-get dist-upgrade -y
|
||||
|
||||
install_pkgs
|
||||
apt clean
|
||||
|
||||
# Manually extract packages for the target arch that can't install it directly
|
||||
# at the same time as the native ones.
|
||||
local ubarch
|
||||
for ubarch in "${LIST_ARCHS[@]}"; do
|
||||
if [[ "${ubarch}" != "amd64" ]]; then
|
||||
local pkg
|
||||
for pkg in "${UNPACK_PKGS[@]}"; do
|
||||
apt download "${pkg}":"${ubarch}"
|
||||
dpkg -x "${pkg}"_*_"${ubarch}".deb /
|
||||
done
|
||||
fi
|
||||
done
|
||||
# TODO: Add clang from the llvm repos. This is problematic since we are
|
||||
# installing libclang-common-6.0-dev:"${ubarch}" from the ubuntu ports repos
|
||||
# which is not available in the llvm repos so it might have a different
|
||||
# version than the ubuntu ones.
|
||||
|
||||
# Install webp manually since the version in Ubuntu is rather old.
|
||||
local target
|
||||
for target in "${LIST_TARGETS[@]}"; do
|
||||
if [[ "${target}" == "x86_64-linux-gnu" ]]; then
|
||||
CC=clang-6.0 CXX=clang++-6.0 ./webp_install.sh
|
||||
else
|
||||
CC=clang-6.0 CXX=clang++-6.0 ./webp_install.sh \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/"${target}" \
|
||||
-DCMAKE_C_COMPILER_TARGET="${target}" \
|
||||
-DCMAKE_CXX_COMPILER_TARGET="${target}" \
|
||||
-DCMAKE_SYSTEM_PROCESSOR="${target%%-*}" \
|
||||
-DCMAKE_SYSTEM_NAME=Linux
|
||||
fi
|
||||
done
|
||||
|
||||
# TODO: Add msan for the target when cross-compiling. This only installs it
|
||||
# for amd64.
|
||||
./msan_install.sh
|
||||
|
||||
# Build and install qemu user-linux targets.
|
||||
./qemu_install.sh
|
||||
|
||||
# Install emscripten SDK.
|
||||
./emsdk_install.sh
|
||||
|
||||
# Cleanup.
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+140
@@ -0,0 +1,140 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) the JPEG XL Project
|
||||
#
|
||||
# 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.
|
||||
|
||||
set -eu
|
||||
|
||||
MYDIR=$(dirname $(realpath "$0"))
|
||||
|
||||
# Convenience flag to pass both CMAKE_C_FLAGS and CMAKE_CXX_FLAGS
|
||||
CMAKE_FLAGS=${CMAKE_FLAGS:-}
|
||||
CMAKE_C_FLAGS=${CMAKE_C_FLAGS:-${CMAKE_FLAGS}}
|
||||
CMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS:-${CMAKE_FLAGS}}
|
||||
CMAKE_EXE_LINKER_FLAGS=${CMAKE_EXE_LINKER_FLAGS:-}
|
||||
|
||||
CLANG_VERSION="${CLANG_VERSION:-}"
|
||||
# Detect the clang version suffix and store it in CLANG_VERSION. For example,
|
||||
# "6.0" for clang 6 or "7" for clang 7.
|
||||
detect_clang_version() {
|
||||
if [[ -n "${CLANG_VERSION}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
local clang_version=$("${CC:-clang}" --version | head -n1)
|
||||
local llvm_tag
|
||||
case "${clang_version}" in
|
||||
"clang version 6."*)
|
||||
CLANG_VERSION="6.0"
|
||||
;;
|
||||
"clang version 7."*)
|
||||
CLANG_VERSION="7"
|
||||
;;
|
||||
"clang version 8."*)
|
||||
CLANG_VERSION="8"
|
||||
;;
|
||||
"clang version 9."*)
|
||||
CLANG_VERSION="9"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown clang version: ${clang_version}" >&2
|
||||
return 1
|
||||
esac
|
||||
}
|
||||
|
||||
# Temporary files cleanup hooks.
|
||||
CLEANUP_FILES=()
|
||||
cleanup() {
|
||||
if [[ ${#CLEANUP_FILES[@]} -ne 0 ]]; then
|
||||
rm -fr "${CLEANUP_FILES[@]}"
|
||||
fi
|
||||
}
|
||||
trap "{ set +x; } 2>/dev/null; cleanup" INT TERM EXIT
|
||||
|
||||
# Install libc++ libraries compiled with msan in the msan_prefix for the current
|
||||
# compiler version.
|
||||
cmd_msan_install() {
|
||||
local tmpdir=$(mktemp -d)
|
||||
CLEANUP_FILES+=("${tmpdir}")
|
||||
# Detect the llvm to install:
|
||||
export CC="${CC:-clang}"
|
||||
export CXX="${CXX:-clang++}"
|
||||
detect_clang_version
|
||||
local llvm_tag
|
||||
case "${CLANG_VERSION}" in
|
||||
"6.0")
|
||||
llvm_tag="llvmorg-6.0.1"
|
||||
;;
|
||||
"7")
|
||||
llvm_tag="llvmorg-7.0.1"
|
||||
;;
|
||||
"8")
|
||||
llvm_tag="llvmorg-8.0.0"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown clang version: ${clang_version}" >&2
|
||||
return 1
|
||||
esac
|
||||
local llvm_targz="${tmpdir}/${llvm_tag}.tar.gz"
|
||||
curl -L --show-error -o "${llvm_targz}" \
|
||||
"https://github.com/llvm/llvm-project/archive/${llvm_tag}.tar.gz"
|
||||
tar -C "${tmpdir}" -zxf "${llvm_targz}"
|
||||
local llvm_root="${tmpdir}/llvm-project-${llvm_tag}"
|
||||
|
||||
local msan_prefix="${HOME}/.msan/${CLANG_VERSION}"
|
||||
rm -rf "${msan_prefix}"
|
||||
|
||||
declare -A CMAKE_EXTRAS
|
||||
CMAKE_EXTRAS[libcxx]="\
|
||||
-DLIBCXX_CXX_ABI=libstdc++ \
|
||||
-DLIBCXX_INSTALL_EXPERIMENTAL_LIBRARY=ON"
|
||||
|
||||
for project in libcxx; do
|
||||
local proj_build="${tmpdir}/build-${project}"
|
||||
local proj_dir="${llvm_root}/${project}"
|
||||
mkdir -p "${proj_build}"
|
||||
cmake -B"${proj_build}" -H"${proj_dir}" \
|
||||
-G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DLLVM_USE_SANITIZER=Memory \
|
||||
-DLLVM_PATH="${llvm_root}/llvm" \
|
||||
-DLLVM_CONFIG_PATH="$(which llvm-config llvm-config-7 llvm-config-6.0 | \
|
||||
head -n1)" \
|
||||
-DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS}" \
|
||||
-DCMAKE_C_FLAGS="${CMAKE_C_FLAGS}" \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}" \
|
||||
-DCMAKE_INSTALL_PREFIX="${msan_prefix}" \
|
||||
${CMAKE_EXTRAS[${project}]}
|
||||
cmake --build "${proj_build}"
|
||||
ninja -C "${proj_build}" install
|
||||
done
|
||||
}
|
||||
|
||||
main() {
|
||||
set -x
|
||||
for version in 6.0 7 8; do
|
||||
if ! which "clang-${version}" >/dev/null; then
|
||||
echo "Skipping msan install for clang version ${version}"
|
||||
continue
|
||||
fi
|
||||
(
|
||||
trap "{ set +x; } 2>/dev/null; cleanup" INT TERM EXIT
|
||||
export CLANG_VERSION=${version}
|
||||
export CC=clang-${version}
|
||||
export CXX=clang++-${version}
|
||||
cmd_msan_install
|
||||
) &
|
||||
done
|
||||
wait
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+92
@@ -0,0 +1,92 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) the JPEG XL Project
|
||||
#
|
||||
# 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.
|
||||
|
||||
QEMU_RELEASE="4.1.0"
|
||||
QEMU_URL="https://download.qemu.org/qemu-${QEMU_RELEASE}.tar.xz"
|
||||
QEMU_ARCHS=(
|
||||
aarch64
|
||||
arm
|
||||
i386
|
||||
# TODO: Consider adding these:
|
||||
# aarch64_be
|
||||
# mips64el
|
||||
# mips64
|
||||
# mips
|
||||
# ppc64
|
||||
# ppc
|
||||
)
|
||||
|
||||
# Ubuntu packages not installed that are needed to build qemu.
|
||||
QEMU_BUILD_DEPS=(
|
||||
libglib2.0-dev
|
||||
libpixman-1-dev
|
||||
flex
|
||||
bison
|
||||
)
|
||||
|
||||
set -eu -x
|
||||
|
||||
# Temporary files cleanup hooks.
|
||||
CLEANUP_FILES=()
|
||||
cleanup() {
|
||||
if [[ ${#CLEANUP_FILES[@]} -ne 0 ]]; then
|
||||
rm -fr "${CLEANUP_FILES[@]}"
|
||||
fi
|
||||
}
|
||||
trap "{ set +x; } 2>/dev/null; cleanup" INT TERM EXIT
|
||||
|
||||
main() {
|
||||
local workdir=$(mktemp -d --suffix=qemu)
|
||||
CLEANUP_FILES+=("${workdir}")
|
||||
|
||||
apt install -y "${QEMU_BUILD_DEPS[@]}"
|
||||
|
||||
local qemutar="${workdir}/qemu.tar.gz"
|
||||
curl --output "${qemutar}" "${QEMU_URL}"
|
||||
tar -Jxf "${qemutar}" -C "${workdir}"
|
||||
local srcdir="${workdir}/qemu-${QEMU_RELEASE}"
|
||||
|
||||
local builddir="${workdir}/build"
|
||||
local prefixdir="${workdir}/prefix"
|
||||
mkdir -p "${builddir}"
|
||||
|
||||
# List of targets to build.
|
||||
local targets=""
|
||||
local make_targets=()
|
||||
local target
|
||||
for target in "${QEMU_ARCHS[@]}"; do
|
||||
targets="${targets} ${target}-linux-user"
|
||||
# Build just the linux-user targets.
|
||||
make_targets+=("${target}-linux-user/all")
|
||||
done
|
||||
|
||||
cd "${builddir}"
|
||||
"${srcdir}/configure" \
|
||||
--prefix="${prefixdir}" \
|
||||
--static --disable-system --enable-linux-user \
|
||||
--target-list="${targets}"
|
||||
|
||||
make -j $(nproc --all || echo 1) "${make_targets[@]}"
|
||||
|
||||
# Manually install these into the non-standard location. This script runs as
|
||||
# root anyway.
|
||||
for target in "${QEMU_ARCHS[@]}"; do
|
||||
cp "${target}-linux-user/qemu-${target}" "/usr/bin/qemu-${target}-static"
|
||||
done
|
||||
|
||||
apt autoremove -y --purge "${QEMU_BUILD_DEPS[@]}"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) the JPEG XL Project
|
||||
#
|
||||
# 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.
|
||||
|
||||
WEBP_RELEASE="1.0.2"
|
||||
WEBP_URL="https://codeload.github.com/webmproject/libwebp/tar.gz/v${WEBP_RELEASE}"
|
||||
|
||||
set -eu -x
|
||||
|
||||
# Temporary files cleanup hooks.
|
||||
CLEANUP_FILES=()
|
||||
cleanup() {
|
||||
if [[ ${#CLEANUP_FILES[@]} -ne 0 ]]; then
|
||||
rm -fr "${CLEANUP_FILES[@]}"
|
||||
fi
|
||||
}
|
||||
trap "{ set +x; } 2>/dev/null; cleanup" INT TERM EXIT
|
||||
|
||||
main() {
|
||||
local workdir=$(mktemp -d --suffix=webp)
|
||||
CLEANUP_FILES+=("${workdir}")
|
||||
|
||||
local webptar="${workdir}/webp.tar.gz"
|
||||
curl --output "${webptar}" "${WEBP_URL}"
|
||||
tar -zxvf "${webptar}" -C "${workdir}"
|
||||
local srcdir="${workdir}/libwebp-${WEBP_RELEASE}"
|
||||
local builddir="${srcdir}/build"
|
||||
cmake -B"${builddir}" -H"${srcdir}" -G Ninja \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
"$@"
|
||||
cmake --build "${builddir}"
|
||||
ninja -C "${builddir}" install
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,109 @@
|
||||
/* Copyright (c) the JPEG XL Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef JPEGXL_DECODE_H_
|
||||
#define JPEGXL_DECODE_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "jpegxl/jpegxl_export.h"
|
||||
#include "jpegxl/memory_manager.h"
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Decoder library version.
|
||||
*
|
||||
* @returns the decoder library version as an integer:
|
||||
* MAJOR_VERSION * 1000000 + MINOR_VERSION * 1000 + PATCH_VERSION. For example,
|
||||
* version 1.2.3 would return 1002003.
|
||||
*/
|
||||
JPEGXL_EXPORT uint32_t JpegxlDecoderVersion(void);
|
||||
|
||||
enum JpegxlSignature {
|
||||
/** Not enough bytes were passed to determine if a valid signature was found.
|
||||
*/
|
||||
JPEGXL_SIG_NOT_ENOUGH_BYTES = 0,
|
||||
|
||||
/** No valid JPEGXL header was found. */
|
||||
JPEGXL_SIG_INVALID = 1,
|
||||
|
||||
/** A valid transcoded JPEG XL image signature was found. */
|
||||
JPEGXL_SIG_BRUNSLI = 2,
|
||||
|
||||
/** A valid JPEG XL image signature was found. */
|
||||
JPEGXL_SIG_JPEGXL = 4,
|
||||
|
||||
/* Mask representing any valid JPEGXL signature. */
|
||||
JPEGXL_SIG_ANY = JPEGXL_SIG_BRUNSLI | JPEGXL_SIG_JPEGXL,
|
||||
};
|
||||
|
||||
/**
|
||||
* JPEG XL signature identification.
|
||||
*
|
||||
* Checks if the passed buffer contains a valid JPEG XL signature header and
|
||||
* returns the type of signature header if found. The passed @p buf of size
|
||||
* @p size doesn't need to be a full image, only the beginning of the file.
|
||||
*
|
||||
* @returns a flag indicating if a JPEG XL signature was found and what type.
|
||||
* - JPEGXL_SIG_INVALID no valid signature found.
|
||||
* - JPEGXL_SIG_BRUNSLI a valid Brunsli signature was found.
|
||||
* - JPEGXL_SIG_JPEGXL a valid JPEG XL signature was found.
|
||||
* - JPEGXL_SIG_NOT_ENOUGH_BYTES not enough bytes were passed to determine
|
||||
* if a valid signature is there.
|
||||
* You can mask the result of this function with JPEGXL_SIG_ANY to determine
|
||||
* if a valid signature (of any kind) was found.
|
||||
*/
|
||||
JPEGXL_EXPORT enum JpegxlSignature JpegxlSignatureCheck(const uint8_t* buf,
|
||||
size_t len);
|
||||
|
||||
/**
|
||||
* Opaque structure that holds the JPEGXL decoder.
|
||||
*
|
||||
* Allocated and initialized with JpegxlDecoderCreate().
|
||||
* Cleaned up and deallocated with JpegxlDecoderDestroy().
|
||||
*/
|
||||
typedef struct JpegxlDecoderStruct JpegxlDecoder;
|
||||
|
||||
/**
|
||||
* Creates an instance of JpegxlDecoder and initializes it.
|
||||
*
|
||||
* @p memory_manager will be used for all the library dynamic allocations made
|
||||
* from this instance. The parameter may be NULL, in which case the default
|
||||
* allocator will be used. See jpegxl/memory_manager.h for details.
|
||||
*
|
||||
* @param memory_manager custom allocator function. It may be NULL. The memory
|
||||
* manager will be copied internally.
|
||||
* @returns @c NULL if the instance can not be allocated or initialized
|
||||
* @returns pointer to initialized JpegxlDecoder otherwise
|
||||
*/
|
||||
JPEGXL_EXPORT JpegxlDecoder* JpegxlDecoderCreate(
|
||||
const JpegxlMemoryManager* memory_manager);
|
||||
|
||||
/**
|
||||
* Deinitializes and frees JpegxlDecoder instance.
|
||||
*
|
||||
* @param dec instance to be cleaned up and deallocated.
|
||||
*/
|
||||
JPEGXL_EXPORT void JpegxlDecoderDestroy(JpegxlDecoder* dec);
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* JPEGXL_DECODE_H_ */
|
||||
@@ -0,0 +1,71 @@
|
||||
/* Copyright (c) the JPEG XL Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef JPEGXL_MEMORY_MANAGER_H_
|
||||
#define JPEGXL_MEMORY_MANAGER_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Allocating function for a memory region of a given size.
|
||||
*
|
||||
* Allocates a contiguous memory region of size @p size bytes. The returned
|
||||
* memory may not be aligned to a specific size or initialized at all.
|
||||
*
|
||||
* @param opaque custom memory manager handle provided by the caller.
|
||||
* @param size in bytes of the requested memory region.
|
||||
* @returns @c 0 if the memory can not be allocated,
|
||||
* @returns pointer to the memory otherwise.
|
||||
*/
|
||||
typedef void* (*jpegxl_alloc_func)(void* opaque, size_t size);
|
||||
|
||||
/**
|
||||
* Deallocating function pointer type.
|
||||
*
|
||||
* This function @b MUST do nothing if @p address is @c 0.
|
||||
*
|
||||
* @param opaque custom memory manager handle provided by the caller.
|
||||
* @param address memory region pointer returned by ::brotli_alloc_func, or @c 0
|
||||
*/
|
||||
typedef void (*jpegxl_free_func)(void* opaque, void* address);
|
||||
|
||||
/**
|
||||
* Memory Manager struct.
|
||||
* These functions, when provided by the caller, will be used to handle memory
|
||||
* allocations.
|
||||
*/
|
||||
typedef struct JpegxlMemoryManagerStruct {
|
||||
/* The opaque pointer that will be passed as the first parameter to all the
|
||||
* functions in this struct. */
|
||||
void* opaque;
|
||||
|
||||
/* Alloc/free functions. These can either be both NULL or none of them NULL.
|
||||
* All dynamic memory will be allocated and freed with these functions if
|
||||
* not NULL. */
|
||||
jpegxl_alloc_func alloc;
|
||||
jpegxl_free_func free;
|
||||
|
||||
/* TODO(deymo): Add cache-aligned alloc/free functions here. */
|
||||
} JpegxlMemoryManager;
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* JPEGXL_MEMORY_MANAGER_H_ */
|
||||
@@ -0,0 +1,152 @@
|
||||
/* Copyright (c) the JPEG XL Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/** API for running data operations in parallel in a multi-threaded environment.
|
||||
* This module allows the JPEG XL caller to define their own way of creating and
|
||||
* assigning threads.
|
||||
*
|
||||
* The JpegxlParallelRunner function type defines a parallel data processing
|
||||
* runner that may be implemented by the caller to allow the library to process
|
||||
* in multiple threads. The multi-threaded processing in this library only
|
||||
* requires to run the same function over each number of a range, possibly
|
||||
* running each call in a different thread. The JPEG XL caller is responsible
|
||||
* for implementing this logic using the thread APIs available in their system.
|
||||
* For convenience, a C++ implementation based on std::thread is provided in
|
||||
* jpegxl/parallel_runner_thread.h (part of the jpegxl_threads library).
|
||||
*
|
||||
* Thread pools usually store small numbers of heterogeneous tasks in a queue.
|
||||
* When tasks are identical or differ only by an integer input parameter, it is
|
||||
* much faster to store just one function of an integer parameter and call it
|
||||
* for each value. Conventional vector-of-tasks can be run in parallel using a
|
||||
* lambda function adapter that simply calls task_funcs[task].
|
||||
*
|
||||
* If no multi-threading is desired, a @c NULL value of JpegxlParallelRunner
|
||||
* will use an internal implementation without multi-threading.
|
||||
*/
|
||||
|
||||
#ifndef JPEGXL_PARALLEL_RUNNER_H_
|
||||
#define JPEGXL_PARALLEL_RUNNER_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Return code used in the JpegxlParallel* functions as return value. A value
|
||||
* of 0 means success and any other value means error. The special value
|
||||
* JPEGXL_PARALLEL_RET_RUNNER_ERROR can be used by the runner to indicate any
|
||||
* other error.
|
||||
*/
|
||||
typedef int JpegxlParallelRetCode;
|
||||
|
||||
#define JPEGXL_PARALLEL_RET_RUNNER_ERROR (-1)
|
||||
|
||||
/**
|
||||
* Parallel run initialization callback. See JpegxlParallelRunner for details.
|
||||
*
|
||||
* This function MUST be called by the JpegxlParallelRunner only once, on the
|
||||
* same thread that called JpegxlParallelRunner, before any parallel execution.
|
||||
* The purpose of this call is to provide the maximum number of threads that the
|
||||
* JpegxlParallelRunner will use, which can be used by JPEG XL to allocate
|
||||
* per-thread storage if needed.
|
||||
*
|
||||
* @param jpegxl_opaque the @p jpegxl_opaque handle provided to
|
||||
* JpegxlParallelRunner() must be passed here.
|
||||
* @param num_threads the maximum number of threads. This value must be
|
||||
* positive.
|
||||
* @returns 0 if the initialization process was successful.
|
||||
* @returns an error code if there was an error, which should be returned by
|
||||
* JpegxlParallelRunner().
|
||||
*/
|
||||
typedef JpegxlParallelRetCode (*JpegxlParallelRunInit)(void* jpegxl_opaque,
|
||||
size_t num_threads);
|
||||
|
||||
/**
|
||||
* Parallel run data processing callback. See JpegxlParallelRunner for details.
|
||||
*
|
||||
* This function MUST be called once for every number in the range [start_range,
|
||||
* end_range) (including start_range but not including end_range) passing this
|
||||
* number as the @p value. Calls for different value may be executed from
|
||||
* different threads in parallel.
|
||||
*
|
||||
* @param jpegxl_opaque the @p jpegxl_opaque handle provided to
|
||||
* JpegxlParallelRunner() must be passed here.
|
||||
* @param value the number in the range [start_range, end_range) of the call.
|
||||
* @param thread_id the thread number where this function is being called from.
|
||||
* This must be lower than the @p num_threads value passed to
|
||||
* JpegxlParallelRunInit.
|
||||
*/
|
||||
typedef void (*JpegxlParallelRunFunction)(void* jpegxl_opaque, uint32_t value,
|
||||
size_t thread_id);
|
||||
|
||||
/**
|
||||
* JpegxlParallelRunner function type. A parallel runner implementation can be
|
||||
* provided by a JPEG XL caller to allow running computations in multiple
|
||||
* threads. This function must call the initialization function @p init in the
|
||||
* same thread that called it and then call the passed @p func once for every
|
||||
* number in the range [start_range, end_range) (including start_range but not
|
||||
* including end_range) possibly from different multiple threads in parallel.
|
||||
*
|
||||
* The JpegxlParallelRunner function does not need to be re-entrant. This means
|
||||
* that the same JpegxlParallelRunner function with the same runner_opaque
|
||||
* provided parameter will not be called from the library from either @p init or
|
||||
* @p func in the same decoder or encoder instance. However, a single decoding
|
||||
* or encoding instance may call the provided JpegxlParallelRunner multiple
|
||||
* times for different parts of the decoding or encoding process.
|
||||
*
|
||||
* @returns 0 if the @p init call succeeded (returned 0) and no other error
|
||||
* occurred in the runner code.
|
||||
* @returns JPEGXL_PARALLEL_RET_RUNNER_ERROR if an error occurred in the runner
|
||||
* code, for example, setting up the threads.
|
||||
* @return the return value of @p init() if non-zero.
|
||||
*/
|
||||
typedef JpegxlParallelRetCode (*JpegxlParallelRunner)(
|
||||
void* runner_opaque, void* jpegxl_opaque, JpegxlParallelRunInit init,
|
||||
JpegxlParallelRunFunction func, uint32_t start_range, uint32_t end_range);
|
||||
|
||||
/* The following is an example of a JpegxlParallelRunner that doesn't use any
|
||||
* multi-threading. Note that this implementation doesn't store any state
|
||||
* between multiple calls of the ExampleSequentialRunner function, so the
|
||||
* runner_opaque value is not used.
|
||||
|
||||
JpegxlParallelRetCode ExampleSequentialRunner(void* runner_opaque,
|
||||
void* jpegxl_opaque,
|
||||
JpegxlParallelRunInit init,
|
||||
JpegxlParallelRunFunction func,
|
||||
uint32_t start_range,
|
||||
uint32_t end_range) {
|
||||
// We only use one thread (the currently running thread).
|
||||
JpegxlParallelRetCode init_ret = (*init)(jpegxl_opaque, 1);
|
||||
if (init_ret != 0) return init_ret;
|
||||
|
||||
// In case of other initialization error (for example when initializing the
|
||||
// threads) one can return JPEGXL_PARALLEL_RET_RUNNER_ERROR.
|
||||
|
||||
for (uint32_t i = start_range; i < end_range; i++) {
|
||||
// Every call is in the thread number 0. These don't need to be in any
|
||||
// order.
|
||||
(*func)(jpegxl_opaque, i, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* JPEGXL_PARALLEL_RUNNER_H_ */
|
||||
@@ -0,0 +1,179 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
// Implementation of a JpegxlParallelRunner using std::threads.
|
||||
//
|
||||
// The main class in this module, ThreadParallelRunner, implements a static
|
||||
// method ThreadParallelRunner::Runner than can be passed as a
|
||||
// JpegxlParallelRunner when using the JPEG XL library. This uses std::thread
|
||||
// internally and related synchronization functions. The number of threads
|
||||
// created is fixed at construction time and the threads are re-used for every
|
||||
// ThreadParallelRunner::Runner call. Only one concurrent Runner() call per
|
||||
// instance is allowed at a time.
|
||||
//
|
||||
// This is a scalable, lower-overhead thread pool runner, especially suitable
|
||||
// for data-parallel computations in the fork-join model, where clients need to
|
||||
// know when all tasks have completed.
|
||||
//
|
||||
// This thread pool can efficiently load-balance millions of tasks using an
|
||||
// atomic counter, thus avoiding per-task virtual or system calls. With 48
|
||||
// hyperthreads and 1M tasks that add to an atomic counter, overall runtime is
|
||||
// 10-20x higher when using std::async, and ~200x for a queue-based thread pool.
|
||||
//
|
||||
// Usage:
|
||||
// ThreadParallelRunner runner;
|
||||
// JpegxlDecode(
|
||||
// ... , &ThreadParallelRunner::Runner, static_cast<void*>(&runner));
|
||||
|
||||
#ifndef JPEGXL_THREAD_PARALLEL_RUNNER_H_
|
||||
#define JPEGXL_THREAD_PARALLEL_RUNNER_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <algorithm> // max
|
||||
#include <atomic>
|
||||
#include <condition_variable> //NOLINT
|
||||
#include <mutex> //NOLINT
|
||||
#include <thread> //NOLINT
|
||||
#include <vector>
|
||||
|
||||
#include "jpegxl/parallel_runner.h"
|
||||
|
||||
namespace jpegxl {
|
||||
|
||||
class ThreadParallelRunner {
|
||||
public:
|
||||
// JpegxlParallelRunner interface.
|
||||
static JpegxlParallelRetCode Runner(void* runner_opaque, void* jpegxl_opaque,
|
||||
JpegxlParallelRunInit init,
|
||||
JpegxlParallelRunFunction func,
|
||||
uint32_t start_range, uint32_t end_range);
|
||||
|
||||
// Starts the given number of worker threads and blocks until they are ready.
|
||||
// "num_worker_threads" defaults to one per hyperthread. If zero, all tasks
|
||||
// run on the main thread.
|
||||
explicit ThreadParallelRunner(
|
||||
int num_worker_threads = std::thread::hardware_concurrency());
|
||||
|
||||
// Waits for all threads to exit.
|
||||
~ThreadParallelRunner();
|
||||
|
||||
// Returns number of worker threads created (some may be sleeping and never
|
||||
// wake up in time to participate in Run). Useful for characterizing
|
||||
// performance; 0 means "run on main thread".
|
||||
size_t NumWorkerThreads() const { return num_worker_threads_; }
|
||||
|
||||
// Returns maximum number of main/worker threads that may call Func. Useful
|
||||
// for allocating per-thread storage.
|
||||
size_t NumThreads() const { return num_threads_; }
|
||||
|
||||
// Runs func(thread, thread) on all thread(s) that may participate in Run.
|
||||
// If NumThreads() == 0, runs on the main thread with thread == 0, otherwise
|
||||
// concurrently called by each worker thread in [0, NumThreads()).
|
||||
template <class Func>
|
||||
void RunOnEachThread(const Func& func) {
|
||||
if (num_worker_threads_ == 0) {
|
||||
const int thread = 0;
|
||||
func(thread, thread);
|
||||
return;
|
||||
}
|
||||
|
||||
data_func_ =
|
||||
reinterpret_cast<JpegxlParallelRunFunction>(&CallClosure<Func>);
|
||||
jpegxl_opaque_ = const_cast<void*>(static_cast<const void*>(&func));
|
||||
StartWorkers(kWorkerOnce);
|
||||
WorkersReadyBarrier();
|
||||
}
|
||||
|
||||
private:
|
||||
// After construction and between calls to Run, workers are "ready", i.e.
|
||||
// waiting on worker_start_cv_. They are "started" by sending a "command"
|
||||
// and notifying all worker_start_cv_ waiters. (That is why all workers
|
||||
// must be ready/waiting - otherwise, the notification will not reach all of
|
||||
// them and the main thread waits in vain for them to report readiness.)
|
||||
using WorkerCommand = uint64_t;
|
||||
|
||||
// Special values; all others encode the begin/end parameters. Note that all
|
||||
// these are no-op ranges (begin >= end) and therefore never used to encode
|
||||
// ranges.
|
||||
static constexpr WorkerCommand kWorkerWait = ~1ULL;
|
||||
static constexpr WorkerCommand kWorkerOnce = ~2ULL;
|
||||
static constexpr WorkerCommand kWorkerExit = ~3ULL;
|
||||
|
||||
// Calls f(task, thread). Used for type erasure of Func arguments. The
|
||||
// signature must match JpegxlParallelRunFunction, hence a void* argument.
|
||||
template <class Closure>
|
||||
static void CallClosure(void* f, const uint32_t task, const size_t thread) {
|
||||
(*reinterpret_cast<const Closure*>(f))(task, thread);
|
||||
}
|
||||
|
||||
void WorkersReadyBarrier() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
// Typically only a single iteration.
|
||||
while (workers_ready_ != threads_.size()) {
|
||||
workers_ready_cv_.wait(lock);
|
||||
}
|
||||
workers_ready_ = 0;
|
||||
|
||||
// Safely handle spurious worker wakeups.
|
||||
worker_start_command_ = kWorkerWait;
|
||||
}
|
||||
|
||||
// Precondition: all workers are ready.
|
||||
void StartWorkers(const WorkerCommand worker_command) {
|
||||
mutex_.lock();
|
||||
worker_start_command_ = worker_command;
|
||||
// Workers will need this lock, so release it before they wake up.
|
||||
mutex_.unlock();
|
||||
worker_start_cv_.notify_all();
|
||||
}
|
||||
|
||||
// Attempts to reserve and perform some work from the global range of tasks,
|
||||
// which is encoded within "command". Returns after all tasks are reserved.
|
||||
static void RunRange(ThreadParallelRunner* self, const WorkerCommand command,
|
||||
const int thread);
|
||||
|
||||
static void ThreadFunc(ThreadParallelRunner* self, int thread);
|
||||
|
||||
// Unmodified after ctor, but cannot be const because we call thread::join().
|
||||
std::vector<std::thread> threads_;
|
||||
|
||||
const uint32_t num_worker_threads_; // == threads_.size()
|
||||
const uint32_t num_threads_;
|
||||
|
||||
std::atomic<int> depth_{0}; // detects if Run is re-entered (not supported).
|
||||
|
||||
std::mutex mutex_; // guards both cv and their variables.
|
||||
std::condition_variable workers_ready_cv_;
|
||||
uint32_t workers_ready_ = 0;
|
||||
std::condition_variable worker_start_cv_;
|
||||
WorkerCommand worker_start_command_;
|
||||
|
||||
// Written by main thread, read by workers (after mutex lock/unlock).
|
||||
JpegxlParallelRunFunction data_func_;
|
||||
void* jpegxl_opaque_;
|
||||
|
||||
// Updated by workers; padding avoids false sharing.
|
||||
uint8_t padding1[64];
|
||||
std::atomic<uint32_t> num_reserved_{0};
|
||||
uint8_t padding2[64];
|
||||
};
|
||||
|
||||
} // namespace jpegxl
|
||||
|
||||
#endif /* JPEGXL_THREAD_PARALLEL_RUNNER_H_ */
|
||||
+438
@@ -0,0 +1,438 @@
|
||||
# Copyright (c) the JPEG XL Project
|
||||
#
|
||||
# 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.
|
||||
|
||||
set(JPEGXL_MAJOR_VERSION 0)
|
||||
set(JPEGXL_MINOR_VERSION 0)
|
||||
set(JPEGXL_PATCH_VERSION 1)
|
||||
set(JPEGXL_LIBRARY_VERSION
|
||||
"${JPEGXL_MAJOR_VERSION}.${JPEGXL_MINOR_VERSION}.${JPEGXL_PATCH_VERSION}")
|
||||
|
||||
# This the library API compatibility version.
|
||||
set(JPEGXL_LIBRARY_SOVERSION "${JPEGXL_MAJOR_VERSION}")
|
||||
|
||||
# TODO(deymo): Split this into encoder and decoder targets
|
||||
set(JPEGXL_INTERNAL_SOURCES
|
||||
jxl/modular/config.h
|
||||
jxl/modular/encoding/context_predict.h
|
||||
jxl/modular/encoding/encoding.cpp
|
||||
jxl/modular/encoding/encoding.h
|
||||
jxl/modular/encoding/weighted_predict.h
|
||||
jxl/modular/image/image.cpp
|
||||
jxl/modular/image/image.h
|
||||
jxl/modular/ma/bit.cpp
|
||||
jxl/modular/ma/bit.h
|
||||
jxl/modular/ma/chance.cpp
|
||||
jxl/modular/ma/chance.h
|
||||
jxl/modular/ma/compound.h
|
||||
jxl/modular/ma/compound_enc.h
|
||||
jxl/modular/ma/rac.h
|
||||
jxl/modular/ma/rac_enc.h
|
||||
jxl/modular/ma/symbol.h
|
||||
jxl/modular/ma/symbol_enc.h
|
||||
jxl/modular/ma/util.h
|
||||
jxl/modular/memio.h
|
||||
jxl/modular/transform/near-lossless.h
|
||||
jxl/modular/transform/palette.h
|
||||
jxl/modular/transform/quantize.h
|
||||
jxl/modular/transform/squeeze.h
|
||||
jxl/modular/transform/subsample.h
|
||||
jxl/modular/transform/subtractgreen.h
|
||||
jxl/modular/transform/transform.cpp
|
||||
jxl/modular/transform/transform.h
|
||||
jxl/modular/transform/ycocg.h
|
||||
jxl/modular/util.h
|
||||
jxl/ac_context.h
|
||||
jxl/ac_strategy.cc
|
||||
jxl/ac_strategy.h
|
||||
jxl/ans_common.cc
|
||||
jxl/ans_common.h
|
||||
jxl/ans_params.h
|
||||
jxl/ar_control_field.cc
|
||||
jxl/ar_control_field.h
|
||||
jxl/aux_out.cc
|
||||
jxl/aux_out.h
|
||||
jxl/aux_out.h
|
||||
jxl/aux_out_fwd.h
|
||||
jxl/base/arch_specific.cc
|
||||
jxl/base/arch_specific.h
|
||||
jxl/base/bits.h
|
||||
jxl/base/byte_order.h
|
||||
jxl/base/cache_aligned.cc
|
||||
jxl/base/cache_aligned.h
|
||||
jxl/base/compiler_specific.h
|
||||
jxl/base/data_parallel.cc
|
||||
jxl/base/data_parallel.h
|
||||
jxl/base/descriptive_statistics.cc
|
||||
jxl/base/descriptive_statistics.h
|
||||
jxl/base/fast_log.h
|
||||
jxl/base/file_io.h
|
||||
jxl/base/iaca.h
|
||||
jxl/base/os_specific.cc
|
||||
jxl/base/os_specific.h
|
||||
jxl/base/override.h
|
||||
jxl/base/padded_bytes.cc
|
||||
jxl/base/padded_bytes.h
|
||||
jxl/base/profiler.h
|
||||
jxl/base/robust_statistics.h
|
||||
jxl/base/span.h
|
||||
jxl/base/status.cc
|
||||
jxl/base/status.h
|
||||
jxl/base/tsc_timer.h
|
||||
jxl/block.h
|
||||
jxl/brotli.cc
|
||||
jxl/brotli.h
|
||||
jxl/brunsli.cc
|
||||
jxl/brunsli.h
|
||||
jxl/butteraugli/butteraugli.cc
|
||||
jxl/butteraugli/butteraugli.h
|
||||
jxl/chroma_from_luma.cc
|
||||
jxl/chroma_from_luma.h
|
||||
jxl/coeff_order.cc
|
||||
jxl/coeff_order.h
|
||||
jxl/coeff_order_fwd.h
|
||||
jxl/color_encoding.cc
|
||||
jxl/color_encoding.h
|
||||
jxl/color_management.cc
|
||||
jxl/color_management.h
|
||||
jxl/common.h
|
||||
jxl/compressed_dc.cc
|
||||
jxl/compressed_dc.h
|
||||
jxl/convolve.h
|
||||
jxl/dct.cc
|
||||
jxl/dct.h
|
||||
jxl/dct_simd_4.h
|
||||
jxl/dct_simd_8.h
|
||||
jxl/dct_simd_any.h
|
||||
jxl/dct_util.cc
|
||||
jxl/dct_util.h
|
||||
jxl/dec_ans.cc
|
||||
jxl/dec_ans.h
|
||||
jxl/dec_bit_reader.h
|
||||
jxl/dec_cache.h
|
||||
jxl/dec_context_map.cc
|
||||
jxl/dec_context_map.h
|
||||
jxl/dec_file.cc
|
||||
jxl/dec_file.h
|
||||
jxl/dec_frame.cc
|
||||
jxl/dec_frame.h
|
||||
jxl/dec_group.cc
|
||||
jxl/dec_group.h
|
||||
jxl/dec_modular.cc
|
||||
jxl/dec_modular.h
|
||||
jxl/dec_params.h
|
||||
jxl/dec_reconstruct.cc
|
||||
jxl/dec_reconstruct.h
|
||||
jxl/dec_xyb.cc
|
||||
jxl/dec_xyb.h
|
||||
jxl/detect_dots.cc
|
||||
jxl/detect_dots.h
|
||||
jxl/dot_dictionary.cc
|
||||
jxl/dot_dictionary.h
|
||||
jxl/enc_ac_strategy.cc
|
||||
jxl/enc_ac_strategy.h
|
||||
jxl/enc_adaptive_quantization.cc
|
||||
jxl/enc_adaptive_quantization.h
|
||||
jxl/enc_ans.cc
|
||||
jxl/enc_ans.h
|
||||
jxl/enc_bit_writer.cc
|
||||
jxl/enc_bit_writer.h
|
||||
jxl/enc_butteraugli_comparator.cc
|
||||
jxl/enc_butteraugli_comparator.h
|
||||
jxl/enc_cache.cc
|
||||
jxl/enc_cache.h
|
||||
jxl/enc_cluster.cc
|
||||
jxl/enc_cluster.h
|
||||
jxl/enc_comparator.cc
|
||||
jxl/enc_comparator.h
|
||||
jxl/enc_context_map.cc
|
||||
jxl/enc_context_map.h
|
||||
jxl/enc_file.cc
|
||||
jxl/enc_file.h
|
||||
jxl/enc_frame.cc
|
||||
jxl/enc_frame.h
|
||||
jxl/enc_gamma_correct.h
|
||||
jxl/enc_group.cc
|
||||
jxl/enc_group.h
|
||||
jxl/enc_modular.cc
|
||||
jxl/enc_modular.h
|
||||
jxl/enc_params.h
|
||||
jxl/enc_xyb.cc
|
||||
jxl/enc_xyb.h
|
||||
jxl/entropy_coder.cc
|
||||
jxl/entropy_coder.h
|
||||
jxl/epf.cc
|
||||
jxl/epf.h
|
||||
jxl/external_image.cc
|
||||
jxl/external_image.h
|
||||
jxl/field_encodings.h
|
||||
jxl/fields.cc
|
||||
jxl/fields.h
|
||||
jxl/frame_header.cc
|
||||
jxl/frame_header.h
|
||||
jxl/gaborish.cc
|
||||
jxl/gaborish.h
|
||||
jxl/gauss_blur.cc
|
||||
jxl/gauss_blur.h
|
||||
jxl/headers.cc
|
||||
jxl/headers.h
|
||||
jxl/icc_codec.cc
|
||||
jxl/icc_codec.h
|
||||
jxl/image.cc
|
||||
jxl/image.h
|
||||
jxl/image_bundle.cc
|
||||
jxl/image_bundle.h
|
||||
jxl/image_ops.h
|
||||
jxl/jpegxl/decode.cc
|
||||
jxl/jxl_inspection.h
|
||||
jxl/lehmer_code.h
|
||||
jxl/linalg.cc
|
||||
jxl/linalg.h
|
||||
jxl/loop_filter.cc
|
||||
jxl/loop_filter.h
|
||||
jxl/memory_manager_internal.cc
|
||||
jxl/memory_manager_internal.h
|
||||
jxl/multiframe.cc
|
||||
jxl/multiframe.h
|
||||
jxl/noise.cc
|
||||
jxl/noise.h
|
||||
jxl/noise_distributions.h
|
||||
jxl/opsin_params.cc
|
||||
jxl/opsin_params.h
|
||||
jxl/optimize.cc
|
||||
jxl/optimize.h
|
||||
jxl/passes_state.cc
|
||||
jxl/passes_state.h
|
||||
jxl/patch_dictionary.cc
|
||||
jxl/patch_dictionary.h
|
||||
jxl/predictor.cc
|
||||
jxl/predictor.h
|
||||
jxl/quant_weights.cc
|
||||
jxl/quant_weights.h
|
||||
jxl/quantizer.cc
|
||||
jxl/quantizer.h
|
||||
jxl/rational_polynomial.h
|
||||
jxl/splines.cc
|
||||
jxl/splines.h
|
||||
jxl/splines_fastmath.h
|
||||
jxl/toc.cc
|
||||
jxl/toc.h
|
||||
jxl/xorshift128plus.h
|
||||
)
|
||||
|
||||
set(JPEGXL_INTERNAL_FLAGS
|
||||
# F_FLAGS
|
||||
-fmerge-all-constants
|
||||
-fno-builtin-fwrite
|
||||
-fno-builtin-fread
|
||||
|
||||
# WARN_FLAGS
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wc++11-compat
|
||||
-Wc++2a-extensions
|
||||
-Wdeprecated-increment-bool
|
||||
-Wfloat-overflow-conversion
|
||||
-Wfloat-zero-conversion
|
||||
-Wfor-loop-analysis
|
||||
-Wformat-security
|
||||
-Wgnu-redeclared-enum
|
||||
-Wimplicit-fallthrough
|
||||
-Winfinite-recursion
|
||||
-Wliteral-conversion
|
||||
-Wno-c++98-compat
|
||||
-Wno-register # Needed by public headers in lcms
|
||||
-Wno-unused-command-line-argument
|
||||
-Wno-sign-compare
|
||||
-Wno-unused-function
|
||||
-Wno-unused-parameter
|
||||
-Wnon-virtual-dtor
|
||||
-Woverloaded-virtual
|
||||
-Wprivate-header
|
||||
-Wself-assign
|
||||
-Wstring-conversion
|
||||
-Wtautological-overlap-compare
|
||||
-Wthread-safety-analysis
|
||||
-Wundefined-func-template
|
||||
-Wunused-comparison
|
||||
-Wvla
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
list(APPEND JPEGXL_INTERNAL_FLAGS
|
||||
-Wno-c++98-compat-pedantic
|
||||
-Wno-cast-align
|
||||
-Wno-double-promotion
|
||||
-Wno-float-equal
|
||||
-Wno-format-nonliteral
|
||||
-Wno-global-constructors
|
||||
-Wno-language-extension-token
|
||||
-Wno-missing-prototypes
|
||||
-Wno-shadow
|
||||
-Wno-shadow-field-in-constructor
|
||||
-Wno-sign-conversion
|
||||
-Wno-unused-member-function
|
||||
-Wno-unused-template
|
||||
-Wno-used-but-marked-unused
|
||||
-Wno-zero-as-null-pointer-constant
|
||||
)
|
||||
else()
|
||||
list(APPEND JPEGXL_INTERNAL_FLAGS
|
||||
-fno-signed-char
|
||||
-fsized-deallocation
|
||||
-fnew-alignment=8
|
||||
-fno-cxx-exceptions
|
||||
-fno-exceptions
|
||||
-fno-slp-vectorize
|
||||
-fno-vectorize
|
||||
|
||||
# Language flags
|
||||
-disable-free
|
||||
-disable-llvm-verifier
|
||||
-fmath-errno
|
||||
)
|
||||
endif ()
|
||||
|
||||
set(JPEGXL_INTERNAL_LIBS
|
||||
brotlicommon-static
|
||||
brotlienc-static
|
||||
brotlidec-static
|
||||
brunslicommon-static
|
||||
brunslidec-static
|
||||
brunslienc-static
|
||||
hwy
|
||||
Threads::Threads
|
||||
${CMAKE_DL_LIBS}
|
||||
)
|
||||
set(JPEGXL_LIBRARY_REQUIRES
|
||||
"libbrotlicommon libbrotlienc libbrotlidec")
|
||||
|
||||
if (JPEGXL_ENABLE_SKCMS)
|
||||
list(APPEND JPEGXL_INTERNAL_FLAGS -DJPEGXL_ENABLE_SKCMS=1)
|
||||
list(APPEND JPEGXL_INTERNAL_LIBS skcms)
|
||||
else ()
|
||||
list(APPEND JPEGXL_INTERNAL_LIBS lcms2)
|
||||
endif ()
|
||||
|
||||
if(JPEGXL_ENABLE_COVERAGE)
|
||||
set(JPEGXL_COVERAGE_FLAGS
|
||||
-g -O0 -fprofile-arcs -ftest-coverage -DJXL_DISABLE_SLOW_TESTS
|
||||
-DJXL_ENABLE_ASSERT=0
|
||||
)
|
||||
endif()
|
||||
|
||||
# Object library. This is used to hold the set of objects and properties.
|
||||
add_library(jpegxl-obj OBJECT ${JPEGXL_INTERNAL_SOURCES})
|
||||
target_compile_options(jpegxl-obj PRIVATE ${JPEGXL_INTERNAL_FLAGS})
|
||||
target_compile_options(jpegxl-obj PUBLIC ${JPEGXL_COVERAGE_FLAGS})
|
||||
if (NOT "${JPEGXL_EMSCRIPTEN}")
|
||||
set_property(TARGET jpegxl-obj PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
target_include_directories(jpegxl-obj PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
$<TARGET_PROPERTY:hwy,INCLUDE_DIRECTORIES>
|
||||
$<TARGET_PROPERTY:brotlicommon-static,INCLUDE_DIRECTORIES>
|
||||
$<TARGET_PROPERTY:brunslicommon-static,INCLUDE_DIRECTORIES>
|
||||
)
|
||||
target_compile_definitions(jpegxl-obj PUBLIC
|
||||
JPEGXL_MAJOR_VERSION=${JPEGXL_MAJOR_VERSION}
|
||||
JPEGXL_MINOR_VERSION=${JPEGXL_MINOR_VERSION}
|
||||
JPEGXL_PATCH_VERSION=${JPEGXL_PATCH_VERSION}
|
||||
# Used to determine if we are building the library when defined or just
|
||||
# including the library when not defined. This is public so libjpegxl shared
|
||||
# library gets this define too.
|
||||
JPEGXL_INTERNAL_LIBRARY_BUILD
|
||||
)
|
||||
|
||||
if (JPEGXL_ENABLE_SKCMS)
|
||||
target_include_directories(jpegxl-obj PRIVATE
|
||||
$<TARGET_PROPERTY:skcms,INCLUDE_DIRECTORIES>
|
||||
)
|
||||
else ()
|
||||
target_include_directories(jpegxl-obj PRIVATE
|
||||
$<TARGET_PROPERTY:lcms2,INCLUDE_DIRECTORIES>
|
||||
)
|
||||
endif ()
|
||||
|
||||
# Headers for exporting/importing public headers
|
||||
include(GenerateExportHeader)
|
||||
# TODO(deymo): Add these visibility properties to the static dependencies of
|
||||
# jpegxl-obj since those are currently compiled with the default visibility.
|
||||
set_target_properties(jpegxl-obj PROPERTIES
|
||||
CXX_VISIBILITY_PRESET hidden
|
||||
VISIBILITY_INLINES_HIDDEN 1
|
||||
DEFINE_SYMBOL JPEGXL_INTERNAL_LIBRARY_BUILD
|
||||
)
|
||||
generate_export_header(jpegxl-obj
|
||||
BASE_NAME JPEGXL
|
||||
EXPORT_FILE_NAME include/jpegxl/jpegxl_export.h)
|
||||
target_include_directories(jpegxl-obj PUBLIC
|
||||
${CMAKE_BINARY_DIR}/include)
|
||||
|
||||
# Private static library. This exposes all the internal functions and is used
|
||||
# for tests.
|
||||
add_library(jpegxl-static STATIC $<TARGET_OBJECTS:jpegxl-obj>)
|
||||
target_link_libraries(jpegxl-static
|
||||
PUBLIC ${JPEGXL_COVERAGE_FLAGS} ${JPEGXL_INTERNAL_LIBS} hwy)
|
||||
target_include_directories(jpegxl-static PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||
"${CMAKE_BINARY_DIR}/include")
|
||||
|
||||
# gperftools (TCMalloc) does not compile on OSX.
|
||||
# TODO(deymo): Move TCMalloc linkage to the tools/ directory since the library
|
||||
# shouldn't do any allocs anyway.
|
||||
if(${JPEGXL_ENABLE_TCMALLOC})
|
||||
target_link_libraries(jpegxl-static PUBLIC gperftools)
|
||||
endif() # JPEGXL_ENABLE_TCMALLOC
|
||||
|
||||
if (NOT "${JPEGXL_EMSCRIPTEN}")
|
||||
|
||||
# Public shared library.
|
||||
add_library(jpegxl SHARED $<TARGET_OBJECTS:jpegxl-obj>)
|
||||
target_link_libraries(jpegxl PUBLIC ${JPEGXL_COVERAGE_FLAGS})
|
||||
target_link_libraries(jpegxl PRIVATE ${JPEGXL_INTERNAL_LIBS})
|
||||
target_include_directories(jpegxl PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||
"${CMAKE_BINARY_DIR}/include")
|
||||
set_target_properties(jpegxl PROPERTIES
|
||||
VERSION ${JPEGXL_LIBRARY_VERSION}
|
||||
SOVERSION ${JPEGXL_LIBRARY_SOVERSION})
|
||||
|
||||
# Add a jpegxl.version file as a version script to tag symbols with the
|
||||
# appropriate version number. This script is also used to limit what's exposed
|
||||
# in the shared library from the static dependencies bundled here.
|
||||
set_target_properties(jpegxl PROPERTIES LINK_DEPENDS ${CMAKE_SOURCE_DIR}/jxl/jpegxl.version)
|
||||
if(APPLE)
|
||||
set_property(TARGET jpegxl APPEND_STRING PROPERTY
|
||||
LINK_FLAGS "-Wl,-exported_symbols_list,${CMAKE_SOURCE_DIR}/jxl/jpegxl_osx.syms")
|
||||
elseif(WIN32)
|
||||
# Nothing needed here, we use __declspec(dllexport) (jpegxl_export.h)
|
||||
else()
|
||||
set_property(TARGET jpegxl APPEND_STRING PROPERTY
|
||||
LINK_FLAGS " -Wl,--version-script=${CMAKE_SOURCE_DIR}/jxl/jpegxl.version")
|
||||
endif() # APPLE
|
||||
install(TARGETS jpegxl
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
install(DIRECTORY ${CMAKE_SOURCE_DIR}/include/jpegxl
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/jpegxl")
|
||||
install(DIRECTORY ${CMAKE_BINARY_DIR}/include/jpegxl
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/jpegxl")
|
||||
|
||||
# Add a pkg-config file for libjpegxl.
|
||||
configure_file("${CMAKE_SOURCE_DIR}/jxl/libjpegxl.pc.in" "libjpegxl.pc" @ONLY)
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libjpegxl.pc"
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
|
||||
|
||||
endif() # JPEGXL_EMSCRIPTEN
|
||||
@@ -0,0 +1,86 @@
|
||||
# Copyright (c) the JPEG XL Project
|
||||
#
|
||||
# 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.
|
||||
|
||||
set(JPEGXL_EXTRAS_SOURCES
|
||||
jxl/extras/codec.cc
|
||||
jxl/extras/codec.h
|
||||
jxl/extras/codec_apng.cc
|
||||
jxl/extras/codec_apng.h
|
||||
jxl/extras/codec_gif.cc
|
||||
jxl/extras/codec_gif.h
|
||||
jxl/extras/codec_jpg.cc
|
||||
jxl/extras/codec_jpg.h
|
||||
jxl/extras/codec_pgx.cc
|
||||
jxl/extras/codec_pgx.h
|
||||
jxl/extras/codec_png.cc
|
||||
jxl/extras/codec_png.h
|
||||
jxl/extras/codec_pnm.cc
|
||||
jxl/extras/codec_pnm.h
|
||||
)
|
||||
|
||||
find_package(GIF REQUIRED 5)
|
||||
find_package(JPEG REQUIRED)
|
||||
find_package(ZLIB REQUIRED) # dependency of PNG
|
||||
find_package(PNG REQUIRED)
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(OpenEXR IMPORTED_TARGET OpenEXR)
|
||||
|
||||
foreach (lib gif jpeg png zlib)
|
||||
string(TOUPPER "${lib}" LIB)
|
||||
find_library(${LIB}_STATIC_LIBRARY
|
||||
NAMES lib${lib}.a
|
||||
HINTS
|
||||
ENV ${LIB}_DIR
|
||||
PATH_SUFFIXES lib
|
||||
)
|
||||
if(NOT "${${LIB}_STATIC_LIBRARY}" STREQUAL "${LIB}_STATIC_LIBRARY-NOTFOUND")
|
||||
list(APPEND JPEGXL_STATIC_EXTRAS_LIBS "${${LIB}_STATIC_LIBRARY}")
|
||||
else()
|
||||
message("Warning: lib${lib}.a not found, using ${${LIB}_LIBRARIES}")
|
||||
list(APPEND JPEGXL_STATIC_EXTRAS_LIBS "${${LIB}_LIBRARIES}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# We only define a static library for jpegxl_extras since it uses internal parts
|
||||
# of jpegxl library which are not accessible from outside the library in the
|
||||
# shared library case.
|
||||
add_library(jpegxl_extras-static STATIC "${JPEGXL_EXTRAS_SOURCES}")
|
||||
target_compile_options(jpegxl_extras-static PRIVATE "${JPEGXL_INTERNAL_FLAGS}")
|
||||
set_property(TARGET jpegxl_extras-static PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
target_include_directories(jpegxl_extras-static PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
"${GIF_INCLUDE_DIRS}"
|
||||
"${JPEG_INCLUDE_DIRS}"
|
||||
"${PNG_INCLUDE_DIRS}"
|
||||
)
|
||||
|
||||
target_link_libraries(jpegxl_extras-static PUBLIC
|
||||
jpegxl-static
|
||||
lodepng
|
||||
${JPEGXL_STATIC_EXTRAS_LIBS}
|
||||
)
|
||||
|
||||
if (JPEGXL_ENABLE_SJPEG)
|
||||
target_compile_definitions(jpegxl_extras-static PUBLIC -DJPEGXL_ENABLE_SJPEG=1)
|
||||
target_link_libraries(jpegxl_extras-static PUBLIC sjpeg)
|
||||
endif ()
|
||||
|
||||
if (OpenEXR_FOUND)
|
||||
target_sources(jpegxl_extras-static PRIVATE
|
||||
jxl/extras/codec_exr.cc
|
||||
jxl/extras/codec_exr.h
|
||||
)
|
||||
target_compile_definitions(jpegxl_extras-static PRIVATE -DJPEGXL_ENABLE_EXR=1)
|
||||
target_link_libraries(jpegxl_extras-static PUBLIC PkgConfig::OpenEXR)
|
||||
endif ()
|
||||
@@ -0,0 +1,36 @@
|
||||
# Copyright (c) the JPEG XL Project
|
||||
#
|
||||
# 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.
|
||||
|
||||
set(JPEGXL_THREADS_SOURCES
|
||||
jxl/threads/thread_parallel_runner.cc
|
||||
)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
# We only provide a static jpegxl_threads library since it is a non-stable C++
|
||||
# API and very small. It is likely better to bundle it in the programs that
|
||||
# need it.
|
||||
add_library(jpegxl_threads STATIC "${JPEGXL_THREADS_SOURCES}")
|
||||
target_compile_options(jpegxl_threads PRIVATE "${JPEGXL_INTERNAL_FLAGS}")
|
||||
set_property(TARGET jpegxl_threads PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
target_include_directories(jpegxl_threads PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
target_link_libraries(jpegxl_threads
|
||||
PUBLIC
|
||||
Threads::Threads
|
||||
PRIVATE
|
||||
#TODO(deymo): Change this to use the shared library. Right now it causes
|
||||
# problems when including both the shared and static library.
|
||||
jpegxl-static
|
||||
)
|
||||
@@ -0,0 +1,105 @@
|
||||
# Copyright (c) the JPEG XL Project
|
||||
#
|
||||
# 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.
|
||||
|
||||
set(TEST_FILES
|
||||
jxl/ac_strategy_test.cc
|
||||
jxl/adaptive_reconstruction_test.cc
|
||||
jxl/ans_common_test.cc
|
||||
jxl/ans_test.cc
|
||||
jxl/bit_reader_test.cc
|
||||
jxl/bits_test.cc
|
||||
jxl/brotli_test.cc
|
||||
jxl/brunsli_test.cc
|
||||
jxl/byte_order_test.cc
|
||||
jxl/coeff_order_test.cc
|
||||
jxl/color_encoding_test.cc
|
||||
jxl/color_management_test.cc
|
||||
jxl/compressed_dc_test.cc
|
||||
jxl/compressed_image_test.cc
|
||||
jxl/convolve_test.cc
|
||||
jxl/data_parallel_test.cc
|
||||
jxl/dct_test.cc
|
||||
jxl/dct_util_test.cc
|
||||
jxl/descriptive_statistics_test.cc
|
||||
jxl/entropy_coder_test.cc
|
||||
jxl/external_image_test.cc
|
||||
jxl/extras/codec_test.cc
|
||||
jxl/fields_test.cc
|
||||
jxl/gaborish_test.cc
|
||||
jxl/gamma_correct_test.cc
|
||||
jxl/gradient_test.cc
|
||||
jxl/iaca_test.cc
|
||||
jxl/icc_codec_test.cc
|
||||
jxl/image_test.cc
|
||||
jxl/jpeg_kpasses_test.cc
|
||||
jxl/jpegxl/decode_test.cc
|
||||
jxl/jxl_test.cc
|
||||
jxl/lehmer_code_test.cc
|
||||
jxl/linalg_test.cc
|
||||
jxl/opsin_image_test.cc
|
||||
jxl/opsin_inverse_test.cc
|
||||
jxl/optimize_test.cc
|
||||
jxl/padded_bytes_test.cc
|
||||
jxl/passes_test.cc
|
||||
jxl/predictor_test.cc
|
||||
jxl/preview_test.cc
|
||||
jxl/quant_weights_test.cc
|
||||
jxl/quantizer_test.cc
|
||||
jxl/rational_polynomial_test.cc
|
||||
jxl/robust_statistics_test.cc
|
||||
jxl/speed_tier_test.cc
|
||||
jxl/splines_test.cc
|
||||
jxl/threads/thread_parallel_runner_test.cc
|
||||
jxl/toc_test.cc
|
||||
jxl/xorshift128plus_test.cc
|
||||
)
|
||||
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/tests)
|
||||
foreach (TESTFILE IN LISTS TEST_FILES)
|
||||
# The TESTNAME is the name without the extension or directory.
|
||||
get_filename_component(TESTNAME ${TESTFILE} NAME_WE)
|
||||
add_executable(${TESTNAME} ${TESTFILE})
|
||||
target_compile_definitions(${TESTNAME} PRIVATE
|
||||
-DTEST_DATA_PATH="${CMAKE_CURRENT_SOURCE_DIR}/third_party/testdata")
|
||||
target_compile_options(${TESTNAME} PRIVATE
|
||||
${JPEGXL_INTERNAL_FLAGS}
|
||||
# Add coverage flags to the test binary so code in the private headers of
|
||||
# the library is also instrumented when running tests that execute it.
|
||||
${JPEGXL_COVERAGE_FLAGS}
|
||||
)
|
||||
target_link_libraries(${TESTNAME}
|
||||
jpegxl-static
|
||||
jpegxl_threads
|
||||
jpegxl_extras-static
|
||||
gmock
|
||||
gtest
|
||||
gtest_main
|
||||
)
|
||||
# Output test targets in the test directory.
|
||||
set_target_properties(${TESTNAME} PROPERTIES PREFIX "tests/")
|
||||
if (WIN32 AND ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
|
||||
set_target_properties(${TESTNAME} PROPERTIES COMPILE_FLAGS "-Wno-error")
|
||||
endif ()
|
||||
if(${CMAKE_VERSION} VERSION_LESS "3.10.3")
|
||||
gtest_discover_tests(${TESTNAME} TIMEOUT 30)
|
||||
else ()
|
||||
gtest_discover_tests(${TESTNAME} DISCOVERY_TIMEOUT 30)
|
||||
endif ()
|
||||
endforeach ()
|
||||
|
||||
# googletest doesn't compile clean with clang-cl (-Wundef)
|
||||
if (WIN32 AND ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
|
||||
set_target_properties(gtest PROPERTIES COMPILE_FLAGS "-Wno-error")
|
||||
set_target_properties(gtest_main PROPERTIES COMPILE_FLAGS "-Wno-error")
|
||||
endif ()
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_AC_CONTEXT_H_
|
||||
#define JXL_AC_CONTEXT_H_
|
||||
|
||||
#include "jxl/base/bits.h"
|
||||
#include "jxl/base/status.h"
|
||||
#include "jxl/coeff_order_fwd.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// Block context used for scanning order, number of non-zeros, AC coefficients.
|
||||
// Equal to the channel.
|
||||
constexpr uint32_t kDCTOrderContextStart = 0;
|
||||
constexpr uint32_t kOrderContexts = 10;
|
||||
|
||||
// The number of predicted nonzeros goes from 0 to 1008. We use
|
||||
// ceil(log2(predicted+1)) as a context for the number of nonzeros, so from 0 to
|
||||
// 10, inclusive.
|
||||
constexpr uint32_t kNonZeroBuckets = 37;
|
||||
|
||||
static const uint16_t kCoeffFreqContext[64] = {
|
||||
0xBAD, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,
|
||||
23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26,
|
||||
27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30,
|
||||
};
|
||||
|
||||
static const uint16_t kCoeffNumNonzeroContext[64] = {
|
||||
0xBAD, 0, 31, 62, 62, 93, 93, 93, 93, 123, 123, 123, 123,
|
||||
152, 152, 152, 152, 152, 152, 152, 152, 180, 180, 180, 180, 180,
|
||||
180, 180, 180, 180, 180, 180, 180, 206, 206, 206, 206, 206, 206,
|
||||
206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206,
|
||||
206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206,
|
||||
};
|
||||
|
||||
// Supremum of ZeroDensityContext(x, y) + 1.
|
||||
constexpr int kZeroDensityContextCount = 458;
|
||||
|
||||
/* This function is used for entropy-sources pre-clustering.
|
||||
*
|
||||
* Ideally, each combination of |nonzeros_left| and |k| should go to its own
|
||||
* bucket; but it implies (64 * 63 / 2) == 2016 buckets. If there is other
|
||||
* dimension (e.g. block context), then number of primary clusters becomes too
|
||||
* big.
|
||||
*
|
||||
* To solve this problem, |nonzeros_left| and |k| values are clustered. It is
|
||||
* known that their sum is at most 64, consequently, the total number buckets
|
||||
* is at most A(64) * B(64).
|
||||
*/
|
||||
// TODO(user): investigate, why disabling pre-clustering makes entropy code
|
||||
// less dense. Perhaps we would need to add HQ clustering algorithm that would
|
||||
// be able to squeeze better by spending more CPU cycles.
|
||||
static JXL_INLINE size_t ZeroDensityContext(size_t nonzeros_left, size_t k,
|
||||
size_t covered_blocks,
|
||||
size_t log2_covered_blocks,
|
||||
size_t prev) {
|
||||
JXL_DASSERT(1 << log2_covered_blocks == covered_blocks);
|
||||
nonzeros_left = (nonzeros_left + covered_blocks - 1) >> log2_covered_blocks;
|
||||
k >>= log2_covered_blocks;
|
||||
JXL_DASSERT(k > 0);
|
||||
JXL_DASSERT(k < 64);
|
||||
JXL_DASSERT(nonzeros_left > 0);
|
||||
JXL_DASSERT(nonzeros_left + k < 65);
|
||||
return (kCoeffNumNonzeroContext[nonzeros_left] + kCoeffFreqContext[k]) * 2 +
|
||||
prev;
|
||||
}
|
||||
|
||||
// Context map for AC coefficients consists of 2 blocks:
|
||||
// |kOrderContexts x : context for number of non-zeros in the block
|
||||
// kNonZeroBuckets| computed from block context and predicted value
|
||||
// (based top and left values)
|
||||
// |kOrderContexts x : context for AC coefficient symbols,
|
||||
// kZeroDensityContextCount| computed from block context,
|
||||
// number of non-zeros left and
|
||||
// index in scan order
|
||||
constexpr uint32_t kNumContexts = (kOrderContexts * kNonZeroBuckets) +
|
||||
(kOrderContexts * kZeroDensityContextCount);
|
||||
|
||||
// Non-zero context is based on number of non-zeros and block context.
|
||||
// For better clustering, contexts with same number of non-zeros are grouped.
|
||||
static inline uint32_t NonZeroContext(uint32_t non_zeros, uint32_t block_ctx) {
|
||||
uint32_t ctx;
|
||||
if (non_zeros >= 64) non_zeros = 64;
|
||||
if (non_zeros < 8)
|
||||
ctx = non_zeros;
|
||||
else
|
||||
ctx = 4 + non_zeros / 2;
|
||||
return ctx * kOrderContexts + block_ctx;
|
||||
}
|
||||
|
||||
// Non-zero context is based on number of non-zeros and block context.
|
||||
// For better clustering, contexts with same number of non-zeros are grouped.
|
||||
constexpr uint32_t ZeroDensityContextsOffset(uint32_t block_ctx) {
|
||||
return kOrderContexts * kNonZeroBuckets +
|
||||
kZeroDensityContextCount * block_ctx;
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_AC_CONTEXT_H_
|
||||
+1540
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,324 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_AC_STRATEGY_H_
|
||||
#define JXL_AC_STRATEGY_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <hwy/static_targets.h>
|
||||
|
||||
#include "jxl/aux_out_fwd.h"
|
||||
#include "jxl/base/compiler_specific.h"
|
||||
#include "jxl/base/data_parallel.h"
|
||||
#include "jxl/base/status.h"
|
||||
#include "jxl/coeff_order_fwd.h"
|
||||
#include "jxl/common.h"
|
||||
#include "jxl/dct.h"
|
||||
#include "jxl/dct_util.h"
|
||||
#include "jxl/image.h"
|
||||
#include "jxl/image_ops.h"
|
||||
|
||||
// Defines the different kinds of transforms, and heuristics to choose between
|
||||
// them.
|
||||
// `AcStrategy` represents what transform should be used, and which sub-block of
|
||||
// that transform we are currently in. Note that DCT4x4 is applied on all four
|
||||
// 4x4 sub-blocks of an 8x8 block.
|
||||
// `AcStrategyImage` defines which strategy should be used for each 8x8 block
|
||||
// of the image. The highest 4 bits represent the strategy to be used, the
|
||||
// lowest 4 represent the index of the block inside that strategy. Blocks should
|
||||
// be aligned, i.e. 32x32 blocks should only start in positions that are
|
||||
// multiples of 32.
|
||||
|
||||
namespace jxl {
|
||||
|
||||
template <size_t V>
|
||||
struct square_root {
|
||||
static constexpr float value =
|
||||
square_root<V / 2>::value * 1.4142135623730951f;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct square_root<1> {
|
||||
static constexpr float value = 1.0f;
|
||||
};
|
||||
|
||||
// Apply the DCT algorithm-intrinsic constants to DCTResampleScale.
|
||||
// Note that the DCTScales constants give results that are scaled with a factor
|
||||
// proportional to 1/sqrt(N), so we counteract that here.
|
||||
// We also use the fact that 1/(sqrt(N) DCTScales(N)) == sqrt(N) IDCTScales(N)
|
||||
// to avoid a division.
|
||||
template <size_t FROM, size_t TO>
|
||||
constexpr float DCTTotalResampleScale(size_t x) {
|
||||
return square_root<FROM>::value * DCTScales<FROM>()[x] *
|
||||
square_root<TO>::value * IDCTScales<TO>()[x] *
|
||||
DCTResampleScales<FROM, TO>::kScales[x];
|
||||
}
|
||||
|
||||
void AFVDCT4x4(const float* JXL_RESTRICT pixels, float* JXL_RESTRICT coeffs);
|
||||
void AFVIDCT4x4(const float* JXL_RESTRICT coeffs, float* JXL_RESTRICT pixels);
|
||||
|
||||
class AcStrategy {
|
||||
public:
|
||||
// Extremal values for the number of blocks/coefficients of a single strategy.
|
||||
static constexpr size_t kMaxCoeffBlocks = 4;
|
||||
static constexpr size_t kMaxBlockDim = kBlockDim * kMaxCoeffBlocks;
|
||||
static constexpr size_t kMaxCoeffArea = kMaxBlockDim * kMaxBlockDim;
|
||||
|
||||
// Raw strategy types.
|
||||
enum Type : uint32_t {
|
||||
// Regular block size DCT (value matches kQuantKind)
|
||||
DCT = 0,
|
||||
// Encode pixels without transforming (value matches kQuantKind)
|
||||
IDENTITY = 1,
|
||||
// Use 2-by-2 DCT (value matches kQuantKind)
|
||||
DCT2X2 = 2,
|
||||
// Use 4-by-4 DCT (value matches kQuantKind)
|
||||
DCT4X4 = 3,
|
||||
// Use 16-by-16 DCT
|
||||
DCT16X16 = 4,
|
||||
// Use 32-by-32 DCT
|
||||
DCT32X32 = 5,
|
||||
// Use 16-by-8 DCT
|
||||
DCT16X8 = 6,
|
||||
// Use 8-by-16 DCT
|
||||
DCT8X16 = 7,
|
||||
// Use 32-by-8 DCT
|
||||
DCT32X8 = 8,
|
||||
// Use 8-by-32 DCT
|
||||
DCT8X32 = 9,
|
||||
// Use 32-by-16 DCT
|
||||
DCT32X16 = 10,
|
||||
// Use 16-by-32 DCT
|
||||
DCT16X32 = 11,
|
||||
// 4x8 and 8x4 DCT
|
||||
DCT4X8 = 12,
|
||||
DCT8X4 = 13,
|
||||
// Corner-DCT.
|
||||
AFV0 = 14,
|
||||
AFV1 = 15,
|
||||
AFV2 = 16,
|
||||
AFV3 = 17,
|
||||
// Marker for num of valid strategies.
|
||||
kNumValidStrategies
|
||||
};
|
||||
|
||||
static constexpr uint32_t TypeBit(const Type type) {
|
||||
return 1u << static_cast<uint32_t>(type);
|
||||
}
|
||||
|
||||
// Returns true if this block is the first 8x8 block (i.e. top-left) of a
|
||||
// possibly multi-block strategy.
|
||||
JXL_INLINE bool IsFirstBlock() const { return is_first_; }
|
||||
|
||||
JXL_INLINE bool IsMultiblock() const {
|
||||
constexpr uint32_t bits = TypeBit(Type::DCT16X16) |
|
||||
TypeBit(Type::DCT32X32) | TypeBit(Type::DCT16X8) |
|
||||
TypeBit(Type::DCT8X16) | TypeBit(Type::DCT32X8) |
|
||||
TypeBit(Type::DCT8X32) | TypeBit(Type::DCT16X32) |
|
||||
TypeBit(Type::DCT32X16);
|
||||
return ((1u << static_cast<uint32_t>(Strategy())) & bits) != 0;
|
||||
}
|
||||
|
||||
// Returns the raw strategy value. Should only be used for tokenization.
|
||||
JXL_INLINE uint8_t RawStrategy() const {
|
||||
return static_cast<uint8_t>(strategy_);
|
||||
}
|
||||
|
||||
JXL_INLINE Type Strategy() const { return strategy_; }
|
||||
|
||||
// Inverse check
|
||||
static JXL_INLINE constexpr bool IsRawStrategyValid(uint8_t raw_strategy) {
|
||||
return raw_strategy < kNumValidStrategies;
|
||||
}
|
||||
static JXL_INLINE AcStrategy FromRawStrategy(uint8_t raw_strategy) {
|
||||
return FromRawStrategy(static_cast<Type>(raw_strategy));
|
||||
}
|
||||
static JXL_INLINE AcStrategy FromRawStrategy(Type raw_strategy) {
|
||||
return AcStrategy(raw_strategy, /*is_first_block=*/true);
|
||||
}
|
||||
|
||||
// "Natural order" means the order of increasing of "anisotropic" frequency of
|
||||
// continuous version of DCT basis.
|
||||
// Round-trip, for any given strategy s:
|
||||
// X = NaturalCoeffOrder(s)[NaturalCoeffOrderLutN(s)[X]]
|
||||
// X = NaturalCoeffOrderLut(s)[NaturalCoeffOrderN(s)[X]]
|
||||
JXL_INLINE const coeff_order_t* NaturalCoeffOrder() const {
|
||||
return CoeffOrder()->order + RawStrategy() * kMaxCoeffArea;
|
||||
}
|
||||
|
||||
JXL_INLINE const coeff_order_t* NaturalCoeffOrderLut() const {
|
||||
return CoeffOrder()->lut + RawStrategy() * kMaxCoeffArea;
|
||||
}
|
||||
|
||||
// Number of 8x8 blocks that this strategy will cover. 0 for non-top-left
|
||||
// blocks inside a multi-block transform.
|
||||
JXL_INLINE size_t covered_blocks_x() const {
|
||||
static constexpr const uint8_t kLut[] = {1, 1, 1, 1, 2, 4, 1, 2, 1,
|
||||
4, 2, 4, 1, 1, 1, 1, 1, 1};
|
||||
static_assert(sizeof(kLut) / sizeof(*kLut) == kNumValidStrategies,
|
||||
"Update LUT");
|
||||
return kLut[size_t(strategy_)];
|
||||
}
|
||||
|
||||
JXL_INLINE size_t covered_blocks_y() const {
|
||||
static constexpr const uint8_t kLut[] = {1, 1, 1, 1, 2, 4, 2, 1, 4,
|
||||
1, 4, 2, 1, 1, 1, 1, 1, 1};
|
||||
static_assert(sizeof(kLut) / sizeof(*kLut) == kNumValidStrategies,
|
||||
"Update LUT");
|
||||
return kLut[size_t(strategy_)];
|
||||
}
|
||||
|
||||
JXL_INLINE size_t log2_covered_blocks() const {
|
||||
static constexpr const uint8_t kLut[] = {0, 0, 0, 0, 2, 4, 1, 1, 2,
|
||||
2, 3, 3, 0, 0, 0, 0, 0, 0};
|
||||
static_assert(sizeof(kLut) / sizeof(*kLut) == kNumValidStrategies,
|
||||
"Update LUT");
|
||||
return kLut[size_t(strategy_)];
|
||||
}
|
||||
|
||||
// 1 / covered_block_x() / covered_block_y(), for fast division.
|
||||
// Should only be called with block_ == 0.
|
||||
JXL_INLINE float inverse_covered_blocks() const {
|
||||
if (strategy_ == Type::DCT32X32) return 1.0f / 16;
|
||||
if (strategy_ == Type::DCT16X16) return 0.25f;
|
||||
if (strategy_ == Type::DCT8X16 || strategy_ == Type::DCT16X8) return 0.5f;
|
||||
if (strategy_ == Type::DCT8X32 || strategy_ == Type::DCT32X8) return 0.25f;
|
||||
if (strategy_ == Type::DCT32X16 || strategy_ == Type::DCT16X32)
|
||||
return 1.0f / 8;
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
JXL_INLINE float InverseNumACCoefficients() const {
|
||||
JXL_DASSERT(IsFirstBlock());
|
||||
if (strategy_ == Type::DCT32X32) return 1.0f / (32 * 32 - 16);
|
||||
if (strategy_ == Type::DCT16X16) return 1.0f / (16 * 16 - 4);
|
||||
if (strategy_ == Type::DCT8X16 || strategy_ == Type::DCT16X8)
|
||||
return 1.0f / (8 * 16 - 2);
|
||||
if (strategy_ == Type::DCT8X32 || strategy_ == Type::DCT32X8)
|
||||
return 1.0f / (8 * 32 - 4);
|
||||
if (strategy_ == Type::DCT32X16 || strategy_ == Type::DCT16X32)
|
||||
return 1.0f / (32 * 16 - 8);
|
||||
return 1.0f / (8 * 8 - 1);
|
||||
}
|
||||
|
||||
// Pixel to coefficients and vice-versa
|
||||
HWY_ATTR void TransformFromPixels(const float* JXL_RESTRICT pixels,
|
||||
size_t pixels_stride,
|
||||
float* JXL_RESTRICT coefficients) const;
|
||||
HWY_ATTR void TransformToPixels(const float* JXL_RESTRICT coefficients,
|
||||
float* JXL_RESTRICT pixels,
|
||||
size_t pixels_stride) const;
|
||||
|
||||
// Same as above, but for DC image.
|
||||
HWY_ATTR void LowestFrequenciesFromDC(const float* dc, size_t dc_stride,
|
||||
float* llf) const;
|
||||
HWY_ATTR void DCFromLowestFrequencies(const float* block, float* dc,
|
||||
size_t dc_stride) const;
|
||||
|
||||
struct CoeffOrderAndLut {
|
||||
coeff_order_t order[kNumValidStrategies * kMaxCoeffArea];
|
||||
coeff_order_t lut[kNumValidStrategies * kMaxCoeffArea];
|
||||
};
|
||||
|
||||
private:
|
||||
friend class AcStrategyRow;
|
||||
JXL_INLINE AcStrategy(Type strategy, bool is_first)
|
||||
: strategy_(strategy), is_first_(is_first) {
|
||||
JXL_DASSERT(IsMultiblock() || is_first == true);
|
||||
}
|
||||
|
||||
Type strategy_;
|
||||
bool is_first_;
|
||||
|
||||
static const CoeffOrderAndLut* CoeffOrder();
|
||||
};
|
||||
|
||||
// Class to use a certain row of the AC strategy.
|
||||
class AcStrategyRow {
|
||||
public:
|
||||
explicit AcStrategyRow(const uint8_t* row) : row_(row) {}
|
||||
AcStrategy operator[](size_t x) const {
|
||||
return AcStrategy(static_cast<AcStrategy::Type>(row_[x] >> 1), row_[x] & 1);
|
||||
}
|
||||
|
||||
private:
|
||||
const uint8_t* JXL_RESTRICT row_;
|
||||
};
|
||||
|
||||
class AcStrategyImage {
|
||||
public:
|
||||
AcStrategyImage() = default;
|
||||
AcStrategyImage(size_t xsize, size_t ysize);
|
||||
AcStrategyImage(AcStrategyImage&&) = default;
|
||||
AcStrategyImage& operator=(AcStrategyImage&&) = default;
|
||||
|
||||
void FillDCT8() {
|
||||
FillImage<uint8_t>((static_cast<uint8_t>(AcStrategy::Type::DCT) << 1) | 1,
|
||||
&layers_);
|
||||
}
|
||||
|
||||
void FillInvalid() { FillImage(INVALID, &layers_); }
|
||||
|
||||
void Set(size_t x, size_t y, AcStrategy::Type type) {
|
||||
#if JXL_ENABLE_ASSERT
|
||||
AcStrategy acs = AcStrategy::FromRawStrategy(type);
|
||||
#endif // JXL_ENABLE_ASSERT
|
||||
JXL_ASSERT(y + acs.covered_blocks_y() <= layers_.ysize());
|
||||
JXL_ASSERT(x + acs.covered_blocks_x() <= layers_.xsize());
|
||||
SetNoBoundsCheck(x, y, type);
|
||||
}
|
||||
|
||||
void SetNoBoundsCheck(size_t x, size_t y, AcStrategy::Type type) {
|
||||
AcStrategy acs = AcStrategy::FromRawStrategy(type);
|
||||
for (size_t iy = 0; iy < acs.covered_blocks_y(); iy++) {
|
||||
for (size_t ix = 0; ix < acs.covered_blocks_x(); ix++) {
|
||||
row_[(y + iy) * stride_ + x + ix] =
|
||||
(static_cast<uint8_t>(type) << 1) | ((iy | ix) == 0 ? 1 : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IsValid(size_t x, size_t y) { return row_[y * stride_ + x] != INVALID; }
|
||||
|
||||
AcStrategyRow ConstRow(size_t y, size_t x_prefix = 0) const {
|
||||
return AcStrategyRow(layers_.ConstRow(y) + x_prefix);
|
||||
}
|
||||
|
||||
AcStrategyRow ConstRow(const Rect& rect, size_t y) const {
|
||||
return ConstRow(rect.y0() + y, rect.x0());
|
||||
}
|
||||
|
||||
size_t PixelsPerRow() const { return layers_.PixelsPerRow(); }
|
||||
|
||||
size_t xsize() const { return layers_.xsize(); }
|
||||
size_t ysize() const { return layers_.ysize(); }
|
||||
|
||||
// Count the number of blocks of a given type.
|
||||
size_t CountBlocks(AcStrategy::Type type) const;
|
||||
|
||||
private:
|
||||
ImageB layers_;
|
||||
uint8_t* JXL_RESTRICT row_;
|
||||
size_t stride_;
|
||||
|
||||
// A value that does not represent a valid combined AC strategy
|
||||
// value. Used as a sentinel.
|
||||
static constexpr uint8_t INVALID = 0xFF;
|
||||
};
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_AC_STRATEGY_H_
|
||||
@@ -0,0 +1,405 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "jxl/ac_strategy.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "jxl/common.h"
|
||||
#include "jxl/dct.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace {
|
||||
|
||||
// Test that DCT -> IDCT is a noop.
|
||||
void TestAcStrategyRoundtrip(AcStrategy::Type type) {
|
||||
AcStrategy acs = AcStrategy::FromRawStrategy(type);
|
||||
HWY_ALIGN float coeffs[AcStrategy::kMaxCoeffArea] = {};
|
||||
HWY_ALIGN float idct[AcStrategy::kMaxCoeffArea];
|
||||
for (size_t i = 0; i < 64 << acs.log2_covered_blocks(); i++) {
|
||||
HWY_ALIGN float input[AcStrategy::kMaxCoeffArea] = {};
|
||||
input[i] = 0.2;
|
||||
acs.TransformFromPixels(input, acs.covered_blocks_x() * 8, coeffs);
|
||||
ASSERT_NEAR(coeffs[0], 0.2 / (64 << acs.log2_covered_blocks()), 1e-6)
|
||||
<< " i = " << i;
|
||||
acs.TransformToPixels(coeffs, idct, acs.covered_blocks_x() * 8);
|
||||
for (size_t j = 0; j < 64 << acs.log2_covered_blocks(); j++) {
|
||||
EXPECT_NEAR(idct[j], input[j], 1e-6) << "j = " << j << " i = " << i;
|
||||
}
|
||||
}
|
||||
// Test DC.
|
||||
std::fill(std::begin(idct), std::end(idct), 0);
|
||||
for (size_t y = 0; y < acs.covered_blocks_y(); y++) {
|
||||
for (size_t x = 0; x < acs.covered_blocks_x(); x++) {
|
||||
HWY_ALIGN float dc[AcStrategy::kMaxCoeffArea] = {};
|
||||
dc[y * acs.covered_blocks_x() * 8 + x] = 0.2;
|
||||
acs.LowestFrequenciesFromDC(dc, acs.covered_blocks_x() * 8, coeffs);
|
||||
acs.DCFromLowestFrequencies(coeffs, idct, acs.covered_blocks_x() * 8);
|
||||
for (size_t j = 0; j < 64 << acs.log2_covered_blocks(); j++) {
|
||||
EXPECT_NEAR(idct[j], dc[j], 1e-6)
|
||||
<< "j = " << j << " x = " << x << " y = " << y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test that DC(2x2) -> DCT coefficients -> IDCT -> downsampled IDCT is a noop.
|
||||
void TestAcStrategyRoundtripDownsample(AcStrategy::Type type) {
|
||||
AcStrategy acs = AcStrategy::FromRawStrategy(type);
|
||||
HWY_ALIGN float coeffs[AcStrategy::kMaxCoeffArea] = {};
|
||||
HWY_ALIGN float idct[AcStrategy::kMaxCoeffArea];
|
||||
for (size_t y = 0; y < acs.covered_blocks_y(); y++) {
|
||||
for (size_t x = 0; x < acs.covered_blocks_x(); x++) {
|
||||
HWY_ALIGN float dc[AcStrategy::kMaxCoeffArea] = {};
|
||||
dc[y * acs.covered_blocks_x() * 8 + x] = 0.2;
|
||||
acs.LowestFrequenciesFromDC(dc, acs.covered_blocks_x() * 8, coeffs);
|
||||
acs.TransformToPixels(coeffs, idct, acs.covered_blocks_x() * 8);
|
||||
// Downsample
|
||||
for (size_t dy = 0; dy < acs.covered_blocks_y(); dy++) {
|
||||
for (size_t dx = 0; dx < acs.covered_blocks_x(); dx++) {
|
||||
float sum = 0;
|
||||
for (size_t iy = 0; iy < 8; iy++) {
|
||||
for (size_t ix = 0; ix < 8; ix++) {
|
||||
sum += idct[(dy * 8 + iy) * 8 * acs.covered_blocks_x() + dx * 8 +
|
||||
ix];
|
||||
}
|
||||
}
|
||||
sum /= 64.0f;
|
||||
EXPECT_NEAR(sum, dc[dy * 8 * acs.covered_blocks_x() + dx], 1e-6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test that IDCT(block with zeros in the non-topleft corner) -> downsampled
|
||||
// IDCT is the same as IDCT -> DC(2x2) of the same block.
|
||||
void TestAcStrategyDownsample(AcStrategy::Type type) {
|
||||
AcStrategy acs = AcStrategy::FromRawStrategy(type);
|
||||
HWY_ALIGN float idct[AcStrategy::kMaxCoeffArea];
|
||||
HWY_ALIGN float idct_acs_downsampled[AcStrategy::kMaxCoeffArea] = {};
|
||||
size_t cx = acs.covered_blocks_y();
|
||||
size_t cy = acs.covered_blocks_x();
|
||||
CoefficientLayout(&cy, &cx);
|
||||
for (size_t y = 0; y < cy; y++) {
|
||||
for (size_t x = 0; x < cx; x++) {
|
||||
HWY_ALIGN float coeffs[AcStrategy::kMaxCoeffArea] = {};
|
||||
coeffs[y * cx * 8 + x] = 0.2;
|
||||
acs.TransformToPixels(coeffs, idct, acs.covered_blocks_x() * 8);
|
||||
acs.DCFromLowestFrequencies(coeffs, idct_acs_downsampled,
|
||||
acs.covered_blocks_x() * 8);
|
||||
// Downsample
|
||||
for (size_t dy = 0; dy < acs.covered_blocks_y(); dy++) {
|
||||
for (size_t dx = 0; dx < acs.covered_blocks_x(); dx++) {
|
||||
float sum = 0;
|
||||
for (size_t iy = 0; iy < 8; iy++) {
|
||||
for (size_t ix = 0; ix < 8; ix++) {
|
||||
sum += idct[(dy * 8 + iy) * 8 * acs.covered_blocks_x() + dx * 8 +
|
||||
ix];
|
||||
}
|
||||
}
|
||||
sum /= 64;
|
||||
EXPECT_NEAR(
|
||||
sum, idct_acs_downsampled[dy * 8 * acs.covered_blocks_x() + dx],
|
||||
1e-6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float I8(int N, int u) {
|
||||
float eps = u == 0 ? std::sqrt(0.5) : 1.0;
|
||||
return std::sqrt(2.0 / N) * eps * cos(u * kPi / (2.0 * N));
|
||||
}
|
||||
|
||||
float D8(int N, int u) { return 1.0f / (N * I8(N, u)); }
|
||||
|
||||
float I(int N, int u) {
|
||||
if (N == 8)
|
||||
return I8(N, u);
|
||||
else
|
||||
return D8(N, u);
|
||||
}
|
||||
|
||||
float D(int N, int u) {
|
||||
if (N == 8)
|
||||
return D8(N, u);
|
||||
else
|
||||
return I8(N, u);
|
||||
}
|
||||
|
||||
float C(int N, int n, int x) {
|
||||
if (n > N) return 1.0 / C(n, N, x);
|
||||
if (n == N)
|
||||
return 1.0;
|
||||
else
|
||||
return cos(x * kPi / (2 * N)) * C(N / 2, n, x);
|
||||
}
|
||||
|
||||
float ScaleF(int N, int n, int x) {
|
||||
return std::sqrt(n * N) * D(N, x) * I(n, x) * C(N, n, x);
|
||||
}
|
||||
|
||||
TEST(AcStrategyTest, TestConstant32) {
|
||||
for (size_t i = 0; i < 32; i++) {
|
||||
EXPECT_NEAR(D(32, i), DCTScales<32>()[i], 1e-4);
|
||||
EXPECT_NEAR(I(32, i), IDCTScales<32>()[i], 1e-4);
|
||||
}
|
||||
for (int i = 0; i < 8; i++) {
|
||||
float e1 = C(32, 8, i) - DCTResampleScales<32, 8>::kScales[i];
|
||||
float e2 = C(8, 32, i) - DCTResampleScales<8, 32>::kScales[i];
|
||||
EXPECT_LT(std::fabs(e1), 1e-4);
|
||||
EXPECT_LT(std::fabs(e2), 1e-4);
|
||||
}
|
||||
EXPECT_NEAR(std::sqrt(8), square_root<8>::value, 1e-4);
|
||||
EXPECT_NEAR(std::sqrt(16), square_root<16>::value, 1e-4);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
float e1 = ScaleF(32, 8, i) - DCTTotalResampleScale<32, 8>(i);
|
||||
EXPECT_LT(std::fabs(e1), 1e-4);
|
||||
}
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float e1 = ScaleF(32, 4, i) - DCTTotalResampleScale<32, 4>(i);
|
||||
EXPECT_LT(std::fabs(e1), 1e-4);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AcStrategyTest, TestConstant16) {
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
EXPECT_NEAR(D(16, i), DCTScales<16>()[i], 1e-4);
|
||||
EXPECT_NEAR(I(16, i), IDCTScales<16>()[i], 1e-4);
|
||||
}
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float e1 = ScaleF(16, 4, i) - DCTTotalResampleScale<16, 4>(i);
|
||||
EXPECT_LT(std::fabs(e1), 1e-4);
|
||||
}
|
||||
for (int i = 0; i < 2; i++) {
|
||||
float e1 = ScaleF(16, 2, i) - DCTTotalResampleScale<16, 2>(i);
|
||||
EXPECT_LT(std::fabs(e1), 1e-4);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AcStrategyTest, TestConstant8) {
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
EXPECT_NEAR(D(8, i), DCTScales<8>()[i], 1e-4);
|
||||
EXPECT_NEAR(I(8, i), IDCTScales<8>()[i], 1e-4);
|
||||
}
|
||||
for (int i = 0; i < 2; i++) {
|
||||
float e1 = ScaleF(8, 2, i) - DCTTotalResampleScale<8, 2>(i);
|
||||
EXPECT_LT(std::fabs(e1), 1e-4);
|
||||
}
|
||||
for (int i = 0; i < 1; i++) {
|
||||
float e1 = ScaleF(8, 1, i) - DCTTotalResampleScale<8, 1>(i);
|
||||
EXPECT_LT(std::fabs(e1), 1e-4);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AcStrategyTest, DownsampleDCT) {
|
||||
TestAcStrategyDownsample(AcStrategy::Type::DCT);
|
||||
}
|
||||
TEST(AcStrategyTest, DownsampleIDENTITY) {
|
||||
TestAcStrategyDownsample(AcStrategy::Type::IDENTITY);
|
||||
}
|
||||
TEST(AcStrategyTest, DownsampleDCT2X2) {
|
||||
TestAcStrategyDownsample(AcStrategy::Type::DCT2X2);
|
||||
}
|
||||
TEST(AcStrategyTest, DownsampleDCT4X4) {
|
||||
TestAcStrategyDownsample(AcStrategy::Type::DCT4X4);
|
||||
}
|
||||
TEST(AcStrategyTest, DownsampleDCT16X16) {
|
||||
TestAcStrategyDownsample(AcStrategy::Type::DCT16X16);
|
||||
}
|
||||
TEST(AcStrategyTest, DownsampleDCT32X32) {
|
||||
TestAcStrategyDownsample(AcStrategy::Type::DCT32X32);
|
||||
}
|
||||
TEST(AcStrategyTest, DownsampleDCT16X8) {
|
||||
TestAcStrategyDownsample(AcStrategy::Type::DCT16X8);
|
||||
}
|
||||
TEST(AcStrategyTest, DownsampleDCT8X16) {
|
||||
TestAcStrategyDownsample(AcStrategy::Type::DCT8X16);
|
||||
}
|
||||
TEST(AcStrategyTest, DownsampleDCT32X8) {
|
||||
TestAcStrategyDownsample(AcStrategy::Type::DCT32X8);
|
||||
}
|
||||
TEST(AcStrategyTest, DownsampleDCT8X32) {
|
||||
TestAcStrategyDownsample(AcStrategy::Type::DCT8X32);
|
||||
}
|
||||
TEST(AcStrategyTest, DownsampleDCT32X16) {
|
||||
TestAcStrategyDownsample(AcStrategy::Type::DCT32X16);
|
||||
}
|
||||
TEST(AcStrategyTest, DownsampleDCT16X32) {
|
||||
TestAcStrategyDownsample(AcStrategy::Type::DCT16X32);
|
||||
}
|
||||
TEST(AcStrategyTest, DownsampleDCT4X8) {
|
||||
TestAcStrategyDownsample(AcStrategy::Type::DCT4X8);
|
||||
}
|
||||
TEST(AcStrategyTest, DownsampleDCT8X4) {
|
||||
TestAcStrategyDownsample(AcStrategy::Type::DCT8X4);
|
||||
}
|
||||
TEST(AcStrategyTest, DownsampleAFV0) {
|
||||
TestAcStrategyDownsample(AcStrategy::Type::AFV0);
|
||||
}
|
||||
TEST(AcStrategyTest, DownsampleAFV1) {
|
||||
TestAcStrategyDownsample(AcStrategy::Type::AFV1);
|
||||
}
|
||||
|
||||
TEST(AcStrategyTest, RoundtripDownsampleDCT) {
|
||||
TestAcStrategyRoundtripDownsample(AcStrategy::Type::DCT);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDownsampleIDENTITY) {
|
||||
TestAcStrategyRoundtripDownsample(AcStrategy::Type::IDENTITY);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDownsampleDCT2X2) {
|
||||
TestAcStrategyRoundtripDownsample(AcStrategy::Type::DCT2X2);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDownsampleDCT4X4) {
|
||||
TestAcStrategyRoundtripDownsample(AcStrategy::Type::DCT4X4);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDownsampleDCT16X16) {
|
||||
TestAcStrategyRoundtripDownsample(AcStrategy::Type::DCT16X16);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDownsampleDCT32X32) {
|
||||
TestAcStrategyRoundtripDownsample(AcStrategy::Type::DCT32X32);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDownsampleDCT16X8) {
|
||||
TestAcStrategyRoundtripDownsample(AcStrategy::Type::DCT16X8);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDownsampleDCT8X16) {
|
||||
TestAcStrategyRoundtripDownsample(AcStrategy::Type::DCT8X16);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDownsampleDCT32X8) {
|
||||
TestAcStrategyRoundtripDownsample(AcStrategy::Type::DCT32X8);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDownsampleDCT8X32) {
|
||||
TestAcStrategyRoundtripDownsample(AcStrategy::Type::DCT8X32);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDownsampleDCT32X16) {
|
||||
TestAcStrategyRoundtripDownsample(AcStrategy::Type::DCT32X16);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDownsampleDCT16X32) {
|
||||
TestAcStrategyRoundtripDownsample(AcStrategy::Type::DCT16X32);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDownsampleDCT4X8) {
|
||||
TestAcStrategyRoundtripDownsample(AcStrategy::Type::DCT4X8);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDownsampleDCT8X4) {
|
||||
TestAcStrategyRoundtripDownsample(AcStrategy::Type::DCT8X4);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDownsampleAFV0) {
|
||||
TestAcStrategyRoundtripDownsample(AcStrategy::Type::AFV0);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDownsampleAFV1) {
|
||||
TestAcStrategyRoundtripDownsample(AcStrategy::Type::AFV1);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDownsampleAFV2) {
|
||||
TestAcStrategyRoundtripDownsample(AcStrategy::Type::AFV2);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDownsampleAFV3) {
|
||||
TestAcStrategyRoundtripDownsample(AcStrategy::Type::AFV3);
|
||||
}
|
||||
|
||||
TEST(AcStrategyTest, RoundtripDCT) {
|
||||
TestAcStrategyRoundtrip(AcStrategy::Type::DCT);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripIDENTITY) {
|
||||
TestAcStrategyRoundtrip(AcStrategy::Type::IDENTITY);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDCT2X2) {
|
||||
TestAcStrategyRoundtrip(AcStrategy::Type::DCT2X2);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDCT4X4) {
|
||||
TestAcStrategyRoundtrip(AcStrategy::Type::DCT4X4);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDCT16X16) {
|
||||
TestAcStrategyRoundtrip(AcStrategy::Type::DCT16X16);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDCT32X32) {
|
||||
TestAcStrategyRoundtrip(AcStrategy::Type::DCT32X32);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDCT16X8) {
|
||||
TestAcStrategyRoundtrip(AcStrategy::Type::DCT16X8);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDCT8X16) {
|
||||
TestAcStrategyRoundtrip(AcStrategy::Type::DCT8X16);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDCT32X8) {
|
||||
TestAcStrategyRoundtrip(AcStrategy::Type::DCT32X8);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDCT8X32) {
|
||||
TestAcStrategyRoundtrip(AcStrategy::Type::DCT8X32);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDCT32X16) {
|
||||
TestAcStrategyRoundtrip(AcStrategy::Type::DCT32X16);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDCT16X32) {
|
||||
TestAcStrategyRoundtrip(AcStrategy::Type::DCT16X32);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDCT4X8) {
|
||||
TestAcStrategyRoundtrip(AcStrategy::Type::DCT4X8);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripDCT8X4) {
|
||||
TestAcStrategyRoundtrip(AcStrategy::Type::DCT8X4);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripAFV0) {
|
||||
TestAcStrategyRoundtrip(AcStrategy::Type::AFV0);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripAFV1) {
|
||||
TestAcStrategyRoundtrip(AcStrategy::Type::AFV1);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripAFV2) {
|
||||
TestAcStrategyRoundtrip(AcStrategy::Type::AFV2);
|
||||
}
|
||||
TEST(AcStrategyTest, RoundtripAFV3) {
|
||||
TestAcStrategyRoundtrip(AcStrategy::Type::AFV3);
|
||||
}
|
||||
|
||||
TEST(AcStrategyTest, RoundtripAFVDCT) {
|
||||
HWY_ALIGN float idct[16];
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
HWY_ALIGN float pixels[16] = {};
|
||||
pixels[i] = 1;
|
||||
HWY_ALIGN float coeffs[16] = {};
|
||||
|
||||
AFVDCT4x4(pixels, coeffs);
|
||||
AFVIDCT4x4(coeffs, idct);
|
||||
for (size_t j = 0; j < 16; j++) {
|
||||
EXPECT_NEAR(idct[j], pixels[j], 1e-6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AcStrategyTest, BenchmarkAFV) {
|
||||
AcStrategy acs = AcStrategy::FromRawStrategy(AcStrategy::Type::AFV0);
|
||||
HWY_ALIGN float pixels[64] = {1};
|
||||
HWY_ALIGN float coeffs[64] = {};
|
||||
for (size_t i = 0; i < 1 << 22; i++) {
|
||||
acs.TransformToPixels(coeffs, pixels, 8);
|
||||
acs.TransformFromPixels(pixels, 8, coeffs);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AcStrategyTest, BenchmarkAFVDCT) {
|
||||
HWY_ALIGN float pixels[64] = {1};
|
||||
HWY_ALIGN float coeffs[64] = {};
|
||||
for (size_t i = 0; i < 1 << 22; i++) {
|
||||
AFVDCT4x4(pixels, coeffs);
|
||||
AFVIDCT4x4(coeffs, pixels);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
} // namespace jxl
|
||||
@@ -0,0 +1,171 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "jxl/ac_strategy.h"
|
||||
#include "jxl/base/compiler_specific.h"
|
||||
#include "jxl/common.h"
|
||||
#include "jxl/epf.h"
|
||||
#include "jxl/image.h"
|
||||
#include "jxl/image_bundle.h"
|
||||
#include "jxl/image_ops.h"
|
||||
#include "jxl/image_test_utils.h"
|
||||
#include "jxl/loop_filter.h"
|
||||
#include "jxl/quant_weights.h"
|
||||
#include "jxl/quantizer.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace {
|
||||
|
||||
constexpr bool kPrint = false;
|
||||
|
||||
const size_t xsize = 8;
|
||||
const size_t ysize = 8;
|
||||
|
||||
void GenerateFlat(const float background, const float foreground,
|
||||
std::vector<Image3F>* images) {
|
||||
for (size_t c = 0; c < Image3F::kNumPlanes; ++c) {
|
||||
Image3F in(xsize, ysize);
|
||||
// Plane c = foreground, all others = background.
|
||||
for (size_t y = 0; y < ysize; ++y) {
|
||||
float* rows[3] = {in.PlaneRow(0, y), in.PlaneRow(1, y),
|
||||
in.PlaneRow(2, y)};
|
||||
for (size_t x = 0; x < xsize; ++x) {
|
||||
rows[0][x] = rows[1][x] = rows[2][x] = background;
|
||||
rows[c][x] = foreground;
|
||||
}
|
||||
}
|
||||
images->push_back(std::move(in));
|
||||
}
|
||||
}
|
||||
|
||||
// Single foreground point at any position in any channel
|
||||
void GeneratePoints(const float background, const float foreground,
|
||||
std::vector<Image3F>* images) {
|
||||
for (size_t c = 0; c < Image3F::kNumPlanes; ++c) {
|
||||
for (size_t y = 0; y < ysize; ++y) {
|
||||
for (size_t x = 0; x < xsize; ++x) {
|
||||
Image3F in(xsize, ysize);
|
||||
FillImage(background, &in);
|
||||
in.PlaneRow(c, y)[x] = foreground;
|
||||
images->push_back(std::move(in));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GenerateHorzEdges(const float background, const float foreground,
|
||||
std::vector<Image3F>* images) {
|
||||
for (size_t c = 0; c < Image3F::kNumPlanes; ++c) {
|
||||
// Begin of foreground rows
|
||||
for (size_t y = 1; y < ysize; ++y) {
|
||||
Image3F in(xsize, ysize);
|
||||
FillImage(background, &in);
|
||||
for (size_t iy = y; iy < ysize; ++iy) {
|
||||
std::fill(in.PlaneRow(c, iy), in.PlaneRow(c, iy) + xsize, foreground);
|
||||
}
|
||||
images->push_back(std::move(in));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GenerateVertEdges(const float background, const float foreground,
|
||||
std::vector<Image3F>* images) {
|
||||
for (size_t c = 0; c < Image3F::kNumPlanes; ++c) {
|
||||
// Begin of foreground columns
|
||||
for (size_t x = 1; x < xsize; ++x) {
|
||||
Image3F in(xsize, ysize);
|
||||
FillImage(background, &in);
|
||||
for (size_t iy = 0; iy < ysize; ++iy) {
|
||||
float* JXL_RESTRICT row = in.PlaneRow(c, iy);
|
||||
for (size_t ix = x; ix < xsize; ++ix) {
|
||||
row[ix] = foreground;
|
||||
}
|
||||
}
|
||||
images->push_back(std::move(in));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensures input remains unchanged by filter - verifies the edge-preserving
|
||||
// nature of the filter because inputs are piecewise constant.
|
||||
void EnsureUnchanged(const float background, const float foreground) {
|
||||
std::vector<Image3F> images;
|
||||
GenerateFlat(background, foreground, &images);
|
||||
GeneratePoints(background, foreground, &images);
|
||||
GenerateHorzEdges(background, foreground, &images);
|
||||
GenerateVertEdges(background, foreground, &images);
|
||||
|
||||
DequantMatrices dequant;
|
||||
size_t xsize_blocks = DivCeil(xsize, kBlockDim);
|
||||
size_t ysize_blocks = DivCeil(ysize, kBlockDim);
|
||||
LoopFilter lf;
|
||||
ImageF sigma(xsize_blocks + 4, ysize_blocks + 4);
|
||||
FillImage(-0.5f, &sigma);
|
||||
|
||||
Image3F storage1(xsize + 4 * kBlockDim, kEpf1InputRows);
|
||||
Image3F storage2(xsize + 2 * kBlockDim, kEpf2InputRows);
|
||||
lf.gab = false;
|
||||
|
||||
for (size_t idx_image = 0; idx_image < images.size(); ++idx_image) {
|
||||
const Image3F& in = images[idx_image];
|
||||
|
||||
if (kPrint) {
|
||||
printf("--In\n");
|
||||
for (size_t y = 0; y < ysize; ++y) {
|
||||
const float* row_x = in.ConstPlaneRow(0, y);
|
||||
const float* row_y = in.ConstPlaneRow(1, y);
|
||||
const float* row_b = in.ConstPlaneRow(2, y);
|
||||
for (size_t x = 0; x < ysize; ++x) {
|
||||
printf("%6.2f|%6.2f|%6.2f ", row_x[x], row_y[x], row_b[x]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
Image3F out = CopyImage(in); // = in_out
|
||||
Image3F padded = PadImageSymmetric(in, 2 * kBlockDim);
|
||||
EdgePreservingFilter(lf, Rect(0, 0, xsize, ysize), padded,
|
||||
Rect(0, 0, xsize_blocks, ysize_blocks), sigma,
|
||||
Rect(0, 0, xsize, ysize), &out, &storage1, &storage2);
|
||||
|
||||
if (kPrint) {
|
||||
printf("--Out\n");
|
||||
for (size_t y = 0; y < ysize; ++y) {
|
||||
const float* row_x = out.ConstPlaneRow(0, y);
|
||||
const float* row_y = out.ConstPlaneRow(1, y);
|
||||
const float* row_b = out.ConstPlaneRow(2, y);
|
||||
for (size_t x = 0; x < ysize; ++x) {
|
||||
printf("%6.2f|%6.2f|%6.2f ", row_x[x], row_y[x], row_b[x]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
VerifyRelativeError(in, out, 1E-3, 1E-4);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AdaptiveReconstructionTest, TestBright) { EnsureUnchanged(1.0f, 128.0f); }
|
||||
TEST(AdaptiveReconstructionTest, TestDark) { EnsureUnchanged(128.0f, 1.0f); }
|
||||
|
||||
} // namespace
|
||||
} // namespace jxl
|
||||
@@ -0,0 +1,143 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "jxl/ans_common.h"
|
||||
|
||||
#include <numeric>
|
||||
|
||||
#include "jxl/ans_params.h"
|
||||
#include "jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
std::vector<int> CreateFlatHistogram(int length, int total_count) {
|
||||
JXL_ASSERT(length > 0);
|
||||
JXL_ASSERT(length <= total_count);
|
||||
const int count = total_count / length;
|
||||
std::vector<int> result(length, count);
|
||||
const int rem_counts = total_count % length;
|
||||
for (int i = 0; i < rem_counts; ++i) {
|
||||
++result[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// First, all trailing non-occuring symbols are removed from the distribution;
|
||||
// if this leaves the distribution empty, a dummy symbol with max weight is
|
||||
// added. This ensures that the resulting distribution sums to total table size.
|
||||
// Then, `entry_size` is chosen to be the largest power of two so that
|
||||
// `table_size` = ANS_TAB_SIZE/`entry_size` is at least as big as the
|
||||
// distribution size.
|
||||
// Note that each entry will only ever contain two different symbols, and
|
||||
// consecutive ranges of offsets, which allows us to use a compact
|
||||
// representation.
|
||||
// Each entry is initialized with only the (symbol=i, offset) pairs; then
|
||||
// positions for which the entry overflows (i.e. distribution[i] > entry_size)
|
||||
// or is not full are computed, and put into a stack in increasing order.
|
||||
// Missing symbols in the distribution are padded with 0 (because `table_size`
|
||||
// >= number of symbols). The `cutoff` value for each entry is initialized to
|
||||
// the number of occupied slots in that entry (i.e. `distributions[i]`). While
|
||||
// the overflowing-symbol stack is not empty (which implies that the
|
||||
// underflowing-symbol stack also is not), the top overfull and underfull
|
||||
// positions are popped from the stack; the empty slots in the underfull entry
|
||||
// are then filled with as many slots as needed from the overfull entry; such
|
||||
// slots are placed after the slots in the overfull entry, and `offsets[1]` is
|
||||
// computed accordingly. The formerly underfull entry is thus now neither
|
||||
// underfull nor overfull, and represents exactly two symbols. The overfull
|
||||
// entry might be either overfull or underfull, and is pushed into the
|
||||
// corresponding stack.
|
||||
void InitAliasTable(std::vector<int> distribution, int range,
|
||||
AliasTable::Entry* JXL_RESTRICT a) {
|
||||
while (!distribution.empty() && distribution.back() == 0) {
|
||||
distribution.pop_back();
|
||||
}
|
||||
// Ensure that a valid table is always returned, even for an empty
|
||||
// alphabet. Otherwise, a specially-crafted stream might crash the
|
||||
// decoder.
|
||||
if (distribution.empty()) {
|
||||
distribution.emplace_back(range);
|
||||
}
|
||||
const int kTableSizeLog = ANS_LOG_MAX_ALPHABET_SIZE;
|
||||
const int kTableSize = ANS_MAX_ALPHA_SIZE;
|
||||
#if JXL_ENABLE_ASSERT
|
||||
int sum = std::accumulate(distribution.begin(), distribution.end(), 0);
|
||||
#endif // JXL_ENABLE_ASSERT
|
||||
JXL_ASSERT(sum == range);
|
||||
JXL_ASSERT(distribution.size() <= (1 << kTableSizeLog));
|
||||
// range must be a power of two
|
||||
JXL_ASSERT((range & (range - 1)) == 0);
|
||||
JXL_ASSERT(kTableSize >= distribution.size());
|
||||
JXL_ASSERT(kTableSize <= range);
|
||||
const int entry_size = range >> kTableSizeLog; // this is exact
|
||||
std::vector<int> underfull_posn;
|
||||
std::vector<int> overfull_posn;
|
||||
int cutoffs[kTableSize];
|
||||
// Initialize entries.
|
||||
for (size_t i = 0; i < distribution.size(); i++) {
|
||||
cutoffs[i] = distribution[i];
|
||||
if (cutoffs[i] > entry_size) {
|
||||
overfull_posn.push_back(i);
|
||||
} else if (cutoffs[i] < entry_size) {
|
||||
underfull_posn.push_back(i);
|
||||
}
|
||||
}
|
||||
for (int i = distribution.size(); i < kTableSize; i++) {
|
||||
cutoffs[i] = 0;
|
||||
underfull_posn.push_back(i);
|
||||
}
|
||||
// Reassign overflow/underflow values.
|
||||
while (!overfull_posn.empty()) {
|
||||
int overfull_i = overfull_posn.back();
|
||||
overfull_posn.pop_back();
|
||||
JXL_ASSERT(!underfull_posn.empty());
|
||||
int underfull_i = underfull_posn.back();
|
||||
underfull_posn.pop_back();
|
||||
int underfull_by = entry_size - cutoffs[underfull_i];
|
||||
cutoffs[overfull_i] -= underfull_by;
|
||||
// overfull positions have their original symbols
|
||||
a[underfull_i].right_value = overfull_i;
|
||||
a[underfull_i].offsets1 = cutoffs[overfull_i];
|
||||
// Slots in the right part of entry underfull_i were taken from the end
|
||||
// of the symbols in entry overfull_i.
|
||||
if (cutoffs[overfull_i] < entry_size) {
|
||||
underfull_posn.push_back(overfull_i);
|
||||
} else if (cutoffs[overfull_i] > entry_size) {
|
||||
overfull_posn.push_back(overfull_i);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < kTableSize; i++) {
|
||||
// cutoffs[i] is properly initialized but the clang-analyzer doesn't infer
|
||||
// it since it is partially initialized across two for-loops.
|
||||
// NOLINTNEXTLINE(clang-analyzer-core.UndefinedBinaryOperatorResult)
|
||||
if (cutoffs[i] == entry_size) {
|
||||
a[i].right_value = i;
|
||||
a[i].offsets1 = 0;
|
||||
a[i].cutoff = 0;
|
||||
} else {
|
||||
// Note that, if cutoff is not equal to entry_size,
|
||||
// a[i].offsets1 was initialized with (overfull cutoff) -
|
||||
// (entry_size - a[i].cutoff). Thus, subtracting
|
||||
// a[i].cutoff cannot make it negative.
|
||||
a[i].offsets1 -= cutoffs[i];
|
||||
a[i].cutoff = cutoffs[i];
|
||||
}
|
||||
const size_t freq0 = i < distribution.size() ? distribution[i] : 0;
|
||||
const size_t i1 = a[i].right_value;
|
||||
const size_t freq1 = i1 < distribution.size() ? distribution[i1] : 0;
|
||||
a[i].freq0 = static_cast<uint16_t>(freq0);
|
||||
a[i].freq1_xor_freq0 = static_cast<uint16_t>(freq1 ^ freq0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
@@ -0,0 +1,152 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_ANS_COMMON_H_
|
||||
#define JXL_ANS_COMMON_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <hwy/cache_control.h>
|
||||
#include <hwy/static_targets.h>
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/ans_params.h"
|
||||
#include "jxl/base/byte_order.h"
|
||||
#include "jxl/base/compiler_specific.h"
|
||||
#include "jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// Returns the precision (number of bits) that should be used to store
|
||||
// a histogram count such that Log2Floor(count) == logcount.
|
||||
static JXL_INLINE uint32_t GetPopulationCountPrecision(uint32_t logcount,
|
||||
uint32_t shift) {
|
||||
return std::min(logcount, shift - ((ANS_LOG_TAB_SIZE - logcount) >> 1));
|
||||
}
|
||||
|
||||
// Returns a histogram where the counts are positive, differ by at most 1,
|
||||
// and add up to total_count. The bigger counts (if any) are at the beginning
|
||||
// of the histogram.
|
||||
std::vector<int> CreateFlatHistogram(int length, int total_count);
|
||||
|
||||
// An alias table implements a mapping from the [0, ANS_TAB_SIZE) range into
|
||||
// the [0, ANS_MAX_ALPHABET_SIZE) range, satisfying the following conditions:
|
||||
// - each symbol occurs as many times as specified by any valid distribution
|
||||
// of frequencies of the symbols. A valid distribution here is an array of
|
||||
// ANS_MAX_ALPHABET_SIZE that contains numbers in the range [0, ANS_TAB_SIZE],
|
||||
// and whose sum is ANS_TAB_SIZE.
|
||||
// - lookups can be done in constant time, and also return how many smaller
|
||||
// input values map into the same symbol, according to some well-defined order
|
||||
// of input values.
|
||||
// - the space used by the alias table is given by a small constant times the
|
||||
// index of the largest symbol with nonzero probability in the distribution.
|
||||
// Each of the entries in the table covers a range of `entry_size` values in the
|
||||
// [0, ANS_TAB_SIZE) range; consecutive entries represent consecutive
|
||||
// sub-ranges. In the range covered by entry `i`, the first `cutoff` values map
|
||||
// to symbol `i`, while the others map to symbol `right_value`.
|
||||
//
|
||||
// TODO(veluca): consider making the order used for computing offsets easier to
|
||||
// define - it is currently defined by the algorithm to compute the alias table.
|
||||
// Beware of breaking the implicit assumption that symbols that come after the
|
||||
// cutoff value should have an offset at least as big as the cutoff.
|
||||
static constexpr uint32_t kLogEntrySize =
|
||||
ANS_LOG_TAB_SIZE - ANS_LOG_MAX_ALPHABET_SIZE;
|
||||
static constexpr uint32_t kEntrySizeMinus1 = (1 << kLogEntrySize) - 1;
|
||||
|
||||
struct AliasTable {
|
||||
struct Symbol {
|
||||
size_t value;
|
||||
size_t offset;
|
||||
size_t freq;
|
||||
};
|
||||
|
||||
// Working set size matters here (~64 tables x 256 entries).
|
||||
// offsets0 is always zero (beginning of [0] side among the same symbol).
|
||||
// offsets1 is an offset of (pos >= cutoff) side decremented by cutoff.
|
||||
#pragma pack(push, 1)
|
||||
struct Entry {
|
||||
uint8_t cutoff; // < kEntrySizeMinus1 when used by ANS.
|
||||
uint8_t right_value; // < alphabet size.
|
||||
uint16_t freq0;
|
||||
|
||||
// Only used if `greater` (see Lookup)
|
||||
uint16_t offsets1; // <= ANS_TAB_SIZE
|
||||
uint16_t freq1_xor_freq0; // for branchless ternary in Lookup
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
// Dividing `value` by `entry_size` determines `i`, the entry which is
|
||||
// responsible for the input. If the remainder is below `cutoff`, then the
|
||||
// mapped symbol is `i`; since `offsets[0]` stores the number of occurences of
|
||||
// `i` "before" the start of this entry, the offset of the input will be
|
||||
// `offsets[0] + remainder`. If the remainder is above cutoff, the mapped
|
||||
// symbol is `right_value`; since `offsets[1]` stores the number of occurences
|
||||
// of `right_value` "before" this entry, minus the `cutoff` value, the input
|
||||
// offset is then `remainder + offsets[1]`.
|
||||
static HWY_ATTR JXL_INLINE Symbol Lookup(const Entry* JXL_RESTRICT table,
|
||||
size_t value) {
|
||||
const size_t i = value >> kLogEntrySize;
|
||||
const size_t pos = value & kEntrySizeMinus1;
|
||||
|
||||
#if JXL_BYTE_ORDER_LITTLE
|
||||
uint64_t entry;
|
||||
memcpy(&entry, &table[i].cutoff, sizeof(entry));
|
||||
const size_t cutoff = entry & 0xFF; // = MOVZX
|
||||
const size_t right_value = (entry >> 8) & 0xFF; // = MOVZX
|
||||
const size_t freq0 = (entry >> 16) & 0xFFFF;
|
||||
#else
|
||||
// Generates multiple loads with complex addressing.
|
||||
const size_t cutoff = table[i].cutoff;
|
||||
const size_t right_value = table[i].right_value;
|
||||
const size_t freq0 = table[i].freq0;
|
||||
#endif
|
||||
|
||||
const bool greater = pos >= cutoff;
|
||||
|
||||
#if JXL_BYTE_ORDER_LITTLE
|
||||
const uint64_t conditional = greater ? entry : 0; // = CMOV
|
||||
const size_t offsets1_or_0 = (conditional >> 32) & 0xFFFF;
|
||||
const size_t freq1_xor_freq0_or_0 = conditional >> 48;
|
||||
#else
|
||||
const size_t offsets1_or_0 = greater ? table[i].offsets1 : 0;
|
||||
const size_t freq1_xor_freq0_or_0 = greater ? table[i].freq1_xor_freq0 : 0;
|
||||
#endif
|
||||
|
||||
// WARNING: moving this code may interfere with CMOV heuristics.
|
||||
Symbol s;
|
||||
s.value = greater ? right_value : i;
|
||||
s.offset = offsets1_or_0 + pos;
|
||||
s.freq = freq0 ^ freq1_xor_freq0_or_0; // = greater ? freq1 : freq0
|
||||
// XOR avoids implementation-defined conversion from unsigned to signed.
|
||||
// Alternatives considered: BEXTR is 2 cycles on HSW, SET+shift causes
|
||||
// spills, simple ternary has a long dependency chain.
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static HWY_ATTR HWY_INLINE void Prefetch(const Entry* JXL_RESTRICT table,
|
||||
size_t value) {
|
||||
const size_t i = value >> kLogEntrySize;
|
||||
hwy::Prefetch(table + i);
|
||||
}
|
||||
};
|
||||
|
||||
// Computes an alias table for a given distribution.
|
||||
void InitAliasTable(std::vector<int> distribution, int range,
|
||||
AliasTable::Entry* JXL_RESTRICT a);
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_ANS_COMMON_H_
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "jxl/ans_common.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "jxl/ans_params.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace {
|
||||
|
||||
HWY_ATTR void VerifyAliasDistribution(const std::vector<int>& distribution,
|
||||
int range) {
|
||||
AliasTable::Entry table[ANS_MAX_ALPHA_SIZE];
|
||||
InitAliasTable(distribution, range, table);
|
||||
std::vector<std::vector<int>> offsets(distribution.size());
|
||||
for (int i = 0; i < range; i++) {
|
||||
AliasTable::Symbol s = AliasTable::Lookup(table, i);
|
||||
offsets[s.value].push_back(s.offset);
|
||||
}
|
||||
for (int i = 0; i < distribution.size(); i++) {
|
||||
ASSERT_EQ(distribution[i], offsets[i].size());
|
||||
std::sort(offsets[i].begin(), offsets[i].end());
|
||||
for (int j = 0; j < offsets[i].size(); j++) ASSERT_EQ(offsets[i][j], j);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ANSCommonTest, AliasDistributionSmoke) {
|
||||
VerifyAliasDistribution({ANS_TAB_SIZE / 2, ANS_TAB_SIZE / 2}, ANS_TAB_SIZE);
|
||||
VerifyAliasDistribution({ANS_TAB_SIZE}, ANS_TAB_SIZE);
|
||||
VerifyAliasDistribution({0, 0, 0, ANS_TAB_SIZE, 0}, ANS_TAB_SIZE);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace jxl
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_ANS_PARAMS_H_
|
||||
#define JXL_ANS_PARAMS_H_
|
||||
|
||||
// Common parameters that are needed for both the ANS entropy encoding and
|
||||
// decoding methods.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "jxl/enc_params.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// TODO(veluca): decide if 12 is the best constant here (valid range is up to
|
||||
// 16). This requires recomputing the Huffman tables in {enc,dec}_ans.cc
|
||||
// 14 gives a 0.2% improvement at d1 and makes d8 slightly worse. This is
|
||||
// likely not worth the increase in encoder complexity.
|
||||
#define ANS_LOG_TAB_SIZE 12u
|
||||
#define ANS_TAB_SIZE (1 << ANS_LOG_TAB_SIZE)
|
||||
#define ANS_TAB_MASK (ANS_TAB_SIZE - 1)
|
||||
|
||||
// 7 is sufficient for 30 bits using ReadHybridUint. Working set is proportional
|
||||
// to ANS_MAX_ALPHA_SIZE, and does not fit in L2 cache for larger values.
|
||||
#define ANS_LOG_MAX_ALPHABET_SIZE 7
|
||||
#define ANS_MAX_ALPHA_SIZE (1 << ANS_LOG_MAX_ALPHABET_SIZE)
|
||||
#define ANS_MAX_ALPHA_MASK (ANS_MAX_ALPHA_SIZE - 1)
|
||||
|
||||
#define ANS_SIGNATURE 0x13 // Initial state, used as CRC.
|
||||
|
||||
struct HistogramParams {
|
||||
enum class ClusteringType {
|
||||
kFastest, // Cluster everything together
|
||||
kFast,
|
||||
kBest,
|
||||
};
|
||||
|
||||
HistogramParams() = default;
|
||||
|
||||
HistogramParams(SpeedTier tier, size_t num_ctx) {
|
||||
if (tier == SpeedTier::kFalcon) {
|
||||
clustering = ClusteringType::kFastest;
|
||||
} else if (tier > SpeedTier::kTortoise) {
|
||||
clustering = ClusteringType::kFast;
|
||||
} else {
|
||||
clustering = ClusteringType::kBest;
|
||||
}
|
||||
}
|
||||
|
||||
ClusteringType clustering = ClusteringType::kBest;
|
||||
};
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_ANS_PARAMS_H_
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <hwy/static_targets.h>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "jxl/ans_params.h"
|
||||
#include "jxl/aux_out_fwd.h"
|
||||
#include "jxl/base/span.h"
|
||||
#include "jxl/dec_ans.h"
|
||||
#include "jxl/dec_bit_reader.h"
|
||||
#include "jxl/enc_ans.h"
|
||||
#include "jxl/enc_bit_writer.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace {
|
||||
|
||||
HWY_ATTR void RoundtripTestcase(int n_histograms, int alphabet_size,
|
||||
const std::vector<Token>& input_values) {
|
||||
constexpr uint16_t kMagic1 = 0x9e33;
|
||||
constexpr uint16_t kMagic2 = 0x8b04;
|
||||
|
||||
BitWriter writer;
|
||||
// Space for magic bytes.
|
||||
BitWriter::Allotment allotment_magic1(&writer, 16);
|
||||
writer.Write(16, kMagic1);
|
||||
ReclaimAndCharge(&writer, &allotment_magic1, 0, nullptr);
|
||||
|
||||
std::vector<uint8_t> context_map;
|
||||
EntropyEncodingData codes;
|
||||
std::vector<std::vector<Token>> input_values_vec;
|
||||
input_values_vec.push_back(input_values);
|
||||
|
||||
BuildAndEncodeHistograms(HistogramParams(), n_histograms, input_values_vec,
|
||||
&codes, &context_map, &writer, 0, nullptr);
|
||||
WriteTokens(input_values_vec[0], codes, context_map, &writer, 0, nullptr);
|
||||
|
||||
// Magic bytes + padding
|
||||
BitWriter::Allotment allotment_magic2(&writer, 24);
|
||||
writer.Write(16, kMagic2);
|
||||
writer.ZeroPadToByte();
|
||||
ReclaimAndCharge(&writer, &allotment_magic2, 0, nullptr);
|
||||
|
||||
// We do not truncate the output. Reading past the end reads out zeroes
|
||||
// anyway.
|
||||
BitReader br(writer.GetSpan());
|
||||
|
||||
ASSERT_EQ(br.ReadBits(16), kMagic1);
|
||||
|
||||
std::vector<uint8_t> dec_context_map;
|
||||
ANSCode decoded_codes;
|
||||
ASSERT_TRUE(DecodeHistograms(&br, n_histograms, ANS_MAX_ALPHA_SIZE,
|
||||
&decoded_codes, &dec_context_map));
|
||||
ASSERT_EQ(dec_context_map, context_map);
|
||||
ANSSymbolReader reader(&decoded_codes, &br);
|
||||
|
||||
for (const Token& symbol : input_values) {
|
||||
uint32_t read_symbol =
|
||||
reader.ReadSymbol(dec_context_map[symbol.context], &br);
|
||||
ASSERT_EQ(read_symbol, symbol.symbol);
|
||||
ASSERT_EQ(br.ReadBits(symbol.nbits), symbol.bits);
|
||||
}
|
||||
ASSERT_TRUE(reader.CheckANSFinalState());
|
||||
|
||||
ASSERT_EQ(br.ReadBits(16), kMagic2);
|
||||
EXPECT_TRUE(br.Close());
|
||||
}
|
||||
|
||||
TEST(ANSTest, EmptyRoundtrip) {
|
||||
RoundtripTestcase(2, ANS_MAX_ALPHA_SIZE, std::vector<Token>());
|
||||
}
|
||||
|
||||
TEST(ANSTest, SingleSymbolRoundtrip) {
|
||||
for (uint32_t i = 0; i < ANS_MAX_ALPHA_SIZE; i++) {
|
||||
RoundtripTestcase(2, ANS_MAX_ALPHA_SIZE, {{0, i, 0, 0}});
|
||||
}
|
||||
for (uint32_t i = 0; i < ANS_MAX_ALPHA_SIZE; i++) {
|
||||
RoundtripTestcase(2, ANS_MAX_ALPHA_SIZE,
|
||||
std::vector<Token>(1024, {0, i, 0, 0}));
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
|
||||
defined(THREAD_SANITIZER)
|
||||
constexpr size_t kReps = 10;
|
||||
#else
|
||||
constexpr size_t kReps = 100;
|
||||
#endif
|
||||
|
||||
void RoundtripRandomStream(int alphabet_size, size_t reps = kReps,
|
||||
size_t num = 1 << 18) {
|
||||
constexpr int kNumHistograms = 3;
|
||||
std::mt19937_64 rng;
|
||||
for (size_t i = 0; i < reps; i++) {
|
||||
std::vector<Token> symbols;
|
||||
for (size_t j = 0; j < num; j++) {
|
||||
int context = std::uniform_int_distribution<>(0, kNumHistograms - 1)(rng);
|
||||
int value = std::uniform_int_distribution<>(0, alphabet_size - 1)(rng);
|
||||
int nbits = std::uniform_int_distribution<>(0, 16)(rng);
|
||||
int bits = std::uniform_int_distribution<>(0, (1 << nbits) - 1)(rng);
|
||||
symbols.emplace_back(context, value, nbits, bits);
|
||||
}
|
||||
RoundtripTestcase(kNumHistograms, alphabet_size, symbols);
|
||||
}
|
||||
}
|
||||
|
||||
void RoundtripRandomUnbalancedStream(int alphabet_size) {
|
||||
constexpr int kNumHistograms = 3;
|
||||
constexpr int kPrecision = 1 << 10;
|
||||
std::mt19937_64 rng;
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::vector<int> distributions[kNumHistograms];
|
||||
for (int j = 0; j < kNumHistograms; j++) {
|
||||
distributions[j].resize(kPrecision);
|
||||
int symbol = 0;
|
||||
int remaining = 1;
|
||||
for (int k = 0; k < kPrecision; k++) {
|
||||
if (remaining == 0) {
|
||||
if (symbol < alphabet_size - 1) symbol++;
|
||||
// There is no meaning behind this distribution: it's anything that
|
||||
// will create a nonuniform distribution and won't have too few
|
||||
// symbols usually. Also we want different distributions we get to be
|
||||
// sufficiently dissimilar.
|
||||
remaining =
|
||||
std::uniform_int_distribution<>(0, (kPrecision - k) / 1)(rng);
|
||||
}
|
||||
distributions[j][k] = symbol;
|
||||
remaining--;
|
||||
}
|
||||
}
|
||||
std::vector<Token> symbols;
|
||||
for (int j = 0; j < 1 << 18; j++) {
|
||||
int context = std::uniform_int_distribution<>(0, kNumHistograms - 1)(rng);
|
||||
int value = distributions[context][std::uniform_int_distribution<>(
|
||||
0, kPrecision - 1)(rng)];
|
||||
int nbits = std::uniform_int_distribution<>(0, 16)(rng);
|
||||
int bits = std::uniform_int_distribution<>(0, (1 << nbits) - 1)(rng);
|
||||
symbols.emplace_back(context, value, nbits, bits);
|
||||
}
|
||||
RoundtripTestcase(kNumHistograms + 1, alphabet_size, symbols);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ANSTest, RandomStreamRoundtrip3Small) { RoundtripRandomStream(3, 1, 16); }
|
||||
|
||||
TEST(ANSTest, RandomStreamRoundtrip3) { RoundtripRandomStream(3); }
|
||||
|
||||
TEST(ANSTest, RandomStreamRoundtripBig) {
|
||||
RoundtripRandomStream(ANS_MAX_ALPHA_SIZE);
|
||||
}
|
||||
|
||||
TEST(ANSTest, RandomUnbalancedStreamRoundtrip3) {
|
||||
RoundtripRandomUnbalancedStream(3);
|
||||
}
|
||||
|
||||
TEST(ANSTest, RandomUnbalancedStreamRoundtripBig) {
|
||||
RoundtripRandomUnbalancedStream(ANS_MAX_ALPHA_SIZE);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace jxl
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "jxl/ar_control_field.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "jxl/ac_strategy.h"
|
||||
#include "jxl/base/compiler_specific.h"
|
||||
#include "jxl/base/data_parallel.h"
|
||||
#include "jxl/base/status.h"
|
||||
#include "jxl/chroma_from_luma.h"
|
||||
#include "jxl/common.h"
|
||||
#include "jxl/enc_adaptive_quantization.h"
|
||||
#include "jxl/enc_params.h"
|
||||
#include "jxl/image.h"
|
||||
#include "jxl/image_bundle.h"
|
||||
#include "jxl/image_ops.h"
|
||||
#include "jxl/quant_weights.h"
|
||||
#include "jxl/quantizer.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
void FindBestArControlField(const Image3F& opsin, PassesEncoderState* enc_state,
|
||||
ThreadPool* pool) {
|
||||
constexpr size_t N = kBlockDim;
|
||||
const size_t xsize_blocks = enc_state->shared.frame_dim.xsize_blocks;
|
||||
const size_t ysize_blocks = enc_state->shared.frame_dim.ysize_blocks;
|
||||
ImageB* JXL_RESTRICT epf_sharpness = &enc_state->shared.epf_sharpness;
|
||||
JXL_ASSERT(epf_sharpness->xsize() == xsize_blocks &&
|
||||
epf_sharpness->ysize() == ysize_blocks);
|
||||
|
||||
// TODO(veluca): choose a speed tier at which the heuristics here should be
|
||||
// enabled, once they are enabled at all.
|
||||
|
||||
FillImage(uint8_t(4), epf_sharpness);
|
||||
return;
|
||||
// TODO(veluca) - the following code ends up returning only 7.
|
||||
|
||||
constexpr float kChannelWeights[3] = {50.0f, 2.0f, 0.4f};
|
||||
const size_t sharpness_stride =
|
||||
static_cast<size_t>(epf_sharpness->PixelsPerRow());
|
||||
const size_t opsin_stride = static_cast<size_t>(opsin.PixelsPerRow());
|
||||
|
||||
const float* JXL_RESTRICT in_row[3] = {opsin.ConstPlaneRow(0, 0),
|
||||
opsin.ConstPlaneRow(1, 0),
|
||||
opsin.ConstPlaneRow(2, 0)};
|
||||
|
||||
const auto process_row = [&](size_t by, int _) {
|
||||
AcStrategyRow acs_row = enc_state->shared.ac_strategy.ConstRow(by);
|
||||
uint8_t* JXL_RESTRICT out_row = epf_sharpness->Row(by);
|
||||
for (size_t bx = 0; bx < xsize_blocks; bx++) {
|
||||
AcStrategy acs = acs_row[bx];
|
||||
if (!acs.IsFirstBlock()) continue;
|
||||
// Find the max Laplacian in a block (for example 32x32 dct).
|
||||
// Then find max Laplacian in two 4x4 pixel grids, offset by 2,2 pixels.
|
||||
// The 1st max indicates amplitude of the artefacts.
|
||||
// The 4x4 pixel grid Laplacian indicates masking of the artefacts.
|
||||
// If masking is high or amplitude is low, then no smoothing is needed.
|
||||
float maxval = 0;
|
||||
float maxarray[64] = {0};
|
||||
float maxarray2[81] = {0};
|
||||
float total_abs = 0;
|
||||
float total_2 = 0;
|
||||
for (size_t iy = 0; iy < acs.covered_blocks_y() * N; iy++) {
|
||||
const size_t cy = by * N + iy;
|
||||
const size_t prevY = cy >= 1 ? cy - 1 : cy;
|
||||
const size_t nextY = cy + 1 < opsin.ysize() ? cy + 1 : cy;
|
||||
for (size_t ix = 0; ix < acs.covered_blocks_x() * N; ix++) {
|
||||
const size_t cx = bx * N + ix;
|
||||
const size_t prevX = cx >= 1 ? cx - 1 : cx;
|
||||
const size_t nextX = cx + 1 < opsin.xsize() ? cx + 1 : cx;
|
||||
float sum = 0;
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
float v = in_row[c][cy * opsin_stride + cx] -
|
||||
0.125f * (in_row[c][cy * opsin_stride + prevX] +
|
||||
in_row[c][cy * opsin_stride + nextX] +
|
||||
in_row[c][nextY * opsin_stride + prevX] +
|
||||
in_row[c][nextY * opsin_stride + cx] +
|
||||
in_row[c][nextY * opsin_stride + nextX] +
|
||||
in_row[c][prevY * opsin_stride + prevX] +
|
||||
in_row[c][prevY * opsin_stride + cx] +
|
||||
in_row[c][prevY * opsin_stride + nextX]);
|
||||
v *= kChannelWeights[c];
|
||||
sum += std::abs(v);
|
||||
}
|
||||
maxarray[(iy / 4) * 8 + (ix / 4)] += sum * (1.0f / 16);
|
||||
maxarray2[((iy + 2) / 4) * 9 + ((ix + 2) / 4)] += sum * (1.0f / 16);
|
||||
total_2 += sum * sum *
|
||||
(1.0f / (N * N * acs.covered_blocks_x() *
|
||||
acs.covered_blocks_y()));
|
||||
total_abs += sum * (1.0f / (N * N * acs.covered_blocks_x() *
|
||||
acs.covered_blocks_y()));
|
||||
}
|
||||
}
|
||||
maxval = std::sqrt(total_2);
|
||||
for (size_t iy = 0; iy < acs.covered_blocks_y(); iy++) {
|
||||
for (size_t ix = 0; ix < acs.covered_blocks_x(); ix++) {
|
||||
// Four 4x4 blocks for masking estimation.
|
||||
const float minval = (maxarray[(2 * iy) * 8 + 2 * ix] +
|
||||
maxarray[(2 * iy) * 8 + 2 * ix + 1] +
|
||||
maxarray[(2 * iy + 1) * 8 + 2 * ix] +
|
||||
maxarray[(2 * iy + 1) * 8 + 2 * ix + 1] +
|
||||
maxarray2[(2 * iy + 1) * 9 + 2 * (ix + 1)]) *
|
||||
0.2f;
|
||||
// Larger kBias, less smoothing for low intensity changes.
|
||||
float kBias = 0.0005f;
|
||||
float delta = (maxval + kBias) / (minval + kBias);
|
||||
int sharpness1 = 7 + (delta - 0.5f) * 0.0;
|
||||
int sharpness2 = 7; // maxval * 10;
|
||||
out_row[bx + sharpness_stride * iy + ix] =
|
||||
std::max(0, std::min(7, std::min(sharpness1, sharpness2)));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
RunOnPool(pool, 0, ysize_blocks, ThreadPool::SkipInit(), process_row,
|
||||
"AR CF");
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_AR_CONTROL_FIELD_H_
|
||||
#define JXL_AR_CONTROL_FIELD_H_
|
||||
|
||||
#include "jxl/ac_strategy.h"
|
||||
#include "jxl/base/data_parallel.h"
|
||||
#include "jxl/chroma_from_luma.h"
|
||||
#include "jxl/common.h"
|
||||
#include "jxl/enc_cache.h"
|
||||
#include "jxl/enc_params.h"
|
||||
#include "jxl/image.h"
|
||||
#include "jxl/quant_weights.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
void FindBestArControlField(const Image3F& opsin, PassesEncoderState* enc_state,
|
||||
ThreadPool* pool);
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_AR_CONTROL_FIELD_H_
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "jxl/aux_out.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <numeric> // accumulate
|
||||
|
||||
#include "jxl/aux_out_fwd.h"
|
||||
#include "jxl/enc_bit_writer.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
namespace {
|
||||
|
||||
static const char* kBlockTypes[uint8_t(BlockType::kNumBlockTypes)] = {
|
||||
"add", "black", "white"};
|
||||
} // namespace
|
||||
|
||||
void AuxOut::Print(size_t num_inputs) const {
|
||||
if (num_inputs == 0) return;
|
||||
|
||||
LayerTotals all_layers;
|
||||
for (size_t i = 0; i < layers.size(); ++i) {
|
||||
all_layers.Assimilate(layers[i]);
|
||||
}
|
||||
|
||||
printf("Average butteraugli iters: %10.2f\n",
|
||||
num_butteraugli_iters * 1.0 / num_inputs);
|
||||
|
||||
for (size_t i = 0; i < layers.size(); ++i) {
|
||||
if (layers[i].total_bits != 0) {
|
||||
printf("Total layer bits %-10s\t", LayerName(i));
|
||||
printf("%10f%%", 100.0 * layers[i].total_bits / all_layers.total_bits);
|
||||
layers[i].Print(num_inputs);
|
||||
}
|
||||
}
|
||||
printf("Total image size ");
|
||||
all_layers.Print(num_inputs);
|
||||
|
||||
const uint32_t dc_pred_total =
|
||||
std::accumulate(dc_pred_usage.begin(), dc_pred_usage.end(), 0u);
|
||||
const uint32_t dc_pred_total_xb =
|
||||
std::accumulate(dc_pred_usage_xb.begin(), dc_pred_usage_xb.end(), 0u);
|
||||
if (dc_pred_total + dc_pred_total_xb != 0) {
|
||||
printf("\nDC pred Y XB:\n");
|
||||
for (size_t i = 0; i < dc_pred_usage.size(); ++i) {
|
||||
printf(" %6u (%5.2f%%) %6u (%5.2f%%)\n", dc_pred_usage[i],
|
||||
100.0 * dc_pred_usage[i] / dc_pred_total, dc_pred_usage_xb[i],
|
||||
100.0 * dc_pred_usage_xb[i] / dc_pred_total_xb);
|
||||
}
|
||||
}
|
||||
|
||||
size_t total_blocks = 0;
|
||||
size_t total_positions = 0;
|
||||
for (uint8_t block_type = 0; block_type < uint8_t(BlockType::kNumBlockTypes);
|
||||
block_type++) {
|
||||
total_blocks += num_block_types[block_type];
|
||||
total_positions += num_position_types[block_type];
|
||||
}
|
||||
if (total_blocks != 0 && total_positions != 0) {
|
||||
printf("\n\t\t Blocks\t\tPositions\t\t\tBlocks/Position\n");
|
||||
for (uint8_t block_type = 0;
|
||||
block_type < uint8_t(BlockType::kNumBlockTypes); block_type++) {
|
||||
printf(" %-5s:", kBlockTypes[block_type]);
|
||||
|
||||
printf(
|
||||
"\t%10f%% %7zu\t %10f%% %7zu\t\t\t%10f%%\n",
|
||||
100.0 * num_block_types[block_type] / total_blocks,
|
||||
num_block_types[block_type],
|
||||
100.0 * num_position_types[block_type] / total_positions,
|
||||
num_position_types[block_type],
|
||||
100.0 * num_block_types[block_type] / num_position_types[block_type]);
|
||||
}
|
||||
printf(" Total:\t\t %7zu\t\t %7zu \t\t\t%10f%%\n\n", total_blocks,
|
||||
total_positions, 100.0 * total_blocks / total_positions);
|
||||
}
|
||||
}
|
||||
|
||||
void AuxOut::DumpCoeffImage(const char* label,
|
||||
const Image3S& coeff_image) const {
|
||||
JXL_ASSERT(coeff_image.xsize() % 64 == 0);
|
||||
Image3S reshuffled(coeff_image.xsize() / 8, coeff_image.ysize() * 8);
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
for (size_t y = 0; y < coeff_image.ysize(); y++) {
|
||||
for (size_t x = 0; x < coeff_image.xsize(); x += 64) {
|
||||
for (size_t i = 0; i < 64; i++) {
|
||||
reshuffled.PlaneRow(c, 8 * y + i / 8)[x / 8 + i % 8] =
|
||||
coeff_image.PlaneRow(c, y)[x + i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DumpImage(label, reshuffled);
|
||||
}
|
||||
|
||||
void ReclaimAndCharge(BitWriter* JXL_RESTRICT writer,
|
||||
BitWriter::Allotment* JXL_RESTRICT allotment,
|
||||
size_t layer, AuxOut* JXL_RESTRICT aux_out) {
|
||||
size_t used_bits, unused_bits;
|
||||
allotment->PrivateReclaim(writer, &used_bits, &unused_bits);
|
||||
|
||||
#if 0
|
||||
printf("Layer %s bits: max %zu used %zu unused %zu\n", LayerName(layer),
|
||||
allotment->MaxBits(), used_bits, unused_bits);
|
||||
#endif
|
||||
|
||||
// This may be a nested call with aux_out == null. Whenever we know that
|
||||
// aux_out is null, we can call ReclaimUnused directly.
|
||||
if (aux_out != nullptr) {
|
||||
aux_out->layers[layer].total_bits += used_bits;
|
||||
aux_out->layers[layer].histogram_bits += allotment->HistogramBits();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
+332
@@ -0,0 +1,332 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_AUX_OUT_H_
|
||||
#define JXL_AUX_OUT_H_
|
||||
|
||||
// Optional output information for debugging and analyzing size usage.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "jxl/aux_out_fwd.h"
|
||||
#include "jxl/base/compiler_specific.h"
|
||||
#include "jxl/base/status.h"
|
||||
#include "jxl/codec_in_out.h"
|
||||
#include "jxl/color_management.h"
|
||||
#include "jxl/common.h"
|
||||
#include "jxl/dec_xyb.h"
|
||||
#include "jxl/image.h"
|
||||
#include "jxl/image_bundle.h"
|
||||
#include "jxl/image_ops.h"
|
||||
#include "jxl/jxl_inspection.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// For LayerName and AuxOut::layers[] index. Order does not matter.
|
||||
enum {
|
||||
kLayerHeader = 0,
|
||||
kLayerTOC,
|
||||
kLayerNoise,
|
||||
kLayerQuant,
|
||||
kLayerDequantTables,
|
||||
kLayerOrder,
|
||||
kLayerDownsampledDC,
|
||||
kLayerDC,
|
||||
kLayerAC,
|
||||
kLayerACTokens,
|
||||
kLayerDictionary,
|
||||
kLayerDots,
|
||||
kLayerSplines,
|
||||
kLayerLossless,
|
||||
kLayerModularGlobal,
|
||||
kLayerModularDcGroup,
|
||||
kLayerModularAcGroup,
|
||||
kLayerAlpha,
|
||||
kLayerDepth,
|
||||
kLayerExtraChannels,
|
||||
kNumImageLayers
|
||||
};
|
||||
|
||||
static inline const char* LayerName(size_t layer) {
|
||||
switch (layer) {
|
||||
case kLayerHeader:
|
||||
return "headers";
|
||||
case kLayerTOC:
|
||||
return "TOC";
|
||||
case kLayerNoise:
|
||||
return "noise";
|
||||
case kLayerQuant:
|
||||
return "quantizer";
|
||||
case kLayerDequantTables:
|
||||
return "quant tables";
|
||||
case kLayerOrder:
|
||||
return "order";
|
||||
case kLayerDC:
|
||||
return "DC+Group";
|
||||
case kLayerAC:
|
||||
return "AC";
|
||||
case kLayerACTokens:
|
||||
return "ACTokens";
|
||||
case kLayerDictionary:
|
||||
return "dictionary";
|
||||
case kLayerDots:
|
||||
return "dots";
|
||||
case kLayerSplines:
|
||||
return "splines";
|
||||
case kLayerLossless:
|
||||
return "lossless";
|
||||
case kLayerModularGlobal:
|
||||
return "modularGlobal";
|
||||
case kLayerModularDcGroup:
|
||||
return "modularDcGroup";
|
||||
case kLayerModularAcGroup:
|
||||
return "modularAcGroup";
|
||||
case kLayerAlpha:
|
||||
return "alpha";
|
||||
case kLayerDepth:
|
||||
return "depth";
|
||||
case kLayerExtraChannels:
|
||||
return "extra channels";
|
||||
default:
|
||||
JXL_ABORT("Invalid layer %zu\n", layer);
|
||||
}
|
||||
}
|
||||
|
||||
struct TestingAux {
|
||||
Image3F* dc = nullptr;
|
||||
Image3F* decoded = nullptr;
|
||||
};
|
||||
|
||||
// Statistics gathered during compression or decompression.
|
||||
struct AuxOut {
|
||||
private:
|
||||
struct LayerTotals {
|
||||
void Assimilate(const LayerTotals& victim) {
|
||||
num_clustered_histograms += victim.num_clustered_histograms;
|
||||
histogram_bits += victim.histogram_bits;
|
||||
extra_bits += victim.extra_bits;
|
||||
total_bits += victim.total_bits;
|
||||
clustered_entropy += victim.clustered_entropy;
|
||||
}
|
||||
void Print(size_t num_inputs) const {
|
||||
printf("%10zd", total_bits);
|
||||
if (histogram_bits != 0) {
|
||||
printf(" [%6.2f %8zd %8zd %12.3f",
|
||||
num_clustered_histograms * 1.0 / num_inputs, histogram_bits >> 3,
|
||||
extra_bits >> 3,
|
||||
(histogram_bits + clustered_entropy + extra_bits) / 8.0);
|
||||
printf("]");
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
size_t num_clustered_histograms = 0;
|
||||
size_t extra_bits = 0;
|
||||
|
||||
// Set via BitsWritten below
|
||||
size_t histogram_bits = 0;
|
||||
size_t total_bits = 0;
|
||||
|
||||
double clustered_entropy = 0.0;
|
||||
};
|
||||
|
||||
public:
|
||||
AuxOut() = default;
|
||||
AuxOut(const AuxOut&) = default;
|
||||
|
||||
void Assimilate(const AuxOut& victim) {
|
||||
for (size_t i = 0; i < layers.size(); ++i) {
|
||||
layers[i].Assimilate(victim.layers[i]);
|
||||
}
|
||||
num_blocks += victim.num_blocks;
|
||||
num_dct2_blocks += victim.num_dct2_blocks;
|
||||
num_dct4_blocks += victim.num_dct4_blocks;
|
||||
num_dct4x8_blocks += victim.num_dct4x8_blocks;
|
||||
num_afv_blocks += victim.num_afv_blocks;
|
||||
num_dct8_blocks += victim.num_dct8_blocks;
|
||||
num_dct8x16_blocks += victim.num_dct8x16_blocks;
|
||||
num_dct8x32_blocks += victim.num_dct8x32_blocks;
|
||||
num_dct16_blocks += victim.num_dct16_blocks;
|
||||
num_dct16x32_blocks += victim.num_dct16x32_blocks;
|
||||
num_dct32_blocks += victim.num_dct32_blocks;
|
||||
num_butteraugli_iters += victim.num_butteraugli_iters;
|
||||
for (size_t i = 0; i < dc_pred_usage.size(); ++i) {
|
||||
dc_pred_usage[i] += victim.dc_pred_usage[i];
|
||||
dc_pred_usage_xb[i] += victim.dc_pred_usage_xb[i];
|
||||
}
|
||||
for (uint8_t block_type = 0;
|
||||
block_type < uint8_t(BlockType::kNumBlockTypes); block_type++) {
|
||||
num_block_types[block_type] += victim.num_block_types[block_type];
|
||||
num_position_types[block_type] += victim.num_position_types[block_type];
|
||||
}
|
||||
}
|
||||
|
||||
void Print(size_t num_inputs) const;
|
||||
|
||||
template <typename T>
|
||||
void DumpImage(const char* label, const Image3<T>& image) const {
|
||||
if (!dump_image) return;
|
||||
if (debug_prefix.empty()) return;
|
||||
std::ostringstream pathname;
|
||||
pathname << debug_prefix << label << ".png";
|
||||
CodecInOut io;
|
||||
io.metadata.bits_per_sample = sizeof(T) * kBitsPerByte;
|
||||
io.metadata.color_encoding = ColorManagement::SRGB();
|
||||
io.SetFromImage(StaticCastImage3<float>(image), io.metadata.color_encoding);
|
||||
(void)dump_image(io, pathname.str());
|
||||
}
|
||||
template <typename T>
|
||||
void DumpImage(const char* label, const Plane<T>& image) {
|
||||
DumpImage(label,
|
||||
Image3<T>(CopyImage(image), CopyImage(image), CopyImage(image)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void DumpXybImage(const char* label, const Image3<T>& image) const {
|
||||
if (!dump_image) return;
|
||||
if (debug_prefix.empty()) return;
|
||||
std::ostringstream pathname;
|
||||
pathname << debug_prefix << label << ".png";
|
||||
|
||||
Image3F linear(image.xsize(), image.ysize());
|
||||
OpsinParams opsin_params;
|
||||
opsin_params.Init();
|
||||
OpsinToLinear(image, Rect(linear), nullptr, &linear, opsin_params);
|
||||
|
||||
CodecInOut io;
|
||||
io.metadata.bits_per_sample = sizeof(T) * kBitsPerByte;
|
||||
io.metadata.color_encoding = ColorManagement::LinearSRGB();
|
||||
io.SetFromImage(std::move(linear), io.metadata.color_encoding);
|
||||
|
||||
(void)dump_image(io, pathname.str());
|
||||
}
|
||||
|
||||
// Normalizes all the channels to range 0-255, creating a false-color image
|
||||
// which allows seeing the information from non-RGB channels in an RGB debug
|
||||
// image.
|
||||
template <typename T>
|
||||
void DumpImageNormalized(const char* label, const Image3<T>& image) const {
|
||||
std::array<T, 3> min;
|
||||
std::array<T, 3> max;
|
||||
Image3MinMax(image, &min, &max);
|
||||
Image3B normalized(image.xsize(), image.ysize());
|
||||
for (size_t c = 0; c < 3; ++c) {
|
||||
float mul = min[c] == max[c] ? 0 : (255.0f / (max[c] - min[c]));
|
||||
for (size_t y = 0; y < image.ysize(); ++y) {
|
||||
const T* JXL_RESTRICT row_in = image.ConstPlaneRow(c, y);
|
||||
uint8_t* JXL_RESTRICT row_out = normalized.PlaneRow(c, y);
|
||||
for (size_t x = 0; x < image.xsize(); ++x) {
|
||||
row_out[x] = static_cast<uint8_t>((row_in[x] - min[c]) * mul);
|
||||
}
|
||||
}
|
||||
}
|
||||
DumpImage(label, normalized);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void DumpPlaneNormalized(const char* label, const Plane<T>& image) const {
|
||||
T min;
|
||||
T max;
|
||||
ImageMinMax(image, &min, &max);
|
||||
Image3B normalized(image.xsize(), image.ysize());
|
||||
for (size_t c = 0; c < 3; ++c) {
|
||||
float mul = min == max ? 0 : (255.0f / (max - min));
|
||||
for (size_t y = 0; y < image.ysize(); ++y) {
|
||||
const T* JXL_RESTRICT row_in = image.ConstRow(y);
|
||||
uint8_t* JXL_RESTRICT row_out = normalized.PlaneRow(c, y);
|
||||
for (size_t x = 0; x < image.xsize(); ++x) {
|
||||
row_out[x] = static_cast<uint8_t>((row_in[x] - min) * mul);
|
||||
}
|
||||
}
|
||||
}
|
||||
DumpImage(label, normalized);
|
||||
}
|
||||
|
||||
// This dumps coefficients as a 16-bit PNG with coefficients of a block placed
|
||||
// in the area that would contain that block in a normal image. To view the
|
||||
// resulting image manually, rescale intensities by using:
|
||||
// $ convert -auto-level IMAGE.PNG - | display -
|
||||
void DumpCoeffImage(const char* label, const Image3S& coeff_image) const;
|
||||
|
||||
void SetInspectorImage3F(const jxl::InspectorImage3F& inspector) {
|
||||
inspector_image3f_ = inspector;
|
||||
}
|
||||
|
||||
// Allows hooking intermediate data inspection into various places of the
|
||||
// processing pipeline. Returns true iff processing should proceed.
|
||||
bool InspectImage3F(const char* label, const Image3F& image) {
|
||||
if (inspector_image3f_ != nullptr) {
|
||||
return inspector_image3f_(label, image);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::array<LayerTotals, kNumImageLayers> layers;
|
||||
size_t num_blocks = 0;
|
||||
|
||||
// Number of blocks that use larger DCT (set by ac_strategy).
|
||||
size_t num_dct2_blocks = 0;
|
||||
size_t num_dct4_blocks = 0;
|
||||
size_t num_dct4x8_blocks = 0;
|
||||
size_t num_afv_blocks = 0;
|
||||
size_t num_dct8_blocks = 0;
|
||||
size_t num_dct8x16_blocks = 0;
|
||||
size_t num_dct8x32_blocks = 0;
|
||||
size_t num_dct16_blocks = 0;
|
||||
size_t num_dct16x32_blocks = 0;
|
||||
size_t num_dct32_blocks = 0;
|
||||
|
||||
std::array<uint32_t, 8> dc_pred_usage = {0};
|
||||
std::array<uint32_t, 8> dc_pred_usage_xb = {0};
|
||||
|
||||
int num_butteraugli_iters = 0;
|
||||
|
||||
// If not empty, additional debugging information (e.g. debug images) is
|
||||
// saved in files with this prefix.
|
||||
std::string debug_prefix;
|
||||
|
||||
// By how much the decoded image was downsampled relative to the encoded
|
||||
// image.
|
||||
size_t downsampling = 1;
|
||||
|
||||
// Number of various types of blocks
|
||||
size_t num_block_types[uint8_t(BlockType::kNumBlockTypes)] = {0};
|
||||
size_t num_position_types[uint8_t(BlockType::kNumBlockTypes)] = {0};
|
||||
|
||||
jxl::InspectorImage3F inspector_image3f_;
|
||||
|
||||
std::function<Status(const CodecInOut&, const std::string&)> dump_image =
|
||||
nullptr;
|
||||
|
||||
// WARNING: this is actually an INPUT to some code, and must be
|
||||
// copy-initialized from aux_out to aux_outs.
|
||||
TestingAux testing_aux;
|
||||
};
|
||||
|
||||
// Used to skip image creation if they won't be written to debug directory.
|
||||
static inline bool WantDebugOutput(const AuxOut* aux_out) {
|
||||
// Need valid pointer and filename.
|
||||
return aux_out != nullptr && !aux_out->debug_prefix.empty();
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_AUX_OUT_H_
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_AUX_OUT_FWD_H_
|
||||
#define JXL_AUX_OUT_FWD_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "jxl/enc_bit_writer.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
struct AuxOut;
|
||||
|
||||
// Helper function that ensures the `bits_written` are charged to `layer` in
|
||||
// `aux_out`. Example usage:
|
||||
// BitWriter::Allotment allotment(&writer, max_bits);
|
||||
// writer.Write(..); writer.Write(..);
|
||||
// ReclaimAndCharge(&writer, &allotment, layer, aux_out);
|
||||
void ReclaimAndCharge(BitWriter* JXL_RESTRICT writer,
|
||||
BitWriter::Allotment* JXL_RESTRICT allotment,
|
||||
size_t layer, AuxOut* JXL_RESTRICT aux_out);
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_AUX_OUT_FWD_H_
|
||||
@@ -0,0 +1,355 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "jxl/base/arch_specific.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#if JXL_ARCH_X64
|
||||
#include <xmmintrin.h>
|
||||
#if !JXL_COMPILER_MSVC
|
||||
#include <cpuid.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if JXL_ARCH_PPC
|
||||
#include <sys/platform/ppc.h> // __ppc_get_timebase_freq
|
||||
#endif
|
||||
|
||||
#if JXL_ARCH_ARM
|
||||
#include <unistd.h> // sysconf
|
||||
#endif
|
||||
|
||||
#include <string.h> // memcpy
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/base/bits.h"
|
||||
#include "jxl/base/compiler_specific.h"
|
||||
#include "jxl/base/os_specific.h"
|
||||
#include "jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace {
|
||||
|
||||
#if JXL_ARCH_X64
|
||||
|
||||
// For Cpuid.
|
||||
#pragma pack(push, 1)
|
||||
struct Regs {
|
||||
uint32_t a;
|
||||
uint32_t b;
|
||||
uint32_t c;
|
||||
uint32_t d;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
// Calls CPUID instruction with eax=level and ecx=count and fills `r`.
|
||||
// The caller must ensure `level` <= the max supported.
|
||||
void Cpuid(const uint32_t level, const uint32_t count, Regs* JXL_RESTRICT r) {
|
||||
#if JXL_COMPILER_MSVC
|
||||
int regs[4];
|
||||
__cpuidex(regs, level, count);
|
||||
r->a = regs[0];
|
||||
r->b = regs[1];
|
||||
r->c = regs[2];
|
||||
r->d = regs[3];
|
||||
#else
|
||||
// WARNING: avoid using __cpuid_count, which is broken: it lacks volatile and
|
||||
// clobber "memory", so the compiler caches CPUID results, not realizing that
|
||||
// CPUID:1b (APIC ID) changes across calls to SetThreadAffinity.
|
||||
__asm__ __volatile__(
|
||||
"xchgq %%rbx,%q1\n"
|
||||
"cpuid\n"
|
||||
"xchgq %%rbx,%q1"
|
||||
: "=a"(r->a), "=r"(r->b), "=c"(r->c), "=d"(r->d)
|
||||
: "0"(level), "2"(count)
|
||||
: "memory");
|
||||
#endif
|
||||
}
|
||||
|
||||
class Info {
|
||||
public:
|
||||
Info() {
|
||||
Regs r;
|
||||
Cpuid(0, 0, &r);
|
||||
max_func_ = r.a;
|
||||
|
||||
char vendor[13];
|
||||
// Note unusual order, reverse of ModR/M encoding.
|
||||
memcpy(&vendor[0], &r.b, 4);
|
||||
memcpy(&vendor[4], &r.d, 4);
|
||||
memcpy(&vendor[8], &r.c, 4);
|
||||
vendor[12] = '\0';
|
||||
intel_ = strcmp(vendor, "GenuineIntel") == 0;
|
||||
amd_ = strcmp(vendor, "AuthenticAMD") == 0;
|
||||
|
||||
Cpuid(0x80000000u, 0, &r);
|
||||
max_ext_func_ = r.a;
|
||||
}
|
||||
|
||||
uint32_t MaxFunc() const { return max_func_; }
|
||||
uint32_t MaxExtFunc() const { return max_ext_func_; }
|
||||
bool Intel() const { return intel_; }
|
||||
bool AMD() const { return amd_; }
|
||||
|
||||
std::string BrandString() const {
|
||||
char brand_string[49];
|
||||
Regs r;
|
||||
|
||||
// Check if brand string is supported (it is on all reasonable Intel/AMD)
|
||||
if (MaxExtFunc() < 0x80000004U) return std::string();
|
||||
|
||||
for (uint32_t i = 0; i < 3; ++i) {
|
||||
Cpuid(0x80000002U + i, 0, &r);
|
||||
memcpy(brand_string + i * 16, &r, sizeof(r));
|
||||
}
|
||||
brand_string[48] = 0;
|
||||
return brand_string;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t max_func_;
|
||||
uint32_t max_ext_func_;
|
||||
bool intel_;
|
||||
bool amd_;
|
||||
};
|
||||
|
||||
// Detects number of packages/cores/logical processors (hyperthreads).
|
||||
class X64_Topology {
|
||||
public:
|
||||
// Enumerates all APIC IDs and partitions them into fields, or returns false
|
||||
// if the topology cannot be detected (e.g. due to missing OS support).
|
||||
static Status Detect(ProcessorTopology* topology) {
|
||||
const Info info;
|
||||
const uint32_t core_bits = CoreBits(info);
|
||||
const uint32_t logical_bits = LogicalBits(info, core_bits);
|
||||
|
||||
uint32_t total_bits = 0;
|
||||
// Order matters:
|
||||
Field logical(logical_bits, &total_bits);
|
||||
Field core(core_bits, &total_bits);
|
||||
Field package(8, &total_bits);
|
||||
|
||||
// Query ProcessorId on each (accessible) logical processor:
|
||||
ThreadAffinity* original_affinity = GetThreadAffinity();
|
||||
for (int cpu : AvailableCPUs()) {
|
||||
if (!PinThreadToCPU(cpu)) {
|
||||
free(original_affinity);
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t id = ProcessorId(info);
|
||||
// xAPIC ID of 255 is invalid. Systems with >= 255 logical processors or
|
||||
// >= 64 cores require x2APIC (Nehalem) and CPUID:11 detection, which is a
|
||||
// separate codepath that we have not yet implemented.
|
||||
if (id >= 255) {
|
||||
JXL_WARNING("x2APIC ID (%x); TODO: implement CPUID:11", id);
|
||||
}
|
||||
|
||||
logical.AddValue(id);
|
||||
core.AddValue(id);
|
||||
package.AddValue(id);
|
||||
}
|
||||
JXL_CHECK(SetThreadAffinity(original_affinity));
|
||||
free(original_affinity);
|
||||
|
||||
topology->logical_per_core = logical.NumValues();
|
||||
topology->cores_per_package = core.NumValues();
|
||||
topology->packages = package.NumValues();
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
// How many bits in the xAPIC ID identify the core (per package).
|
||||
// #active cores <= (1 << bits).
|
||||
static uint32_t CoreBits(const Info& info) {
|
||||
Regs r;
|
||||
if (info.Intel()) {
|
||||
if (info.MaxFunc() < 4) return 0;
|
||||
Cpuid(4, 0, &r);
|
||||
return static_cast<uint32_t>(CeilLog2Nonzero((r.a >> 26) + 1));
|
||||
}
|
||||
|
||||
if (info.AMD()) {
|
||||
if (info.MaxExtFunc() < 0x80000008u) return 0;
|
||||
Cpuid(0x80000008u, 0, &r);
|
||||
return static_cast<uint32_t>(CeilLog2Nonzero((r.c & 0xFF) + 1));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// How many bits in the xAPIC ID identify the logical processor (per core).
|
||||
static uint32_t LogicalBits(const Info& info, const uint32_t core_bits) {
|
||||
Regs r;
|
||||
Cpuid(1, 0, &r);
|
||||
|
||||
// No hyperthreading
|
||||
if ((r.d & (1U << 28)) == 0) return 0;
|
||||
|
||||
// Early AMD falsely claim hyperthreading
|
||||
if (info.AMD() && (r.c & 2)) return 0;
|
||||
|
||||
const uint32_t logical_per_package = (r.b >> 16) & 0xFF;
|
||||
const uint32_t core_and_logical_bits =
|
||||
static_cast<uint32_t>(CeilLog2Nonzero(logical_per_package));
|
||||
JXL_ASSERT(core_and_logical_bits >= core_bits);
|
||||
return core_and_logical_bits - core_bits;
|
||||
}
|
||||
|
||||
// Variable-length/position field within an xAPIC ID. Counts the total
|
||||
// number of values encountered for all given id.
|
||||
class Field {
|
||||
public:
|
||||
Field(const uint32_t bits, uint32_t* JXL_RESTRICT total_bits)
|
||||
: mask_((1U << bits) - 1), shift_(*total_bits) {
|
||||
*total_bits += bits;
|
||||
}
|
||||
|
||||
void AddValue(const uint32_t id) { values_.insert((id >> shift_) & mask_); }
|
||||
|
||||
size_t NumValues() const { return values_.size(); }
|
||||
|
||||
private:
|
||||
const uint32_t mask_; // zero for zero-width fields
|
||||
const uint32_t shift_;
|
||||
std::set<uint32_t> values_;
|
||||
};
|
||||
|
||||
// Returns initial APIC ID or x2APIC ID, which uniquely identifies the current
|
||||
// logical processor. Returns 0 on old CPUs.
|
||||
static uint32_t ProcessorId(const Info& info) {
|
||||
Regs r;
|
||||
|
||||
// Support 32-bit IDs: we will only use 8 bits, but returning the full ID
|
||||
// allows the caller to raise a warning when x2APIC is active.
|
||||
if (info.MaxFunc() >= 11) {
|
||||
Cpuid(11, 0, &r);
|
||||
// Ensure we have CPUID:11 (number of enabled logical processors != 0).
|
||||
if (r.b != 0) {
|
||||
// r.d is a 32-bit ID; whether or not x2APIC is actually supported and
|
||||
// enabled, its lower 8 bits match the initial APIC ID (CPUID:1b).
|
||||
return r.d;
|
||||
}
|
||||
}
|
||||
|
||||
// No CPUID:11 => just return initial APIC ID (8-bit).
|
||||
Cpuid(1, 0, &r);
|
||||
return r.b >> 24;
|
||||
}
|
||||
};
|
||||
|
||||
double X64_DetectNominalClockRate() {
|
||||
const Info info;
|
||||
const std::string& brand_string = info.BrandString();
|
||||
// Brand strings include the maximum configured frequency. These prefixes are
|
||||
// defined by Intel CPUID documentation.
|
||||
const char* prefixes[3] = {"MHz", "GHz", "THz"};
|
||||
const double multipliers[3] = {1E6, 1E9, 1E12};
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
const size_t pos_prefix = brand_string.find(prefixes[i]);
|
||||
if (pos_prefix != std::string::npos) {
|
||||
const size_t pos_space = brand_string.rfind(' ', pos_prefix - 1);
|
||||
if (pos_space != std::string::npos) {
|
||||
const std::string digits =
|
||||
brand_string.substr(pos_space + 1, pos_prefix - pos_space - 1);
|
||||
return std::stod(digits) * multipliers[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
#elif JXL_ARCH_PPC
|
||||
|
||||
double PPC_DetectNominalClockRate() {
|
||||
double freq = -1;
|
||||
char line[200];
|
||||
char* s;
|
||||
char* value;
|
||||
|
||||
FILE* f = fopen("/proc/cpuinfo", "r");
|
||||
if (f != nullptr) {
|
||||
while (fgets(line, sizeof(line), f) != nullptr) {
|
||||
// NOTE: the ':' is the only character we can rely on
|
||||
if (!(value = strchr(line, ':'))) continue;
|
||||
// terminate the valuename
|
||||
*value++ = '\0';
|
||||
// skip any leading spaces
|
||||
while (*value == ' ') value++;
|
||||
if ((s = strchr(value, '\n'))) *s = '\0';
|
||||
|
||||
if (!strncasecmp(line, "clock", strlen("clock")) &&
|
||||
sscanf(value, "%lf", &freq) == 1) {
|
||||
freq *= 1E6;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
return freq;
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
#endif // JXL_ARCH_*
|
||||
|
||||
} // namespace
|
||||
|
||||
Status DetectProcessorTopology(ProcessorTopology* pt) {
|
||||
if (GetProcessorTopologyFromOS(pt)) return true;
|
||||
#if JXL_ARCH_X64
|
||||
if (X64_Topology::Detect(pt)) return true;
|
||||
#elif JXL_ARCH_ARM
|
||||
// TODO(deymo): Actually look up the CPU topology and model the big/small
|
||||
// core split.
|
||||
pt->logical_per_core = 1;
|
||||
pt->cores_per_package = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
pt->packages = 1;
|
||||
return true;
|
||||
#endif
|
||||
return JXL_FAILURE("Unable to detect processor topology");
|
||||
}
|
||||
|
||||
double NominalClockRate() {
|
||||
// Thread-safe caching - this is called several times.
|
||||
#if JXL_ARCH_X64
|
||||
static const double cycles_per_second = X64_DetectNominalClockRate();
|
||||
return cycles_per_second;
|
||||
#elif JXL_ARCH_PPC
|
||||
static const double cycles_per_second = PPC_DetectNominalClockRate();
|
||||
return cycles_per_second;
|
||||
#else
|
||||
return 0.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
double InvariantTicksPerSecond() {
|
||||
#if JXL_ARCH_PPC
|
||||
static const double cycles_per_second = __ppc_get_timebase_freq();
|
||||
return cycles_per_second;
|
||||
#elif JXL_ARCH_X64
|
||||
return NominalClockRate();
|
||||
#else
|
||||
return 1E9; // nanoseconds - matches tsc_timer.h CLOCK_MONOTONIC fallback.
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_BASE_ARCH_SPECIFIC_H_
|
||||
#define JXL_BASE_ARCH_SPECIFIC_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <hwy/arch.h> // kMaxVectorSize
|
||||
|
||||
#include "jxl/base/compiler_specific.h"
|
||||
#include "jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
#define JXL_ARCH_X64 1
|
||||
#else
|
||||
#define JXL_ARCH_X64 0
|
||||
#endif
|
||||
|
||||
#if defined(__powerpc64__) || defined(_M_PPC)
|
||||
#define JXL_ARCH_PPC 1
|
||||
#else
|
||||
#define JXL_ARCH_PPC 0
|
||||
#endif
|
||||
|
||||
#if defined(__aarch64__) || defined(__arm__)
|
||||
#define JXL_ARCH_ARM 1
|
||||
#else
|
||||
#define JXL_ARCH_ARM 0
|
||||
#endif
|
||||
|
||||
struct ProcessorTopology {
|
||||
size_t logical_per_core = 1;
|
||||
size_t cores_per_package = 1;
|
||||
size_t packages = 1;
|
||||
};
|
||||
|
||||
// Relatively expensive, preferably only call once.
|
||||
Status DetectProcessorTopology(ProcessorTopology* pt);
|
||||
|
||||
// Returns the nominal (without Turbo Boost) CPU clock rate [Hertz]. Useful for
|
||||
// (roughly) characterizing the CPU speed.
|
||||
double NominalClockRate();
|
||||
|
||||
// Returns tsc_timer frequency, useful for converting ticks to seconds. This is
|
||||
// unaffected by CPU throttling ("invariant"). Thread-safe. Returns timebase
|
||||
// frequency on PPC, NominalClockRate on X64, otherwise 1E9.
|
||||
double InvariantTicksPerSecond();
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_BASE_ARCH_SPECIFIC_H_
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_BASE_BITS_H_
|
||||
#define JXL_BASE_BITS_H_
|
||||
|
||||
// Specialized instructions for processing register-sized bit arrays.
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#include <nmmintrin.h>
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <hwy/type_traits.h>
|
||||
|
||||
#include "jxl/base/compiler_specific.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
static JXL_INLINE size_t PopCount(hwy::SizeTag<4> /* tag */, const uint32_t x) {
|
||||
#ifdef _MSC_VER
|
||||
return _mm_popcnt_u32(x);
|
||||
#else
|
||||
return static_cast<size_t>(__builtin_popcount(x));
|
||||
#endif
|
||||
}
|
||||
static JXL_INLINE size_t PopCount(hwy::SizeTag<8> /* tag */, const uint64_t x) {
|
||||
#ifdef _MSC_VER
|
||||
return _mm_popcnt_u64(x);
|
||||
#else
|
||||
return static_cast<size_t>(__builtin_popcountll(x));
|
||||
#endif
|
||||
}
|
||||
template <typename T>
|
||||
static JXL_INLINE size_t PopCount(T x) {
|
||||
static_assert(!hwy::IsSigned<T>(), "PopCount: use unsigned");
|
||||
return PopCount(hwy::SizeTag<sizeof(T)>(), x);
|
||||
}
|
||||
|
||||
// Undefined results for x == 0.
|
||||
static JXL_INLINE size_t NumZeroBitsAboveMSBNonzero(hwy::SizeTag<4> /* tag */,
|
||||
const uint32_t x) {
|
||||
#ifdef _MSC_VER
|
||||
unsigned long index;
|
||||
_BitScanReverse(&index, x);
|
||||
return 31 - index;
|
||||
#else
|
||||
return static_cast<size_t>(__builtin_clz(x));
|
||||
#endif
|
||||
}
|
||||
static JXL_INLINE size_t NumZeroBitsAboveMSBNonzero(hwy::SizeTag<8> /* tag */,
|
||||
const uint64_t x) {
|
||||
#ifdef _MSC_VER
|
||||
unsigned long index;
|
||||
_BitScanReverse64(&index, x);
|
||||
return 63 - index;
|
||||
#else
|
||||
return static_cast<size_t>(__builtin_clzll(x));
|
||||
#endif
|
||||
}
|
||||
template <typename T>
|
||||
static JXL_INLINE size_t NumZeroBitsAboveMSBNonzero(const T x) {
|
||||
static_assert(!hwy::IsSigned<T>(),
|
||||
"NumZeroBitsAboveMSBNonzero: use unsigned");
|
||||
return NumZeroBitsAboveMSBNonzero(hwy::SizeTag<sizeof(T)>(), x);
|
||||
}
|
||||
|
||||
// Undefined results for x == 0.
|
||||
static JXL_INLINE size_t NumZeroBitsBelowLSBNonzero(hwy::SizeTag<4> /* tag */,
|
||||
const uint32_t x) {
|
||||
#ifdef _MSC_VER
|
||||
unsigned long index;
|
||||
_BitScanForward(&index, x);
|
||||
return index;
|
||||
#else
|
||||
return static_cast<size_t>(__builtin_ctz(x));
|
||||
#endif
|
||||
}
|
||||
static JXL_INLINE size_t NumZeroBitsBelowLSBNonzero(hwy::SizeTag<8> /* tag */,
|
||||
const uint64_t x) {
|
||||
#ifdef _MSC_VER
|
||||
unsigned long index;
|
||||
_BitScanForward64(&index, x);
|
||||
return index;
|
||||
#else
|
||||
return static_cast<size_t>(__builtin_ctzll(x));
|
||||
#endif
|
||||
}
|
||||
template <typename T>
|
||||
static JXL_INLINE size_t NumZeroBitsBelowLSBNonzero(T x) {
|
||||
static_assert(!hwy::IsSigned<T>(),
|
||||
"NumZeroBitsBelowLSBNonzero: use unsigned");
|
||||
return NumZeroBitsBelowLSBNonzero(hwy::SizeTag<sizeof(T)>(), x);
|
||||
}
|
||||
|
||||
// Returns bit width for x == 0.
|
||||
template <typename T>
|
||||
static JXL_INLINE size_t NumZeroBitsAboveMSB(const T x) {
|
||||
return (x == 0) ? sizeof(T) * 8 : NumZeroBitsAboveMSBNonzero(x);
|
||||
}
|
||||
|
||||
// Returns bit width for x == 0.
|
||||
template <typename T>
|
||||
static JXL_INLINE size_t NumZeroBitsBelowLSB(const T x) {
|
||||
return (x == 0) ? sizeof(T) * 8 : NumZeroBitsBelowLSBNonzero(x);
|
||||
}
|
||||
|
||||
// Returns base-2 logarithm, rounded down.
|
||||
template <typename T>
|
||||
static JXL_INLINE size_t FloorLog2Nonzero(const T x) {
|
||||
return (sizeof(T) * 8 - 1) ^ NumZeroBitsAboveMSBNonzero(x);
|
||||
}
|
||||
|
||||
// Returns base-2 logarithm, rounded up.
|
||||
template <typename T>
|
||||
static JXL_INLINE size_t CeilLog2Nonzero(const T x) {
|
||||
const size_t floor_log2 = FloorLog2Nonzero(x);
|
||||
if ((x & (x - 1)) == 0) return floor_log2; // power of two
|
||||
return floor_log2 + 1;
|
||||
}
|
||||
|
||||
// Reverses bit order.
|
||||
static JXL_INLINE uint8_t FlipByte(const uint8_t x) {
|
||||
// TODO(veluca): consider trying out alternative strategies, such as a single
|
||||
// LUT.
|
||||
static constexpr uint8_t kNibbleLut[16] = {
|
||||
0b0000, 0b1000, 0b0100, 0b1100, 0b0010, 0b1010, 0b0110, 0b1110,
|
||||
0b0001, 0b1001, 0b0101, 0b1101, 0b0011, 0b1011, 0b0111, 0b1111,
|
||||
};
|
||||
return (kNibbleLut[x & 0xF] << 4) | kNibbleLut[x >> 4];
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_BASE_BITS_H_
|
||||
@@ -0,0 +1,212 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_BASE_BYTE_ORDER_H_
|
||||
#define JXL_BASE_BYTE_ORDER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h> // memcpy
|
||||
|
||||
#include "jxl/base/compiler_specific.h"
|
||||
|
||||
#if JXL_COMPILER_MSVC
|
||||
#include <intrin.h> // _byteswap_*
|
||||
#endif
|
||||
|
||||
#if (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
|
||||
#define JXL_BYTE_ORDER_LITTLE 1
|
||||
#else
|
||||
// This means that we don't know that the byte order is little endian, in
|
||||
// this case we use endian-neutral code that works for both little- and
|
||||
// big-endian.
|
||||
#define JXL_BYTE_ORDER_LITTLE 0
|
||||
#endif
|
||||
|
||||
// Returns whether the system is little-endian (least-significant byte first).
|
||||
#if JXL_BYTE_ORDER_LITTLE
|
||||
static constexpr bool IsLittleEndian() { return true; }
|
||||
#else
|
||||
static inline bool IsLittleEndian() {
|
||||
const uint32_t multibyte = 1;
|
||||
uint8_t byte;
|
||||
memcpy(&byte, &multibyte, 1);
|
||||
return byte == 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if JXL_COMPILER_MSVC
|
||||
#define JXL_BSWAP32(x) _byteswap_ulong(x)
|
||||
#define JXL_BSWAP64(x) _byteswap_uint64(x)
|
||||
#else
|
||||
#define JXL_BSWAP32(x) __builtin_bswap32(x)
|
||||
#define JXL_BSWAP64(x) __builtin_bswap64(x)
|
||||
#endif
|
||||
|
||||
static JXL_INLINE uint32_t LoadBE16(const uint8_t* p) {
|
||||
const uint32_t byte1 = p[0];
|
||||
const uint32_t byte0 = p[1];
|
||||
return (byte1 << 8) | byte0;
|
||||
}
|
||||
|
||||
static JXL_INLINE uint32_t LoadLE16(const uint8_t* p) {
|
||||
const uint32_t byte0 = p[0];
|
||||
const uint32_t byte1 = p[1];
|
||||
return (byte1 << 8) | byte0;
|
||||
}
|
||||
|
||||
static JXL_INLINE uint32_t LoadBE32(const uint8_t* p) {
|
||||
#if JXL_BYTE_ORDER_LITTLE
|
||||
uint32_t big;
|
||||
memcpy(&big, p, 4);
|
||||
return JXL_BSWAP32(big);
|
||||
#else
|
||||
// Byte-order-independent - can't assume this machine is big endian.
|
||||
const uint32_t byte3 = p[0];
|
||||
const uint32_t byte2 = p[1];
|
||||
const uint32_t byte1 = p[2];
|
||||
const uint32_t byte0 = p[3];
|
||||
return (byte3 << 24) | (byte2 << 16) | (byte1 << 8) | byte0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static JXL_INLINE uint32_t LoadLE32(const uint8_t* p) {
|
||||
#if JXL_BYTE_ORDER_LITTLE
|
||||
uint32_t little;
|
||||
memcpy(&little, p, 4);
|
||||
return little;
|
||||
#else
|
||||
// Byte-order-independent - can't assume this machine is big endian.
|
||||
const uint32_t byte0 = p[0];
|
||||
const uint32_t byte1 = p[1];
|
||||
const uint32_t byte2 = p[2];
|
||||
const uint32_t byte3 = p[3];
|
||||
return (byte3 << 24) | (byte2 << 16) | (byte1 << 8) | byte0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static JXL_INLINE uint64_t LoadLE64(const uint8_t* p) {
|
||||
#if JXL_BYTE_ORDER_LITTLE
|
||||
uint64_t little;
|
||||
memcpy(&little, p, 8);
|
||||
return little;
|
||||
#else
|
||||
// Byte-order-independent - can't assume this machine is big endian.
|
||||
const uint64_t byte0 = p[0];
|
||||
const uint64_t byte1 = p[1];
|
||||
const uint64_t byte2 = p[2];
|
||||
const uint64_t byte3 = p[3];
|
||||
const uint64_t byte4 = p[4];
|
||||
const uint64_t byte5 = p[5];
|
||||
const uint64_t byte6 = p[6];
|
||||
const uint64_t byte7 = p[7];
|
||||
return (byte7 << 56) | (byte6 << 48) | (byte5 << 40) | (byte4 << 32) |
|
||||
(byte3 << 24) | (byte2 << 16) | (byte1 << 8) | byte0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static JXL_INLINE void StoreBE16(const uint32_t native, uint8_t* p) {
|
||||
p[0] = (native >> 8) & 0xFF;
|
||||
p[1] = native & 0xFF;
|
||||
}
|
||||
|
||||
static JXL_INLINE void StoreLE16(const uint32_t native, uint8_t* p) {
|
||||
p[1] = (native >> 8) & 0xFF;
|
||||
p[0] = native & 0xFF;
|
||||
}
|
||||
|
||||
static JXL_INLINE void StoreBE32(const uint32_t native, uint8_t* p) {
|
||||
#if JXL_BYTE_ORDER_LITTLE
|
||||
const uint32_t big = JXL_BSWAP32(native);
|
||||
memcpy(p, &big, 4);
|
||||
#else
|
||||
// Byte-order-independent - can't assume this machine is big endian.
|
||||
p[0] = native >> 24;
|
||||
p[1] = (native >> 16) & 0xFF;
|
||||
p[2] = (native >> 8) & 0xFF;
|
||||
p[3] = native & 0xFF;
|
||||
#endif
|
||||
}
|
||||
|
||||
static JXL_INLINE void StoreLE32(const uint32_t native, uint8_t* p) {
|
||||
#if JXL_BYTE_ORDER_LITTLE
|
||||
const uint32_t little = native;
|
||||
memcpy(p, &little, 4);
|
||||
#else
|
||||
// Byte-order-independent - can't assume this machine is big endian.
|
||||
p[3] = native >> 24;
|
||||
p[2] = (native >> 16) & 0xFF;
|
||||
p[1] = (native >> 8) & 0xFF;
|
||||
p[0] = native & 0xFF;
|
||||
#endif
|
||||
}
|
||||
|
||||
static JXL_INLINE void StoreLE64(const uint64_t native, uint8_t* p) {
|
||||
#if JXL_BYTE_ORDER_LITTLE
|
||||
const uint64_t little = native;
|
||||
memcpy(p, &little, 8);
|
||||
#else
|
||||
// Byte-order-independent - can't assume this machine is big endian.
|
||||
p[7] = native >> 56;
|
||||
p[6] = (native >> 48) & 0xFF;
|
||||
p[5] = (native >> 40) & 0xFF;
|
||||
p[4] = (native >> 32) & 0xFF;
|
||||
p[3] = (native >> 24) & 0xFF;
|
||||
p[2] = (native >> 16) & 0xFF;
|
||||
p[1] = (native >> 8) & 0xFF;
|
||||
p[0] = native & 0xFF;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Big/Little Endian order.
|
||||
struct OrderBE {};
|
||||
struct OrderLE {};
|
||||
|
||||
// Wrappers for calling from generic code.
|
||||
static JXL_INLINE void Store16(OrderBE /*tag*/, const uint32_t native,
|
||||
uint8_t* p) {
|
||||
return StoreBE16(native, p);
|
||||
}
|
||||
|
||||
static JXL_INLINE void Store16(OrderLE /*tag*/, const uint32_t native,
|
||||
uint8_t* p) {
|
||||
return StoreLE16(native, p);
|
||||
}
|
||||
|
||||
static JXL_INLINE void Store32(OrderBE /*tag*/, const uint32_t native,
|
||||
uint8_t* p) {
|
||||
return StoreBE32(native, p);
|
||||
}
|
||||
|
||||
static JXL_INLINE void Store32(OrderLE /*tag*/, const uint32_t native,
|
||||
uint8_t* p) {
|
||||
return StoreLE32(native, p);
|
||||
}
|
||||
|
||||
static JXL_INLINE uint32_t Load16(OrderBE /*tag*/, const uint8_t* p) {
|
||||
return LoadBE16(p);
|
||||
}
|
||||
|
||||
static JXL_INLINE uint32_t Load16(OrderLE /*tag*/, const uint8_t* p) {
|
||||
return LoadLE16(p);
|
||||
}
|
||||
|
||||
static JXL_INLINE uint32_t Load32(OrderBE /*tag*/, const uint8_t* p) {
|
||||
return LoadBE32(p);
|
||||
}
|
||||
|
||||
static JXL_INLINE uint32_t Load32(OrderLE /*tag*/, const uint8_t* p) {
|
||||
return LoadLE32(p);
|
||||
}
|
||||
|
||||
#endif // JXL_BASE_BYTE_ORDER_H_
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "jxl/base/cache_aligned.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Disabled: slower than malloc + alignment.
|
||||
#define JXL_USE_MMAP 0
|
||||
|
||||
#if JXL_USE_MMAP
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm> // std::max
|
||||
#include <atomic>
|
||||
#include <hwy/arch.h> // for kMaxVectorSize
|
||||
|
||||
#include "jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace {
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct AllocationHeader {
|
||||
void* allocated;
|
||||
size_t allocated_size;
|
||||
uint8_t left_padding[kMaxVectorSize];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
std::atomic<uint64_t> num_allocations{0};
|
||||
std::atomic<uint64_t> bytes_in_use{0};
|
||||
std::atomic<uint64_t> max_bytes_in_use{0};
|
||||
|
||||
} // namespace
|
||||
|
||||
void CacheAligned::PrintStats() {
|
||||
printf("Allocations: %zu (max bytes in use: %E)\n",
|
||||
size_t(num_allocations.load(std::memory_order_relaxed)),
|
||||
double(max_bytes_in_use.load(std::memory_order_relaxed)));
|
||||
}
|
||||
|
||||
size_t CacheAligned::NextOffset() {
|
||||
static std::atomic<uint32_t> next{0};
|
||||
constexpr uint32_t kGroups = CacheAligned::kAlias / CacheAligned::kAlignment;
|
||||
const uint32_t group = next.fetch_add(1, std::memory_order_relaxed) % kGroups;
|
||||
return CacheAligned::kAlignment * group;
|
||||
}
|
||||
|
||||
void* CacheAligned::Allocate(const size_t payload_size, size_t offset) {
|
||||
JXL_ASSERT(payload_size <= std::numeric_limits<size_t>::max() / 2);
|
||||
JXL_ASSERT((offset % kAlignment == 0) && offset <= kAlias);
|
||||
|
||||
// What: | misalign | unused | AllocationHeader |payload
|
||||
// Size: |<= kAlias | offset | |payload_size
|
||||
// ^allocated.^aligned.^header............^payload
|
||||
// The header must immediately precede payload, which must remain aligned.
|
||||
// To avoid wasting space, the header resides at the end of `unused`,
|
||||
// which therefore cannot be empty (offset == 0).
|
||||
if (offset == 0) {
|
||||
offset = kAlignment; // = round_up(sizeof(AllocationHeader), kAlignment)
|
||||
static_assert(sizeof(AllocationHeader) <= kAlignment, "Else: round up");
|
||||
}
|
||||
|
||||
#if JXL_USE_MMAP
|
||||
const size_t allocated_size = offset + payload_size;
|
||||
const int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE;
|
||||
void* allocated =
|
||||
mmap(nullptr, allocated_size, PROT_READ | PROT_WRITE, flags, -1, 0);
|
||||
if (allocated == MAP_FAILED) return nullptr;
|
||||
const uintptr_t aligned = reinterpret_cast<uintptr_t>(allocated);
|
||||
#else
|
||||
const size_t allocated_size = kAlias + offset + payload_size;
|
||||
void* allocated = malloc(allocated_size);
|
||||
if (allocated == nullptr) return nullptr;
|
||||
// Always round up even if already aligned - we already asked for kAlias
|
||||
// extra bytes and there's no way to give them back.
|
||||
uintptr_t aligned = reinterpret_cast<uintptr_t>(allocated) + kAlias;
|
||||
static_assert((kAlias & (kAlias - 1)) == 0, "kAlias must be a power of 2");
|
||||
static_assert(kAlias >= kAlignment, "Cannot align to more than kAlias");
|
||||
aligned &= ~(kAlias - 1);
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
// No effect.
|
||||
uintptr_t page_aligned = reinterpret_cast<uintptr_t>(allocated);
|
||||
page_aligned &= ~(4096 - 1);
|
||||
if (madvise(reinterpret_cast<void*>(page_aligned), allocated_size,
|
||||
MADV_WILLNEED) != 0) {
|
||||
JXL_NOTIFY_ERROR("madvise failed");
|
||||
}
|
||||
#elif 0
|
||||
// INCREASES both first and subsequent decode times.
|
||||
if (mlock(allocated, allocated_size) != 0) {
|
||||
JXL_NOTIFY_ERROR("mlock failed");
|
||||
}
|
||||
#endif
|
||||
|
||||
// Update statistics (#allocations and max bytes in use)
|
||||
num_allocations.fetch_add(1, std::memory_order_relaxed);
|
||||
const uint64_t prev_bytes =
|
||||
bytes_in_use.fetch_add(allocated_size, std::memory_order_acq_rel);
|
||||
uint64_t expected_max = max_bytes_in_use.load(std::memory_order_acquire);
|
||||
for (;;) {
|
||||
const uint64_t desired =
|
||||
std::max(expected_max, prev_bytes + allocated_size);
|
||||
if (max_bytes_in_use.compare_exchange_strong(expected_max, desired,
|
||||
std::memory_order_acq_rel)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const uintptr_t payload = aligned + offset; // still aligned
|
||||
|
||||
// Stash `allocated` and payload_size inside header for use by Free().
|
||||
AllocationHeader* header = reinterpret_cast<AllocationHeader*>(payload) - 1;
|
||||
header->allocated = allocated;
|
||||
header->allocated_size = allocated_size;
|
||||
|
||||
return JXL_ASSUME_ALIGNED(reinterpret_cast<void*>(payload), 64);
|
||||
}
|
||||
|
||||
void CacheAligned::Free(const void* aligned_pointer) {
|
||||
if (aligned_pointer == nullptr) {
|
||||
return;
|
||||
}
|
||||
const uintptr_t payload = reinterpret_cast<uintptr_t>(aligned_pointer);
|
||||
JXL_ASSERT(payload % kAlignment == 0);
|
||||
const AllocationHeader* header =
|
||||
reinterpret_cast<const AllocationHeader*>(payload) - 1;
|
||||
|
||||
// Subtract (2's complement negation).
|
||||
bytes_in_use.fetch_add(~header->allocated_size + 1,
|
||||
std::memory_order_acq_rel);
|
||||
|
||||
#if JXL_USE_MMAP
|
||||
munmap(header->allocated, header->allocated_size);
|
||||
#else
|
||||
free(header->allocated);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_BASE_CACHE_ALIGNED_H_
|
||||
#define JXL_BASE_CACHE_ALIGNED_H_
|
||||
|
||||
// Memory allocator with support for alignment + misalignment.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "jxl/base/arch_specific.h"
|
||||
#include "jxl/base/compiler_specific.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// Functions that depend on the cache line size.
|
||||
class CacheAligned {
|
||||
public:
|
||||
static void PrintStats();
|
||||
|
||||
static constexpr size_t kPointerSize = sizeof(void*);
|
||||
static constexpr size_t kCacheLineSize = 64;
|
||||
// To avoid RFOs, match L2 fill size (pairs of lines).
|
||||
static constexpr size_t kAlignment = 2 * kCacheLineSize;
|
||||
// Minimum multiple for which cache set conflicts and/or loads blocked by
|
||||
// preceding stores can occur.
|
||||
static constexpr size_t kAlias = 2048;
|
||||
|
||||
// Returns a 'random' (cyclical) offset suitable for Allocate.
|
||||
static size_t NextOffset();
|
||||
|
||||
// Returns null or memory whose address is congruent to `offset` (mod kAlias).
|
||||
// This reduces cache conflicts and load/store stalls, especially with large
|
||||
// allocations that would otherwise have similar alignments. At least
|
||||
// `payload_size` (which can be zero) bytes will be accessible.
|
||||
static void* Allocate(size_t payload_size, size_t offset);
|
||||
|
||||
static void* Allocate(const size_t payload_size) {
|
||||
return Allocate(payload_size, NextOffset());
|
||||
}
|
||||
|
||||
static void Free(const void* aligned_pointer);
|
||||
};
|
||||
|
||||
// Avoids the need for a function pointer (deleter) in CacheAlignedUniquePtr.
|
||||
struct CacheAlignedDeleter {
|
||||
void operator()(uint8_t* aligned_pointer) const {
|
||||
return CacheAligned::Free(aligned_pointer);
|
||||
}
|
||||
};
|
||||
|
||||
using CacheAlignedUniquePtr = std::unique_ptr<uint8_t[], CacheAlignedDeleter>;
|
||||
|
||||
// Does not invoke constructors.
|
||||
static inline CacheAlignedUniquePtr AllocateArray(const size_t bytes) {
|
||||
return CacheAlignedUniquePtr(
|
||||
static_cast<uint8_t*>(CacheAligned::Allocate(bytes)),
|
||||
CacheAlignedDeleter());
|
||||
}
|
||||
|
||||
static inline CacheAlignedUniquePtr AllocateArray(const size_t bytes,
|
||||
const size_t offset) {
|
||||
return CacheAlignedUniquePtr(
|
||||
static_cast<uint8_t*>(CacheAligned::Allocate(bytes, offset)),
|
||||
CacheAlignedDeleter());
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_BASE_CACHE_ALIGNED_H_
|
||||
@@ -0,0 +1,144 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_BASE_COMPILER_SPECIFIC_H_
|
||||
#define JXL_BASE_COMPILER_SPECIFIC_H_
|
||||
|
||||
// Macros for compiler version + nonstandard keywords, e.g. __builtin_expect.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// #if is shorter and safer than #ifdef. *_VERSION are zero if not detected,
|
||||
// otherwise 100 * major + minor version. Note that other packages check for
|
||||
// #ifdef COMPILER_MSVC, so we cannot use that same name.
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define JXL_COMPILER_MSVC _MSC_VER
|
||||
#else
|
||||
#define JXL_COMPILER_MSVC 0
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define JXL_COMPILER_GCC (__GNUC__ * 100 + __GNUC_MINOR__)
|
||||
#else
|
||||
#define JXL_COMPILER_GCC 0
|
||||
#endif
|
||||
|
||||
#ifdef __clang__
|
||||
#define JXL_COMPILER_CLANG (__clang_major__ * 100 + __clang_minor__)
|
||||
// Clang pretends to be GCC for compatibility.
|
||||
#undef JXL_COMPILER_GCC
|
||||
#define JXL_COMPILER_GCC 0
|
||||
#else
|
||||
#define JXL_COMPILER_CLANG 0
|
||||
#endif
|
||||
|
||||
#if JXL_COMPILER_MSVC
|
||||
#define JXL_RESTRICT __restrict
|
||||
#elif JXL_COMPILER_GCC || JXL_COMPILER_CLANG
|
||||
#define JXL_RESTRICT __restrict__
|
||||
#else
|
||||
#define JXL_RESTRICT
|
||||
#endif
|
||||
|
||||
#if JXL_COMPILER_MSVC
|
||||
#define JXL_INLINE __forceinline
|
||||
#define JXL_NOINLINE __declspec(noinline)
|
||||
#else
|
||||
#define JXL_INLINE inline __attribute__((always_inline))
|
||||
#define JXL_NOINLINE __attribute__((noinline))
|
||||
#endif
|
||||
|
||||
#if JXL_COMPILER_MSVC
|
||||
#define JXL_NORETURN __declspec(noreturn)
|
||||
#elif JXL_COMPILER_GCC || JXL_COMPILER_CLANG
|
||||
#define JXL_NORETURN __attribute__((noreturn))
|
||||
#endif
|
||||
|
||||
#if JXL_COMPILER_MSVC
|
||||
#define JXL_UNREACHABLE __assume(false)
|
||||
#elif JXL_COMPILER_CLANG || JXL_COMPILER_GCC >= 405
|
||||
#define JXL_UNREACHABLE __builtin_unreachable()
|
||||
#else
|
||||
#define JXL_UNREACHABLE
|
||||
#endif
|
||||
|
||||
#if JXL_COMPILER_MSVC
|
||||
// Unsupported, __assume is not the same.
|
||||
#define JXL_LIKELY(expr) expr
|
||||
#define JXL_UNLIKELY(expr) expr
|
||||
#else
|
||||
#define JXL_LIKELY(expr) __builtin_expect(!!(expr), 1)
|
||||
#define JXL_UNLIKELY(expr) __builtin_expect(!!(expr), 0)
|
||||
#endif
|
||||
|
||||
#if JXL_COMPILER_MSVC
|
||||
#include <intrin.h>
|
||||
|
||||
#pragma intrinsic(_ReadWriteBarrier)
|
||||
#define JXL_COMPILER_FENCE _ReadWriteBarrier()
|
||||
#elif JXL_COMPILER_GCC || JXL_COMPILER_CLANG
|
||||
#define JXL_COMPILER_FENCE asm volatile("" : : : "memory")
|
||||
#else
|
||||
#define JXL_COMPILER_FENCE
|
||||
#endif
|
||||
|
||||
// Returns a void* pointer which the compiler then assumes is N-byte aligned.
|
||||
// Example: float* JXL_RESTRICT aligned = (float*)JXL_ASSUME_ALIGNED(in, 32);
|
||||
//
|
||||
// The assignment semantics are required by GCC/Clang. ICC provides an in-place
|
||||
// __assume_aligned, whereas MSVC's __assume appears unsuitable.
|
||||
#if JXL_COMPILER_CLANG
|
||||
// Early versions of Clang did not support __builtin_assume_aligned.
|
||||
#define JXL_HAS_ASSUME_ALIGNED __has_builtin(__builtin_assume_aligned)
|
||||
#elif JXL_COMPILER_GCC
|
||||
#define JXL_HAS_ASSUME_ALIGNED 1
|
||||
#else
|
||||
#define JXL_HAS_ASSUME_ALIGNED 0
|
||||
#endif
|
||||
|
||||
#if JXL_HAS_ASSUME_ALIGNED
|
||||
#define JXL_ASSUME_ALIGNED(ptr, align) __builtin_assume_aligned((ptr), (align))
|
||||
#else
|
||||
#define JXL_ASSUME_ALIGNED(ptr, align) (ptr) /* not supported */
|
||||
#endif
|
||||
|
||||
#ifdef __has_attribute
|
||||
#define JXL_HAVE_ATTRIBUTE(x) __has_attribute(x)
|
||||
#else
|
||||
#define JXL_HAVE_ATTRIBUTE(x) 0
|
||||
#endif
|
||||
|
||||
// Raises warnings if the function return value is unused. Should appear as the
|
||||
// first part of a function definition/declaration.
|
||||
#if JXL_HAVE_ATTRIBUTE(nodiscard)
|
||||
#define JXL_MUST_USE_RESULT [[nodiscard]]
|
||||
#elif JXL_COMPILER_CLANG && JXL_HAVE_ATTRIBUTE(warn_unused_result)
|
||||
#define JXL_MUST_USE_RESULT __attribute__((warn_unused_result))
|
||||
#else
|
||||
#define JXL_MUST_USE_RESULT
|
||||
#endif
|
||||
|
||||
#if JXL_HAVE_ATTRIBUTE(__format__)
|
||||
#define JXL_FORMAT(idx_fmt, idx_arg) \
|
||||
__attribute__((__format__(__printf__, idx_fmt, idx_arg)))
|
||||
#else
|
||||
#define JXL_FORMAT(idx_fmt, idx_arg)
|
||||
#endif
|
||||
|
||||
#if JXL_COMPILER_MSVC
|
||||
using ssize_t = intptr_t;
|
||||
#endif
|
||||
|
||||
#endif // JXL_BASE_COMPILER_SPECIFIC_H_
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "jxl/base/data_parallel.h"
|
||||
|
||||
#define DATA_PARALLEL_TRACE 0
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// static
|
||||
JpegxlParallelRetCode ThreadPool::SequentialRunnerStatic(
|
||||
void* runner_opaque, void* jpegxl_opaque, JpegxlParallelRunInit init,
|
||||
JpegxlParallelRunFunction func, uint32_t start_range, uint32_t end_range) {
|
||||
JpegxlParallelRetCode init_ret = (*init)(jpegxl_opaque, 1);
|
||||
if (init_ret != 0) return init_ret;
|
||||
|
||||
for (uint32_t i = start_range; i < end_range; i++) {
|
||||
(*func)(jpegxl_opaque, i, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if DATA_PARALLEL_TRACE
|
||||
void TraceRunBegin(const char* /*caller*/, double* t0) { *t0 = Now(); }
|
||||
|
||||
void TraceRunEnd(const char* caller, double t0) {
|
||||
const double elapsed = Now() - t0;
|
||||
fprintf(stderr, "%27s: %5.1f ms\n", caller, elapsed * 1E3);
|
||||
}
|
||||
#else
|
||||
void TraceRunBegin(const char* /*caller*/, double* /*t0*/) {}
|
||||
void TraceRunEnd(const char* /*caller*/, double /*t0*/) {}
|
||||
#endif
|
||||
|
||||
} // namespace jxl
|
||||
@@ -0,0 +1,170 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_BASE_DATA_PARALLEL_H_
|
||||
#define JXL_BASE_DATA_PARALLEL_H_
|
||||
|
||||
// Portable, low-overhead C++11 ThreadPool alternative to OpenMP for
|
||||
// data-parallel computations.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "include/jpegxl/parallel_runner.h"
|
||||
#include "jxl/base/bits.h"
|
||||
#include "jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
class ThreadPool {
|
||||
public:
|
||||
// Use this type as an InitFunc to skip the initialization step in Run().
|
||||
// When this is used the return value of Run() is always true and does not
|
||||
// need to be checked.
|
||||
struct SkipInit {};
|
||||
|
||||
ThreadPool(JpegxlParallelRunner runner, void* runner_opaque)
|
||||
: runner_(runner ? runner : &ThreadPool::SequentialRunnerStatic),
|
||||
runner_opaque_(runner ? runner_opaque : static_cast<void*>(this)) {}
|
||||
|
||||
ThreadPool(const ThreadPool&) = delete;
|
||||
ThreadPool& operator&(const ThreadPool&) = delete;
|
||||
|
||||
// Runs init_func(num_threads) followed by data_func(task, thread) on worker
|
||||
// thread(s) for every task in [begin, end). init_func() must return a Status
|
||||
// indicating whether the initialization succeeded.
|
||||
// "thread" is an integer smaller than num_threads.
|
||||
// Not thread-safe - no two calls to Run may overlap.
|
||||
// Subsequent calls will reuse the same threads.
|
||||
//
|
||||
// Precondition: begin <= end.
|
||||
template <class InitFunc, class DataFunc>
|
||||
Status Run(uint32_t begin, uint32_t end, const InitFunc& init_func,
|
||||
const DataFunc& data_func, const char* caller = "") {
|
||||
JXL_ASSERT(begin <= end);
|
||||
if (begin == end) return true;
|
||||
RunCallState<InitFunc, DataFunc> call_state(init_func, data_func);
|
||||
// The runner_ uses the C convention and returns 0 in case of error, so we
|
||||
// convert it to an Status.
|
||||
return (*runner_)(runner_opaque_, static_cast<void*>(&call_state),
|
||||
&call_state.CallInitFunc, &call_state.CallDataFunc, begin,
|
||||
end) == 0;
|
||||
}
|
||||
|
||||
// Specialization that returns bool when SkipInit is used.
|
||||
template <class DataFunc>
|
||||
bool Run(uint32_t begin, uint32_t end, const SkipInit /* tag */,
|
||||
const DataFunc& data_func, const char* caller = "") {
|
||||
return Run(begin, end, ReturnTrueInit, data_func, caller);
|
||||
}
|
||||
|
||||
private:
|
||||
static Status ReturnTrueInit(size_t num_threads) { return true; }
|
||||
|
||||
// class holding the state of a Run() call to pass to the runner_ as an
|
||||
// opaque_jpegxl pointer.
|
||||
template <class InitFunc, class DataFunc>
|
||||
class RunCallState final {
|
||||
public:
|
||||
RunCallState(const InitFunc& init_func, const DataFunc& data_func)
|
||||
: init_func_(init_func), data_func_(data_func) {}
|
||||
|
||||
// JpegxlParallelRunInit interface.
|
||||
static int CallInitFunc(void* jpegxl_opaque, size_t num_threads) {
|
||||
const auto* self =
|
||||
static_cast<RunCallState<InitFunc, DataFunc>*>(jpegxl_opaque);
|
||||
// Returns -1 when the internal init function returns false Status to
|
||||
// indicate an error.
|
||||
return self->init_func_(num_threads) ? 0 : -1;
|
||||
}
|
||||
|
||||
// JpegxlParallelRunFunction interface.
|
||||
static void CallDataFunc(void* jpegxl_opaque, uint32_t value,
|
||||
size_t thread_id) {
|
||||
const auto* self =
|
||||
static_cast<RunCallState<InitFunc, DataFunc>*>(jpegxl_opaque);
|
||||
return self->data_func_(value, thread_id);
|
||||
}
|
||||
|
||||
private:
|
||||
const InitFunc& init_func_;
|
||||
const DataFunc& data_func_;
|
||||
};
|
||||
|
||||
// Default JpegxlParallelRunner used when no runner is provided by the
|
||||
// caller. This runner doesn't use any threading and thread_id is always 0.
|
||||
static JpegxlParallelRetCode SequentialRunnerStatic(
|
||||
void* runner_opaque, void* jpegxl_opaque, JpegxlParallelRunInit init,
|
||||
JpegxlParallelRunFunction func, uint32_t start_range, uint32_t end_range);
|
||||
|
||||
// The caller supplied runner function and its opaque void*.
|
||||
const JpegxlParallelRunner runner_;
|
||||
void* const runner_opaque_;
|
||||
};
|
||||
|
||||
void TraceRunBegin(const char* caller, double* t0);
|
||||
void TraceRunEnd(const char* caller, double t0);
|
||||
|
||||
// TODO(deymo): Convert the return value to a Status when not using SkipInit.
|
||||
template <class InitFunc, class DataFunc>
|
||||
bool RunOnPool(ThreadPool* pool, const uint32_t begin, const uint32_t end,
|
||||
const InitFunc& init_func, const DataFunc& data_func,
|
||||
const char* caller) {
|
||||
Status ret = true;
|
||||
double t0;
|
||||
TraceRunBegin(caller, &t0);
|
||||
if (pool == nullptr) {
|
||||
ThreadPool default_pool(nullptr, nullptr);
|
||||
ret = default_pool.Run(begin, end, init_func, data_func, caller);
|
||||
} else {
|
||||
ret = pool->Run(begin, end, init_func, data_func, caller);
|
||||
}
|
||||
TraceRunEnd(caller, t0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Accelerates multiple unsigned 32-bit divisions with the same divisor by
|
||||
// precomputing a multiplier. This is useful for splitting a contiguous range of
|
||||
// indices (the task index) into 2D indices. Exhaustively tested on dividends
|
||||
// up to 4M with non-power of two divisors up to 2K.
|
||||
class Divider {
|
||||
public:
|
||||
// "d" is the divisor (what to divide by).
|
||||
explicit Divider(const uint32_t d) : shift_(FloorLog2Nonzero(d)) {
|
||||
// Power of two divisors (including 1) are not supported because it is more
|
||||
// efficient to special-case them at a higher level.
|
||||
JXL_ASSERT((d & (d - 1)) != 0);
|
||||
|
||||
// ceil_log2 = floor_log2 + 1 because we ruled out powers of two above.
|
||||
const uint64_t next_pow2 = 1ULL << (shift_ + 1);
|
||||
|
||||
mul_ = ((next_pow2 - d) << 32) / d + 1;
|
||||
}
|
||||
|
||||
// "n" is the numerator (what is being divided).
|
||||
inline uint32_t operator()(const uint32_t n) const {
|
||||
// Algorithm from "Division by Invariant Integers using Multiplication".
|
||||
// Its "sh1" is hardcoded to 1 because we don't need to handle d=1.
|
||||
const uint32_t hi = (uint64_t(mul_) * n) >> 32;
|
||||
return (hi + ((n - hi) >> 1)) >> shift_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t mul_;
|
||||
const int shift_;
|
||||
};
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_BASE_DATA_PARALLEL_H_
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "jxl/base/descriptive_statistics.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
void Stats::Assimilate(const Stats& other) {
|
||||
const int64_t total_n = n_ + other.n_;
|
||||
if (total_n == 0) return; // Nothing to do; prevents div by zero.
|
||||
|
||||
min_ = std::min(min_, other.min_);
|
||||
max_ = std::max(max_, other.max_);
|
||||
|
||||
product_ *= other.product_;
|
||||
|
||||
const double product_n = n_ * other.n_;
|
||||
const double n2 = n_ * n_;
|
||||
const double other_n2 = other.n_ * other.n_;
|
||||
// Warning: multiplying int64 can overflow here.
|
||||
const double total_n2 = static_cast<double>(total_n) * total_n;
|
||||
const double total_n3 = static_cast<double>(total_n2) * total_n;
|
||||
// Precompute reciprocal for speed - used at least twice.
|
||||
const double inv_total_n = 1.0 / total_n;
|
||||
const double inv_total_n2 = 1.0 / total_n2;
|
||||
|
||||
const double delta = other.m1_ - m1_;
|
||||
const double delta2 = delta * delta;
|
||||
const double delta3 = delta * delta2;
|
||||
const double delta4 = delta2 * delta2;
|
||||
|
||||
m1_ = (n_ * m1_ + other.n_ * other.m1_) * inv_total_n;
|
||||
|
||||
const double new_m2 = m2_ + other.m2_ + delta2 * product_n * inv_total_n;
|
||||
|
||||
const double new_m3 =
|
||||
m3_ + other.m3_ + delta3 * product_n * (n_ - other.n_) * inv_total_n2 +
|
||||
3.0 * delta * (n_ * other.m2_ - other.n_ * m2_) * inv_total_n;
|
||||
|
||||
m4_ += other.m4_ +
|
||||
delta4 * product_n * (n2 - product_n + other_n2) / total_n3 +
|
||||
6.0 * delta2 * (n2 * other.m2_ + other_n2 * m2_) * inv_total_n2 +
|
||||
4.0 * delta * (n_ * other.m3_ - other.n_ * m3_) * inv_total_n;
|
||||
|
||||
m2_ = new_m2;
|
||||
m3_ = new_m3;
|
||||
n_ = total_n;
|
||||
}
|
||||
|
||||
std::string Stats::ToString(int exclude) const {
|
||||
if (Count() == 0) return std::string("(none)");
|
||||
|
||||
char buf[300];
|
||||
size_t pos = 0;
|
||||
int ret; // snprintf - bytes written or negative for error.
|
||||
|
||||
if ((exclude & kNoCount) == 0) {
|
||||
ret = snprintf(buf + pos, sizeof(buf) - pos, "Count=%6zu ",
|
||||
static_cast<size_t>(Count()));
|
||||
JXL_ASSERT(ret > 0);
|
||||
pos += ret;
|
||||
}
|
||||
|
||||
if ((exclude & kNoMeanSD) == 0) {
|
||||
ret = snprintf(buf + pos, sizeof(buf) - pos, "Mean=%9.6f SD=%8.5f ", Mean(),
|
||||
StandardDeviation());
|
||||
JXL_ASSERT(ret > 0);
|
||||
pos += ret;
|
||||
}
|
||||
|
||||
if ((exclude & kNoMinMax) == 0) {
|
||||
ret = snprintf(buf + pos, sizeof(buf) - pos, "Min=%8.5f Max=%8.5f ", Min(),
|
||||
Max());
|
||||
JXL_ASSERT(ret > 0);
|
||||
pos += ret;
|
||||
}
|
||||
|
||||
if ((exclude & kNoSkewKurt) == 0) {
|
||||
ret = snprintf(buf + pos, sizeof(buf) - pos, "Skew=%5.2f Kurt=%7.2f ",
|
||||
Skewness(), Kurtosis());
|
||||
JXL_ASSERT(ret > 0);
|
||||
pos += ret;
|
||||
}
|
||||
|
||||
if ((exclude & kNoGeomean) == 0) {
|
||||
ret = snprintf(buf + pos, sizeof(buf) - pos, "GeoMean=%9.6f ",
|
||||
GeometricMean());
|
||||
JXL_ASSERT(ret > 0);
|
||||
pos += ret;
|
||||
}
|
||||
|
||||
JXL_ASSERT(pos < sizeof(buf));
|
||||
return buf;
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_BASE_DESCRIPTIVE_STATISTICS_H_
|
||||
#define JXL_BASE_DESCRIPTIVE_STATISTICS_H_
|
||||
|
||||
// For analyzing the range/distribution of scalars.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// Descriptive statistics of a variable (4 moments).
|
||||
class Stats {
|
||||
public:
|
||||
void Notify(const float x) {
|
||||
++n_;
|
||||
|
||||
min_ = std::min(min_, x);
|
||||
max_ = std::max(max_, x);
|
||||
|
||||
product_ *= x;
|
||||
|
||||
// Online moments. Reference: https://goo.gl/9ha694
|
||||
const double d = x - m1_;
|
||||
const double d_div_n = d / n_;
|
||||
const double d2n1_div_n = d * (n_ - 1) * d_div_n;
|
||||
const int64_t n_poly = n_ * n_ - 3 * n_ + 3;
|
||||
m1_ += d_div_n;
|
||||
m4_ += d_div_n * (d_div_n * (d2n1_div_n * n_poly + 6.0 * m2_) - 4.0 * m3_);
|
||||
m3_ += d_div_n * (d2n1_div_n * (n_ - 2) - 3.0 * m2_);
|
||||
m2_ += d2n1_div_n;
|
||||
}
|
||||
|
||||
void Assimilate(const Stats& other);
|
||||
|
||||
int64_t Count() const { return n_; }
|
||||
|
||||
float Min() const { return min_; }
|
||||
float Max() const { return max_; }
|
||||
|
||||
double GeometricMean() const {
|
||||
return n_ == 0 ? 0.0 : pow(product_, 1.0 / n_);
|
||||
}
|
||||
|
||||
double Mean() const { return m1_; }
|
||||
// Same as Mu2. Assumes n_ is large.
|
||||
double SampleVariance() const {
|
||||
return n_ == 0 ? 0.0 : m2_ / static_cast<int>(n_);
|
||||
}
|
||||
// Unbiased estimator for population variance even for smaller n_.
|
||||
double Variance() const {
|
||||
if (n_ == 0) return 0.0;
|
||||
if (n_ == 1) return m2_;
|
||||
return m2_ / static_cast<int>(n_ - 1);
|
||||
}
|
||||
double StandardDeviation() const { return std::sqrt(Variance()); }
|
||||
// Near zero for normal distributions; if positive on a unimodal distribution,
|
||||
// the right tail is fatter. Assumes n_ is large.
|
||||
double SampleSkewness() const {
|
||||
if (std::abs(m2_) < 1E-7) return 0.0;
|
||||
return m3_ * std::sqrt(static_cast<double>(n_)) / std::pow(m2_, 1.5);
|
||||
}
|
||||
// Corrected for bias (same as Wikipedia and Minitab but not Excel).
|
||||
double Skewness() const {
|
||||
if (n_ == 0) return 0.0;
|
||||
const double biased = SampleSkewness();
|
||||
const double r = (n_ - 1.0) / n_;
|
||||
return biased * std::pow(r, 1.5);
|
||||
}
|
||||
// Near zero for normal distributions; smaller values indicate fewer/smaller
|
||||
// outliers and larger indicates more/larger outliers. Assumes n_ is large.
|
||||
double SampleKurtosis() const {
|
||||
if (std::abs(m2_) < 1E-7) return 0.0;
|
||||
return m4_ * n_ / (m2_ * m2_);
|
||||
}
|
||||
// Corrected for bias (same as Wikipedia and Minitab but not Excel).
|
||||
double Kurtosis() const {
|
||||
if (n_ == 0) return 0.0;
|
||||
const double biased = SampleKurtosis();
|
||||
const double r = (n_ - 1.0) / n_;
|
||||
return biased * r * r;
|
||||
}
|
||||
|
||||
// Central moments, useful for "method of moments"-based parameter estimation
|
||||
// of a mixture of two Gaussians. Assumes Count() != 0.
|
||||
double Mu1() const { return m1_; }
|
||||
double Mu2() const { return m2_ / static_cast<int>(n_); }
|
||||
double Mu3() const { return m3_ / static_cast<int>(n_); }
|
||||
double Mu4() const { return m4_ / static_cast<int>(n_); }
|
||||
|
||||
// Which statistics to EXCLUDE in ToString
|
||||
enum {
|
||||
kNoCount = 1,
|
||||
kNoMeanSD = 2,
|
||||
kNoMinMax = 4,
|
||||
kNoSkewKurt = 8,
|
||||
kNoGeomean = 16
|
||||
};
|
||||
|
||||
std::string ToString(int exclude = 0) const;
|
||||
|
||||
private:
|
||||
int64_t n_ = 0; // signed for faster conversion + safe subtraction
|
||||
|
||||
float min_ = 1E30f;
|
||||
float max_ = -1E30f;
|
||||
|
||||
double product_ = 1.0;
|
||||
|
||||
// Moments
|
||||
double m1_ = 0.0;
|
||||
double m2_ = 0.0;
|
||||
double m3_ = 0.0;
|
||||
double m4_ = 0.0;
|
||||
};
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_BASE_DESCRIPTIVE_STATISTICS_H_
|
||||
@@ -0,0 +1,133 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_BASE_FAST_LOG_H_
|
||||
#define JXL_BASE_FAST_LOG_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// A lookup table for small values of log2(int) to be used in entropy
|
||||
// computation.
|
||||
//
|
||||
// ", ".join(["%.16ff" % x for x in [0.0]+[log2(x) for x in range(1, 256)]])
|
||||
static const float kLog2Table[] = {
|
||||
0.0000000000000000f, 0.0000000000000000f, 1.0000000000000000f,
|
||||
1.5849625007211563f, 2.0000000000000000f, 2.3219280948873622f,
|
||||
2.5849625007211561f, 2.8073549220576042f, 3.0000000000000000f,
|
||||
3.1699250014423126f, 3.3219280948873626f, 3.4594316186372978f,
|
||||
3.5849625007211565f, 3.7004397181410922f, 3.8073549220576037f,
|
||||
3.9068905956085187f, 4.0000000000000000f, 4.0874628412503400f,
|
||||
4.1699250014423122f, 4.2479275134435852f, 4.3219280948873626f,
|
||||
4.3923174227787607f, 4.4594316186372973f, 4.5235619560570131f,
|
||||
4.5849625007211570f, 4.6438561897747244f, 4.7004397181410926f,
|
||||
4.7548875021634691f, 4.8073549220576037f, 4.8579809951275728f,
|
||||
4.9068905956085187f, 4.9541963103868758f, 5.0000000000000000f,
|
||||
5.0443941193584534f, 5.0874628412503400f, 5.1292830169449664f,
|
||||
5.1699250014423122f, 5.2094533656289501f, 5.2479275134435852f,
|
||||
5.2854022188622487f, 5.3219280948873626f, 5.3575520046180838f,
|
||||
5.3923174227787607f, 5.4262647547020979f, 5.4594316186372973f,
|
||||
5.4918530963296748f, 5.5235619560570131f, 5.5545888516776376f,
|
||||
5.5849625007211570f, 5.6147098441152083f, 5.6438561897747244f,
|
||||
5.6724253419714961f, 5.7004397181410926f, 5.7279204545631996f,
|
||||
5.7548875021634691f, 5.7813597135246599f, 5.8073549220576046f,
|
||||
5.8328900141647422f, 5.8579809951275719f, 5.8826430493618416f,
|
||||
5.9068905956085187f, 5.9307373375628867f, 5.9541963103868758f,
|
||||
5.9772799234999168f, 6.0000000000000000f, 6.0223678130284544f,
|
||||
6.0443941193584534f, 6.0660891904577721f, 6.0874628412503400f,
|
||||
6.1085244567781700f, 6.1292830169449672f, 6.1497471195046822f,
|
||||
6.1699250014423122f, 6.1898245588800176f, 6.2094533656289510f,
|
||||
6.2288186904958804f, 6.2479275134435861f, 6.2667865406949019f,
|
||||
6.2854022188622487f, 6.3037807481771031f, 6.3219280948873617f,
|
||||
6.3398500028846252f, 6.3575520046180847f, 6.3750394313469254f,
|
||||
6.3923174227787598f, 6.4093909361377026f, 6.4262647547020979f,
|
||||
6.4429434958487288f, 6.4594316186372982f, 6.4757334309663976f,
|
||||
6.4918530963296748f, 6.5077946401986964f, 6.5235619560570131f,
|
||||
6.5391588111080319f, 6.5545888516776376f, 6.5698556083309478f,
|
||||
6.5849625007211561f, 6.5999128421871278f, 6.6147098441152092f,
|
||||
6.6293566200796095f, 6.6438561897747253f, 6.6582114827517955f,
|
||||
6.6724253419714952f, 6.6865005271832185f, 6.7004397181410917f,
|
||||
6.7142455176661224f, 6.7279204545631988f, 6.7414669864011465f,
|
||||
6.7548875021634691f, 6.7681843247769260f, 6.7813597135246599f,
|
||||
6.7944158663501062f, 6.8073549220576037f, 6.8201789624151887f,
|
||||
6.8328900141647422f, 6.8454900509443757f, 6.8579809951275719f,
|
||||
6.8703647195834048f, 6.8826430493618416f, 6.8948177633079437f,
|
||||
6.9068905956085187f, 6.9188632372745955f, 6.9307373375628867f,
|
||||
6.9425145053392399f, 6.9541963103868758f, 6.9657842846620879f,
|
||||
6.9772799234999168f, 6.9886846867721664f, 7.0000000000000000f,
|
||||
7.0112272554232540f, 7.0223678130284544f, 7.0334230015374501f,
|
||||
7.0443941193584534f, 7.0552824355011898f, 7.0660891904577721f,
|
||||
7.0768155970508317f, 7.0874628412503400f, 7.0980320829605272f,
|
||||
7.1085244567781700f, 7.1189410727235076f, 7.1292830169449664f,
|
||||
7.1395513523987937f, 7.1497471195046822f, 7.1598713367783891f,
|
||||
7.1699250014423130f, 7.1799090900149345f, 7.1898245588800176f,
|
||||
7.1996723448363644f, 7.2094533656289492f, 7.2191685204621621f,
|
||||
7.2288186904958804f, 7.2384047393250794f, 7.2479275134435861f,
|
||||
7.2573878426926521f, 7.2667865406949019f, 7.2761244052742384f,
|
||||
7.2854022188622487f, 7.2946207488916270f, 7.3037807481771031f,
|
||||
7.3128829552843557f, 7.3219280948873617f, 7.3309168781146177f,
|
||||
7.3398500028846243f, 7.3487281542310781f, 7.3575520046180847f,
|
||||
7.3663222142458151f, 7.3750394313469254f, 7.3837042924740528f,
|
||||
7.3923174227787607f, 7.4008794362821844f, 7.4093909361377026f,
|
||||
7.4178525148858991f, 7.4262647547020979f, 7.4346282276367255f,
|
||||
7.4429434958487288f, 7.4512111118323299f, 7.4594316186372973f,
|
||||
7.4676055500829976f, 7.4757334309663976f, 7.4838157772642564f,
|
||||
7.4918530963296748f, 7.4998458870832057f, 7.5077946401986964f,
|
||||
7.5156998382840436f, 7.5235619560570131f, 7.5313814605163119f,
|
||||
7.5391588111080319f, 7.5468944598876373f, 7.5545888516776376f,
|
||||
7.5622424242210728f, 7.5698556083309478f, 7.5774288280357487f,
|
||||
7.5849625007211561f, 7.5924570372680806f, 7.5999128421871278f,
|
||||
7.6073303137496113f, 7.6147098441152075f, 7.6220518194563764f,
|
||||
7.6293566200796095f, 7.6366246205436488f, 7.6438561897747244f,
|
||||
7.6510516911789290f, 7.6582114827517955f, 7.6653359171851765f,
|
||||
7.6724253419714952f, 7.6794800995054464f, 7.6865005271832185f,
|
||||
7.6934869574993252f, 7.7004397181410926f, 7.7073591320808825f,
|
||||
7.7142455176661224f, 7.7210991887071856f, 7.7279204545631996f,
|
||||
7.7347096202258392f, 7.7414669864011465f, 7.7481928495894596f,
|
||||
7.7548875021634691f, 7.7615512324444795f, 7.7681843247769260f,
|
||||
7.7747870596011737f, 7.7813597135246608f, 7.7879025593914317f,
|
||||
7.7944158663501062f, 7.8008998999203047f, 7.8073549220576037f,
|
||||
7.8137811912170374f, 7.8201789624151887f, 7.8265484872909159f,
|
||||
7.8328900141647422f, 7.8392037880969445f, 7.8454900509443757f,
|
||||
7.8517490414160571f, 7.8579809951275719f, 7.8641861446542798f,
|
||||
7.8703647195834048f, 7.8765169465650002f, 7.8826430493618425f,
|
||||
7.8887432488982601f, 7.8948177633079446f, 7.9008668079807496f,
|
||||
7.9068905956085187f, 7.9128893362299619f, 7.9188632372745955f,
|
||||
7.9248125036057813f, 7.9307373375628867f, 7.9366379390025719f,
|
||||
7.9425145053392399f, 7.9483672315846778f, 7.9541963103868758f,
|
||||
7.9600019320680806f, 7.9657842846620870f, 7.9715435539507720f,
|
||||
7.9772799234999168f, 7.9829935746943104f, 7.9886846867721664f,
|
||||
7.9943534368588578f};
|
||||
|
||||
// Faster logarithm for small integers, with the property of log2(0) == 0.
|
||||
static inline float FastLog2(uint32_t v) {
|
||||
if (v < (sizeof(kLog2Table) / sizeof(kLog2Table[0]))) {
|
||||
return kLog2Table[v];
|
||||
}
|
||||
return static_cast<float>(log2(v));
|
||||
}
|
||||
|
||||
static inline float FastLog2f(float f) {
|
||||
int exp;
|
||||
float fr = std::frexp(f, &exp);
|
||||
// TODO(veluca): improve constants.
|
||||
return exp + (-1.34752046f * fr + 3.98979143f) * fr - 2.64898502f;
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_BASE_FAST_LOG_H_
|
||||
@@ -0,0 +1,107 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_BASE_FILE_IO_H_
|
||||
#define JXL_BASE_FILE_IO_H_
|
||||
|
||||
// Helper functions for reading/writing files.
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "jxl/base/compiler_specific.h"
|
||||
#include "jxl/base/padded_bytes.h"
|
||||
#include "jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// Returns extension including the dot, or empty string if none. Assumes
|
||||
// filename is not a hidden file (e.g. ".bashrc"). May be called with a pathname
|
||||
// if the filename contains a dot and/or no other path component does.
|
||||
static inline std::string Extension(const std::string& filename) {
|
||||
const size_t pos = filename.rfind('.');
|
||||
if (pos == std::string::npos) return std::string();
|
||||
return filename.substr(pos);
|
||||
}
|
||||
|
||||
// RAII, ensures files are closed even when returning early.
|
||||
class FileWrapper {
|
||||
public:
|
||||
FileWrapper(const FileWrapper& other) = delete;
|
||||
FileWrapper& operator=(const FileWrapper& other) = delete;
|
||||
|
||||
explicit FileWrapper(const std::string& pathname, const char* mode)
|
||||
: file_(fopen(pathname.c_str(), mode)) {}
|
||||
|
||||
~FileWrapper() {
|
||||
if (file_ != nullptr) {
|
||||
const int err = fclose(file_);
|
||||
JXL_CHECK(err == 0);
|
||||
}
|
||||
}
|
||||
|
||||
// We intend to use FileWrapper as a replacement of FILE.
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
operator FILE*() const { return file_; }
|
||||
|
||||
private:
|
||||
FILE* const file_;
|
||||
};
|
||||
|
||||
template <typename ContainerType>
|
||||
static inline Status ReadFile(const std::string& pathname,
|
||||
ContainerType* JXL_RESTRICT bytes) {
|
||||
FileWrapper f(pathname, "rb");
|
||||
if (f == nullptr) return JXL_FAILURE("Failed to open file for reading");
|
||||
|
||||
if (fseek(f, 0, SEEK_END) != 0) return JXL_FAILURE("Failed to seek end");
|
||||
bytes->resize(ftell(f));
|
||||
if (bytes->empty()) return JXL_FAILURE("Zero-length file");
|
||||
if (fseek(f, 0, SEEK_SET) != 0) return JXL_FAILURE("Failed to seek set");
|
||||
|
||||
size_t pos = 0;
|
||||
while (pos < bytes->size()) {
|
||||
// Needed in case ContainerType is std::string, whose data() is const.
|
||||
char* bytes_writable = reinterpret_cast<char*>(&(*bytes)[0]);
|
||||
const size_t bytes_read =
|
||||
fread(bytes_writable + pos, 1, bytes->size() - pos, f);
|
||||
if (bytes_read == 0) return JXL_FAILURE("Failed to read");
|
||||
pos += bytes_read;
|
||||
}
|
||||
JXL_ASSERT(pos == bytes->size());
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename ContainerType>
|
||||
static inline Status WriteFile(const ContainerType& bytes,
|
||||
const std::string& pathname) {
|
||||
FileWrapper f(pathname, "wb");
|
||||
if (f == nullptr) return JXL_FAILURE("Failed to open file for writing");
|
||||
|
||||
size_t pos = 0;
|
||||
while (pos < bytes.size()) {
|
||||
const size_t bytes_written =
|
||||
fwrite(bytes.data() + pos, 1, bytes.size() - pos, f);
|
||||
if (bytes_written == 0) return JXL_FAILURE("Failed to write");
|
||||
pos += bytes_written;
|
||||
}
|
||||
JXL_ASSERT(pos == bytes.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_BASE_FILE_IO_H_
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_BASE_IACA_H_
|
||||
#define JXL_BASE_IACA_H_
|
||||
|
||||
#include <hwy/compiler_specific.h>
|
||||
|
||||
// IACA (Intel's Code Analyzer) analyzes instruction latencies, but only for
|
||||
// code between special markers. These functions embed such markers in an
|
||||
// executable, but only for reading via IACA - they deliberately trigger a
|
||||
// crash if executed to ensure they are removed in normal builds.
|
||||
|
||||
#ifndef JXL_IACA_ENABLED
|
||||
#define JXL_IACA_ENABLED 0
|
||||
#endif
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// Call before the region of interest.
|
||||
static HWY_INLINE void BeginIACA() {
|
||||
#if JXL_IACA_ENABLED && (JXL_COMPILER_GCC || JXL_COMPILER_CLANG)
|
||||
asm volatile(
|
||||
// UD2 "instruction" raises an invalid opcode exception.
|
||||
".byte 0x0F, 0x0B\n\t"
|
||||
// Magic sequence recognized by IACA (MOV + addr32 fs:NOP). This actually
|
||||
// clobbers EBX, but we don't care because the code won't be run, and we
|
||||
// want IACA to observe the same code the compiler would have generated
|
||||
// without this marker.
|
||||
"movl $111, %%ebx\n\t"
|
||||
".byte 0x64, 0x67, 0x90\n\t"
|
||||
:
|
||||
:
|
||||
// (Allegedly) clobbering memory may prevent reordering.
|
||||
: "memory");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Call after the region of interest.
|
||||
static HWY_INLINE void EndIACA() {
|
||||
#if JXL_IACA_ENABLED && (JXL_COMPILER_GCC || JXL_COMPILER_CLANG)
|
||||
asm volatile(
|
||||
// See above.
|
||||
"movl $222, %%ebx\n\t"
|
||||
".byte 0x64, 0x67, 0x90\n\t"
|
||||
// UD2
|
||||
".byte 0x0F, 0x0B\n\t"
|
||||
:
|
||||
:
|
||||
// (Allegedly) clobbering memory may prevent reordering.
|
||||
: "memory");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Add to a scope to mark a region.
|
||||
struct ScopeIACA {
|
||||
HWY_INLINE ScopeIACA() { BeginIACA(); }
|
||||
HWY_INLINE ~ScopeIACA() { EndIACA(); }
|
||||
};
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_BASE_IACA_H_
|
||||
@@ -0,0 +1,366 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "jxl/base/os_specific.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
#include <random>
|
||||
|
||||
#include "jxl/base/arch_specific.h" // ProcessorTopology
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define OS_WIN 1
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
#else
|
||||
#define OS_WIN 0
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#define OS_LINUX 1
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
#include <pthread.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#define OS_LINUX 0
|
||||
#endif
|
||||
|
||||
#ifdef __MACH__
|
||||
#define OS_MAC 1
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_time.h>
|
||||
#include <mach/thread_act.h>
|
||||
#include <mach/thread_policy.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#define OS_MAC 0
|
||||
#endif
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
#define OS_FREEBSD 1
|
||||
#include <sys/cpuset.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#define OS_FREEBSD 0
|
||||
#endif
|
||||
|
||||
namespace jxl {
|
||||
|
||||
double Now() {
|
||||
#if OS_WIN
|
||||
LARGE_INTEGER counter;
|
||||
(void)QueryPerformanceCounter(&counter);
|
||||
LARGE_INTEGER freq;
|
||||
(void)QueryPerformanceFrequency(&freq);
|
||||
return double(counter.QuadPart) / freq.QuadPart;
|
||||
#elif OS_MAC
|
||||
const auto t = mach_absolute_time();
|
||||
// On OSX/iOS platform the elapsed time is cpu time unit
|
||||
// We have to query the time base information to convert it back
|
||||
// See https://developer.apple.com/library/mac/qa/qa1398/_index.html
|
||||
static mach_timebase_info_data_t timebase;
|
||||
if (timebase.denom == 0) {
|
||||
(void)mach_timebase_info(&timebase);
|
||||
}
|
||||
return double(t) * timebase.numer / timebase.denom * 1E-9;
|
||||
#else
|
||||
timespec t;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
return t.tv_sec + t.tv_nsec * 1E-9;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Emulate Linux type (cpu_set_t) + interface on other platforms
|
||||
|
||||
#if OS_FREEBSD
|
||||
using cpu_set_t = cpuset_t;
|
||||
#elif OS_WIN || OS_MAC
|
||||
using cpu_set_t = uint64_t;
|
||||
|
||||
static inline void CPU_ZERO(cpu_set_t* set) { *set = 0; }
|
||||
|
||||
static inline int CPU_ISSET(int cpu, const cpu_set_t* set) {
|
||||
return (*set & (1ULL << cpu)) != 0;
|
||||
}
|
||||
|
||||
static inline void CPU_SET(int cpu, cpu_set_t* set) { *set |= (1ULL << cpu); }
|
||||
#endif
|
||||
|
||||
struct ThreadAffinity {
|
||||
cpu_set_t set;
|
||||
};
|
||||
|
||||
#if OS_MAC
|
||||
namespace {
|
||||
Status GetSystemValue(const char* name, size_t* value) {
|
||||
int64_t value_i64 = 0;
|
||||
size_t size = sizeof(value_i64);
|
||||
const int err = sysctlbyname(name, &value_i64, &size, nullptr, 0);
|
||||
if (err != 0) return JXL_FAILURE("sysctl packages failed");
|
||||
JXL_ASSERT(value_i64 >= 0);
|
||||
*value = static_cast<size_t>(value_i64);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns mask with the lowest N bits set, one per logical processor.
|
||||
cpu_set_t SetOfAllLogicalProcessors() {
|
||||
size_t logical;
|
||||
// On failure, assume there is at least one logical processor.
|
||||
if (!GetSystemValue("machdep.cpu.thread_count", &logical)) return 1;
|
||||
|
||||
if (logical > 64) {
|
||||
printf("Warning: more than 64 logical processors, update cpu_set_t");
|
||||
return ~0ull;
|
||||
}
|
||||
if (logical == 64) return ~0ull;
|
||||
|
||||
return (1ull << logical) - 1;
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
Status GetProcessorTopologyFromOS(ProcessorTopology* pt) {
|
||||
#if OS_MAC
|
||||
size_t packages, cores, logical; // totals, not per package/core!
|
||||
JXL_RETURN_IF_ERROR(GetSystemValue("hw.packages", &packages));
|
||||
JXL_RETURN_IF_ERROR(GetSystemValue("machdep.cpu.core_count", &cores));
|
||||
JXL_RETURN_IF_ERROR(GetSystemValue("machdep.cpu.thread_count", &logical));
|
||||
|
||||
// All succeeded: now set `pt`
|
||||
pt->packages = packages;
|
||||
pt->cores_per_package = cores / packages;
|
||||
pt->logical_per_core = logical / cores;
|
||||
|
||||
return true;
|
||||
#else
|
||||
// Not needed on X64 if the affinity APIs work (DetectProcessorTopology will
|
||||
// succeed)
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
ThreadAffinity* GetThreadAffinity() {
|
||||
ThreadAffinity* affinity =
|
||||
static_cast<ThreadAffinity*>(malloc(sizeof(ThreadAffinity)));
|
||||
#if OS_WIN
|
||||
DWORD_PTR process_affinity, system_affinity;
|
||||
const BOOL ok = GetProcessAffinityMask(GetCurrentProcess(), &process_affinity,
|
||||
&system_affinity);
|
||||
JXL_CHECK(ok);
|
||||
affinity->set = process_affinity;
|
||||
#elif OS_LINUX
|
||||
CPU_ZERO(&affinity->set);
|
||||
const int err =
|
||||
pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t), &affinity->set);
|
||||
JXL_CHECK(err == 0);
|
||||
#elif OS_FREEBSD
|
||||
const pid_t pid = getpid(); // current thread
|
||||
const int err = cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, pid,
|
||||
sizeof(cpuset_t), &affinity->set);
|
||||
JXL_CHECK(err == 0);
|
||||
#elif OS_MAC
|
||||
static cpu_set_t all = SetOfAllLogicalProcessors();
|
||||
affinity->set = all;
|
||||
#endif
|
||||
return affinity;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
ThreadAffinity* OriginalThreadAffinity() {
|
||||
static ThreadAffinity* original = GetThreadAffinity();
|
||||
return original;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status SetThreadAffinity(ThreadAffinity* affinity) {
|
||||
// Ensure original is initialized before changing.
|
||||
const ThreadAffinity* const original = OriginalThreadAffinity();
|
||||
JXL_CHECK(original != nullptr);
|
||||
|
||||
#if OS_WIN
|
||||
const HANDLE hThread = GetCurrentThread();
|
||||
const DWORD_PTR prev = SetThreadAffinityMask(hThread, affinity->set);
|
||||
if (prev == 0) return JXL_FAILURE("SetThreadAffinityMask failed");
|
||||
return true;
|
||||
#elif OS_LINUX
|
||||
const int err =
|
||||
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &affinity->set);
|
||||
if (err != 0) return JXL_FAILURE("pthread_setaffinity_np failed");
|
||||
return true;
|
||||
#elif OS_FREEBSD
|
||||
const pid_t pid = getpid(); // current thread
|
||||
const int err = cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, pid,
|
||||
sizeof(cpuset_t), &affinity->set);
|
||||
if (err != 0) return JXL_FAILURE("cpuset_setaffinity failed");
|
||||
return true;
|
||||
#elif OS_MAC
|
||||
// As of 2019-03 we are unaware of a way to reliably restrict a thread to
|
||||
// core(s); THREAD_AFFINITY_POLICY is only a hint.
|
||||
(void)affinity;
|
||||
return false;
|
||||
#else
|
||||
printf("Don't know how to SetThreadAffinity on this platform.\n");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<int> AvailableCPUs() {
|
||||
std::vector<int> cpus;
|
||||
cpus.reserve(128);
|
||||
#if OS_WIN || OS_LINUX || OS_FREEBSD || OS_MAC
|
||||
const ThreadAffinity* const affinity = OriginalThreadAffinity();
|
||||
for (int cpu = 0; cpu < static_cast<int>(sizeof(cpu_set_t)) * 8; ++cpu) {
|
||||
if (CPU_ISSET(cpu, &affinity->set)) {
|
||||
cpus.push_back(static_cast<int>(cpu));
|
||||
}
|
||||
}
|
||||
#else
|
||||
cpus.push_back(0);
|
||||
#endif
|
||||
return cpus;
|
||||
}
|
||||
|
||||
Status PinThreadToCPU(const int cpu) {
|
||||
#if OS_WIN || OS_LINUX || OS_FREEBSD || OS_MAC
|
||||
ThreadAffinity affinity;
|
||||
CPU_ZERO(&affinity.set);
|
||||
CPU_SET(cpu, &affinity.set);
|
||||
return SetThreadAffinity(&affinity);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
Status PinThreadToRandomCPU() {
|
||||
std::vector<int> cpus = AvailableCPUs();
|
||||
|
||||
// Remove first two CPUs because interrupts are often pinned to them.
|
||||
JXL_CHECK(cpus.size() > 2);
|
||||
cpus.erase(cpus.begin(), cpus.begin() + 2);
|
||||
|
||||
// Random choice to prevent burning up the same core.
|
||||
std::random_device device;
|
||||
std::ranlux48 generator(device());
|
||||
std::shuffle(cpus.begin(), cpus.end(), generator);
|
||||
const int cpu = cpus.front();
|
||||
|
||||
return PinThreadToCPU(cpu);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
size_t DetectTotalMemoryMiB() {
|
||||
#if OS_LINUX || OS_FREEBSD || OS_MAC
|
||||
const long page_size = sysconf(_SC_PAGESIZE);
|
||||
const long num_pages = sysconf(_SC_PHYS_PAGES);
|
||||
if (page_size == -1 || num_pages == -1) {
|
||||
JXL_WARNING("Failed to detect page size (%ld) and/or num pages (%ld)",
|
||||
page_size, num_pages);
|
||||
return 0;
|
||||
}
|
||||
JXL_ASSERT(page_size > 0 && num_pages > 0);
|
||||
const uint64_t bytes =
|
||||
static_cast<uint64_t>(num_pages) * static_cast<uint64_t>(page_size);
|
||||
return bytes >> 20;
|
||||
#elif OS_WIN
|
||||
MEMORYSTATUSEX ms;
|
||||
ms.dwLength = sizeof(ms);
|
||||
if (!GlobalMemoryStatusEx(&ms)) {
|
||||
JXL_WARNING("Failed to get memory status");
|
||||
return 0;
|
||||
}
|
||||
const uint64_t bytes = ms.ullTotalPhys;
|
||||
// `bytes` excludes nonpaged pool reserved during boot; round up to whole MiB
|
||||
// to improve the estimate.
|
||||
return (bytes + (1U << 20) - 1) >> 20;
|
||||
#else
|
||||
JXL_WARNING("Implement DetectTotalMemoryMiB for this platform");
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
size_t TotalMemoryMiB() {
|
||||
static size_t mib = DetectTotalMemoryMiB();
|
||||
return mib;
|
||||
}
|
||||
|
||||
/*
|
||||
Status RunCommand(const std::vector<std::string>& args) {
|
||||
#if _POSIX_VERSION >= 200112L
|
||||
// Avoid system(), but do not try to be over-zealous about not passing along
|
||||
// some special resources further (such as: inherited-not-marked-FD_CLOEXEC
|
||||
// file descriptors).
|
||||
std::vector<const char*> c_args;
|
||||
c_args.reserve(args.size() + 1);
|
||||
for (size_t i = 0; i < args.size(); ++i) {
|
||||
c_args.push_back(args[i].c_str());
|
||||
}
|
||||
c_args.push_back(nullptr);
|
||||
const pid_t pid = fork();
|
||||
if (pid == -1) // fork() failed.
|
||||
return false;
|
||||
if (pid != 0) { // Parent process.
|
||||
int ret_status;
|
||||
if (pid != waitpid(pid, &ret_status, 0)) {
|
||||
return false; // waitpid() error.
|
||||
}
|
||||
return ret_status == 0;
|
||||
} else { // Child process.
|
||||
execvp(c_args[0],
|
||||
// Address benign-but-annoying execvp() signature weirdness.
|
||||
const_cast<char* const*>(c_args.data()));
|
||||
JXL_ABORT("Failed to run command.\n");
|
||||
}
|
||||
#elif OS_WIN
|
||||
// Synthesize a string for system(). And warn about it.
|
||||
// TODO(user): Fix this - research the safe way to run a command on Windows.
|
||||
// Likely, the solution is along these lines:
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/ProcThread/creating-processes
|
||||
std::ostringstream cmd;
|
||||
std::copy(args.begin(), args.end(),
|
||||
std::ostream_iterator<std::string>(cmd, " "));
|
||||
printf(stderr, "Warning: Using system() on string: %s\n", cmd.str.c_str());
|
||||
int ret = system(cmd.str.c_str());
|
||||
if (errno != ENOENT && // Windows: Command interpreter not found.
|
||||
ret == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
#error Neither a POSIX-1.2001 nor a Windows System.
|
||||
#endif
|
||||
}
|
||||
*/
|
||||
|
||||
} // namespace jxl
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_BASE_OS_SPECIFIC_H_
|
||||
#define JXL_BASE_OS_SPECIFIC_H_
|
||||
|
||||
// OS-specific functions (e.g. timing and thread affinity)
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/base/arch_specific.h"
|
||||
#include "jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// Returns current time [seconds] from a monotonic clock with unspecified
|
||||
// starting point - only suitable for computing elapsed time.
|
||||
double Now();
|
||||
|
||||
// Called by arch_specific. Returns false if `pt` remains unchanged. Only
|
||||
// implemented/needed on OSX.
|
||||
struct ProcessorTopology;
|
||||
|
||||
Status GetProcessorTopologyFromOS(ProcessorTopology* pt);
|
||||
|
||||
// Returns logical processor numbers in [0, N), where N is the number of bits in
|
||||
// the thread's initial affinity (unaffected by any SetThreadAffinity).
|
||||
std::vector<int> AvailableCPUs();
|
||||
|
||||
// Opaque.
|
||||
struct ThreadAffinity;
|
||||
|
||||
// Returns current affinity; useful for restoring the original value.
|
||||
// Caller must free() the pointer - dynamic allocation is required because
|
||||
// ThreadAffinity is an incomplete type.
|
||||
ThreadAffinity* GetThreadAffinity();
|
||||
|
||||
// Restores a previous affinity returned by GetThreadAffinity.
|
||||
Status SetThreadAffinity(ThreadAffinity* affinity);
|
||||
|
||||
// Ensures the thread is running on the specified cpu, and no others.
|
||||
// Useful for reducing nanobenchmark variability (fewer context switches).
|
||||
// Calls SetThreadAffinity.
|
||||
Status PinThreadToCPU(int cpu);
|
||||
|
||||
// Random choice of CPU avoids overloading any one core. Calls PinThreadToCPU.
|
||||
Status PinThreadToRandomCPU();
|
||||
|
||||
// Returns total physical memory size [MiB], or 0 if unknown. This function
|
||||
// returns a cached value initialized on the first call.
|
||||
size_t TotalMemoryMiB();
|
||||
|
||||
// Executes a command in a subprocess.
|
||||
//Status RunCommand(const std::vector<std::string>& args);
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_BASE_OS_SPECIFIC_H_
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_BASE_OVERRIDE_H_
|
||||
#define JXL_BASE_OVERRIDE_H_
|
||||
|
||||
// 'Trool' for command line arguments: force enable/disable, or use default.
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// No effect if kDefault, otherwise forces a feature (typically a FrameHeader
|
||||
// flag) on or off.
|
||||
enum class Override : int { kOn = 1, kOff = 0, kDefault = -1 };
|
||||
|
||||
static inline Override OverrideFromBool(bool flag) {
|
||||
return flag ? Override::kOn : Override::kOff;
|
||||
}
|
||||
|
||||
static inline bool ApplyOverride(Override o, bool default_condition) {
|
||||
if (o == Override::kOn) return true;
|
||||
if (o == Override::kOff) return false;
|
||||
return default_condition;
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_BASE_OVERRIDE_H_
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "jxl/base/padded_bytes.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
namespace jxl {
|
||||
|
||||
void PaddedBytes::IncreaseCapacityTo(size_t capacity) {
|
||||
JXL_ASSERT(capacity > capacity_);
|
||||
|
||||
// BitWriter writes up to 7 bytes past the end.
|
||||
CacheAlignedUniquePtr new_data = AllocateArray(capacity + 8);
|
||||
if (new_data == nullptr) {
|
||||
// Allocation failed, discard all data to ensure this is noticed.
|
||||
size_ = capacity_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (data_ == nullptr) {
|
||||
// First allocation: ensure first byte is initialized (won't be copied).
|
||||
new_data[0] = 0;
|
||||
} else {
|
||||
// Subsequent resize: copy existing data to new location.
|
||||
memcpy(new_data.get(), data_.get(), size_);
|
||||
// Ensure that the first new byte is initialized, to allow write_bits to
|
||||
// safely append to the newly-resized PaddedBytes.
|
||||
new_data[size_] = 0;
|
||||
}
|
||||
|
||||
capacity_ = capacity;
|
||||
std::swap(new_data, data_);
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
@@ -0,0 +1,203 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_BASE_PADDED_BYTES_H_
|
||||
#define JXL_BASE_PADDED_BYTES_H_
|
||||
|
||||
// std::vector replacement with padding to reduce bounds checks in WriteBits
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm> // std::max
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "jxl/base/cache_aligned.h"
|
||||
#include "jxl/base/compiler_specific.h"
|
||||
#include "jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// Provides a subset of the std::vector interface with some differences:
|
||||
// - allows BitWriter to write 64 bits at a time without bounds checking;
|
||||
// - ONLY zero-initializes the first byte (required by BitWriter);
|
||||
// - ensures cache-line alignment.
|
||||
class PaddedBytes {
|
||||
public:
|
||||
// Required for output params.
|
||||
PaddedBytes() : size_(0), capacity_(0) {}
|
||||
|
||||
explicit PaddedBytes(size_t size) : size_(size), capacity_(0) {
|
||||
if (size != 0) IncreaseCapacityTo(size);
|
||||
}
|
||||
|
||||
PaddedBytes(size_t size, uint8_t value) : size_(size), capacity_(0) {
|
||||
if (size != 0) {
|
||||
IncreaseCapacityTo(size);
|
||||
}
|
||||
if (size_ != 0) {
|
||||
memset(data(), value, size);
|
||||
}
|
||||
}
|
||||
|
||||
PaddedBytes(const PaddedBytes& other) : size_(other.size_), capacity_(0) {
|
||||
if (size_ != 0) IncreaseCapacityTo(size_);
|
||||
if (data() != nullptr) memcpy(data(), other.data(), size_);
|
||||
}
|
||||
PaddedBytes& operator=(const PaddedBytes& other) {
|
||||
// Self-assignment is safe.
|
||||
resize(other.size());
|
||||
if (data() != nullptr) memmove(data(), other.data(), size_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// default is not OK - need to set other.size_ to 0!
|
||||
PaddedBytes(PaddedBytes&& other) noexcept
|
||||
: size_(other.size_),
|
||||
capacity_(other.capacity_),
|
||||
data_(std::move(other.data_)) {
|
||||
other.size_ = other.capacity_ = 0;
|
||||
}
|
||||
PaddedBytes& operator=(PaddedBytes&& other) noexcept {
|
||||
size_ = other.size_;
|
||||
capacity_ = other.capacity_;
|
||||
data_ = std::move(other.data_);
|
||||
|
||||
if (&other != this) {
|
||||
other.size_ = other.capacity_ = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void swap(PaddedBytes& other) {
|
||||
std::swap(size_, other.size_);
|
||||
std::swap(capacity_, other.capacity_);
|
||||
std::swap(data_, other.data_);
|
||||
}
|
||||
|
||||
void reserve(size_t capacity) {
|
||||
if (capacity > capacity_) IncreaseCapacityTo(capacity);
|
||||
}
|
||||
// NOTE: unlike vector, this does not initialize the new data!
|
||||
// However, we guarantee that write_bits can safely append after
|
||||
// the resize, as we zero-initialize the first new byte of data.
|
||||
void resize(size_t size) {
|
||||
if (size > capacity_) IncreaseCapacityTo(size);
|
||||
size_ = (data() == nullptr) ? 0 : size;
|
||||
}
|
||||
|
||||
// Explicitely initializes the new data with the value.
|
||||
void resize(size_t size, uint8_t value) {
|
||||
size_t old_size = size_;
|
||||
resize(size);
|
||||
if (size_ > old_size) {
|
||||
memset(data() + old_size, value, size_ - old_size);
|
||||
}
|
||||
}
|
||||
|
||||
// Amortized constant complexity due to exponential growth.
|
||||
void push_back(uint8_t x) {
|
||||
if (size_ == capacity_) {
|
||||
IncreaseCapacityTo(std::max<size_t>(3 * capacity_ / 2, 64));
|
||||
if (data() == nullptr) return;
|
||||
}
|
||||
|
||||
data_[size_++] = x;
|
||||
}
|
||||
|
||||
size_t size() const { return size_; }
|
||||
size_t capacity() const { return capacity_; }
|
||||
|
||||
uint8_t* data() { return data_.get(); }
|
||||
const uint8_t* data() const { return data_.get(); }
|
||||
|
||||
// std::vector operations implemented in terms of the public interface above.
|
||||
|
||||
void clear() { resize(0); }
|
||||
bool empty() const { return size() == 0; }
|
||||
|
||||
void assign(std::initializer_list<uint8_t> il) {
|
||||
resize(il.size());
|
||||
memcpy(data(), il.begin(), il.size());
|
||||
}
|
||||
|
||||
void assign(const uint8_t* begin, const uint8_t* end) {
|
||||
resize(end - begin);
|
||||
memcpy(data(), begin, end - begin);
|
||||
}
|
||||
|
||||
uint8_t* begin() { return data(); }
|
||||
const uint8_t* begin() const { return data(); }
|
||||
uint8_t* end() { return begin() + size(); }
|
||||
const uint8_t* end() const { return begin() + size(); }
|
||||
|
||||
uint8_t& operator[](const size_t i) {
|
||||
BoundsCheck(i);
|
||||
return data()[i];
|
||||
}
|
||||
const uint8_t& operator[](const size_t i) const {
|
||||
BoundsCheck(i);
|
||||
return data()[i];
|
||||
}
|
||||
|
||||
uint8_t& back() {
|
||||
JXL_ASSERT(size() != 0);
|
||||
return data()[size() - 1];
|
||||
}
|
||||
const uint8_t& back() const {
|
||||
JXL_ASSERT(size() != 0);
|
||||
return data()[size() - 1];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void append(const T& other) {
|
||||
size_t old_size = size();
|
||||
resize(size() + other.size());
|
||||
memcpy(data() + old_size, other.data(), other.size());
|
||||
}
|
||||
|
||||
void append(const uint8_t* begin, const uint8_t* end) {
|
||||
size_t old_size = size();
|
||||
resize(size() + end - begin);
|
||||
memcpy(data() + old_size, begin, end - begin);
|
||||
}
|
||||
|
||||
private:
|
||||
void BoundsCheck(size_t i) const {
|
||||
// <= is safe due to padding and required by BitWriter.
|
||||
JXL_ASSERT(i <= size());
|
||||
}
|
||||
|
||||
// Copies existing data to newly allocated "data_". If allocation fails,
|
||||
// data() == nullptr and size_ = capacity_ = 0.
|
||||
void IncreaseCapacityTo(size_t capacity);
|
||||
|
||||
size_t size_;
|
||||
size_t capacity_;
|
||||
CacheAlignedUniquePtr data_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
static inline void Append(const T& s, PaddedBytes* out,
|
||||
size_t* JXL_RESTRICT byte_pos) {
|
||||
memcpy(out->data() + *byte_pos, s.data(), s.size());
|
||||
*byte_pos += s.size();
|
||||
JXL_CHECK(*byte_pos <= out->size());
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_BASE_PADDED_BYTES_H_
|
||||
@@ -0,0 +1,753 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_BASE_PROFILER_H_
|
||||
#define JXL_BASE_PROFILER_H_
|
||||
|
||||
// High precision, low overhead time measurements. Returns exact call counts and
|
||||
// total elapsed time for user-defined 'zones' (code regions, i.e. C++ scopes).
|
||||
//
|
||||
// Usage: add this header to BUILD srcs; instrument regions of interest:
|
||||
// { PROFILER_ZONE("name"); /*code*/ } or
|
||||
// void FuncToMeasure() { PROFILER_FUNC; /*code*/ }.
|
||||
// After all threads have exited any zones, invoke PROFILER_PRINT_RESULTS() to
|
||||
// print call counts and average durations [CPU cycles] to stdout, sorted in
|
||||
// descending order of total duration.
|
||||
|
||||
// Configuration settings:
|
||||
|
||||
// If zero, this file has no effect and no measurements will be recorded.
|
||||
#ifndef PROFILER_ENABLED
|
||||
#define PROFILER_ENABLED 0
|
||||
#endif
|
||||
|
||||
// How many mebibytes to allocate (if PROFILER_ENABLED) per thread that
|
||||
// enters at least one zone. Once this buffer is full, the thread will analyze
|
||||
// and discard packets, thus temporarily adding some observer overhead.
|
||||
// Each zone occupies 16 bytes.
|
||||
#ifndef PROFILER_THREAD_STORAGE
|
||||
#define PROFILER_THREAD_STORAGE 32ULL
|
||||
#endif
|
||||
|
||||
#if PROFILER_ENABLED
|
||||
|
||||
#define PROFILER_PRINT_OVERHEAD 0
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h> // memcpy
|
||||
|
||||
#include <algorithm> // min/max
|
||||
#include <atomic>
|
||||
#include <cinttypes> // PRIu64
|
||||
#include <hwy/static_targets.h>
|
||||
#include <new>
|
||||
|
||||
#include "jxl/base/arch_specific.h"
|
||||
#include "jxl/base/cache_aligned.h"
|
||||
#include "jxl/base/compiler_specific.h"
|
||||
#include "jxl/base/robust_statistics.h"
|
||||
#include "jxl/base/status.h"
|
||||
#include "jxl/base/tsc_timer.h"
|
||||
|
||||
// Non-portable aspects:
|
||||
// - SSE2 128-bit load/store (write-combining, UpdateOrAdd)
|
||||
// - RDTSCP timestamps (serializing, high-resolution)
|
||||
// - assumes string literals are stored within an 8 MiB range
|
||||
// - compiler-specific annotations (restrict, alignment, fences)
|
||||
#if JXL_ARCH_X64
|
||||
#include <emmintrin.h>
|
||||
#if JXL_COMPILER_MSVC
|
||||
#include <intrin.h>
|
||||
#else
|
||||
#include <x86intrin.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// Upper bounds for various fixed-size data structures (guarded via JXL_ASSERT):
|
||||
|
||||
// How many unique threads can enter a zone (those that don't do not count).
|
||||
// Memory use is about kMaxThreads * PROFILER_THREAD_STORAGE MiB.
|
||||
// WARNING: fiber libraries and multiple ThreadPool can spawn >100 threads.
|
||||
static constexpr size_t kMaxThreads = 256;
|
||||
|
||||
// Maximum nesting of zones.
|
||||
static constexpr size_t kMaxDepth = 64;
|
||||
|
||||
// Total number of zones.
|
||||
static constexpr size_t kMaxZones = 256;
|
||||
|
||||
// Represents zone entry/exit events. Stores a full-resolution timestamp plus
|
||||
// an offset (representing zone name or identifying exit packets). POD.
|
||||
class Packet {
|
||||
public:
|
||||
// If offsets do not fit, UpdateOrAdd will overrun our heap allocation
|
||||
// (governed by kMaxZones). We have seen ~100 MiB static binaries.
|
||||
static constexpr size_t kOffsetBits = 27;
|
||||
static constexpr uintptr_t kOffsetBias = 1ULL << (kOffsetBits - 1);
|
||||
|
||||
// We need full-resolution timestamps; at an effective rate of 4 GHz,
|
||||
// this permits 34 second zone durations (for longer durations, split into
|
||||
// multiple zones). Wraparound is handled by masking.
|
||||
static constexpr size_t kTimestampBits = 64 - kOffsetBits;
|
||||
static constexpr uintptr_t kTimestampMask = (1ULL << kTimestampBits) - 1;
|
||||
|
||||
static Packet Make(const uintptr_t biased_offset, const uint64_t timestamp) {
|
||||
JXL_ASSERT(biased_offset < (1ULL << kOffsetBits));
|
||||
|
||||
Packet packet;
|
||||
packet.bits_ =
|
||||
(biased_offset << kTimestampBits) + (timestamp & kTimestampMask);
|
||||
return packet;
|
||||
}
|
||||
|
||||
uint64_t Timestamp() const { return bits_ & kTimestampMask; }
|
||||
|
||||
uintptr_t BiasedOffset() const { return (bits_ >> kTimestampBits); }
|
||||
|
||||
private:
|
||||
uint64_t bits_;
|
||||
};
|
||||
static_assert(sizeof(Packet) == 8, "Wrong Packet size");
|
||||
|
||||
// Returns the address of a string literal. Assuming zone names are also
|
||||
// literals and stored nearby, we can represent them as offsets, which are
|
||||
// faster to compute than hashes or even a static index.
|
||||
//
|
||||
// This function must not be static - each call (even from other translation
|
||||
// units) must return the same value.
|
||||
inline uintptr_t StringOrigin() {
|
||||
// Chosen such that no zone name is a prefix nor suffix of this string
|
||||
// to ensure they aren't merged (offset 0 identifies zone-exit packets).
|
||||
static const char* string_origin = "__#Origin#__";
|
||||
|
||||
return reinterpret_cast<uintptr_t>(string_origin) - Packet::kOffsetBias;
|
||||
}
|
||||
|
||||
// Representation of an active zone, stored in a stack. Used to deduct
|
||||
// child duration from the parent's self time. POD.
|
||||
struct ProfilerNode {
|
||||
Packet packet;
|
||||
uint64_t child_total;
|
||||
};
|
||||
|
||||
// Holds statistics for all zones with the same name. POD.
|
||||
struct Accumulator {
|
||||
static constexpr size_t kNumCallBits = 64 - Packet::kOffsetBits;
|
||||
|
||||
uintptr_t BiasedOffset() const { return num_calls >> kNumCallBits; }
|
||||
uint64_t NumCalls() const { return num_calls & ((1ULL << kNumCallBits) - 1); }
|
||||
|
||||
// UpdateOrAdd relies upon this layout.
|
||||
uint64_t num_calls = 0; // upper bits = biased_offset.
|
||||
uint64_t total_duration = 0;
|
||||
};
|
||||
#if JXL_ARCH_X64
|
||||
static_assert(sizeof(Accumulator) == 2 * sizeof(uint64_t), "Accumulator size");
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
inline T ClampedSubtract(const T minuend, const T subtrahend) {
|
||||
if (subtrahend > minuend) {
|
||||
return 0;
|
||||
}
|
||||
return minuend - subtrahend;
|
||||
}
|
||||
|
||||
// Per-thread call graph (stack) and Accumulator for each zone.
|
||||
class Results {
|
||||
public:
|
||||
Results() {
|
||||
// Zero-initialize first accumulator to avoid a check for num_zones_ == 0.
|
||||
memset(zones_, 0, sizeof(Accumulator));
|
||||
}
|
||||
|
||||
// Used for computing overhead when this thread encounters its first Zone.
|
||||
// This has no observable effect apart from increasing "analyze_elapsed_".
|
||||
uint64_t ZoneDuration(const Packet* packets) {
|
||||
JXL_CHECK(depth_ == 0);
|
||||
JXL_CHECK(num_zones_ == 0);
|
||||
AnalyzePackets(packets, 2);
|
||||
const uint64_t duration = zones_[0].total_duration;
|
||||
zones_[0].num_calls = 0;
|
||||
zones_[0].total_duration = 0;
|
||||
JXL_CHECK(depth_ == 0);
|
||||
num_zones_ = 0;
|
||||
return duration;
|
||||
}
|
||||
|
||||
void SetSelfOverhead(const uint64_t self_overhead) {
|
||||
self_overhead_ = self_overhead;
|
||||
}
|
||||
|
||||
void SetChildOverhead(const uint64_t child_overhead) {
|
||||
child_overhead_ = child_overhead;
|
||||
}
|
||||
|
||||
// Draw all required information from the packets, which can be discarded
|
||||
// afterwards. Called whenever this thread's storage is full.
|
||||
void AnalyzePackets(const Packet* packets, const size_t num_packets) {
|
||||
const uint64_t t0 = TicksBefore();
|
||||
|
||||
for (size_t i = 0; i < num_packets; ++i) {
|
||||
const Packet p = packets[i];
|
||||
// Entering a zone
|
||||
if (p.BiasedOffset() != Packet::kOffsetBias) {
|
||||
JXL_ASSERT(depth_ < kMaxDepth);
|
||||
nodes_[depth_].packet = p;
|
||||
nodes_[depth_].child_total = 0;
|
||||
++depth_;
|
||||
continue;
|
||||
}
|
||||
|
||||
JXL_ASSERT(depth_ != 0);
|
||||
const ProfilerNode& node = nodes_[depth_ - 1];
|
||||
// Masking correctly handles unsigned wraparound.
|
||||
const uint64_t duration =
|
||||
(p.Timestamp() - node.packet.Timestamp()) & Packet::kTimestampMask;
|
||||
const uint64_t self_duration = ClampedSubtract(
|
||||
duration, self_overhead_ + child_overhead_ + node.child_total);
|
||||
|
||||
UpdateOrAdd(node.packet.BiasedOffset(), 1, self_duration);
|
||||
--depth_;
|
||||
|
||||
// Deduct this nested node's time from its parent's self_duration.
|
||||
if (depth_ != 0) {
|
||||
nodes_[depth_ - 1].child_total += duration + child_overhead_;
|
||||
}
|
||||
}
|
||||
|
||||
const uint64_t t1 = TicksAfter();
|
||||
analyze_elapsed_ += t1 - t0;
|
||||
}
|
||||
|
||||
// Incorporates results from another thread. Call after all threads have
|
||||
// exited any zones.
|
||||
void Assimilate(const Results& other) {
|
||||
const uint64_t t0 = TicksBefore();
|
||||
JXL_ASSERT(depth_ == 0);
|
||||
JXL_ASSERT(other.depth_ == 0);
|
||||
|
||||
for (size_t i = 0; i < other.num_zones_; ++i) {
|
||||
const Accumulator& zone = other.zones_[i];
|
||||
UpdateOrAdd(zone.BiasedOffset(), zone.NumCalls(), zone.total_duration);
|
||||
}
|
||||
const uint64_t t1 = TicksAfter();
|
||||
analyze_elapsed_ += t1 - t0 + other.analyze_elapsed_;
|
||||
}
|
||||
|
||||
// Single-threaded.
|
||||
void Print() {
|
||||
const uint64_t t0 = TicksBefore();
|
||||
MergeDuplicates();
|
||||
|
||||
// Sort by decreasing total (self) cost.
|
||||
std::sort(zones_, zones_ + num_zones_,
|
||||
[](const Accumulator& r1, const Accumulator& r2) {
|
||||
return r1.total_duration > r2.total_duration;
|
||||
});
|
||||
|
||||
const uintptr_t string_origin = StringOrigin();
|
||||
uint64_t total_visible_duration = 0;
|
||||
for (size_t i = 0; i < num_zones_; ++i) {
|
||||
const Accumulator& r = zones_[i];
|
||||
const uint64_t num_calls = r.NumCalls();
|
||||
const char* name =
|
||||
reinterpret_cast<const char*>(string_origin + r.BiasedOffset());
|
||||
if (name[0] != '@') {
|
||||
total_visible_duration += r.total_duration;
|
||||
printf("%-40s: %10" PRIu64 " x %15" PRIu64 "= %15" PRIu64 "\n", name,
|
||||
num_calls, r.total_duration / num_calls, r.total_duration);
|
||||
}
|
||||
}
|
||||
|
||||
const uint64_t t1 = TicksAfter();
|
||||
analyze_elapsed_ += t1 - t0;
|
||||
printf("Total clocks during analysis: %" PRIu64 "\n", analyze_elapsed_);
|
||||
printf("Total clocks measured: %" PRIu64 "\n", total_visible_duration);
|
||||
}
|
||||
|
||||
// Single-threaded. Clears all results as if no zones had been recorded.
|
||||
void Reset() {
|
||||
analyze_elapsed_ = 0;
|
||||
JXL_CHECK(depth_ == 0);
|
||||
num_zones_ = 0;
|
||||
memset(nodes_, 0, sizeof(nodes_));
|
||||
memset(zones_, 0, sizeof(zones_));
|
||||
}
|
||||
|
||||
private:
|
||||
#if JXL_ARCH_X64
|
||||
static bool SameOffset(const __m128i zone, const uintptr_t biased_offset) {
|
||||
const uint64_t num_calls = _mm_cvtsi128_si64(zone);
|
||||
return (num_calls >> Accumulator::kNumCallBits) == biased_offset;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Updates an existing Accumulator (uniquely identified by biased_offset) or
|
||||
// adds one if this is the first time this thread analyzed that zone.
|
||||
// Uses a self-organizing list data structure, which avoids dynamic memory
|
||||
// allocations and is far faster than unordered_map. Loads, updates and
|
||||
// stores the entire Accumulator with vector instructions.
|
||||
void UpdateOrAdd(const uintptr_t biased_offset, const uint64_t num_calls,
|
||||
const uint64_t duration) {
|
||||
JXL_ASSERT(biased_offset < (1ULL << Packet::kOffsetBits));
|
||||
|
||||
#if JXL_ARCH_X64
|
||||
const __m128i num_calls_64 = _mm_cvtsi64_si128(num_calls);
|
||||
const __m128i duration_64 = _mm_cvtsi64_si128(duration);
|
||||
const __m128i add_duration_call =
|
||||
_mm_unpacklo_epi64(num_calls_64, duration_64);
|
||||
|
||||
__m128i* const JXL_RESTRICT zones = reinterpret_cast<__m128i*>(zones_);
|
||||
|
||||
// Special case for first zone: (maybe) update, without swapping.
|
||||
__m128i prev = _mm_load_si128(zones);
|
||||
if (SameOffset(prev, biased_offset)) {
|
||||
prev = _mm_add_epi64(prev, add_duration_call);
|
||||
JXL_ASSERT(SameOffset(prev, biased_offset));
|
||||
_mm_store_si128(zones, prev);
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for a zone with the same offset.
|
||||
for (size_t i = 1; i < num_zones_; ++i) {
|
||||
__m128i zone = _mm_load_si128(zones + i);
|
||||
if (SameOffset(zone, biased_offset)) {
|
||||
zone = _mm_add_epi64(zone, add_duration_call);
|
||||
JXL_ASSERT(SameOffset(zone, biased_offset));
|
||||
// Swap with predecessor (more conservative than move to front,
|
||||
// but at least as successful).
|
||||
_mm_store_si128(zones + i - 1, zone);
|
||||
_mm_store_si128(zones + i, prev);
|
||||
return;
|
||||
}
|
||||
prev = zone;
|
||||
}
|
||||
|
||||
// Not found; create a new Accumulator.
|
||||
const __m128i biased_offset_64 = _mm_slli_epi64(
|
||||
_mm_cvtsi64_si128(biased_offset), Accumulator::kNumCallBits);
|
||||
const __m128i zone = _mm_add_epi64(biased_offset_64, add_duration_call);
|
||||
JXL_ASSERT(SameOffset(zone, biased_offset));
|
||||
|
||||
JXL_ASSERT(num_zones_ < kMaxZones);
|
||||
_mm_store_si128(zones + num_zones_, zone);
|
||||
++num_zones_;
|
||||
#else
|
||||
// Special case for first zone: (maybe) update, without swapping.
|
||||
if (zones_[0].BiasedOffset() == biased_offset) {
|
||||
zones_[0].total_duration += duration;
|
||||
zones_[0].num_calls += num_calls;
|
||||
JXL_ASSERT(zones_[0].BiasedOffset() == biased_offset);
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for a zone with the same offset.
|
||||
for (size_t i = 1; i < num_zones_; ++i) {
|
||||
if (zones_[i].BiasedOffset() == biased_offset) {
|
||||
zones_[i].total_duration += duration;
|
||||
zones_[i].num_calls += num_calls;
|
||||
JXL_ASSERT(zones_[i].BiasedOffset() == biased_offset);
|
||||
// Swap with predecessor (more conservative than move to front,
|
||||
// but at least as successful).
|
||||
const Accumulator prev = zones_[i - 1];
|
||||
zones_[i - 1] = zones_[i];
|
||||
zones_[i] = prev;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Not found; create a new Accumulator.
|
||||
JXL_ASSERT(num_zones_ < kMaxZones);
|
||||
Accumulator* JXL_RESTRICT zone = zones_ + num_zones_;
|
||||
zone->num_calls = (biased_offset << Accumulator::kNumCallBits) + num_calls;
|
||||
zone->total_duration = duration;
|
||||
JXL_ASSERT(zone->BiasedOffset() == biased_offset);
|
||||
++num_zones_;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Each instantiation of a function template seems to get its own copy of
|
||||
// __func__ and GCC doesn't merge them. An N^2 search for duplicates is
|
||||
// acceptable because we only expect a few dozen zones.
|
||||
void MergeDuplicates() {
|
||||
const uintptr_t string_origin = StringOrigin();
|
||||
for (size_t i = 0; i < num_zones_; ++i) {
|
||||
const uintptr_t biased_offset = zones_[i].BiasedOffset();
|
||||
const char* name =
|
||||
reinterpret_cast<const char*>(string_origin + biased_offset);
|
||||
// Separate num_calls from biased_offset so we can add them together.
|
||||
uint64_t num_calls = zones_[i].NumCalls();
|
||||
|
||||
// Add any subsequent duplicates to num_calls and total_duration.
|
||||
for (size_t j = i + 1; j < num_zones_;) {
|
||||
if (!strcmp(name, reinterpret_cast<const char*>(
|
||||
string_origin + zones_[j].BiasedOffset()))) {
|
||||
num_calls += zones_[j].NumCalls();
|
||||
zones_[i].total_duration += zones_[j].total_duration;
|
||||
// Fill hole with last item.
|
||||
zones_[j] = zones_[--num_zones_];
|
||||
} else { // Name differed, try next Accumulator.
|
||||
++j;
|
||||
}
|
||||
}
|
||||
|
||||
JXL_ASSERT(num_calls < (1ULL << Accumulator::kNumCallBits));
|
||||
|
||||
// Re-pack regardless of whether any duplicates were found.
|
||||
zones_[i].num_calls =
|
||||
(biased_offset << Accumulator::kNumCallBits) + num_calls;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t analyze_elapsed_ = 0;
|
||||
uint64_t self_overhead_ = 0;
|
||||
uint64_t child_overhead_ = 0;
|
||||
|
||||
size_t depth_ = 0; // Number of active zones.
|
||||
size_t num_zones_ = 0; // Number of retired zones.
|
||||
|
||||
alignas(64) ProfilerNode nodes_[kMaxDepth]; // Stack
|
||||
alignas(64) Accumulator zones_[kMaxZones]; // Self-organizing list
|
||||
};
|
||||
|
||||
// Per-thread packet storage, allocated via CacheAligned.
|
||||
class ThreadSpecific {
|
||||
static constexpr size_t kBufferCapacity =
|
||||
CacheAligned::kCacheLineSize / sizeof(Packet);
|
||||
|
||||
public:
|
||||
// "name" is used to sanity-check offsets fit in kOffsetBits.
|
||||
explicit ThreadSpecific(const char* name)
|
||||
: packets_(static_cast<Packet*>(
|
||||
CacheAligned::Allocate(PROFILER_THREAD_STORAGE << 20))),
|
||||
num_packets_(0),
|
||||
max_packets_(PROFILER_THREAD_STORAGE << 17),
|
||||
string_origin_(StringOrigin()) {
|
||||
// Even in optimized builds (with NDEBUG), verify that this zone's name
|
||||
// offset fits within the allotted space. If not, UpdateOrAdd is likely to
|
||||
// overrun zones_[]. We also JXL_ASSERT(), but users often do not run debug
|
||||
// builds. Checking here on the cold path (only reached once per thread)
|
||||
// is cheap, but it only covers one zone.
|
||||
const uintptr_t biased_offset =
|
||||
reinterpret_cast<uintptr_t>(name) - string_origin_;
|
||||
JXL_CHECK(biased_offset <= (1ULL << Packet::kOffsetBits));
|
||||
}
|
||||
|
||||
~ThreadSpecific() { CacheAligned::Free(packets_); }
|
||||
|
||||
// Depends on Zone => defined below.
|
||||
void ComputeOverhead();
|
||||
|
||||
void WriteEntry(const char* name, const uint64_t timestamp) {
|
||||
const uintptr_t biased_offset =
|
||||
reinterpret_cast<uintptr_t>(name) - string_origin_;
|
||||
Write(Packet::Make(biased_offset, timestamp));
|
||||
}
|
||||
|
||||
void WriteExit(const uint64_t timestamp) {
|
||||
const uintptr_t biased_offset = Packet::kOffsetBias;
|
||||
Write(Packet::Make(biased_offset, timestamp));
|
||||
}
|
||||
|
||||
void AnalyzeRemainingPackets() {
|
||||
#if JXL_ARCH_X64
|
||||
// Ensures prior weakly-ordered streaming stores are globally visible.
|
||||
_mm_sfence();
|
||||
|
||||
// Storage full => empty it.
|
||||
if (num_packets_ + buffer_size_ > max_packets_) {
|
||||
results_.AnalyzePackets(packets_, num_packets_);
|
||||
num_packets_ = 0;
|
||||
}
|
||||
memcpy(packets_ + num_packets_, buffer_, buffer_size_ * sizeof(Packet));
|
||||
num_packets_ += buffer_size_;
|
||||
buffer_size_ = 0;
|
||||
#endif
|
||||
|
||||
results_.AnalyzePackets(packets_, num_packets_);
|
||||
num_packets_ = 0;
|
||||
}
|
||||
|
||||
Results& GetResults() { return results_; }
|
||||
|
||||
private:
|
||||
#if JXL_ARCH_X64 && HWY_BITS != 0
|
||||
// Overwrites `to` without loading it into cache (read-for-ownership).
|
||||
// Copies kCacheLineSize bytes from/to naturally aligned addresses.
|
||||
template <typename T>
|
||||
static HWY_ATTR void StreamCacheLine(const T* JXL_RESTRICT from,
|
||||
T* JXL_RESTRICT to) {
|
||||
static_assert(16 % sizeof(T) == 0, "T must fit in a lane");
|
||||
constexpr size_t kLanes = 16 / sizeof(T);
|
||||
const hwy::Desc<T, kLanes> d;
|
||||
JXL_COMPILER_FENCE;
|
||||
const auto v0 = Load(d, from + 0 * kLanes);
|
||||
const auto v1 = Load(d, from + 1 * kLanes);
|
||||
const auto v2 = Load(d, from + 2 * kLanes);
|
||||
const auto v3 = Load(d, from + 3 * kLanes);
|
||||
static_assert(sizeof(v0) * 4 == 64, "Wrong #vectors");
|
||||
// Fences prevent the compiler from reordering loads/stores, which may
|
||||
// interfere with write-combining.
|
||||
JXL_COMPILER_FENCE;
|
||||
Stream(v0, d, to + 0 * kLanes);
|
||||
Stream(v1, d, to + 1 * kLanes);
|
||||
Stream(v2, d, to + 2 * kLanes);
|
||||
Stream(v3, d, to + 3 * kLanes);
|
||||
JXL_COMPILER_FENCE;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Write packet to buffer/storage, emptying them as needed.
|
||||
void Write(const Packet packet) {
|
||||
#if JXL_ARCH_X64 && HWY_BITS != 0
|
||||
// Buffer full => copy to storage.
|
||||
if (buffer_size_ == kBufferCapacity) {
|
||||
// Storage full => empty it.
|
||||
if (num_packets_ + kBufferCapacity > max_packets_) {
|
||||
results_.AnalyzePackets(packets_, num_packets_);
|
||||
num_packets_ = 0;
|
||||
}
|
||||
// This buffering halves observer overhead and decreases the overall
|
||||
// runtime by about 3%.
|
||||
StreamCacheLine(buffer_, packets_ + num_packets_);
|
||||
num_packets_ += kBufferCapacity;
|
||||
buffer_size_ = 0;
|
||||
}
|
||||
buffer_[buffer_size_] = packet;
|
||||
++buffer_size_;
|
||||
#else
|
||||
// Write directly to storage.
|
||||
if (num_packets_ >= max_packets_) {
|
||||
results_.AnalyzePackets(packets_, num_packets_);
|
||||
num_packets_ = 0;
|
||||
}
|
||||
packets_[num_packets_] = packet;
|
||||
++num_packets_;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Write-combining buffer to avoid cache pollution. Must be the first
|
||||
// non-static member to ensure cache-line alignment.
|
||||
#if JXL_ARCH_X64
|
||||
Packet buffer_[kBufferCapacity];
|
||||
size_t buffer_size_ = 0;
|
||||
#endif
|
||||
|
||||
// Contiguous storage for zone enter/exit packets.
|
||||
Packet* const JXL_RESTRICT packets_;
|
||||
size_t num_packets_;
|
||||
const size_t max_packets_;
|
||||
// Cached here because we already read this cache line on zone entry/exit.
|
||||
uintptr_t string_origin_;
|
||||
Results results_;
|
||||
};
|
||||
|
||||
class ThreadList {
|
||||
public:
|
||||
// Thread-safe.
|
||||
void Add(ThreadSpecific* const ts) {
|
||||
const uint32_t index = num_threads_.fetch_add(1, std::memory_order_relaxed);
|
||||
JXL_CHECK(index < kMaxThreads);
|
||||
threads_[index] = ts;
|
||||
}
|
||||
|
||||
// Single-threaded.
|
||||
void PrintResults() {
|
||||
const uint32_t num_threads = num_threads_.load(std::memory_order_relaxed);
|
||||
for (uint32_t i = 0; i < num_threads; ++i) {
|
||||
threads_[i]->AnalyzeRemainingPackets();
|
||||
}
|
||||
|
||||
// Combine all threads into a single Result.
|
||||
for (uint32_t i = 1; i < num_threads; ++i) {
|
||||
threads_[0]->GetResults().Assimilate(threads_[i]->GetResults());
|
||||
}
|
||||
|
||||
if (num_threads != 0) {
|
||||
threads_[0]->GetResults().Print();
|
||||
|
||||
for (uint32_t i = 0; i < num_threads; ++i) {
|
||||
threads_[i]->GetResults().Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Owning pointers.
|
||||
alignas(64) ThreadSpecific* threads_[kMaxThreads];
|
||||
std::atomic<uint32_t> num_threads_{0};
|
||||
};
|
||||
|
||||
// RAII zone enter/exit recorder constructed by the ZONE macro; also
|
||||
// responsible for initializing ThreadSpecific.
|
||||
class Zone {
|
||||
public:
|
||||
// "name" must be a string literal (see StringOrigin).
|
||||
JXL_NOINLINE explicit Zone(const char* name) {
|
||||
JXL_COMPILER_FENCE;
|
||||
ThreadSpecific* JXL_RESTRICT thread_specific = StaticThreadSpecific();
|
||||
if (JXL_UNLIKELY(thread_specific == nullptr)) {
|
||||
void* mem = CacheAligned::Allocate(sizeof(ThreadSpecific));
|
||||
thread_specific = new (mem) ThreadSpecific(name);
|
||||
// Must happen before ComputeOverhead, which re-enters this ctor.
|
||||
Threads().Add(thread_specific);
|
||||
StaticThreadSpecific() = thread_specific;
|
||||
thread_specific->ComputeOverhead();
|
||||
}
|
||||
|
||||
// (Capture timestamp ASAP, not inside WriteEntry.)
|
||||
JXL_COMPILER_FENCE;
|
||||
const uint64_t timestamp = TicksBefore();
|
||||
thread_specific->WriteEntry(name, timestamp);
|
||||
}
|
||||
|
||||
JXL_NOINLINE ~Zone() {
|
||||
JXL_COMPILER_FENCE;
|
||||
const uint64_t timestamp = TicksAfter();
|
||||
StaticThreadSpecific()->WriteExit(timestamp);
|
||||
JXL_COMPILER_FENCE;
|
||||
}
|
||||
|
||||
// Call exactly once after all threads have exited all zones.
|
||||
static void PrintResults() { Threads().PrintResults(); }
|
||||
|
||||
private:
|
||||
// Returns reference to the thread's ThreadSpecific pointer (initially null).
|
||||
// Function-local static avoids needing a separate definition.
|
||||
static ThreadSpecific*& StaticThreadSpecific() {
|
||||
static thread_local ThreadSpecific* thread_specific;
|
||||
return thread_specific;
|
||||
}
|
||||
|
||||
// Returns the singleton ThreadList. Non time-critical.
|
||||
static ThreadList& Threads() {
|
||||
static ThreadList threads_;
|
||||
return threads_;
|
||||
}
|
||||
};
|
||||
|
||||
// Creates a zone starting from here until the end of the current scope.
|
||||
// Timestamps will be recorded when entering and exiting the zone.
|
||||
// "name" must be a string literal, which is ensured by merging with "".
|
||||
#define PROFILER_ZONE(name) \
|
||||
JXL_COMPILER_FENCE; \
|
||||
const ::jxl::Zone zone("" name); \
|
||||
JXL_COMPILER_FENCE
|
||||
|
||||
// Creates a zone for an entire function (when placed at its beginning).
|
||||
// Shorter/more convenient than ZONE.
|
||||
#define PROFILER_FUNC \
|
||||
JXL_COMPILER_FENCE; \
|
||||
const ::jxl::Zone zone(__func__); \
|
||||
JXL_COMPILER_FENCE
|
||||
|
||||
#define PROFILER_PRINT_RESULTS ::jxl::Zone::PrintResults
|
||||
|
||||
inline void ThreadSpecific::ComputeOverhead() {
|
||||
// Delay after capturing timestamps before/after the actual zone runs. Even
|
||||
// with frequency throttling disabled, this has a multimodal distribution,
|
||||
// including 32, 34, 48, 52, 59, 62.
|
||||
uint64_t self_overhead;
|
||||
{
|
||||
const size_t kNumSamples = 32;
|
||||
uint32_t samples[kNumSamples];
|
||||
for (size_t idx_sample = 0; idx_sample < kNumSamples; ++idx_sample) {
|
||||
const size_t kNumDurations = 1024;
|
||||
uint32_t durations[kNumDurations];
|
||||
|
||||
for (size_t idx_duration = 0; idx_duration < kNumDurations;
|
||||
++idx_duration) {
|
||||
{
|
||||
PROFILER_ZONE("Dummy Zone (never shown)");
|
||||
}
|
||||
#if JXL_ARCH_X64
|
||||
const uint64_t duration = results_.ZoneDuration(buffer_);
|
||||
buffer_size_ = 0;
|
||||
#else
|
||||
const uint64_t duration = results_.ZoneDuration(packets_);
|
||||
num_packets_ = 0;
|
||||
#endif
|
||||
durations[idx_duration] = static_cast<uint32_t>(duration);
|
||||
JXL_CHECK(num_packets_ == 0);
|
||||
}
|
||||
CountingSort(durations, durations + kNumDurations);
|
||||
samples[idx_sample] = HalfSampleMode()(durations, kNumDurations);
|
||||
}
|
||||
// Median.
|
||||
CountingSort(samples, samples + kNumSamples);
|
||||
self_overhead = samples[kNumSamples / 2];
|
||||
#if PROFILER_PRINT_OVERHEAD
|
||||
printf("Overhead: %zu\n", self_overhead);
|
||||
#endif
|
||||
results_.SetSelfOverhead(self_overhead);
|
||||
}
|
||||
|
||||
// Delay before capturing start timestamp / after end timestamp.
|
||||
const size_t kNumSamples = 32;
|
||||
uint32_t samples[kNumSamples];
|
||||
for (size_t idx_sample = 0; idx_sample < kNumSamples; ++idx_sample) {
|
||||
const size_t kNumDurations = 16;
|
||||
uint32_t durations[kNumDurations];
|
||||
for (size_t idx_duration = 0; idx_duration < kNumDurations;
|
||||
++idx_duration) {
|
||||
const size_t kReps = 10000;
|
||||
// Analysis time should not be included => must fit within buffer.
|
||||
JXL_CHECK(kReps * 2 < max_packets_);
|
||||
#if JXL_ARCH_X64
|
||||
_mm_mfence();
|
||||
#endif
|
||||
const uint64_t t0 = TicksBefore();
|
||||
for (size_t i = 0; i < kReps; ++i) {
|
||||
PROFILER_ZONE("Dummy");
|
||||
}
|
||||
#if JXL_ARCH_X64
|
||||
_mm_sfence();
|
||||
#endif
|
||||
const uint64_t t1 = TicksAfter();
|
||||
#if JXL_ARCH_X64
|
||||
JXL_CHECK(num_packets_ + buffer_size_ == kReps * 2);
|
||||
buffer_size_ = 0;
|
||||
#else
|
||||
JXL_CHECK(num_packets_ == kReps * 2);
|
||||
#endif
|
||||
num_packets_ = 0;
|
||||
const uint64_t avg_duration = (t1 - t0 + kReps / 2) / kReps;
|
||||
durations[idx_duration] =
|
||||
static_cast<uint32_t>(ClampedSubtract(avg_duration, self_overhead));
|
||||
}
|
||||
CountingSort(durations, durations + kNumDurations);
|
||||
samples[idx_sample] = HalfSampleMode()(durations, kNumDurations);
|
||||
}
|
||||
CountingSort(samples, samples + kNumSamples);
|
||||
const uint64_t child_overhead = samples[9 * kNumSamples / 10];
|
||||
#if PROFILER_PRINT_OVERHEAD
|
||||
printf("Child overhead: %zu\n", child_overhead);
|
||||
#endif
|
||||
results_.SetChildOverhead(child_overhead);
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#else // !PROFILER_ENABLED
|
||||
#define PROFILER_ZONE(name)
|
||||
#define PROFILER_FUNC
|
||||
#define PROFILER_PRINT_RESULTS()
|
||||
#endif
|
||||
|
||||
#endif // JXL_BASE_PROFILER_H_
|
||||
@@ -0,0 +1,361 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_BASE_ROBUST_STATISTICS_H_
|
||||
#define JXL_BASE_ROBUST_STATISTICS_H_
|
||||
|
||||
// Robust statistics: Mode, Median, MedianAbsoluteDeviation.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/base/arch_specific.h"
|
||||
#include "jxl/base/compiler_specific.h"
|
||||
#include "jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// Round up for integers
|
||||
template <class T, typename std::enable_if<
|
||||
std::numeric_limits<T>::is_integer>::type* = nullptr>
|
||||
inline T Half(T x) {
|
||||
return (x + 1) / 2;
|
||||
}
|
||||
|
||||
// Mul is faster than div.
|
||||
template <class T, typename std::enable_if<
|
||||
!std::numeric_limits<T>::is_integer>::type* = nullptr>
|
||||
inline T Half(T x) {
|
||||
return x * 0.5;
|
||||
}
|
||||
|
||||
// Returns the median value. Side effect: values <= median will appear before,
|
||||
// values >= median after the middle index.
|
||||
// Guarantees average speed O(num_values).
|
||||
template <typename T>
|
||||
T Median(T* samples, const size_t num_samples) {
|
||||
JXL_ASSERT(num_samples != 0);
|
||||
std::nth_element(samples, samples + num_samples / 2, samples + num_samples);
|
||||
T result = samples[num_samples / 2];
|
||||
// If even size, find largest element in the partially sorted vector to
|
||||
// use as second element to average with
|
||||
if ((num_samples & 1) == 0) {
|
||||
T biggest = *std::max_element(samples, samples + num_samples / 2);
|
||||
result = Half(result + biggest);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T Median(std::vector<T>* samples) {
|
||||
return Median(samples->data(), samples->size());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline T Median3(const T a, const T b, const T c) {
|
||||
return std::max(std::min(a, b), std::min(c, std::max(a, b)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline T Median5(const T a, const T b, const T c, const T d, const T e) {
|
||||
return Median3(e, std::max(std::min(a, b), std::min(c, d)),
|
||||
std::min(std::max(a, b), std::max(c, d)));
|
||||
}
|
||||
|
||||
// Returns a robust measure of variability.
|
||||
template <typename T>
|
||||
T MedianAbsoluteDeviation(const T* samples, const size_t num_samples,
|
||||
const T median) {
|
||||
JXL_ASSERT(num_samples != 0);
|
||||
std::vector<T> abs_deviations;
|
||||
abs_deviations.reserve(num_samples);
|
||||
for (size_t i = 0; i < num_samples; ++i) {
|
||||
abs_deviations.push_back(std::abs(samples[i] - median));
|
||||
}
|
||||
return Median(&abs_deviations);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T MedianAbsoluteDeviation(const std::vector<T>& samples, const T median) {
|
||||
return MedianAbsoluteDeviation(samples.data(), samples.size(), median);
|
||||
}
|
||||
|
||||
// Half{Range/Sample}Mode are implementations of "Robust estimators of the mode
|
||||
// and skewness of continuous data". The mode is less affected by outliers in
|
||||
// highly-skewed distributions than the median.
|
||||
|
||||
// Robust estimator of the mode for data given as sorted values.
|
||||
// O(N*logN), N=num_values.
|
||||
class HalfSampleMode {
|
||||
public:
|
||||
// Returns mode. "sorted" must be in ascending order.
|
||||
template <typename T>
|
||||
T operator()(const T* const JXL_RESTRICT sorted,
|
||||
const size_t num_values) const {
|
||||
int64_t center = num_values / 2;
|
||||
int64_t width = num_values;
|
||||
|
||||
// Zoom in on modal intervals of decreasing width. Stop before we reach
|
||||
// width=1, i.e. single values, for which there is no "slope".
|
||||
while (width > 2) {
|
||||
// Round up so we can still reach the outer edges of odd widths.
|
||||
width = Half(width);
|
||||
|
||||
center = CenterOfIntervalWithMinSlope(sorted, num_values, center, width);
|
||||
}
|
||||
|
||||
return sorted[center]; // mode := middle value in modal interval.
|
||||
}
|
||||
|
||||
private:
|
||||
// Returns center of the densest region [c-radius, c+radius].
|
||||
template <typename T>
|
||||
static JXL_INLINE int64_t CenterOfIntervalWithMinSlope(
|
||||
const T* JXL_RESTRICT sorted, const int64_t total_values,
|
||||
const int64_t center, const int64_t width) {
|
||||
const int64_t radius = Half(width);
|
||||
|
||||
auto compute_slope = [radius, total_values, sorted](
|
||||
int64_t c, int64_t* actual_center = nullptr) {
|
||||
// For symmetry, check 2*radius+1 values, i.e. [min, max].
|
||||
const int64_t min = std::max(c - radius, int64_t(0));
|
||||
const int64_t max = std::min(c + radius, total_values - 1);
|
||||
JXL_ASSERT(min < max);
|
||||
JXL_ASSERT(sorted[min] <=
|
||||
sorted[max] + std::numeric_limits<float>::epsilon());
|
||||
const float dx = max - min + 1;
|
||||
const float slope = (sorted[max] - sorted[min]) / dx;
|
||||
|
||||
if (actual_center != nullptr) {
|
||||
// c may be out of bounds, so return center of the clamped bounds.
|
||||
*actual_center = Half(min + max);
|
||||
}
|
||||
return slope;
|
||||
};
|
||||
|
||||
// First find min_slope for all centers.
|
||||
float min_slope = std::numeric_limits<float>::max();
|
||||
for (int64_t c = center - radius; c <= center + radius; ++c) {
|
||||
min_slope = std::min(min_slope, compute_slope(c));
|
||||
}
|
||||
|
||||
// Candidates := centers with slope ~= min_slope.
|
||||
std::vector<int64_t> candidates;
|
||||
for (int64_t c = center - radius; c <= center + radius; ++c) {
|
||||
int64_t actual_center;
|
||||
const float slope = compute_slope(c, &actual_center);
|
||||
if (slope <= min_slope * 1.001f) {
|
||||
candidates.push_back(actual_center);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the median.
|
||||
JXL_ASSERT(!candidates.empty());
|
||||
if (candidates.size() == 1) return candidates[0];
|
||||
return Median(&candidates);
|
||||
}
|
||||
};
|
||||
|
||||
// Robust estimator of the mode for data given as a CDF.
|
||||
// O(N*logN), N=num_bins.
|
||||
class HalfRangeMode {
|
||||
public:
|
||||
// Returns mode expressed as a histogram bin index. "cdf" must be weakly
|
||||
// monotonically increasing, e.g. from std::partial_sum.
|
||||
int operator()(const uint32_t* JXL_RESTRICT cdf,
|
||||
const size_t num_bins) const {
|
||||
int center = num_bins / 2;
|
||||
int width = num_bins;
|
||||
|
||||
// Zoom in on modal intervals of decreasing width. Stop before we reach
|
||||
// width=1, i.e. original bins, because those are noisy.
|
||||
while (width > 2) {
|
||||
// Round up so we can still reach the outer edges of odd widths.
|
||||
width = Half(width);
|
||||
|
||||
center = CenterOfIntervalWithMaxDensity(cdf, num_bins, center, width);
|
||||
}
|
||||
|
||||
return center; // mode := midpoint of modal interval.
|
||||
}
|
||||
|
||||
private:
|
||||
// Returns center of the densest interval [c-radius, c+radius].
|
||||
static JXL_INLINE int CenterOfIntervalWithMaxDensity(
|
||||
const uint32_t* JXL_RESTRICT cdf, const int total_bins, const int center,
|
||||
const int width) {
|
||||
const int radius = Half(width);
|
||||
|
||||
auto compute_density = [radius, total_bins, cdf](
|
||||
int c, int* actual_center = nullptr) {
|
||||
// For symmetry, check 2*radius+1 bins, i.e. [min, max].
|
||||
const int min = std::max(c - radius, 1); // for -1 below
|
||||
const int max = std::min(c + radius, total_bins - 1);
|
||||
JXL_ASSERT(min < max);
|
||||
JXL_ASSERT(cdf[min] <= cdf[max - 1]);
|
||||
const int num_bins = max - min + 1;
|
||||
// Sum over [min, max] == CDF(max) - CDF(min-1).
|
||||
const float density = float(cdf[max] - cdf[min - 1]) / num_bins;
|
||||
|
||||
if (actual_center != nullptr) {
|
||||
// c may be out of bounds, so take center of the clamped bounds.
|
||||
*actual_center = Half(min + max);
|
||||
}
|
||||
return density;
|
||||
};
|
||||
|
||||
// First find max_density for all centers.
|
||||
float max_density = 0.0f;
|
||||
for (int c = center - radius; c <= center + radius; ++c) {
|
||||
max_density = std::max(max_density, compute_density(c));
|
||||
}
|
||||
|
||||
// Candidates := centers with density ~= max_density.
|
||||
std::vector<int> candidates;
|
||||
for (int c = center - radius; c <= center + radius; ++c) {
|
||||
int actual_center;
|
||||
const float density = compute_density(c, &actual_center);
|
||||
if (density >= max_density * 0.999f) {
|
||||
candidates.push_back(actual_center);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the median.
|
||||
JXL_ASSERT(!candidates.empty());
|
||||
if (candidates.size() == 1) return candidates[0];
|
||||
return Median(&candidates);
|
||||
}
|
||||
};
|
||||
|
||||
// Sorts integral values in ascending order. About 3x faster than std::sort for
|
||||
// input distributions with very few unique values.
|
||||
template <class T>
|
||||
void CountingSort(T* begin, T* end) {
|
||||
// Unique values and their frequency (similar to flat_map).
|
||||
using Unique = std::pair<T, int>;
|
||||
std::vector<Unique> unique;
|
||||
for (const T* p = begin; p != end; ++p) {
|
||||
const T value = *p;
|
||||
const auto pos =
|
||||
std::find_if(unique.begin(), unique.end(),
|
||||
[value](const Unique& u) { return u.first == value; });
|
||||
if (pos == unique.end()) {
|
||||
unique.push_back(std::make_pair(*p, 1));
|
||||
} else {
|
||||
++pos->second;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort in ascending order of value (pair.first).
|
||||
std::sort(unique.begin(), unique.end());
|
||||
|
||||
// Write that many copies of each unique value to the array.
|
||||
T* JXL_RESTRICT p = begin;
|
||||
for (const auto& value_count : unique) {
|
||||
std::fill(p, p + value_count.second, value_count.first);
|
||||
p += value_count.second;
|
||||
}
|
||||
JXL_ASSERT(p == end);
|
||||
}
|
||||
|
||||
struct Bivariate {
|
||||
Bivariate(float x, float y) : x(x), y(y) {}
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
|
||||
class Line {
|
||||
public:
|
||||
constexpr Line(const float slope, const float intercept)
|
||||
: slope_(slope), intercept_(intercept) {}
|
||||
|
||||
constexpr float slope() const { return slope_; }
|
||||
constexpr float intercept() const { return intercept_; }
|
||||
|
||||
// Robust line fit using Siegel's repeated-median algorithm.
|
||||
explicit Line(const std::vector<Bivariate>& points) {
|
||||
const size_t N = points.size();
|
||||
// This straightforward N^2 implementation is OK for small N.
|
||||
JXL_ASSERT(N < 10 * 1000);
|
||||
|
||||
// One for every point i.
|
||||
std::vector<float> medians;
|
||||
medians.reserve(N);
|
||||
|
||||
// One for every j != i. Never cleared to avoid reallocation.
|
||||
std::vector<float> slopes(N - 1);
|
||||
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
// Index within slopes[] (avoids the hole where j == i).
|
||||
size_t idx_slope = 0;
|
||||
|
||||
for (size_t j = 0; j < N; ++j) {
|
||||
if (j == i) continue;
|
||||
|
||||
const float dy = points[j].y - points[i].y;
|
||||
const float dx = points[j].x - points[i].x;
|
||||
JXL_ASSERT(std::abs(dx) > 1E-7f); // x must be distinct
|
||||
slopes[idx_slope++] = dy / dx;
|
||||
}
|
||||
JXL_ASSERT(idx_slope == N - 1);
|
||||
|
||||
const float median = Median(&slopes);
|
||||
medians.push_back(median);
|
||||
}
|
||||
|
||||
slope_ = Median(&medians);
|
||||
|
||||
// Solve for intercept, overwriting medians[].
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
medians[i] = points[i].y - slope_ * points[i].x;
|
||||
}
|
||||
intercept_ = Median(&medians);
|
||||
}
|
||||
|
||||
constexpr float operator()(float x) const { return x * slope_ + intercept_; }
|
||||
|
||||
private:
|
||||
float slope_;
|
||||
float intercept_;
|
||||
};
|
||||
|
||||
static inline void EvaluateQuality(const Line& line,
|
||||
const std::vector<Bivariate>& points,
|
||||
float* JXL_RESTRICT max_l1,
|
||||
float* JXL_RESTRICT median_abs_deviation) {
|
||||
// For computing median_abs_deviation.
|
||||
std::vector<float> abs_deviations;
|
||||
abs_deviations.reserve(points.size());
|
||||
|
||||
*max_l1 = 0.0f;
|
||||
for (const Bivariate& point : points) {
|
||||
const float l1 = std::abs(line(point.x) - point.y);
|
||||
*max_l1 = std::max(*max_l1, l1);
|
||||
abs_deviations.push_back(l1);
|
||||
}
|
||||
|
||||
*median_abs_deviation = Median(&abs_deviations);
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_BASE_ROBUST_STATISTICS_H_
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_BASE_SPAN_H_
|
||||
#define JXL_BASE_SPAN_H_
|
||||
|
||||
// Span (array view) is a non-owning container that provides cheap "cut"
|
||||
// operations and could be used as "ArrayLike" data source for PaddedBytes.
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
template <typename T>
|
||||
class Span {
|
||||
public:
|
||||
constexpr Span() noexcept : Span(nullptr, 0) {}
|
||||
|
||||
constexpr Span(T* array, size_t length) noexcept
|
||||
: ptr_(array), len_(length) {}
|
||||
|
||||
template <size_t N>
|
||||
explicit constexpr Span(T (&a)[N]) noexcept : Span(a, N) {}
|
||||
|
||||
template <typename ArrayLike>
|
||||
explicit constexpr Span(const ArrayLike& other) noexcept
|
||||
: Span(reinterpret_cast<T*>(other.data()), other.size()) {
|
||||
static_assert(sizeof(*other.data()) == sizeof(T),
|
||||
"Incompatible type of source.");
|
||||
}
|
||||
|
||||
constexpr T* data() const noexcept { return ptr_; }
|
||||
|
||||
constexpr size_t size() const noexcept { return len_; }
|
||||
|
||||
constexpr T& operator[](size_t i) const noexcept {
|
||||
// MSVC 2015 accepts this as constexpr, but not ptr_[i]
|
||||
return *(data() + i);
|
||||
}
|
||||
|
||||
void remove_prefix(size_t n) noexcept {
|
||||
JXL_ASSERT(size() >= n);
|
||||
ptr_ += n;
|
||||
len_ -= n;
|
||||
}
|
||||
|
||||
private:
|
||||
T* ptr_;
|
||||
size_t len_;
|
||||
};
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_BASE_SPAN_H_
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "jxl/base/status.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "jxl/base/os_specific.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
bool Debug(const char* format, ...) {
|
||||
// Show the debug messages in debug or opt mode, not in release mode.
|
||||
#if JXL_DEBUG_WARNING
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vfprintf(stderr, format, args);
|
||||
va_end(args);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Abort(const char* file, int line, const char* format, ...) {
|
||||
char buf[2000];
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsnprintf(buf, sizeof(buf), format, args);
|
||||
va_end(args);
|
||||
|
||||
const std::string call_stack;
|
||||
|
||||
fprintf(stderr, "Abort at %s:%d: %s\n%s\n", file, line, buf,
|
||||
call_stack.c_str());
|
||||
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
|
||||
defined(THREAD_SANITIZER)
|
||||
// If compiled with UBSAN, triggering an error gives us a call stack.
|
||||
printf("Deliberate ubsan div zero %zu\n", 10 / (TotalMemoryMiB() >> 30));
|
||||
#endif
|
||||
abort();
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
@@ -0,0 +1,185 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_BASE_STATUS_H_
|
||||
#define JXL_BASE_STATUS_H_
|
||||
|
||||
// Error handling: Status return type + helper macros.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "jxl/base/compiler_specific.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// Uncomment to abort when JXL_FAILURE is reached
|
||||
// #define JXL_CRASH_ON_ERROR
|
||||
|
||||
#ifndef JXL_ENABLE_ASSERT
|
||||
#define JXL_ENABLE_ASSERT 1
|
||||
#endif
|
||||
|
||||
// Pass -DJXL_DEBUG_ON_ERROR at compile time to print debug messages when a
|
||||
// function returns JXL_FAILURE or calls JXL_NOTIFY_ERROR. Note that this is
|
||||
// irrelevant if you also pass -DJXL_CRASH_ON_ERROR.
|
||||
#ifdef JXL_DEBUG_ON_ERROR
|
||||
#undef JXL_DEBUG_ON_ERROR
|
||||
#define JXL_DEBUG_ON_ERROR 1
|
||||
#else // JXL_DEBUG_ON_ERROR
|
||||
#ifdef NDEBUG
|
||||
#define JXL_DEBUG_ON_ERROR 0
|
||||
#else // JXL_DEBUG_ON_ERROR
|
||||
#define JXL_DEBUG_ON_ERROR 1
|
||||
#endif // NDEBUG
|
||||
#endif // JXL_DEBUG_ON_ERROR
|
||||
|
||||
// The Verbose level for the library
|
||||
#ifndef JXL_DEBUG_V_LEVEL
|
||||
#define JXL_DEBUG_V_LEVEL 0
|
||||
#endif // JXL_DEBUG_V_LEVEL
|
||||
|
||||
// Print a debug message on standard error. You should use the JXL_DEBUG macro
|
||||
// instead of calling Debug directly. This function returns false, so it can be
|
||||
// used as a return value in JXL_FAILURE.
|
||||
JXL_FORMAT(1, 2)
|
||||
bool Debug(const char* format, ...);
|
||||
|
||||
// Print a debug message on standard error if "enabled" is true. "enabled" is
|
||||
// normally a macro that evaluates to 0 or 1 at compile time, so the Debug
|
||||
// function is never called and optimized out in release builds. Note that the
|
||||
// arguments are compiled but not evaluated when enabled is false. The format
|
||||
// string must be a explicit string in the call, for example:
|
||||
// JXL_DEBUG(JXL_DEBUG_MYMODULE, "my module message: %d", some_var);
|
||||
// Add a header at the top of your module's .cc or .h file (depending on whether
|
||||
// you have JXL_DEBUG calls from the .h as well) like this:
|
||||
// #ifndef JXL_DEBUG_MYMODULE
|
||||
// #define JXL_DEBUG_MYMODULE 0
|
||||
// #endif JXL_DEBUG_MYMODULE
|
||||
#define JXL_DEBUG(enabled, format, ...) \
|
||||
do { \
|
||||
if (enabled) { \
|
||||
::jxl::Debug(("%s:%d: " format "\n"), __FILE__, __LINE__, \
|
||||
##__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// JXL_DEBUG version that prints the debug message if the global verbose level
|
||||
// defined at compile time by JXL_DEBUG_V_LEVEL is greater or equal than the
|
||||
// passed level.
|
||||
#define JXL_DEBUG_V(level, ...) \
|
||||
JXL_DEBUG(level <= JXL_DEBUG_V_LEVEL, __VA_ARGS__)
|
||||
|
||||
// Warnings (via JXL_WARNING) are enabled by default in debug builds (opt and
|
||||
// debug).
|
||||
#ifdef JXL_DEBUG_WARNING
|
||||
#undef JXL_DEBUG_WARNING
|
||||
#define JXL_DEBUG_WARNING 1
|
||||
#else // JXL_DEBUG_WARNING
|
||||
#ifdef NDEBUG
|
||||
#define JXL_DEBUG_WARNING 0
|
||||
#else // JXL_DEBUG_WARNING
|
||||
#define JXL_DEBUG_WARNING 1
|
||||
#endif // NDEBUG
|
||||
#endif // JXL_DEBUG_WARNING
|
||||
#define JXL_WARNING(...) JXL_DEBUG(JXL_DEBUG_WARNING, __VA_ARGS__)
|
||||
|
||||
// Exits the program after printing file/line plus a formatted string.
|
||||
JXL_FORMAT(3, 4)
|
||||
JXL_NORETURN bool Abort(const char* file, int line, const char* format, ...);
|
||||
|
||||
// Exits the program after printing file/line plus a formatted string.
|
||||
#define JXL_ABORT(...) Abort(__FILE__, __LINE__, __VA_ARGS__)
|
||||
|
||||
// Does not guarantee running the code, use only for debug mode checks.
|
||||
#if JXL_ENABLE_ASSERT
|
||||
#define JXL_ASSERT(condition) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
::jxl::Abort(__FILE__, __LINE__, "Assert %s", #condition); \
|
||||
} \
|
||||
} while (0)
|
||||
#else
|
||||
#define JXL_ASSERT(condition) \
|
||||
do { \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
// Same as above, but only runs in debug builds (builds where NDEBUG is not
|
||||
// defined). This is useful for slower asserts that we want to run more rarely
|
||||
// than usual. These will run on asan, msan and other debug builds, but not in
|
||||
// opt or release.
|
||||
#if !defined(NDEBUG) || defined(ADDRESS_SANITIZER) || \
|
||||
defined(MEMORY_SANITIZER) || defined(THREAD_SANITIZER)
|
||||
#define JXL_DASSERT(condition) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
::jxl::Abort(__FILE__, __LINE__, "Debug Assert %s", #condition); \
|
||||
} \
|
||||
} while (0)
|
||||
#else
|
||||
#define JXL_DASSERT(condition) \
|
||||
do { \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
// Always runs the condition, so can be used for non-debug calls.
|
||||
#define JXL_CHECK(condition) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
::jxl::Abort(__FILE__, __LINE__, "Check %s", #condition); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// Always runs the condition, so can be used for non-debug calls.
|
||||
#define JXL_RETURN_IF_ERROR(condition) \
|
||||
do { \
|
||||
if (!(condition)) return false; \
|
||||
} while (0)
|
||||
|
||||
// Annotation for the location where an error condition is first noticed.
|
||||
// Error codes are too unspecific to pinpoint the exact location, so we
|
||||
// add a build flag that crashes and dumps stack at the actual error source.
|
||||
#ifdef JXL_CRASH_ON_ERROR
|
||||
#define JXL_NOTIFY_ERROR(...) \
|
||||
(void)::jxl::Abort(__FILE__, __LINE__, __VA_ARGS__)
|
||||
#define JXL_FAILURE(...) ::jxl::Abort(__FILE__, __LINE__, __VA_ARGS__)
|
||||
#else // JXL_CRASH_ON_ERROR
|
||||
#define JXL_NOTIFY_ERROR(...) JXL_DEBUG(JXL_DEBUG_ON_ERROR, __VA_ARGS__)
|
||||
#define JXL_FAILURE(format, ...) \
|
||||
((JXL_DEBUG_ON_ERROR) && \
|
||||
::jxl::Debug(("%s:%d: " format "\n"), __FILE__, __LINE__, ##__VA_ARGS__) && \
|
||||
false)
|
||||
#endif // JXL_CRASH_ON_ERROR
|
||||
|
||||
// Drop-in replacement for bool that raises compiler warnings if not used
|
||||
// after being returned from a function. Example:
|
||||
// Status LoadFile(...) { return true; } is more compact than
|
||||
// bool JXL_MUST_USE_RESULT LoadFile(...) { return true; }
|
||||
class JXL_MUST_USE_RESULT Status {
|
||||
public:
|
||||
// We want implicit constructor from bool to allow returning "true" or "false"
|
||||
// on a function when using Status.
|
||||
Status(bool ok) : ok_(ok) {} // NOLINT(google-explicit-constructor)
|
||||
|
||||
// We also want implicit cast to bool to check for return values of functions.
|
||||
operator bool() const { return ok_; } // NOLINT(google-explicit-constructor)
|
||||
|
||||
private:
|
||||
bool ok_;
|
||||
};
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_BASE_STATUS_H_
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_THREAD_TEST_UTILS_H_
|
||||
#define JXL_THREAD_TEST_UTILS_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "include/jpegxl/parallel_runner.h"
|
||||
#include "include/jpegxl/thread_parallel_runner.h"
|
||||
#include "jxl/base/data_parallel.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// Helper class to pass an internal ThreadPool-like object using threads. This
|
||||
// is only suitable for tests or tools that access the internal API of JPEG XL.
|
||||
// In other cases the caller will provide a JpegxlParallelRunner() for handling
|
||||
// this. This class uses jpegxl::ThreadParallelRunner (from jpegxl_threads
|
||||
// library). For interface details check jpegxl::ThreadParallelRunner.
|
||||
class ThreadPoolInternal : public ThreadPool {
|
||||
public:
|
||||
// Starts the given number of worker threads and blocks until they are ready.
|
||||
// "num_worker_threads" defaults to one per hyperthread. If zero, all tasks
|
||||
// run on the main thread.
|
||||
explicit ThreadPoolInternal(
|
||||
int num_worker_threads = std::thread::hardware_concurrency())
|
||||
: ThreadPool(&jpegxl::ThreadParallelRunner::Runner,
|
||||
static_cast<void*>(&runner_)),
|
||||
runner_(num_worker_threads) {}
|
||||
|
||||
ThreadPoolInternal(const ThreadPoolInternal&) = delete;
|
||||
ThreadPoolInternal& operator&(const ThreadPoolInternal&) = delete;
|
||||
|
||||
size_t NumThreads() const { return runner_.NumThreads(); }
|
||||
size_t NumWorkerThreads() const { return runner_.NumWorkerThreads(); }
|
||||
|
||||
template <class Func>
|
||||
void RunOnEachThread(const Func& func) {
|
||||
runner_.RunOnEachThread(func);
|
||||
}
|
||||
|
||||
private:
|
||||
jpegxl::ThreadParallelRunner runner_;
|
||||
};
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_THREAD_TEST_UTILS_H_
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_BASE_TSC_TIMER_H_
|
||||
#define JXL_BASE_TSC_TIMER_H_
|
||||
|
||||
// High-resolution (~10 ns) timestamps, using fences to prevent reordering and
|
||||
// ensure exactly the desired regions are measured.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <ctime>
|
||||
#include <hwy/cache_control.h> // LoadFence
|
||||
|
||||
#include "jxl/base/arch_specific.h"
|
||||
#include "jxl/base/compiler_specific.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// TicksBefore/After return absolute timestamps and must be placed immediately
|
||||
// before and after the region to measure. The functions are distinct because
|
||||
// they use different fences.
|
||||
//
|
||||
// Background: RDTSC is not 'serializing'; earlier instructions may complete
|
||||
// after it, and/or later instructions may complete before it. 'Fences' ensure
|
||||
// regions' elapsed times are independent of such reordering. The only
|
||||
// documented unprivileged serializing instruction is CPUID, which acts as a
|
||||
// full fence (no reordering across it in either direction). Unfortunately
|
||||
// the latency of CPUID varies wildly (perhaps made worse by not initializing
|
||||
// its EAX input). Because it cannot reliably be deducted from the region's
|
||||
// elapsed time, it must not be included in the region to measure (i.e.
|
||||
// between the two RDTSC).
|
||||
//
|
||||
// The newer RDTSCP is sometimes described as serializing, but it actually
|
||||
// only serves as a half-fence with release semantics. Although all
|
||||
// instructions in the region will complete before the final timestamp is
|
||||
// captured, subsequent instructions may leak into the region and increase the
|
||||
// elapsed time. Inserting another fence after the final RDTSCP would prevent
|
||||
// such reordering without affecting the measured region.
|
||||
//
|
||||
// Fortunately, such a fence exists. The LFENCE instruction is only documented
|
||||
// to delay later loads until earlier loads are visible. However, Intel's
|
||||
// reference manual says it acts as a full fence (waiting until all earlier
|
||||
// instructions have completed, and delaying later instructions until it
|
||||
// completes). AMD assigns the same behavior to MFENCE.
|
||||
//
|
||||
// We need a fence before the initial RDTSC to prevent earlier instructions
|
||||
// from leaking into the region, and arguably another after RDTSC to avoid
|
||||
// region instructions from completing before the timestamp is recorded.
|
||||
// When surrounded by fences, the additional RDTSCP half-fence provides no
|
||||
// benefit, so the initial timestamp can be recorded via RDTSC, which has
|
||||
// lower overhead than RDTSCP because it does not read TSC_AUX. In summary,
|
||||
// we define Before = LFENCE/RDTSC/LFENCE; After = RDTSCP/LFENCE.
|
||||
//
|
||||
// Using Before+Before leads to higher variance and overhead than After+After.
|
||||
// However, After+After includes an LFENCE in the region measurements, which
|
||||
// adds a delay dependent on earlier loads. The combination of Before+After
|
||||
// is faster than Before+Before and more consistent than Stop+Stop because
|
||||
// the first LFENCE already delayed subsequent loads before the measured
|
||||
// region. This combination seems not to have been considered in prior work:
|
||||
// http://akaros.cs.berkeley.edu/lxr/akaros/kern/arch/x86/rdtsc_test.c
|
||||
//
|
||||
// Note: performance counters can measure 'exact' instructions-retired or
|
||||
// (unhalted) cycle counts. The RDPMC instruction is not serializing and also
|
||||
// requires fences. Unfortunately, it is not accessible on all OSes and we
|
||||
// prefer to avoid kernel-mode drivers. Performance counters are also affected
|
||||
// by several under/over-count errata, so we use the TSC instead.
|
||||
|
||||
// Returns a 64-bit timestamp in unit of 'ticks'; to convert to seconds,
|
||||
// divide by InvariantTicksPerSecond. Although 32-bit ticks are faster to read,
|
||||
// they overflow too quickly to measure long regions.
|
||||
static inline uint64_t TicksBefore() {
|
||||
uint64_t t;
|
||||
#if JXL_ARCH_PPC
|
||||
asm volatile("mfspr %0, %1" : "=r"(t) : "i"(268));
|
||||
#elif JXL_ARCH_X64 && JXL_COMPILER_MSVC
|
||||
LoadFence();
|
||||
JXL_COMPILER_FENCE;
|
||||
t = __rdtsc();
|
||||
LoadFence();
|
||||
JXL_COMPILER_FENCE;
|
||||
#elif JXL_ARCH_X64 && (JXL_COMPILER_CLANG || JXL_COMPILER_GCC)
|
||||
asm volatile(
|
||||
"lfence\n\t"
|
||||
"rdtsc\n\t"
|
||||
"shl $32, %%rdx\n\t"
|
||||
"or %%rdx, %0\n\t"
|
||||
"lfence"
|
||||
: "=a"(t)
|
||||
:
|
||||
// "memory" avoids reordering. rdx = TSC >> 32.
|
||||
// "cc" = flags modified by SHL.
|
||||
: "rdx", "memory", "cc");
|
||||
#else
|
||||
// Fall back to OS - unsure how to reliably query cntvct_el0 frequency.
|
||||
timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
t = ts.tv_sec * 1000000000LL + ts.tv_nsec;
|
||||
#endif
|
||||
return t;
|
||||
}
|
||||
|
||||
static inline uint64_t TicksAfter() {
|
||||
uint64_t t;
|
||||
#if JXL_ARCH_X64 && JXL_COMPILER_MSVC
|
||||
JXL_COMPILER_FENCE;
|
||||
unsigned aux;
|
||||
t = __rdtscp(&aux);
|
||||
LoadFence();
|
||||
JXL_COMPILER_FENCE;
|
||||
#elif JXL_ARCH_X64 && (JXL_COMPILER_CLANG || JXL_COMPILER_GCC)
|
||||
// Use inline asm because __rdtscp generates code to store TSC_AUX (ecx).
|
||||
asm volatile(
|
||||
"rdtscp\n\t"
|
||||
"shl $32, %%rdx\n\t"
|
||||
"or %%rdx, %0\n\t"
|
||||
"lfence"
|
||||
: "=a"(t)
|
||||
:
|
||||
// "memory" avoids reordering. rcx = TSC_AUX. rdx = TSC >> 32.
|
||||
// "cc" = flags modified by SHL.
|
||||
: "rcx", "rdx", "memory", "cc");
|
||||
#else
|
||||
t = TicksBefore(); // no difference on other platforms.
|
||||
#endif
|
||||
return t;
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_BASE_TSC_TIMER_H_
|
||||
@@ -0,0 +1,276 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <array>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "jxl/aux_out.h"
|
||||
#include "jxl/aux_out_fwd.h"
|
||||
#include "jxl/base/data_parallel.h"
|
||||
#include "jxl/base/span.h"
|
||||
#include "jxl/base/thread_pool_internal.h"
|
||||
#include "jxl/common.h"
|
||||
#include "jxl/dec_bit_reader.h"
|
||||
#include "jxl/enc_bit_writer.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace {
|
||||
|
||||
HWY_ATTR void BitReaderTest_ExtendsWithZeroes() {
|
||||
for (size_t size = 4; size < 32; ++size) {
|
||||
std::vector<uint8_t> data(size, 0xff);
|
||||
|
||||
for (size_t n_bytes = 0; n_bytes < size; n_bytes++) {
|
||||
BitReader br(Span<const uint8_t>(data.data(), n_bytes));
|
||||
// Read all the bits
|
||||
for (size_t i = 0; i < n_bytes * kBitsPerByte; i++) {
|
||||
ASSERT_EQ(br.ReadBits(1), 1) << "n_bytes=" << n_bytes << " i=" << i;
|
||||
}
|
||||
|
||||
// PEEK more than the declared size - all will be zero. Cannot consume.
|
||||
for (size_t i = 0; i < BitReader::kMaxBitsPerCall; i++) {
|
||||
ASSERT_EQ(br.PeekBits(i), 0)
|
||||
<< "size=" << size << "n_bytes=" << n_bytes << " i=" << i;
|
||||
}
|
||||
|
||||
EXPECT_TRUE(br.Close());
|
||||
}
|
||||
}
|
||||
}
|
||||
TEST(BitReaderTest, ExtendsWithZeroes) { BitReaderTest_ExtendsWithZeroes(); }
|
||||
|
||||
struct Symbol {
|
||||
uint32_t num_bits;
|
||||
uint32_t value;
|
||||
};
|
||||
|
||||
// Reading from output gives the same values.
|
||||
TEST(BitReaderTest, TestRoundTrip) {
|
||||
ThreadPoolInternal pool(8);
|
||||
pool.Run(0, 1000, ThreadPool::SkipInit(),
|
||||
[](const int task, const int /* thread */) HWY_ATTR {
|
||||
constexpr size_t kMaxBits = 8000;
|
||||
BitWriter writer;
|
||||
BitWriter::Allotment allotment(&writer, kMaxBits);
|
||||
|
||||
std::vector<Symbol> symbols;
|
||||
symbols.reserve(1000);
|
||||
|
||||
std::mt19937 rng(55537 + 129 * task);
|
||||
std::uniform_int_distribution<> dist(1, 32); // closed interval
|
||||
|
||||
for (;;) {
|
||||
const uint32_t num_bits = dist(rng);
|
||||
if (writer.BitsWritten() + num_bits > kMaxBits) break;
|
||||
const uint32_t value = rng() >> (32 - num_bits);
|
||||
symbols.push_back({num_bits, value});
|
||||
writer.Write(num_bits, value);
|
||||
}
|
||||
|
||||
writer.ZeroPadToByte();
|
||||
ReclaimAndCharge(&writer, &allotment, 0, nullptr);
|
||||
BitReader reader(writer.GetSpan());
|
||||
for (const Symbol& s : symbols) {
|
||||
EXPECT_EQ(s.value, reader.ReadBits(s.num_bits));
|
||||
}
|
||||
EXPECT_TRUE(reader.Close());
|
||||
});
|
||||
}
|
||||
|
||||
// SkipBits is the same as reading that many bits.
|
||||
TEST(BitReaderTest, TestSkip) {
|
||||
ThreadPoolInternal pool(8);
|
||||
pool.Run(
|
||||
0, 96, ThreadPool::SkipInit(),
|
||||
[](const int task, const int /* thread */) HWY_ATTR {
|
||||
constexpr size_t kSize = 100;
|
||||
|
||||
for (size_t skip = 0; skip < 128; ++skip) {
|
||||
BitWriter writer;
|
||||
BitWriter::Allotment allotment(&writer, kSize * kBitsPerByte);
|
||||
// Start with "task" 1-bits.
|
||||
for (int i = 0; i < task; ++i) {
|
||||
writer.Write(1, 1);
|
||||
}
|
||||
|
||||
// Write 0-bits that we will skip over
|
||||
for (size_t i = 0; i < skip; ++i) {
|
||||
writer.Write(1, 0);
|
||||
}
|
||||
|
||||
// Write terminator bits '101'
|
||||
writer.Write(3, 5);
|
||||
EXPECT_EQ(task + skip + 3, writer.BitsWritten());
|
||||
writer.ZeroPadToByte();
|
||||
AuxOut aux_out;
|
||||
ReclaimAndCharge(&writer, &allotment, 0, &aux_out);
|
||||
EXPECT_LT(aux_out.layers[0].total_bits, kSize * 8);
|
||||
|
||||
BitReader reader1(writer.GetSpan());
|
||||
BitReader reader2(writer.GetSpan());
|
||||
// Verify initial 1-bits
|
||||
for (int i = 0; i < task; ++i) {
|
||||
EXPECT_EQ(1, reader1.ReadBits(1));
|
||||
EXPECT_EQ(1, reader2.ReadBits(1));
|
||||
}
|
||||
|
||||
// SkipBits or manually read "skip" bits
|
||||
reader1.SkipBits(skip);
|
||||
for (size_t i = 0; i < skip; ++i) {
|
||||
EXPECT_EQ(0, reader2.ReadBits(1)) << " skip=" << skip << " i=" << i;
|
||||
}
|
||||
EXPECT_EQ(reader1.TotalBitsConsumed(), reader2.TotalBitsConsumed());
|
||||
|
||||
// Ensure both readers see the terminator bits.
|
||||
EXPECT_EQ(5, reader1.ReadBits(3));
|
||||
EXPECT_EQ(5, reader2.ReadBits(3));
|
||||
|
||||
EXPECT_TRUE(reader1.Close());
|
||||
EXPECT_TRUE(reader2.Close());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Verifies byte order and different groupings of bits.
|
||||
HWY_ATTR void BitReaderTest_TestOrder() {
|
||||
constexpr size_t kMaxBits = 16;
|
||||
|
||||
// u(1) - bits written into LSBs of first byte
|
||||
{
|
||||
BitWriter writer;
|
||||
BitWriter::Allotment allotment(&writer, kMaxBits);
|
||||
for (size_t i = 0; i < 5; ++i) {
|
||||
writer.Write(1, 1);
|
||||
}
|
||||
for (size_t i = 0; i < 5; ++i) {
|
||||
writer.Write(1, 0);
|
||||
}
|
||||
for (size_t i = 0; i < 6; ++i) {
|
||||
writer.Write(1, 1);
|
||||
}
|
||||
|
||||
writer.ZeroPadToByte();
|
||||
ReclaimAndCharge(&writer, &allotment, 0, nullptr);
|
||||
BitReader reader(writer.GetSpan());
|
||||
EXPECT_EQ(0x1F, reader.ReadFixedBits<8>());
|
||||
EXPECT_EQ(0xFC, reader.ReadFixedBits<8>());
|
||||
EXPECT_TRUE(reader.Close());
|
||||
}
|
||||
|
||||
// u(8) - get bytes in the same order
|
||||
{
|
||||
BitWriter writer;
|
||||
BitWriter::Allotment allotment(&writer, kMaxBits);
|
||||
writer.Write(8, 0xF8);
|
||||
writer.Write(8, 0x3F);
|
||||
|
||||
writer.ZeroPadToByte();
|
||||
ReclaimAndCharge(&writer, &allotment, 0, nullptr);
|
||||
BitReader reader(writer.GetSpan());
|
||||
EXPECT_EQ(0xF8, reader.ReadFixedBits<8>());
|
||||
EXPECT_EQ(0x3F, reader.ReadFixedBits<8>());
|
||||
EXPECT_TRUE(reader.Close());
|
||||
}
|
||||
|
||||
// u(16) - little-endian bytes
|
||||
{
|
||||
BitWriter writer;
|
||||
BitWriter::Allotment allotment(&writer, kMaxBits);
|
||||
writer.Write(16, 0xF83F);
|
||||
|
||||
writer.ZeroPadToByte();
|
||||
ReclaimAndCharge(&writer, &allotment, 0, nullptr);
|
||||
BitReader reader(writer.GetSpan());
|
||||
EXPECT_EQ(0x3F, reader.ReadFixedBits<8>());
|
||||
EXPECT_EQ(0xF8, reader.ReadFixedBits<8>());
|
||||
EXPECT_TRUE(reader.Close());
|
||||
}
|
||||
|
||||
// Non-byte-aligned, mixed sizes
|
||||
{
|
||||
BitWriter writer;
|
||||
BitWriter::Allotment allotment(&writer, kMaxBits);
|
||||
writer.Write(1, 1);
|
||||
writer.Write(3, 6);
|
||||
writer.Write(8, 0xDB);
|
||||
writer.Write(4, 8);
|
||||
|
||||
writer.ZeroPadToByte();
|
||||
ReclaimAndCharge(&writer, &allotment, 0, nullptr);
|
||||
BitReader reader(writer.GetSpan());
|
||||
EXPECT_EQ(0xBD, reader.ReadFixedBits<8>());
|
||||
EXPECT_EQ(0x8D, reader.ReadFixedBits<8>());
|
||||
EXPECT_TRUE(reader.Close());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BitReaderTest, TestOrder) { BitReaderTest_TestOrder(); }
|
||||
|
||||
HWY_ATTR void BitReaderTest_TotalCountersTest() {
|
||||
uint8_t buf[4] = {1, 2, 3, 4};
|
||||
BitReader reader(Span<const uint8_t>(buf, sizeof(buf)));
|
||||
|
||||
EXPECT_EQ(sizeof(buf), reader.TotalBytes());
|
||||
EXPECT_EQ(0, reader.TotalBitsConsumed());
|
||||
reader.ReadFixedBits<1>();
|
||||
EXPECT_EQ(1, reader.TotalBitsConsumed());
|
||||
|
||||
reader.ReadFixedBits<10>();
|
||||
EXPECT_EQ(11, reader.TotalBitsConsumed());
|
||||
|
||||
reader.ReadFixedBits<4>();
|
||||
EXPECT_EQ(15, reader.TotalBitsConsumed());
|
||||
|
||||
reader.ReadFixedBits<1>();
|
||||
EXPECT_EQ(16, reader.TotalBitsConsumed());
|
||||
|
||||
reader.ReadFixedBits<16>();
|
||||
EXPECT_EQ(32, reader.TotalBitsConsumed());
|
||||
|
||||
EXPECT_TRUE(reader.Close());
|
||||
}
|
||||
|
||||
TEST(BitReaderTest, TotalCountersTest) { BitReaderTest_TotalCountersTest(); }
|
||||
|
||||
HWY_ATTR void BitReaderTest_MoveTest() {
|
||||
uint8_t buf[4] = {1, 2, 3, 4};
|
||||
BitReader reader2;
|
||||
{
|
||||
BitReader reader1(Span<const uint8_t>(buf, sizeof(buf)));
|
||||
|
||||
EXPECT_EQ(0, reader1.TotalBitsConsumed());
|
||||
reader1.ReadFixedBits<16>();
|
||||
EXPECT_EQ(16, reader1.TotalBitsConsumed());
|
||||
|
||||
reader2 = std::move(reader1);
|
||||
// From this point reader1 is invalid, but can continue to access reader2
|
||||
// and we don't need to call Close() on reader1.
|
||||
}
|
||||
|
||||
EXPECT_EQ(16, reader2.TotalBitsConsumed());
|
||||
EXPECT_EQ(3U, reader2.ReadFixedBits<8>());
|
||||
EXPECT_EQ(24, reader2.TotalBitsConsumed());
|
||||
|
||||
EXPECT_TRUE(reader2.Close());
|
||||
}
|
||||
|
||||
TEST(BitReaderTest, MoveTest) { BitReaderTest_MoveTest(); }
|
||||
|
||||
} // namespace
|
||||
} // namespace jxl
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "jxl/base/bits.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace {
|
||||
|
||||
TEST(BitsTest, TestPopCount) {
|
||||
EXPECT_EQ(0, PopCount(0u));
|
||||
EXPECT_EQ(1, PopCount(1u));
|
||||
EXPECT_EQ(1, PopCount(2u));
|
||||
EXPECT_EQ(2, PopCount(3u));
|
||||
EXPECT_EQ(1, PopCount(0x80000000u));
|
||||
EXPECT_EQ(31, PopCount(0x7FFFFFFFu));
|
||||
EXPECT_EQ(32, PopCount(0xFFFFFFFFu));
|
||||
|
||||
EXPECT_EQ(1, PopCount(0x80000000ull));
|
||||
EXPECT_EQ(31, PopCount(0x7FFFFFFFull));
|
||||
EXPECT_EQ(32, PopCount(0xFFFFFFFFull));
|
||||
EXPECT_EQ(33, PopCount(0x10FFFFFFFFull));
|
||||
EXPECT_EQ(63, PopCount(0xFFFEFFFFFFFFFFFFull));
|
||||
EXPECT_EQ(64, PopCount(0xFFFFFFFFFFFFFFFFull));
|
||||
}
|
||||
|
||||
TEST(BitsTest, TestNumZeroBits) {
|
||||
// Zero input is well-defined.
|
||||
EXPECT_EQ(32, NumZeroBitsAboveMSB(0u));
|
||||
EXPECT_EQ(64, NumZeroBitsAboveMSB(0ull));
|
||||
EXPECT_EQ(32, NumZeroBitsBelowLSB(0u));
|
||||
EXPECT_EQ(64, NumZeroBitsBelowLSB(0ull));
|
||||
|
||||
EXPECT_EQ(31, NumZeroBitsAboveMSB(1u));
|
||||
EXPECT_EQ(30, NumZeroBitsAboveMSB(2u));
|
||||
EXPECT_EQ(63, NumZeroBitsAboveMSB(1ull));
|
||||
EXPECT_EQ(62, NumZeroBitsAboveMSB(2ull));
|
||||
|
||||
EXPECT_EQ(0, NumZeroBitsBelowLSB(1u));
|
||||
EXPECT_EQ(0, NumZeroBitsBelowLSB(1ull));
|
||||
EXPECT_EQ(1, NumZeroBitsBelowLSB(2u));
|
||||
EXPECT_EQ(1, NumZeroBitsBelowLSB(2ull));
|
||||
|
||||
EXPECT_EQ(0, NumZeroBitsAboveMSB(0x80000000u));
|
||||
EXPECT_EQ(0, NumZeroBitsAboveMSB(0x8000000000000000ull));
|
||||
EXPECT_EQ(31, NumZeroBitsBelowLSB(0x80000000u));
|
||||
EXPECT_EQ(63, NumZeroBitsBelowLSB(0x8000000000000000ull));
|
||||
}
|
||||
|
||||
TEST(BitsTest, TestFloorLog2) {
|
||||
// for input = [1, 7]
|
||||
const int expected[7] = {0, 1, 1, 2, 2, 2, 2};
|
||||
for (uint32_t i = 1; i <= 7; ++i) {
|
||||
EXPECT_EQ(expected[i - 1], FloorLog2Nonzero(i)) << " " << i;
|
||||
EXPECT_EQ(expected[i - 1], FloorLog2Nonzero(uint64_t(i))) << " " << i;
|
||||
}
|
||||
|
||||
EXPECT_EQ(31, FloorLog2Nonzero(0x80000000u));
|
||||
EXPECT_EQ(31, FloorLog2Nonzero(0x80000001u));
|
||||
EXPECT_EQ(31, FloorLog2Nonzero(0xFFFFFFFFu));
|
||||
|
||||
EXPECT_EQ(31, FloorLog2Nonzero(0x80000000ull));
|
||||
EXPECT_EQ(31, FloorLog2Nonzero(0x80000001ull));
|
||||
EXPECT_EQ(31, FloorLog2Nonzero(0xFFFFFFFFull));
|
||||
|
||||
EXPECT_EQ(63, FloorLog2Nonzero(0x8000000000000000ull));
|
||||
EXPECT_EQ(63, FloorLog2Nonzero(0x8000000000000001ull));
|
||||
EXPECT_EQ(63, FloorLog2Nonzero(0xFFFFFFFFFFFFFFFFull));
|
||||
}
|
||||
|
||||
TEST(BitsTest, TestCeilLog2) {
|
||||
// for input = [1, 7]
|
||||
const int expected[7] = {0, 1, 2, 2, 3, 3, 3};
|
||||
for (uint32_t i = 1; i <= 7; ++i) {
|
||||
EXPECT_EQ(expected[i - 1], CeilLog2Nonzero(i)) << " " << i;
|
||||
EXPECT_EQ(expected[i - 1], CeilLog2Nonzero(uint64_t(i))) << " " << i;
|
||||
}
|
||||
|
||||
EXPECT_EQ(31, CeilLog2Nonzero(0x80000000u));
|
||||
EXPECT_EQ(32, CeilLog2Nonzero(0x80000001u));
|
||||
EXPECT_EQ(32, CeilLog2Nonzero(0xFFFFFFFFu));
|
||||
|
||||
EXPECT_EQ(31, CeilLog2Nonzero(0x80000000ull));
|
||||
EXPECT_EQ(32, CeilLog2Nonzero(0x80000001ull));
|
||||
EXPECT_EQ(32, CeilLog2Nonzero(0xFFFFFFFFull));
|
||||
|
||||
EXPECT_EQ(63, CeilLog2Nonzero(0x8000000000000000ull));
|
||||
EXPECT_EQ(64, CeilLog2Nonzero(0x8000000000000001ull));
|
||||
EXPECT_EQ(64, CeilLog2Nonzero(0xFFFFFFFFFFFFFFFFull));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace jxl
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_BLOCK_H_
|
||||
#define JXL_BLOCK_H_
|
||||
|
||||
// Adapters for DCT input/output: from/to contiguous blocks or image rows.
|
||||
|
||||
#include <hwy/static_targets.h>
|
||||
|
||||
#include "jxl/base/compiler_specific.h"
|
||||
#include "jxl/common.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// Adapters for source/destination.
|
||||
//
|
||||
// Block: (x, y) <-> (N * y + x)
|
||||
// Lines: (x, y) <-> (stride * y + x)
|
||||
//
|
||||
// I.e. Block is a specialization of Lines with fixed stride.
|
||||
//
|
||||
// FromXXX should implement Read and Load (Read vector).
|
||||
// ToXXX should implement Write and Store (Write vector).
|
||||
|
||||
template <size_t N>
|
||||
using BlockDesc = HWY_CAPPED(float, N);
|
||||
|
||||
// Here and in the following, the SZ template parameter specifies the number of
|
||||
// values to load/store. Needed because we want to handle 4x4 sub-blocks of
|
||||
// 16x16 blocks.
|
||||
template <size_t ROWS, size_t COLS = ROWS>
|
||||
class FromBlock {
|
||||
public:
|
||||
explicit FromBlock(const float* block) : block_(block) {}
|
||||
|
||||
FromBlock View(size_t dx, size_t dy) const {
|
||||
return FromBlock<ROWS, COLS>(Address(dx, dy));
|
||||
}
|
||||
|
||||
template <size_t SZ>
|
||||
HWY_ATTR HWY_INLINE hwy::VT<BlockDesc<SZ>> LoadPart(const size_t row,
|
||||
size_t i) const {
|
||||
static_assert(SZ <= COLS, "Requesting more values that are present!");
|
||||
return Load(BlockDesc<SZ>(), block_ + row * COLS + i);
|
||||
}
|
||||
|
||||
HWY_ATTR HWY_INLINE hwy::VT<BlockDesc<COLS>> LoadVec(const size_t row,
|
||||
size_t i) const {
|
||||
return LoadPart<COLS>(row, i);
|
||||
}
|
||||
|
||||
HWY_ATTR HWY_INLINE float Read(const size_t row, const size_t i) const {
|
||||
return *Address(row, i);
|
||||
}
|
||||
|
||||
constexpr HWY_INLINE const float* Address(const size_t row,
|
||||
const size_t i) const {
|
||||
return block_ + row * COLS + i;
|
||||
}
|
||||
|
||||
private:
|
||||
const float* block_;
|
||||
};
|
||||
|
||||
template <size_t ROWS, size_t COLS = ROWS>
|
||||
class ToBlock {
|
||||
public:
|
||||
explicit ToBlock(float* block) : block_(block) {}
|
||||
|
||||
ToBlock View(size_t dx, size_t dy) const {
|
||||
return ToBlock<ROWS, COLS>(Address(dx, dy));
|
||||
}
|
||||
|
||||
template <size_t SZ>
|
||||
HWY_ATTR HWY_INLINE void StorePart(const hwy::VT<BlockDesc<SZ>>& v,
|
||||
const size_t row, const size_t i) const {
|
||||
static_assert(SZ <= COLS, "Requesting more values that are present!");
|
||||
Store(v, BlockDesc<SZ>(), Address(row, i));
|
||||
}
|
||||
|
||||
HWY_ATTR HWY_INLINE void StoreVec(const hwy::VT<BlockDesc<COLS>>& v,
|
||||
const size_t row, size_t i) const {
|
||||
return StorePart<COLS>(v, row, i);
|
||||
}
|
||||
|
||||
HWY_ATTR HWY_INLINE void Write(float v, const size_t row,
|
||||
const size_t i) const {
|
||||
*Address(row, i) = v;
|
||||
}
|
||||
|
||||
constexpr HWY_INLINE float* Address(const size_t row, const size_t i) const {
|
||||
return block_ + row * COLS + i;
|
||||
}
|
||||
|
||||
private:
|
||||
float* block_;
|
||||
};
|
||||
|
||||
// Same as ToBlock, but multiplies result by (N * N)
|
||||
template <size_t ROWS, size_t COLS = ROWS>
|
||||
class ScaleToBlock {
|
||||
public:
|
||||
explicit HWY_ATTR ScaleToBlock(float* block) : block_(block) {}
|
||||
|
||||
ScaleToBlock View(size_t dx, size_t dy) const {
|
||||
return ScaleToBlock<ROWS, COLS>(Address(dx, dy));
|
||||
}
|
||||
|
||||
template <size_t SZ>
|
||||
HWY_ATTR HWY_INLINE void StorePart(const hwy::VT<BlockDesc<SZ>>& v,
|
||||
const size_t row, const size_t i) const {
|
||||
using BlockDesc = jxl::BlockDesc<SZ>;
|
||||
const auto mul_ = Set(BlockDesc(), 1.0f / (COLS * ROWS));
|
||||
Store(v * mul_, BlockDesc(), Address(row, i));
|
||||
}
|
||||
|
||||
HWY_ATTR HWY_INLINE void StoreVec(const hwy::VT<BlockDesc<COLS>>& v,
|
||||
const size_t row, size_t i) const {
|
||||
return StorePart<COLS>(v, row, i);
|
||||
}
|
||||
|
||||
HWY_ATTR HWY_INLINE void Write(float v, const size_t row,
|
||||
const size_t i) const {
|
||||
constexpr float mul_ = 1.0f / (COLS * ROWS);
|
||||
*Address(row, i) = v * mul_;
|
||||
}
|
||||
|
||||
constexpr HWY_INLINE float* Address(const size_t row, const size_t i) const {
|
||||
return block_ + row * COLS + i;
|
||||
}
|
||||
|
||||
private:
|
||||
float* block_;
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
class FromLines {
|
||||
public:
|
||||
FromLines(const float* top_left, size_t stride)
|
||||
: top_left_(top_left), stride_(stride) {}
|
||||
|
||||
FromLines View(size_t dx, size_t dy) const {
|
||||
return FromLines(Address(dx, dy), stride_);
|
||||
}
|
||||
|
||||
template <size_t SZ>
|
||||
HWY_ATTR HWY_INLINE hwy::VT<BlockDesc<SZ>> LoadPart(const size_t row,
|
||||
const size_t i) const {
|
||||
return Load(BlockDesc<SZ>(), Address(row, i));
|
||||
}
|
||||
|
||||
HWY_ATTR HWY_INLINE hwy::VT<BlockDesc<N>> LoadVec(const size_t row,
|
||||
size_t i) const {
|
||||
return LoadPart<N>(row, i);
|
||||
}
|
||||
|
||||
HWY_ATTR HWY_INLINE float Read(const size_t row, const size_t i) const {
|
||||
return *Address(row, i);
|
||||
}
|
||||
|
||||
HWY_INLINE const float* Address(const size_t row, const size_t i) const {
|
||||
return top_left_ + row * stride_ + i;
|
||||
}
|
||||
|
||||
private:
|
||||
const float* HWY_RESTRICT top_left_;
|
||||
size_t stride_; // move to next line by adding this to pointer
|
||||
};
|
||||
|
||||
// Pointers are restrict-qualified: assumes we don't use both FromLines and
|
||||
// ToLines in the same DCT. NOTE: Transpose uses From/ToBlock, not *Lines.
|
||||
template <size_t N>
|
||||
class ToLines {
|
||||
public:
|
||||
ToLines(float* top_left, size_t stride)
|
||||
: top_left_(top_left), stride_(stride) {}
|
||||
|
||||
ToLines View(const ToLines& other, size_t dx, size_t dy) const {
|
||||
return ToLines(Address(dx, dy), stride_);
|
||||
}
|
||||
|
||||
template <size_t SZ>
|
||||
HWY_ATTR HWY_INLINE void StorePart(const hwy::VT<BlockDesc<SZ>>& v,
|
||||
const size_t row, const size_t i) const {
|
||||
Store(v, BlockDesc<SZ>(), Address(row, i));
|
||||
}
|
||||
|
||||
HWY_ATTR HWY_INLINE void StoreVec(const hwy::VT<BlockDesc<N>>& v,
|
||||
const size_t row, size_t i) const {
|
||||
return StorePart<N>(v, row, i);
|
||||
}
|
||||
|
||||
HWY_ATTR HWY_INLINE void Write(float v, const size_t row,
|
||||
const size_t i) const {
|
||||
*Address(row, i) = v;
|
||||
}
|
||||
|
||||
HWY_INLINE float* Address(const size_t row, const size_t i) const {
|
||||
return top_left_ + row * stride_ + i;
|
||||
}
|
||||
|
||||
private:
|
||||
float* HWY_RESTRICT top_left_;
|
||||
size_t stride_; // move to next line by adding this to pointer
|
||||
};
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_BLOCK_H_
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "jxl/brotli.h"
|
||||
|
||||
#include <string.h> // memcpy
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "brotli/decode.h"
|
||||
#include "brotli/encode.h"
|
||||
#include "jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
Status BrotliCompress(int quality, const uint8_t* in, const size_t in_size,
|
||||
PaddedBytes* JXL_RESTRICT out) {
|
||||
std::unique_ptr<BrotliEncoderState, decltype(BrotliEncoderDestroyInstance)*>
|
||||
enc(BrotliEncoderCreateInstance(nullptr, nullptr, nullptr),
|
||||
BrotliEncoderDestroyInstance);
|
||||
if (!enc) return JXL_FAILURE("BrotliEncoderCreateInstance failed");
|
||||
|
||||
BrotliEncoderSetParameter(enc.get(), BROTLI_PARAM_QUALITY, quality);
|
||||
BrotliEncoderSetParameter(enc.get(), BROTLI_PARAM_LGWIN, 24);
|
||||
BrotliEncoderSetParameter(enc.get(), BROTLI_PARAM_LGBLOCK, 0);
|
||||
BrotliEncoderSetParameter(enc.get(), BROTLI_PARAM_MODE, BROTLI_MODE_FONT);
|
||||
|
||||
const size_t kBufferSize = 128 * 1024;
|
||||
PaddedBytes temp_buffer(kBufferSize);
|
||||
|
||||
size_t avail_in = in_size;
|
||||
const uint8_t* next_in = in;
|
||||
|
||||
size_t total_out = 0;
|
||||
|
||||
while (true) {
|
||||
size_t out_size;
|
||||
size_t avail_out = kBufferSize;
|
||||
uint8_t* next_out = temp_buffer.data();
|
||||
if (!BrotliEncoderCompressStream(enc.get(), BROTLI_OPERATION_FINISH,
|
||||
&avail_in, &next_in, &avail_out, &next_out,
|
||||
&total_out)) {
|
||||
return JXL_FAILURE("Brotli compression failed");
|
||||
}
|
||||
out_size = next_out - temp_buffer.data();
|
||||
out->resize(out->size() + out_size);
|
||||
memcpy(out->data() + out->size() - out_size, temp_buffer.data(), out_size);
|
||||
if (BrotliEncoderIsFinished(enc.get())) break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Status BrotliCompress(int quality, const uint8_t* in, const size_t in_size,
|
||||
uint8_t* JXL_RESTRICT out,
|
||||
size_t* JXL_RESTRICT total_out_size) {
|
||||
PaddedBytes temp_buffer;
|
||||
if (!BrotliCompress(quality, in, in_size, &temp_buffer)) {
|
||||
return false;
|
||||
}
|
||||
*total_out_size = temp_buffer.size();
|
||||
memcpy(out, temp_buffer.data(), temp_buffer.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
Status BrotliCompress(int quality, const PaddedBytes& in,
|
||||
PaddedBytes* JXL_RESTRICT out) {
|
||||
return BrotliCompress(quality, in.data(), in.size(), out);
|
||||
}
|
||||
|
||||
Status BrotliDecompress(const Span<const uint8_t> in, size_t max_output_size,
|
||||
size_t* JXL_RESTRICT bytes_read,
|
||||
PaddedBytes* JXL_RESTRICT out) {
|
||||
std::unique_ptr<BrotliDecoderState, decltype(BrotliDecoderDestroyInstance)*>
|
||||
s(BrotliDecoderCreateInstance(nullptr, nullptr, nullptr),
|
||||
BrotliDecoderDestroyInstance);
|
||||
if (!s) return JXL_FAILURE("BrotliDecoderCreateInstance failed");
|
||||
|
||||
const size_t kBufferSize = 128 * 1024;
|
||||
PaddedBytes temp_buffer(kBufferSize);
|
||||
|
||||
size_t avail_in = in.size();
|
||||
if (in.size() == 0) return false;
|
||||
const uint8_t* next_in = in.data();
|
||||
BrotliDecoderResult code;
|
||||
|
||||
while (true) {
|
||||
size_t out_size;
|
||||
size_t avail_out = kBufferSize;
|
||||
uint8_t* next_out = temp_buffer.data();
|
||||
code = BrotliDecoderDecompressStream(s.get(), &avail_in, &next_in,
|
||||
&avail_out, &next_out, nullptr);
|
||||
out_size = next_out - temp_buffer.data();
|
||||
out->resize(out->size() + out_size);
|
||||
if (out->size() > max_output_size)
|
||||
return JXL_FAILURE("Brotli output too large");
|
||||
memcpy(out->data() + out->size() - out_size, temp_buffer.data(), out_size);
|
||||
if (code != BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) break;
|
||||
}
|
||||
if (code != BROTLI_DECODER_RESULT_SUCCESS)
|
||||
return JXL_FAILURE("Brotli decompression failed");
|
||||
*bytes_read += (in.size() - avail_in);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef JXL_BROTLI_H_
|
||||
#define JXL_BROTLI_H_
|
||||
|
||||
// Convenience functions for Brotli compression/decompression.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "jxl/base/compiler_specific.h"
|
||||
#include "jxl/base/padded_bytes.h"
|
||||
#include "jxl/base/span.h"
|
||||
#include "jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// Appends to out.
|
||||
Status BrotliCompress(int quality, const PaddedBytes& in,
|
||||
PaddedBytes* JXL_RESTRICT out);
|
||||
|
||||
// Appends to out + *total_out_size.
|
||||
Status BrotliCompress(int quality, const uint8_t* in, const size_t in_size,
|
||||
uint8_t* JXL_RESTRICT out,
|
||||
size_t* JXL_RESTRICT total_out_size);
|
||||
|
||||
// Appends to out and ADDS to "bytes_read", which must be pre-initialized.
|
||||
Status BrotliDecompress(const Span<const uint8_t> in, size_t max_output_size,
|
||||
size_t* JXL_RESTRICT bytes_read,
|
||||
PaddedBytes* JXL_RESTRICT out);
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // JXL_BROTLI_H_
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) the JPEG XL Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "jxl/brotli.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "jxl/base/data_parallel.h"
|
||||
#include "jxl/base/thread_pool_internal.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace {
|
||||
|
||||
TEST(BrotliTest, TestCompressEmpty) {
|
||||
PaddedBytes in;
|
||||
PaddedBytes out;
|
||||
EXPECT_TRUE(BrotliCompress(6, in, &out));
|
||||
EXPECT_TRUE(in.empty());
|
||||
}
|
||||
|
||||
TEST(BrotliTest, TestDecompressEmpty) {
|
||||
size_t bytes_read = 0;
|
||||
PaddedBytes in;
|
||||
PaddedBytes out;
|
||||
EXPECT_FALSE(BrotliDecompress(Span<const uint8_t>(in), 1, &bytes_read, &out));
|
||||
EXPECT_EQ(0, bytes_read);
|
||||
EXPECT_TRUE(in.empty());
|
||||
EXPECT_TRUE(out.empty());
|
||||
}
|
||||
|
||||
TEST(BrotliTest, TestRoundTrip) {
|
||||
ThreadPoolInternal pool(0);
|
||||
pool.Run(1, 65, ThreadPool::SkipInit(), [](const int task, const int thread) {
|
||||
const size_t size = task;
|
||||
|
||||
PaddedBytes in(size);
|
||||
std::mt19937_64 rng(thread * 65537 + task * 129);
|
||||
std::generate(in.begin(), in.end(), rng);
|
||||
PaddedBytes compressed;
|
||||
PaddedBytes out;
|
||||
|
||||
for (int quality = 1; quality < 7; ++quality) {
|
||||
compressed.clear();
|
||||
EXPECT_TRUE(BrotliCompress(quality, in, &compressed));
|
||||
size_t bytes_read = 0;
|
||||
out.clear();
|
||||
EXPECT_TRUE(BrotliDecompress(Span<const uint8_t>(compressed), size,
|
||||
&bytes_read, &out));
|
||||
EXPECT_EQ(compressed.size(), bytes_read);
|
||||
EXPECT_EQ(in.size(), out.size());
|
||||
for (size_t i = 0; i < in.size(); ++i) {
|
||||
if (in[i] != out[i]) {
|
||||
printf("Mismatch at %zu (%zu)\n", i, size);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace jxl
|
||||
Loaded 100 of 582 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user