
FormatConverter C++ library
v1.5.2
Table of contents
- Overview
- Versions
- Library files
- FormatConverter class description
- Multithreading
- Performance benchmark
- Build and connect to your project
- Portability
Overview
FormatConverter library is designed to convert pixel formats of images between each other. FormatConverter supports all uncompressed pixel formats (conversion between each other) listed in Fourcc enum (RGB24, BGR24, YUYV, UYVY, GRAY, YUV24, NV12, NV21, YU12, YV12) of Frame library (defines video frame object and pixel formats). Conversions run on SIMD kernels (AVX2 / SSE4.1 / ARM NEON with a plain C++ fallback): the kernels are selected when the library runs, see Portability. Conversion is single threaded by default and can optionally be spread over image rows with OpenMP, see Multithreading. Apart from the Frame library the library needs no other code: OpenMP (supplied with the compiler) is optional. Main file FormatConverter.h contains declaration of FormatConverter class. The library uses C++17 standard and is compatible with Linux and Windows. The benchmark application needs nothing but the library. Pixel formats visualization:
FormatConverter is general-purpose software: it is not tied to any sector or application, and the developer of a program decides what the program does with it.
Table 1 - Bytes layout of supported pixel formats. Example of 4x4 pixels image.
Versions
Table 2 - Library versions.
| Version | Release date | What’s new |
|---|---|---|
| 1.0.0 | 26.12.2025 | First version. |
| 1.1.0 | 28.03.2026 | - Code optimized for better performance. |
| 1.2.0 | 03.08.2026 | - SIMD (AVX2 / SSE / ARM NEON) conversion kernels added. Results are bit-identical to v1.1.0. |
| 1.3.0 | 23.08.2026 | - Optional OpenMP row-level parallelism added. - Conversion between identical pixel formats is a plain data copy. - Look-up tables left over from v1.1.0 removed together with the class constructor. - Fixed YU12 ↔ YV12 conversion for frame heights that are not a multiple of 4. - convert(…) validates frame geometry. - Test application removed, benchmark application no longer depends on OpenCV. |
| 1.4.0 | 02.09.2026 | - Added parallelism is now opt-in and bounded; conversion results are unchanged and still independent of the team size. - setMaxThreads(…) no longer creates the OpenMP team eagerly. - Every parallel region now carries an if(threads > 1) clause. |
| 1.5.0 | 21.09.2026 | - Performance optimizations. - Optimized CMake build flags (/Ob3 in Release). |
| 1.5.1 | 06.10.2026 | - convert(…): a destination frame that has to be re-allocated is released first. A destination that was a clone of the source frame got the source data overwritten with zeros before (and the conversion failed), now it gets its own memory. - No memory leak if the destination memory can not be allocated. - Documentation: colour conversion formulas and destination memory rules. - Frame library updated to v5.1.1. - Conversion results are bit-identical to v1.5.0. |
| 1.5.2 | 15.10.2026 | - Frame library is not a git submodule any more: a stand alone build expects it in the folder next to FormatConverter (../Frame), a parent project adds it by itself. The README describes both cases. - The SIMD level (AVX2 / SSE4.1 / NEON / plain C++) is selected when the library runs: a binary built on a machine with AVX2 runs on every CPU of the architecture. CMake option FORMAT_CONVERTER_SIMD is replaced by FORMAT_CONVERTER_ENABLE_SIMD. - The conversions of whole frames are built as one table per SIMD level (internal restructuring). Conversion results and the behaviour for invalid input are bit-identical to v1.5.1. - The benchmark application is built with the warning and exploit mitigation options of the library and reports a failed memory allocation. - Documentation: build and connect instructions, CMake options, Portability, file list, benchmark results of the current version. |
Library files
The library is supplied only by source code. The user is given a set of files in the form of a CMake project (repository). The repository does not contain the Frame library, see Build and connect to your project. The repository structure is shown below:
CMakeLists.txt ------------------- Main CMake file of the library.
src ------------------------------ Folder with library source code.
CMakeLists.txt --------------- CMake file of the library.
FormatConverter.cpp ---------- C++ implementation file.
FormatConverter.h ------------ Main library header file.
FormatConverterKernels.h ----- Internal header: conversion tables and SIMD levels.
FormatConverterTable.h ------- Internal conversions of whole frames (built once per SIMD level).
FormatConverterSimd.h -------- Internal SIMD conversion kernels.
FormatConverterDispatch.cpp -- Internal selection of the SIMD level when the library runs.
FormatConverterAvx2.cpp ------ Conversions with AVX2 instructions (x86).
FormatConverterSse41.cpp ----- Conversions with SSSE3 and SSE4.1 instructions (x86).
FormatConverterNeon.cpp ------ Conversions with NEON instructions (ARM).
FormatConverterPortable.cpp -- Conversions in plain C++.
FormatConverterVersion.h ----- Header file with library version.
FormatConverterVersion.h.in -- Service CMake file to generate version file.
benchmark ------------------------ Folder for benchmark application.
CMakeLists.txt --------------- CMake file of the benchmark application.
main.cpp --------------------- Source C++ file of the benchmark application.
FormatConverter class description
FormatConverter class declaration
FormatConverter.h file contains FormatConverter class declaration. Class declaration:
namespace rapidpixel
{
/**
* @brief Frame pixel format converter class.
*/
class FormatConverter
{
public:
/// Convert pixel format.
bool convert(Frame& src, Frame& dst);
/// Set upper limit for the number of threads used by convert(...).
void setMaxThreads(int maxThreads);
/// Get the number of threads convert(...) may use at most.
int getMaxThreads() const;
/// Get version string of FormatConverter class.
static std::string getVersion();
};
}
The class declares no constructor and holds no conversion tables: the kernels compute the colour transform coefficients inline, so an object is 4 bytes and costs nothing to create. It can be a stack variable of a per-frame function or a long living member, whichever fits the application.
getVersion method
The getVersion() method returns string of current version of FormatConverter class. Method declaration:
static std::string getVersion();
Method can be used without FormatConverter class instance. Example:
std::cout << "FormatConverter class version: " << rapidpixel::FormatConverter::getVersion() << std::endl;
Console output:
FormatConverter class version: 1.5.2
convert method
The convert(…) method intended to convert Frame class data between pixel formats. The method understands which pixel format to convert from based on the fourcc field of the Frame class objects. Method declaration:
bool convert(Frame& src, Frame& dst);
| Parameter | Description |
|---|---|
| src | Source Frame object. Method doesn’t support H264, HEVC and JPEG pixel formats. Frame width and height must be even and in range [4, 16384], the size field must match the frame geometry and pixel format (this is what the Frame constructors do). |
| dst | Destination Frame object. User must at least initialize fourcc field of destination Frame object. Method doesn’t support H264, HEVC and JPEG pixel formats. If the frame data doesn’t match the source geometry and the destination pixel format the method releases it (a frame that does not own its data, for example a clone, only forgets the pointer) and allocates new memory. A destination with data, the source geometry and the size of its pixel format is used as it is, also if it does not own the memory (the converted data is written into the memory the clone points to). The memory of the source and the destination must not overlap. |
Returns: TRUE if pixel format converted or FALSE if not (no source data, invalid geometry, unsupported pixel format, inconsistent frame size, overlapping source and destination memory or the destination memory can not be allocated: the destination has no data then).
Colour conversion (BT.601 coefficients, full range 0..255, fixed point integer math, identical results for all SIMD back ends and thread counts):
- RGB to YUV: Y = (38R + 75G + 15B) » 7, U = (-22R - 42G + 64B + 16384) » 7, V = (64R - 54G - 10B + 16384) » 7.
- YUV to RGB: R = Y + Dv + ((103Dv) » 8), G = Y - Dv + ((-88Du + 73Dv) » 8), B = Y + Du + ((198Du) » 8), clamped to [0, 255], where Du = U - 128 and Dv = V - 128 (R = Y + 1.402Dv, G = Y - 0.344Du - 0.714Dv, B = Y + 1.772Du).
- RGB to GRAY: (77R + 150G + 29B) » 8. GRAY from a YUV format is the Y channel, GRAY to YUV formats gives U = V = 128.
- The chroma of a sub-sampled destination (YUYV, UYVY: pixel pairs; NV12, NV21, YU12, YV12: 2x2 blocks) is taken from the top-left pixel of the pair or block (no averaging). The chroma of a sub-sampled source is used for every pixel it covers.
- Limited range (16..235) or BT.709 video is converted with the same formulas: convert it to full range BT.601 before if exact colours are needed.
If the source and the destination pixel formats are identical the method just copies the frame data, which is the fastest path per byte moved.
By default the method converts the whole frame on the calling thread. Row-level parallelism is enabled with setMaxThreads, see Multithreading.
Example:
// Init frame converter.
rapidpixel::FormatConverter converter;
// Init source image filled by 0.
rapidpixel::Frame src(640, 480, rapidpixel::Fourcc::BGR24);
// Init output image.
rapidpixel::Frame dst;
dst.fourcc = rapidpixel::Fourcc::YUV24; // Inform converter about destination format.
// Convert.
if (!converter.convert(src, dst))
{
std::cout << "Conversion failed" << std::endl;
}
setMaxThreads method
The setMaxThreads(…) method enables parallel conversion and sets an upper limit for the number of threads that convert(…) may use. Parallel conversion is disabled by default, so this method has to be called to make the converter use more than one thread. Method declaration:
void setMaxThreads(int maxThreads);
| Parameter | Description |
|---|---|
| maxThreads | Maximum number of threads. Value 1 (the default state of the object) keeps conversion single threaded. Values <= 0 select the number of threads automatically: at most 4 threads per conversion (see Number of threads). The limit is never larger than omp_get_max_threads(). |
Returns: none. The setting belongs to the FormatConverter object and must be applied before conversions start (it is not synchronised with conversions that are already running). The method only records the limit: it does not create the OpenMP team. The team is created by the first conversion that actually asks for more than one thread, so a converter whose limit is never raised never creates one. Read Multithreading before raising the limit in a pipeline that converts every frame - the team costs more than the conversion it accelerates.
Example:
rapidpixel::FormatConverter converter; // single threaded, nothing to configure
converter.setMaxThreads(0); // automatic: up to 4 threads per conversion
converter.setMaxThreads(4); // never use more than 4 threads
converter.setMaxThreads(1); // back to single threaded conversion
To keep some CPU cores free for other software, pass the number of cores the converter is allowed to occupy:
#include <thread>
rapidpixel::FormatConverter converter;
// Leave 2 cores of the machine free.
const int cores = static_cast<int>(std::thread::hardware_concurrency());
converter.setMaxThreads(cores > 2 ? cores - 2 : 1);
getMaxThreads method
The getMaxThreads() method returns the thread limit that is in effect for the converter. Method declaration:
int getMaxThreads() const;
Returns: the number of threads convert(…) may use at most, after setMaxThreads(…) is applied to the value provided by OpenMP. The value is 1 until setMaxThreads(…) is called and is always >= 1. After setMaxThreads(0) (automatic selection) the method returns the value provided by OpenMP (omp_get_max_threads()), while one conversion uses at most 4 threads, see Number of threads. The team size of a particular conversion can be smaller, because it also depends on the frame size, see Multithreading.
Example:
rapidpixel::FormatConverter converter;
std::cout << converter.getMaxThreads() << std::endl; // 1
converter.setMaxThreads(18);
std::cout << converter.getMaxThreads() << std::endl; // 18 on a 20 core machine
Multithreading
Parallel conversion is disabled by default. A freshly constructed FormatConverter converts the whole frame on the calling thread and does not create any OpenMP team. Call setMaxThreads to enable row-level parallelism.
convert(…) processes the image row by row. The row loop of every conversion that computes pixels or rearranges chroma rows is an OpenMP parallel for with schedule(static), so each thread gets one contiguous band of rows and threads never share output bytes. A conversion between identical pixel formats is a plain data copy (one memcpy on the calling thread). The same holds for the conversions that only move whole planes (GRAY from and to the 4:2:0 formats, YU12 ↔ YV12): they use memcpy and memset on the calling thread, and the luma plane copy of the conversions among NV12, NV21, YU12 and YV12 is one memcpy as well. The result does not depend on the number of threads: the same input always produces byte-identical output whether the conversion runs on 1 thread or on 64.
What a thread team costs
The cost of a team is not the work it does - it is the time it is alive. An OpenMP runtime keeps the workers of a live team spinning after a parallel region ends, so the next region can start without paying a thread wake-up. A converter called once per video frame opens a region far more often than any runtime’s spin window, so the workers never reach their sleep point and the team is charged for the gaps between frames as well as for the conversions themselves.
Table 3 - FullHD (1920x1080) YUYV to YUV24 at 30 fps, one conversion per frame, measured as whole-process CPU over the run. The conversion itself needs under 1 ms of each 33 ms frame.
| Team size | CPU consumed | Conversion time |
|---|---|---|
| 1 (default) | 0.1 core | 2.80 ms |
| 2 | 0.6 core | 0.86 ms |
| 4 | 1.7 cores | 0.60 ms |
| 8 | 6.2 cores | 0.89 ms |
| 12 | 9.3 cores | 0.81 ms |
Four threads buy 2.2 ms of a 33 ms budget and cost 1.6 extra cores to do it. That trade is worth making when the latency of a single conversion is the constraint and the cores are otherwise idle - a batch converter, an offline tool. It is the wrong trade for a steady video pipeline, which is why the default is serial and why callers have to opt in.
The environment can change the picture: OMP_WAIT_POLICY=passive (or, on the Intel runtime, KMP_BLOCKTIME=0) makes workers sleep the moment a region ends and removes the idle cost almost entirely. Do not rely on it. It is a property of the process the library is linked into, not of the library, and a component that only behaves well under a particular environment variable is a component that misbehaves by default.
Thread safety
The library never calls omp_set_num_threads(…) or omp_set_dynamic(…). The team size is passed to every parallel region with the OpenMP num_threads clause, which affects that region only. As a consequence:
- the OpenMP configuration of your application is never modified by the library;
- convert(…) does not change the FormatConverter object: it may be called from several threads at the same time, including through one object shared between threads (the frames of the calls must be different). setMaxThreads(…) changes the object: it must not be called while another thread uses the same object;
- calling convert(…) from inside your own
#pragma omp parallelregion is safe.
Number of threads
For every conversion the team size is calculated as:
threads = min( width * height / 65536, // one thread per 65536 pixels
height, // cannot exceed the number of rows
threadsLimit ) // limit of the converter
threads = max( threads, 1 )
Since getMaxThreads() returns 1 until setMaxThreads(…) is called, the default converter always ends up with a single thread. After parallelism is enabled the first term keeps small frames single threaded anyway: starting a team of threads costs more than converting a 320x240 image.
When setMaxThreads(0) (or <= 0) is used for automatic thread selection, the thread count is automatically capped to 4 threads (MAX_AUTO_THREADS). This avoids memory bus saturation and contention on modern hybrid CPU architectures (e.g. Intel Alder Lake / Raptor Lake with P-cores and E-cores), ensuring conversions run at maximum memory bandwidth without latency spikes from slower efficiency cores. When higher concurrency is specifically desired, an explicit limit can be provided via setMaxThreads(n) (where n > 0), which will be honored up to omp_get_max_threads().
Table 4 - Team size on a 20 core machine after setMaxThreads(0).
| Frame size | width * height / 65536 | Threads used | Threads used by default |
|---|---|---|---|
| 64 x 64 | 0 | 1 | 1 |
| 320 x 240 | 1 | 1 | 1 |
| 640 x 480 | 4 | 4 | 1 |
| 1280 x 720 | 14 | 4 | 1 |
| 1920 x 1080 | 31 | 4 | 1 |
| 3840 x 2160 | 126 | 4 | 1 |
Enabling and limiting the number of threads
Table 5 - Ways to control the thread count, from the widest scope to the narrowest.
| Way | Scope | Description |
|---|---|---|
| Build without OpenMP | Whole library | If the compiler is invoked without OpenMP support the library builds and runs single threaded; setMaxThreads(…) is accepted but getMaxThreads() always returns 1. |
| OMP_NUM_THREADS environment variable | Whole process | Standard OpenMP setting. It defines the value returned by omp_get_max_threads(), which is the upper bound of what the converter can request. |
| setMaxThreads(n) | One FormatConverter | Enables parallel conversion and sets a hard upper limit for that converter object. n = 0 means automatic thread selection (capped to 4 to prevent bus/E-core choking), n = 1 means single threaded, n > 1 sets explicit thread cap. |
The limit is always at least 1 and never larger than omp_get_max_threads().
Example - a 20 core machine, 2 cores must stay free for other software:
#include <iostream>
#include <thread>
#include "FormatConverter.h"
rapidpixel::FormatConverter converter;
const int cores = static_cast<int>(std::thread::hardware_concurrency());
converter.setMaxThreads(cores > 2 ? cores - 2 : 1);
// 18
std::cout << converter.getMaxThreads() << std::endl;
Example - different converters with different budgets in one application:
rapidpixel::FormatConverter previewConverter;
previewConverter.setMaxThreads(2); // low priority preview stream
rapidpixel::FormatConverter recordConverter;
recordConverter.setMaxThreads(0); // recording may use automatic thread selection
rapidpixel::FormatConverter uiConverter;
// untouched: stays on the calling thread
Example - limiting the whole process from the command line:
OMP_NUM_THREADS=6 ./YourApplication
Performance benchmark
The conversion kernels process whole vectors of pixels (32 pixels with AVX2, 16 pixels with SSE4.1 and NEON) and two vectors per loop step:
- 2x SIMD loop unrolling: two vector blocks per loop step reduce the loop counter and branch overhead and increase the instruction-level parallelism.
- Byte shuffles: RGB24 ↔ BGR24 and the conversions that only move bytes (YUYV ↔ UYVY, YUYV and UYVY to YUV24, NV12 ↔ NV21, YU12 ↔ YV12 …) are done with direct in-register byte permutations.
- Row pairs for 4:2:0 formats: the two rows of a 2x2 chroma block are processed one after the other in the same loop step, without a branch per row; the second row reuses the chroma that the first row has just loaded into the L1 cache of the CPU.
- Plane copies: the luma plane copy and the conversion between identical formats are one standard
memcpyon the calling thread (no thread synchronization, no false sharing of cache lines).
Measured on 13th Gen Intel Core i7-13700H, Windows 11, MSVC 2022 (AVX2 level). The benchmark application converts every pair of the 10 pixel formats (100 pairs, 5 conversions per pair); the table shows the sum of the times of all 100 pairs. For every pair the minimum of several runs of the application is used.
Table 6 - Total conversion time of all 100 pairs.
| Resolution | Single-threaded (setMaxThreads(1)) | Multi-threaded (setMaxThreads(0), at most 4 threads) |
|---|---|---|
| 640 x 480 | 2.34 ms | 1.17 ms |
| 1280 x 720 | 8.94 ms | 3.67 ms |
| 1920 x 1080 | 20.78 ms | 9.07 ms |
Build and connect to your project
The library needs a C++17 compiler, CMake and the Frame library. OpenMP (supplied with the compiler) is optional. Typical install commands for Linux:
sudo apt-get install build-essential cmake
Build stand alone
The repository does not contain sub-repositories. The Frame library must be cloned into the same folder as FormatConverter. The folder structure must be:
<work folder>
FormatConverter
Frame
Typical commands to build the library and the benchmark application (use the URLs of the repositories provided to you):
git clone <FormatConverter repository URL>
git clone <Frame repository URL>
cd FormatConverter
mkdir build
cd build
cmake ..
make
On Windows use cmake --build . --config Release instead of make. If FormatConverter is the top-level CMake project, the main CMakeLists.txt file adds the folder ../Frame before the library itself. If the folder does not exist CMake stops with an error.
CMake options
Table 7 - CMake options of the repository. In the options below ${PARENT} is the prefix defined by the top-level project (empty if FormatConverter is the top-level project).
| Option | Default | Description |
|---|---|---|
| ${PARENT}_FORMAT_CONVERTER | ON | Build the library. |
| ${PARENT}_FORMAT_CONVERTER_BENCHMARK | ON (stand alone), OFF (included in other project) | Build the benchmark application. |
| ${PARENT}_SUBMODULE_CACHE_OVERWRITE | ON | Forced rewriting of the options above in the CMake cache. The top-level project sets it to OFF when it includes FormatConverter as a sub-repository. |
| CMAKE_BUILD_TYPE | Release (if not specified) | Build type. |
| FORMAT_CONVERTER_ENABLE_SIMD | ON | Build the SIMD kernels (AVX2, SSE4.1, NEON). The kernels in plain C++ are always built. If the option is OFF the library converts with the plain C++ kernels only. |
OpenMP is an optional library: the library uses it if CMake finds it (find_package(OpenMP)). Without OpenMP the library builds and converts on the calling thread only, see Multithreading. If OpenMP is found the library links OpenMP::OpenMP_CXX, so a program that links the library also needs the OpenMP runtime of the compiler (for example libgomp and pthread with GCC); CMake adds it to targets that link FormatConverter.
Table 8 - Macros that the CMake file of the library defines for the library sources (not intended to be set by the user). A macro removes the conversions of one SIMD level from the build, the library then selects the best level that is left, see Portability.
| Macro | Defined if | Effect |
|---|---|---|
| FORMAT_CONVERTER_NO_SIMD | FORMAT_CONVERTER_ENABLE_SIMD is OFF, the target is a universal macOS binary or the compiler can not ask the CPU (x86) | Only the plain C++ conversions are built. |
| FORMAT_CONVERTER_NO_SSE41 | The compiler can not build the SSE4.1 conversions (x86) | The SSE4.1 level is left out. |
| FORMAT_CONVERTER_NO_AVX2 | The compiler can not build the AVX2 conversions (x86) | The AVX2 level is left out. |
| FORMAT_CONVERTER_NO_NEON | The compiler can not build the NEON conversions (ARM) | The NEON level is left out. |
CMake prints a status message with the left out levels.
Connect to your project
If you want to connect FormatConverter library to your CMake project as source code, add the repositories of FormatConverter and of the Frame library to your repository as sub-repositories. FormatConverter does not add the Frame library when it is included in another project: your project must add it. For example, if your repository has structure:
CMakeLists.txt
src
CMakeLists.txt
yourLib.h
yourLib.cpp
Create folder 3rdparty in your repository folder and copy Frame and FormatConverter repository folders there. The structure of your repository will be as follows:
CMakeLists.txt
src
CMakeLists.txt
yourLib.h
yourLib.cpp
3rdparty
Frame
FormatConverter
Create a CMakeLists.txt file in the 3rdparty folder. CMakeLists.txt should contain (the libraries are added in dependency order: Frame first, then FormatConverter):
cmake_minimum_required(VERSION 3.13)
################################################################################
## 3RD-PARTY
## dependencies for the project
################################################################################
project(3rdparty LANGUAGES CXX)
################################################################################
## SETTINGS
## basic 3rd-party settings before use
################################################################################
# To inherit the top-level architecture when the project is used as a submodule.
SET(PARENT ${PARENT}_YOUR_PROJECT_3RDPARTY)
# Disable self-overwriting of parameters inside included subdirectories.
SET(${PARENT}_SUBMODULE_CACHE_OVERWRITE OFF CACHE BOOL "" FORCE)
################################################################################
## CONFIGURATION
## 3rd-party submodules configuration
################################################################################
SET(${PARENT}_SUBMODULE_FRAME ON CACHE BOOL "" FORCE)
if (${PARENT}_SUBMODULE_FRAME)
SET(${PARENT}_FRAME ON CACHE BOOL "" FORCE)
SET(${PARENT}_FRAME_TEST OFF CACHE BOOL "" FORCE)
endif()
SET(${PARENT}_SUBMODULE_FORMAT_CONVERTER ON CACHE BOOL "" FORCE)
if (${PARENT}_SUBMODULE_FORMAT_CONVERTER)
SET(${PARENT}_FORMAT_CONVERTER ON CACHE BOOL "" FORCE)
SET(${PARENT}_FORMAT_CONVERTER_BENCHMARK OFF CACHE BOOL "" FORCE)
endif()
################################################################################
## INCLUDING SUBDIRECTORIES
## Adding subdirectories according to the 3rd-party configuration
################################################################################
if (${PARENT}_SUBMODULE_FRAME)
add_subdirectory(Frame)
endif()
if (${PARENT}_SUBMODULE_FORMAT_CONVERTER)
add_subdirectory(FormatConverter)
endif()
File 3rdparty/CMakeLists.txt adds the folders Frame and FormatConverter to your project and automatically excludes the benchmark application from compiling. The structure of your repository will be:
CMakeLists.txt
src
CMakeLists.txt
yourLib.h
yourLib.cpp
3rdparty
CMakeLists.txt
Frame
FormatConverter
Next, you need to include the 3rdparty folder in the main CMakeLists.txt file of your repository. Add the following line at the end of your main CMakeLists.txt:
add_subdirectory(3rdparty)
Next, you have to include FormatConverter library in your src/CMakeLists.txt file:
target_link_libraries(${PROJECT_NAME} FormatConverter)
A project that does not add the Frame library fails to compile with the error Frame.h: No such file or directory.
Done!
Portability
Instruction sets. The conversions are built for every instruction set of the target architecture and the best one is selected when the library runs (the CPU is asked once per process). A binary built on a machine with AVX2 runs on a CPU without AVX2. The selection was checked with CPU emulation (QEMU): the emulated Haswell uses AVX2, the emulated SandyBridge, Nehalem and Penryn use SSE4.1 and the emulated Conroe (no SSE4.1) uses plain C++.
Table 9 - SIMD levels of the library.
| Target | Levels, best first |
|---|---|
| x86, x86-64 | AVX2 (CPU feature AVX2 and operating system support of the AVX registers), SSE4.1 (SSSE3 and SSE4.1), plain C++ for CPUs without SSE4.1. |
| 64-bit ARM | NEON. |
| 32-bit ARM, Linux | NEON if the kernel reports it, otherwise plain C++. |
| other | Plain C++. |
Every level is built from its own source file with the instruction set option of that file only (a global option such as -mavx2 is never used), and all levels give identical results byte by byte. The CMake option FORMAT_CONVERTER_ENABLE_SIMD and the macros that remove a level from the build are described in CMake options.
Compilers and architectures. The library was built and run with MSVC 2022 (x64, Release and Debug), GCC 12 and 13 and Clang 15 and 18 (x86-64 Linux, Release and Debug) and GCC 13 for AArch64 (under qemu-user).
Byte order and integer sizes. The library works with bytes of pixels and does not depend on the byte order. The size of an int must be at least 32 bits (checked at compile time).