Update JPEG-XL with latest changes.

This includes all changes up to 2019-12-27 17:38:53 +0100.
This commit is contained in:
jpegxl-bot committed 2019-12-27 18:12:34 +01:00
commit ff09371267
582 files changed
+112377

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
BasedOnStyle: Google
IncludeCategories:
- Regex: '^<hwy/'
Priority: 2
+56
View File
@@ -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'
+7
View File
@@ -0,0 +1,7 @@
# Build output directories
/build
/build*
/docker/*.log
# The downloaded corpora files for benchmark.
/third_party/corpora
+548
View File
@@ -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
View File
@@ -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
View File
@@ -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
+10
View File
@@ -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/).
+23
View File
@@ -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
+203
View File
@@ -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.
+39
View File
@@ -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.
+83
View File
@@ -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)
Executable
+956
View File
@@ -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 "$@"
+181
View File
@@ -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
```
+90
View File
@@ -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
+306
View File
@@ -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.
+67
View File
@@ -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.
+6
View File
@@ -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.
+20
View File
@@ -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
+6
View File
@@ -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
```
+23
View File
@@ -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;
}
```
+241
View File
@@ -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))
```
+6
View File
@@ -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
```
+54
View File
@@ -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
```
+6
View File
@@ -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
```
+9
View File
@@ -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
```
+36
View File
@@ -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;
}
+9
View File
@@ -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
```
+60
View File
@@ -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
```
+258
View File
@@ -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
```
+19
View File
@@ -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
```
+22
View File
@@ -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
```
+130
View File
@@ -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
```
+44
View File
@@ -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
```
+30
View File
@@ -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
```
+181
View File
@@ -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`).
+18
View File
@@ -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/*;
+7
View File
@@ -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.
+89
View File
@@ -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 "$@"
+46
View File
@@ -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 "$@"
+225
View File
@@ -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 "$@"
+140
View File
@@ -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 "$@"
+92
View File
@@ -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 "$@"
+46
View File
@@ -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 "$@"
+109
View File
@@ -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_ */
+71
View File
@@ -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_ */
+152
View File
@@ -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_ */
+179
View File
@@ -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
View File
@@ -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
+86
View File
@@ -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 ()
+36
View File
@@ -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
)
+105
View File
@@ -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 ()
+113
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+324
View File
@@ -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_
+405
View File
@@ -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
+171
View File
@@ -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
+143
View File
@@ -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
+152
View File
@@ -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_
+48
View File
@@ -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
+68
View File
@@ -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
View File
@@ -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
+136
View File
@@ -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
+34
View File
@@ -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
View File
@@ -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
View File
@@ -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_
+37
View File
@@ -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_
+355
View File
@@ -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
+66
View File
@@ -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
View File
@@ -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_
+212
View File
@@ -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_
+156
View File
@@ -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
+84
View File
@@ -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_
+144
View File
@@ -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_
+46
View File
@@ -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
+170
View File
@@ -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_
+111
View File
@@ -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
+135
View File
@@ -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_
+133
View File
@@ -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_
+107
View File
@@ -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_
+74
View File
@@ -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_
+366
View File
@@ -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
+72
View File
@@ -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_
+38
View File
@@ -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_
+47
View File
@@ -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
+203
View File
@@ -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_
+753
View File
@@ -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_
+361
View File
@@ -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_
+67
View File
@@ -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_
+57
View File
@@ -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
+185
View File
@@ -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_
+61
View File
@@ -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_
+142
View File
@@ -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_
+276
View File
@@ -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
+105
View File
@@ -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
View File
@@ -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
View File
@@ -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
+46
View File
@@ -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_
+78
View File
@@ -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