Files
punktfunk/crates/pyrowave-sys/vendor/pyrowave/pyrowave_c_test.cpp
T
enricobuehler 4c3b11445c feat(host): vendor PyroWave + minimal Granite subset as crates/pyrowave-sys
Phase 0 of design/pyrowave-codec-plan.md — the opt-in wired-LAN ultra-low-
latency codec. Vendored at upstream 509e4f88 (API 0.4.0, Granite 44362775,
volk + vulkan-headers pins in PUNKTFUNK-VENDOR.txt), pruned to the 6.6 MB
the standalone no-renderer build needs; scripts/vendor-pyrowave.sh
reproduces the tree (a pin bump is protocol-affecting, plan §4.2).

build.rs drives the wrapper CMakeLists (static archives incl. a static
C-API lib upstream only ships shared) + bindgen over pyrowave.h; Linux and
Windows only, empty stub elsewhere (Apple gets a native Metal port, §4.7).
Offline-safe by construction: no network, no system lib, vendored Vulkan
headers — same model as the opus dep (flatpak builder has no network).

Phase-0 validation on .21 (RTX 5070 Ti, driver 610.43.03):
- upstream pyrowave-c-test + interop test (incl. dmabuf/DRM-modifier
  Vulkan<->Vulkan) pass, from the pristine AND the pruned tree
- GPU kernel times at ~1.6 bpp noise: encode/decode 0.090/0.042 ms @800p,
  0.146/0.067 @1080p, 0.226/0.103 @1440p, 0.477/0.201 @4K — order of
  magnitude under NVENC's 1-2 ms retrieve, CBR lands within ~100 B of
  target
- cargo test -p pyrowave-sys green (static link + API-version pin check)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 00:35:10 +02:00

575 lines
18 KiB
C++

// Copyright (c) 2026 Hans-Kristian Arntzen
// SPDX-License-Identifier: MIT
#include "vulkan/vulkan.h"
#include "pyrowave.h"
#include <stdio.h>
#include <cstdlib>
#include <exception>
#include <vector>
// Smoke test the C API.
#define ASSERT_THAT(x) do { \
if (!(x)) { fprintf(stderr, "Fatal error executing %s at line %d.\n", #x, __LINE__); std::terminate(); } \
} while(false)
#define CHECKED(x) do { \
pyrowave_result res = x; \
if (res != PYROWAVE_SUCCESS) { fprintf(stderr, "Got pyrowave result %d while executing %s at line %d.\n", res, #x, __LINE__); std::terminate(); } \
} while(false)
static void test_encoder_create_validation()
{
pyrowave_encoder_create_info info = {};
info.width = 64;
info.height = 64;
// No device.
pyrowave_encoder encoder, dummy;
ASSERT_THAT(pyrowave_encoder_create(&info, &encoder) == PYROWAVE_ERROR_INVALID_ARGUMENT);
pyrowave_device device;
CHECKED(pyrowave_create_default_device(&device));
info.device = device;
CHECKED(pyrowave_encoder_create(&info, &encoder));
// 0 size not allowed.
info.width = 0;
info.height = 0;
ASSERT_THAT(pyrowave_encoder_create(&info, &dummy) == PYROWAVE_ERROR_INVALID_ARGUMENT);
// Odd size not allowed.
info.width = 65;
info.height = 64;
ASSERT_THAT(pyrowave_encoder_create(&info, &dummy) == PYROWAVE_ERROR_INVALID_ARGUMENT);
info.width = 64;
info.height = 65;
ASSERT_THAT(pyrowave_encoder_create(&info, &dummy) == PYROWAVE_ERROR_INVALID_ARGUMENT);
// Odd size allowed for 444.
info.chroma = PYROWAVE_CHROMA_SUBSAMPLING_444;
CHECKED(pyrowave_encoder_create(&info, &dummy));
pyrowave_encoder_destroy(dummy);
pyrowave_encoder_destroy(encoder);
pyrowave_device_destroy(device);
}
static void test_decoder_create_validation()
{
pyrowave_decoder_create_info info = {};
info.width = 64;
info.height = 64;
// No device.
pyrowave_decoder decoder, dummy;
ASSERT_THAT(pyrowave_decoder_create(&info, &decoder) == PYROWAVE_ERROR_INVALID_ARGUMENT);
pyrowave_device device;
CHECKED(pyrowave_create_default_device(&device));
info.device = device;
CHECKED(pyrowave_decoder_create(&info, &decoder));
// 0 size not allowed.
info.width = 0;
info.height = 0;
ASSERT_THAT(pyrowave_decoder_create(&info, &dummy) == PYROWAVE_ERROR_INVALID_ARGUMENT);
// Odd size not allowed.
info.width = 65;
info.height = 64;
ASSERT_THAT(pyrowave_decoder_create(&info, &dummy) == PYROWAVE_ERROR_INVALID_ARGUMENT);
info.width = 64;
info.height = 65;
ASSERT_THAT(pyrowave_decoder_create(&info, &dummy) == PYROWAVE_ERROR_INVALID_ARGUMENT);
// Odd size allowed for 444.
info.chroma = PYROWAVE_CHROMA_SUBSAMPLING_444;
CHECKED(pyrowave_decoder_create(&info, &dummy));
pyrowave_decoder_destroy(dummy);
// Smoke test that creating device on fragment path doesn't explode.
info.fragment_path = true;
CHECKED(pyrowave_decoder_create(&info, &dummy));
pyrowave_decoder_destroy(dummy);
pyrowave_decoder_destroy(decoder);
pyrowave_device_destroy(device);
}
static void test_decode_cpu_buffer_validation(bool fragment_path)
{
pyrowave_decoder_create_info info = {};
info.width = 16;
info.height = 16;
info.fragment_path = fragment_path;
pyrowave_decoder decoder;
CHECKED(pyrowave_create_default_device(&info.device));
CHECKED(pyrowave_decoder_create(&info, &decoder));
// This shouldn't be ready.
ASSERT_THAT(!pyrowave_decoder_decode_is_ready(decoder, false));
ASSERT_THAT(!pyrowave_decoder_decode_is_ready(decoder, true));
pyrowave_cpu_buffer cpu_buffer = {};
uint8_t luma[16][16] = {};
uint8_t cb[8][16] = {}; // Test strided readback while we're at it.
uint8_t cr[8][16] = {};
cpu_buffer.format = PYROWAVE_CPU_BUFFER_FORMAT_YUV420P;
cpu_buffer.row_stride_in_bytes[0] = 16;
cpu_buffer.row_stride_in_bytes[1] = 16;
cpu_buffer.row_stride_in_bytes[2] = 16;
cpu_buffer.plane_size_in_bytes[0] = 16 * 16;
cpu_buffer.plane_size_in_bytes[1] = 16 * 8;
cpu_buffer.plane_size_in_bytes[2] = 16 * 8;
cpu_buffer.data[0] = &luma[0][0];
cpu_buffer.data[1] = &cb[0][0];
cpu_buffer.data[2] = &cr[0][0];
cpu_buffer.width = 16;
cpu_buffer.height = 16;
// NV12 is banned for decode.
cpu_buffer.format = PYROWAVE_CPU_BUFFER_FORMAT_NV12;
ASSERT_THAT(pyrowave_decoder_decode_cpu_buffer_synchronous(decoder, &cpu_buffer) == PYROWAVE_ERROR_INVALID_ARGUMENT);
cpu_buffer.format = PYROWAVE_CPU_BUFFER_FORMAT_YUV420P;
CHECKED(pyrowave_decoder_decode_cpu_buffer_synchronous(decoder, &cpu_buffer));
// Assert that we do indeed decode a gray image.
for (uint32_t y = 0; y < 16; y++)
for (uint32_t x = 0; x < 16; x++)
ASSERT_THAT(luma[y][x] == 0x7f || luma[y][x] == 0x80);
for (uint32_t y = 0; y < 8; y++)
for (uint32_t x = 0; x < 8; x++)
ASSERT_THAT(cb[y][x] == 0x7f || cb[y][x] == 0x80);
for (uint32_t y = 0; y < 8; y++)
for (uint32_t x = 0; x < 8; x++)
ASSERT_THAT(cr[y][x] == 0x7f || cr[y][x] == 0x80);
pyrowave_decoder_destroy(decoder);
pyrowave_device_destroy(info.device);
}
static void test_encode_cpu_buffer_validation(bool nv12)
{
pyrowave_encoder_create_info info = {};
info.width = 16;
info.height = 16;
pyrowave_encoder encoder;
CHECKED(pyrowave_create_default_device(&info.device));
CHECKED(pyrowave_encoder_create(&info, &encoder));
const pyrowave_rate_control rate_control = { 1024 };
pyrowave_cpu_buffer cpu_buffer = {};
uint8_t y[16][16] = {};
uint8_t cb[8][8] = {};
uint8_t cr[8][8] = {};
cpu_buffer.format = nv12 ? PYROWAVE_CPU_BUFFER_FORMAT_NV12 : PYROWAVE_CPU_BUFFER_FORMAT_YUV420P;
cpu_buffer.row_stride_in_bytes[0] = 16;
cpu_buffer.row_stride_in_bytes[1] = nv12 ? 16 : 8;
cpu_buffer.row_stride_in_bytes[2] = nv12 ? 0 : 8;
cpu_buffer.plane_size_in_bytes[0] = 16 * 16;
cpu_buffer.plane_size_in_bytes[1] = (nv12 ? 2 : 1) * 8 * 8;
cpu_buffer.plane_size_in_bytes[2] = nv12 ? 0 : 8 * 8;
cpu_buffer.data[0] = &y[0][0];
cpu_buffer.data[1] = &cb[0][0];
cpu_buffer.data[2] = &cr[0][0];
cpu_buffer.width = 16;
cpu_buffer.height = 16;
CHECKED(pyrowave_encoder_encode_cpu_synchronous(encoder, &cpu_buffer, &rate_control));
// Mismatching width/height against encoder.
cpu_buffer.width = 15;
cpu_buffer.height = 16;
ASSERT_THAT(pyrowave_encoder_encode_cpu_synchronous(encoder, &cpu_buffer, &rate_control) == PYROWAVE_ERROR_INVALID_ARGUMENT);
cpu_buffer.width = 16;
cpu_buffer.height = 15;
ASSERT_THAT(pyrowave_encoder_encode_cpu_synchronous(encoder, &cpu_buffer, &rate_control) == PYROWAVE_ERROR_INVALID_ARGUMENT);
// Too small row strides.
cpu_buffer.width = 16;
cpu_buffer.height = 16;
cpu_buffer.row_stride_in_bytes[1] = nv12 ? 15 : 7;
ASSERT_THAT(pyrowave_encoder_encode_cpu_synchronous(encoder, &cpu_buffer, &rate_control) == PYROWAVE_ERROR_INVALID_ARGUMENT);
// Too small plane size.
cpu_buffer.row_stride_in_bytes[1] = nv12 ? 16 : 8;
cpu_buffer.plane_size_in_bytes[1] = (nv12 ? 2 : 1) * 8 * 8 - 1;
ASSERT_THAT(pyrowave_encoder_encode_cpu_synchronous(encoder, &cpu_buffer, &rate_control) == PYROWAVE_ERROR_INVALID_ARGUMENT);
pyrowave_encoder_destroy(encoder);
pyrowave_device_destroy(info.device);
}
static void test_basic_encoder_roundtrip(bool fragment_decode, bool nv12_encode, pyrowave_chroma_subsampling chroma)
{
if (chroma == PYROWAVE_CHROMA_SUBSAMPLING_444 && nv12_encode)
return;
if (fragment_decode)
return;
pyrowave_device device;
CHECKED(pyrowave_create_default_device(&device));
constexpr int Width = 34;
constexpr int Height = 30;
pyrowave_decoder_create_info decoder_info = {};
decoder_info.device = device;
decoder_info.width = Width; // Test somewhat odd size. Quite relevant for fragment path as well.
decoder_info.height = Height;
decoder_info.fragment_path = fragment_decode;
decoder_info.chroma = chroma;
pyrowave_encoder_create_info encoder_info = {};
encoder_info.device = device;
encoder_info.width = Width;
encoder_info.height = Height;
encoder_info.chroma = chroma;
pyrowave_decoder decoder;
pyrowave_encoder encoder;
CHECKED(pyrowave_decoder_create(&decoder_info, &decoder));
CHECKED(pyrowave_encoder_create(&encoder_info, &encoder));
uint8_t luma[Height][Width] = {};
uint8_t cb[Height][Width] = {};
uint8_t cr[Height][Width] = {};
uint8_t cbcr[Height][Width][2] = {};
uint8_t decode_luma[Height][Width] = {};
uint8_t decode_cb[Height][Width] = {};
uint8_t decode_cr[Height][Width] = {};
pyrowave_cpu_buffer cpu_buffer = {};
cpu_buffer.format =
nv12_encode
? PYROWAVE_CPU_BUFFER_FORMAT_NV12
: (chroma == PYROWAVE_CHROMA_SUBSAMPLING_444
? PYROWAVE_CPU_BUFFER_FORMAT_YUV444P
: PYROWAVE_CPU_BUFFER_FORMAT_YUV420P);
cpu_buffer.row_stride_in_bytes[0] = Width;
cpu_buffer.row_stride_in_bytes[1] = nv12_encode ? sizeof(cbcr[0]) : sizeof(cb[0]);
cpu_buffer.row_stride_in_bytes[2] = nv12_encode ? 0 : sizeof(cr[0]);
cpu_buffer.plane_size_in_bytes[0] = sizeof(luma);
cpu_buffer.plane_size_in_bytes[1] = nv12_encode ? sizeof(cbcr) : sizeof(cb);
cpu_buffer.plane_size_in_bytes[2] = nv12_encode ? 0 : sizeof(cr);
cpu_buffer.data[0] = &luma[0][0];
if (nv12_encode)
{
cpu_buffer.data[1] = &cbcr[0][0][0];
}
else
{
cpu_buffer.data[1] = &cb[0][0];
cpu_buffer.data[2] = &cr[0][0];
}
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
luma[y][x] = uint8_t(3 * x + 5 * y);
uint8_t cb_signal = 7 * x + 3 * y;
uint8_t cr_signal = 3 * x + 5 * y;
if (nv12_encode)
{
cbcr[y][x][0] = cb_signal;
cbcr[y][x][1] = cr_signal;
}
else
{
cb[y][x] = cb_signal;
cr[y][x] = cr_signal;
}
}
}
cpu_buffer.width = Width;
cpu_buffer.height = Height;
const pyrowave_rate_control rate_control = { 64 * 1024 }; // Just give it something massive.
CHECKED(pyrowave_encoder_encode_cpu_synchronous(encoder, &cpu_buffer, &rate_control));
size_t num_packets;
CHECKED(pyrowave_encoder_compute_num_packets(encoder, 64 * 1024, &num_packets));
ASSERT_THAT(num_packets == 1);
std::vector<uint8_t> bitstream(64 * 1024);
pyrowave_packet packet = {};
CHECKED(pyrowave_encoder_packetize(encoder, &packet, 64 * 1024, &num_packets, bitstream.data(), bitstream.size()));
ASSERT_THAT(num_packets == 1);
ASSERT_THAT(packet.offset == 0);
ASSERT_THAT(packet.size != 0);
ASSERT_THAT(packet.size <= bitstream.size());
bitstream.resize(packet.size);
CHECKED(pyrowave_decoder_push_packet(decoder, bitstream.data() + packet.offset, packet.size));
ASSERT_THAT(pyrowave_decoder_decode_is_ready(decoder, false));
pyrowave_decoder_clear(decoder);
ASSERT_THAT(!pyrowave_decoder_decode_is_ready(decoder, false));
CHECKED(pyrowave_decoder_push_packet(decoder, bitstream.data() + packet.offset, packet.size));
ASSERT_THAT(pyrowave_decoder_decode_is_ready(decoder, false));
cpu_buffer.data[0] = &decode_luma[0][0];
cpu_buffer.data[1] = &decode_cb[0][0];
cpu_buffer.data[2] = &decode_cr[0][0];
cpu_buffer.row_stride_in_bytes[1] = sizeof(decode_cb[0]);
cpu_buffer.row_stride_in_bytes[2] = sizeof(decode_cr[0]);
cpu_buffer.plane_size_in_bytes[1] = sizeof(decode_cb);
cpu_buffer.plane_size_in_bytes[2] = sizeof(decode_cr);
cpu_buffer.format = chroma == PYROWAVE_CHROMA_SUBSAMPLING_444 ?
PYROWAVE_CPU_BUFFER_FORMAT_YUV444P : PYROWAVE_CPU_BUFFER_FORMAT_YUV420P;
CHECKED(pyrowave_decoder_decode_cpu_buffer_synchronous(decoder, &cpu_buffer));
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
int d = std::abs(int(decode_luma[y][x]) - int(luma[y][x]));
// With the "infinite" bitrate we get here,
// accept a maximum 1 ULP error.
ASSERT_THAT(d <= 1);
if (chroma == PYROWAVE_CHROMA_SUBSAMPLING_444 || (!nv12_encode && y < Height / 2 && x < Width / 2))
{
// Allow more error for chroma.
d = std::abs(int(decode_cb[y][x]) - int(cb[y][x]));
ASSERT_THAT(d <= 1);
d = std::abs(int(decode_cr[y][x]) - int(cr[y][x]));
ASSERT_THAT(d <= 1);
}
}
}
if (nv12_encode)
{
for (int y = 0; y < Height / 2; y++)
{
for (int x = 0; x < Width / 2; x++)
{
int d = std::abs(int(decode_cb[y][x]) - int(cbcr[y][x][0]));
ASSERT_THAT(d <= 1);
d = std::abs(int(decode_cr[y][x]) - int(cbcr[y][x][1]));
ASSERT_THAT(d <= 1);
}
}
}
pyrowave_decoder_destroy(decoder);
pyrowave_encoder_destroy(encoder);
}
static void test_basic_system_stability()
{
pyrowave_device device;
CHECKED(pyrowave_create_default_device(&device));
// 4K, upper bound of normal usage.
constexpr int Width = 3840;
constexpr int Height = 2160;
pyrowave_decoder_create_info decoder_info = {};
decoder_info.device = device;
decoder_info.width = Width; // Test somewhat odd size. Quite relevant for fragment path as well.
decoder_info.height = Height;
pyrowave_encoder_create_info encoder_info = {};
encoder_info.device = device;
encoder_info.width = Width;
encoder_info.height = Height;
pyrowave_decoder decoder;
pyrowave_encoder encoder;
CHECKED(pyrowave_decoder_create(&decoder_info, &decoder));
CHECKED(pyrowave_encoder_create(&encoder_info, &encoder));
std::vector<uint8_t> luma(Width * Height);
std::vector<uint16_t> cbcr(Width * Height / 4);
std::vector<uint8_t> decode_luma(Width * Height);
std::vector<uint8_t> decode_cb(Width * Height / 4);
std::vector<uint8_t> decode_cr(Width * Height / 4);
pyrowave_cpu_buffer encode_buffer = {}, decode_buffer = {};
encode_buffer.format = PYROWAVE_CPU_BUFFER_FORMAT_NV12;
encode_buffer.row_stride_in_bytes[0] = Width;
encode_buffer.row_stride_in_bytes[1] = Width;
encode_buffer.plane_size_in_bytes[0] = Width * Height;
encode_buffer.plane_size_in_bytes[1] = Width * Height / 2;
encode_buffer.data[0] = luma.data();
encode_buffer.data[1] = cbcr.data();
encode_buffer.width = Width;
encode_buffer.height = Height;
decode_buffer.format = PYROWAVE_CPU_BUFFER_FORMAT_YUV420P;
decode_buffer.row_stride_in_bytes[0] = Width;
decode_buffer.row_stride_in_bytes[1] = Width / 2;
decode_buffer.row_stride_in_bytes[2] = Width / 2;
decode_buffer.plane_size_in_bytes[0] = Width * Height;
decode_buffer.plane_size_in_bytes[1] = Width * Height / 4;
decode_buffer.plane_size_in_bytes[2] = Width * Height / 4;
decode_buffer.data[0] = decode_luma.data();
decode_buffer.data[1] = decode_cb.data();
decode_buffer.data[2] = decode_cr.data();
decode_buffer.width = Width;
decode_buffer.height = Height;
const auto mirror = [](int v) -> uint8_t
{
v &= 511;
if (v > 255)
v = 511 - v;
ASSERT_THAT(v >= 0 && v <= 255);
return uint8_t(v);
};
// Just generate a synthetic signal.
for (int y = 0; y < Height; y++)
for (int x = 0; x < Width; x++)
luma[y * Width + x] = mirror(3 * x + 5 * y);
for (int y = 0; y < Height / 2; y++)
for (int x = 0; x < Width / 2; x++)
cbcr[y * Width / 2 + x] = mirror(7 * x + y * 3) * 0x100 + mirror(y * 5 + x * 7);
std::vector<uint8_t> bitstream;
std::vector<pyrowave_packet> packets;
for (int iter = 0; iter < 100; iter++)
{
// 240mbit equivalent for 60 fps.
const pyrowave_rate_control rate_control = { 500000 };
// Get some test coverage for async compute path.
CHECKED(pyrowave_device_set_queue_type(device, iter % 2 ? VK_QUEUE_COMPUTE_BIT : VK_QUEUE_GRAPHICS_BIT));
bitstream.reserve(rate_control.maximum_bitstream_size);
CHECKED(pyrowave_encoder_encode_cpu_synchronous(encoder, &encode_buffer, &rate_control));
size_t num_packets, after_packets;
CHECKED(pyrowave_encoder_compute_num_packets(encoder, 8 * 1024, &num_packets));
ASSERT_THAT(num_packets > 1);
packets.resize(num_packets);
bitstream.resize(rate_control.maximum_bitstream_size);
CHECKED(pyrowave_encoder_packetize(encoder, packets.data(), 8 * 1024, &after_packets, bitstream.data(), bitstream.size()));
ASSERT_THAT(num_packets == after_packets);
// Verify that the bitstream is sound. We should be able to decode it.
size_t total_bitstream_size = 0;
for (auto &packet : packets)
{
CHECKED(pyrowave_decoder_push_packet(decoder, bitstream.data() + packet.offset, packet.size));
// When we push the last packet, we should get a complete frame.
ASSERT_THAT(pyrowave_decoder_decode_is_ready(decoder, false) == (&packet == &packets.back()));
total_bitstream_size += packet.size;
}
// Verify that we tightly hit our rate control budget.
ASSERT_THAT(total_bitstream_size <= rate_control.maximum_bitstream_size);
ASSERT_THAT(total_bitstream_size >= 95 * rate_control.maximum_bitstream_size / 100);
CHECKED(pyrowave_decoder_decode_cpu_buffer_synchronous(decoder, &decode_buffer));
}
pyrowave_decoder_destroy(decoder);
pyrowave_encoder_destroy(encoder);
// Verify that PSNR is under control.
double y_error = 0.0;
double cb_error = 0.0;
double cr_error = 0.0;
for (int i = 0; i < Width * Height; i++)
{
double y_d = double(luma[i]) - double(decode_luma[i]);
y_error += y_d * y_d;
}
for (int i = 0; i < Width * Height / 4; i++)
{
double cb_d = double(cbcr[i] & 0xff) - double(decode_cb[i]);
cb_error += cb_d * cb_d;
double cr_d = double(cbcr[i] >> 8) - double(decode_cr[i]);
cr_error += cr_d * cr_d;
}
double y_signal = 255.0 * 255.0 * Width * Height;
double chroma_signal = 255.0 * 255.0 * (Width * Height / 4);
double y_psnr = y_signal / y_error;
double cb_psnr = chroma_signal / cb_error;
double cr_psnr = chroma_signal / cr_error;
// 40 dB, arbitrarily chosen for testing purposes.
ASSERT_THAT(y_psnr > 10000.0);
ASSERT_THAT(cb_psnr > 10000.0);
ASSERT_THAT(cr_psnr > 10000.0);
// We're not hitting 60 dB. That'd mean we cheated or something or added bugs in the test code.
ASSERT_THAT(y_psnr < 1000000.0);
ASSERT_THAT(cb_psnr < 1000000.0);
ASSERT_THAT(cr_psnr < 1000000.0);
int blah = 0;
pyrowave_device_report_performance_stats(device, [](void *userdata, const char *msg)
{
*static_cast<int *>(userdata) = 42;
printf("performance cb: %s\n", msg);
}, &blah, true);
// Verify that userdata gets passed correctly.
ASSERT_THAT(blah == 42);
pyrowave_device_destroy(device);
}
int main()
{
printf("Running system stability test ...\n");
test_basic_system_stability();
// Correctness tests for small-ish outputs.
for (int variant = 0; variant < 8; variant++)
{
printf("Running roundtrip variant %d test ...\n", variant);
test_basic_encoder_roundtrip(
(variant & 1) != 0, (variant & 2) != 0,
(variant & 4) != 0 ? PYROWAVE_CHROMA_SUBSAMPLING_444 : PYROWAVE_CHROMA_SUBSAMPLING_420);
}
// Validate that we handle error inputs gracefully.
printf("Running error handling tests ...\n");
test_decode_cpu_buffer_validation(false);
test_decode_cpu_buffer_validation(true);
test_encode_cpu_buffer_validation(false);
test_encode_cpu_buffer_validation(true);
test_encoder_create_validation();
test_decoder_create_validation();
printf("Passed all tests :)\n");
}