test(vkdecode): the AV1 rung finally has pixels to answer to
A parity and smoke harness for AV1, mirroring the H.264 and H.265 legs that proved those rungs bit-identical to libavcodec on four drivers before either ran on glass. This was the milestone's largest test gap: the adversarial review found four blocking defects in the AV1 conversion — flags unset on 274 frames of 274, a units error in LoopRestorationSize, per-reference info describing the wrong picture, film-grain fields left zero — and every one of them would have shown on frame 1 of a parity run, while clippy and 164 green unit tests said nothing at all. The golden is 250 per-frame SHA-256s in DISPLAY order, not 274. The vector carries 274 coded frames in 250 temporal units; the 24 extras are hidden ALTREFs, decoded and referenced but never shown, and the rung delivers what dpb.outputs names. The count is re-derived from the planner rather than assumed. Cross-checked between ffmpeg 8.1.1 on macOS arm64 and 8.0.1 on Linux x86_64, whose raw outputs are byte-identical — and then against a third party neither build knows about: the vendored vector ships upstream's own per-frame MD5s, and re-running those reproduces all 250. The golden agrees with a decode nobody in this program performed. I reproduced both independently before committing. 8-bit NV12, traced from the sequence header rather than presumed (seq_profile 0, high_bitdepth 0, mono_chrome 0), so the P010 scar does not apply here — and the header says which check to make if a Main 10 golden is ever added. film_grain_params_present is 0, which is load-bearing: grain synthesis is part of the Vulkan decode profile, so this golden is only comparable against a grain-less profile key. Anti-vacuity is the point of the exercise, so it is structural. The golden guard asserts the exact count, that every line is a bare digest, and that all entries are DISTINCT — 250 copies of one digest would let a decoder frozen on a single frame pass parity. The parity body asserts the golden set and the access-unit count before it touches hardware, so an IVF reader returning nothing cannot become "0 frames compared, pass". The agent verified the guards fire by mutating the golden three ways. assert_bit_identical now names the FIRST divergent frame, which is what localises a defect; that improves all six legs, not just AV1. AV1 has no four-byte-start-code twin, deliberately: OBUs are length-delimited, so there is no prefix for a driver to mis-skip. Documented where a reader would otherwise see an omission. Nothing here has run on a GPU. The harness exists precisely so the four review defects can be answered by measurement instead of argument.
This commit is contained in:
@@ -1,18 +1,18 @@
|
||||
//! Shared Vulkan Video bring-up for the `#[ignore]`d GPU legs.
|
||||
//!
|
||||
//! `tests/gpu_smoke.rs` and `tests/gpu_parity.rs` each drive TWO codecs, and the
|
||||
//! `tests/gpu_smoke.rs` and `tests/gpu_parity.rs` each drive THREE codecs, and the
|
||||
//! path from "a Vulkan loader exists" to "a [`DeviceHandles`] a decoder can be
|
||||
//! constructed on" is the same ~150 unsafe lines every time: loader → instance →
|
||||
//! pick a physical device whose queue families carry the codec's decode ops →
|
||||
//! logical device with the decode extensions plus `timelineSemaphore` and
|
||||
//! `synchronization2`. Four copies of that would be four places for a
|
||||
//! `synchronization2`. Six copies of that would be six places for a
|
||||
//! fleet-only failure to hide, so it lives here once, parameterised by the one
|
||||
//! thing that genuinely differs between the callers ([`Graphics`]: the parity
|
||||
//! legs read back on a graphics queue and so REQUIRE one, while the smoke legs
|
||||
//! accept a decode-only device and fall back to the decode family — which also
|
||||
//! decides whether pool images end up EXCLUSIVE or CONCURRENT, so it is not
|
||||
//! cosmetic). [`Request::report_families`] exists so a caller CAN suppress the
|
||||
//! per-family table, but all four legs currently ask for it: it is the first
|
||||
//! per-family table, but every leg currently asks for it: it is the first
|
||||
//! thing a fleet failure report needs, and it is a physical-device property
|
||||
//! query, never a recorded RESULT_STATUS query, so it cannot trip the RADV VCN
|
||||
//! hang.
|
||||
@@ -64,6 +64,39 @@ pub const TEST_25FPS_H265: &[u8] = include_bytes!(
|
||||
"../../../pf-bitstream/vendor/cros-codecs/src/codec/h265/test_data/test-25fps.h265"
|
||||
);
|
||||
|
||||
/// The vendored AV1 twin: 320x240 Main 4:2:0 8-bit, no film grain, **250 temporal
|
||||
/// units carrying 274 coded frames** — 24 units carry two frames each, and those 24
|
||||
/// extras are HIDDEN (decoded, referenced, never shown; the vector contains no
|
||||
/// `show_existing_frame` at all). 250 frames are displayed, which is what the rung
|
||||
/// delivers and what `data/test-25fps-av1.nv12.sha256` hashes.
|
||||
///
|
||||
/// Note the file name: it is `test-25fps.ivf.av1`, not `test-25fps.av1.ivf` — the
|
||||
/// directory holds BOTH, byte-identical, and only the former has the `.md5`/`.crc`
|
||||
/// reference hashes beside it. pf-bitstream's planner tests include this one.
|
||||
pub const TEST_25FPS_AV1: &[u8] = include_bytes!(
|
||||
"../../../pf-bitstream/vendor/cros-codecs/src/codec/av1/test_data/test-25fps.ivf.av1"
|
||||
);
|
||||
|
||||
/// Split the AV1 vector into access units — one IVF packet per TEMPORAL UNIT.
|
||||
///
|
||||
/// Unlike the two Annex-B splitters below there is nothing to re-derive here: AV1
|
||||
/// carries no start codes, so an access unit is not something a scan recovers from
|
||||
/// the elementary stream — it is the container's framing, and the vector's container
|
||||
/// is IVF. `IvfIterator` is the vendored parser's own reader (the same one
|
||||
/// pf-bitstream's AV1 planner tests walk), so this is a rename for symmetry with
|
||||
/// [`split_h264_aus`]/[`split_h265_aus`] rather than a second implementation that
|
||||
/// could disagree with production.
|
||||
///
|
||||
/// The consequence for the parity legs is worth stating: AV1 has **no prefix-width
|
||||
/// leg** and needs none. The four-byte-start-code hazard that made HEVC unplayable
|
||||
/// on every driver (see [`h265_four_byte_start_codes`]) simply has no AV1
|
||||
/// counterpart — OBUs are length-delimited, the host hands whole temporal units
|
||||
/// across, and there is no prefix for a driver to mis-skip. Its absence here is
|
||||
/// deliberate, not an omission.
|
||||
pub fn split_av1_aus(stream: &[u8]) -> Vec<&[u8]> {
|
||||
cros_codecs::bitstream_utils::IvfIterator::new(stream).collect()
|
||||
}
|
||||
|
||||
/// Test-only H.264 AU splitter, mirroring pf-bitstream's
|
||||
/// (`#[cfg(test)]`-private there): a new AU starts at a non-VCL NALU following
|
||||
/// slices, or at a slice whose `first_mb_in_slice` is 0 (the first bit of the byte
|
||||
@@ -190,11 +223,11 @@ pub fn h265_four_byte_start_codes(stream: &[u8]) -> Vec<u8> {
|
||||
|
||||
/// The slice of a decoder's surface the GPU legs drive.
|
||||
///
|
||||
/// `VkH264Decoder` and `VkH265Decoder` expose it method-for-method (the crate
|
||||
/// docs say so deliberately) but share no trait — codec DISPATCH is the client
|
||||
/// wiring's job, not this crate's. Binding it here lets each GPU leg run ONE body
|
||||
/// against both codecs, which is the only way "the H.265 leg proves the same thing
|
||||
/// the H.264 leg does" can be a fact instead of a claim about two hand-copied
|
||||
/// `VkH264Decoder`, `VkH265Decoder` and `VkAv1Decoder` expose it method-for-method
|
||||
/// (the crate docs say so deliberately) but share no trait — codec DISPATCH is the
|
||||
/// client wiring's job, not this crate's. Binding it here lets each GPU leg run ONE
|
||||
/// body against all three codecs, which is the only way "the AV1 leg proves the same
|
||||
/// thing the H.264 leg does" can be a fact instead of a claim about three hand-copied
|
||||
/// functions.
|
||||
pub trait TestDecoder {
|
||||
fn decode(&mut self, au: &[u8]) -> Result<Option<DecodedVkFrame>, VkDecodeError>;
|
||||
@@ -210,7 +243,7 @@ pub trait TestDecoder {
|
||||
fn debug_snapshot(&self) -> String;
|
||||
}
|
||||
|
||||
/// Forwarding impl — one macro so the two decoders can never drift into being
|
||||
/// Forwarding impl — one macro so the three decoders can never drift into being
|
||||
/// driven differently by accident.
|
||||
macro_rules! impl_test_decoder {
|
||||
($ty:ty) => {
|
||||
@@ -246,6 +279,7 @@ macro_rules! impl_test_decoder {
|
||||
|
||||
impl_test_decoder!(pf_vkdecode::VkH264Decoder);
|
||||
impl_test_decoder!(pf_vkdecode::VkH265Decoder);
|
||||
impl_test_decoder!(pf_vkdecode::VkAv1Decoder);
|
||||
|
||||
/// Serializes the GPU legs within one test binary. Hold it for the whole leg.
|
||||
///
|
||||
@@ -293,6 +327,18 @@ pub const H265: Codec = Codec {
|
||||
extension: ash::khr::video_decode_h265::NAME,
|
||||
};
|
||||
|
||||
/// AV1 decode (`VkAv1Decoder`).
|
||||
///
|
||||
/// The narrowest of the three on the fleet: `VK_KHR_video_decode_av1` only reached
|
||||
/// core drivers in 2024, so a box that decodes both H.26x codecs may still report
|
||||
/// "no physical device with VK_KHR_video_decode_av1", which is a fact about the box.
|
||||
/// On RADV the extension is additionally behind `RADV_PERFTEST=video_decode`, the
|
||||
/// same knob the other two need.
|
||||
pub const AV1: Codec = Codec {
|
||||
op: vk::VideoCodecOperationFlagsKHR::DECODE_AV1,
|
||||
extension: ash::khr::video_decode_av1::NAME,
|
||||
};
|
||||
|
||||
/// What a caller needs from the GRAPHICS queue family — the one behavioural
|
||||
/// difference between the smoke and parity bring-ups, an explicit parameter so
|
||||
/// it cannot drift back into being an accident of two copied loops.
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
# SHA-256 per DELIVERED frame of test-25fps.ivf.av1, DISPLAY order — 250 frames.
|
||||
#
|
||||
# Each frame is the 320x240 render region as tightly packed NV12:
|
||||
# Y plane 320*240 bytes, then interleaved UV 320*120 bytes = 115200 bytes/frame.
|
||||
#
|
||||
# EIGHT-BIT NV12, not P010. The vector's sequence header carries
|
||||
# `high_bitdepth = 0` / `mono_chrome = 0` / `seq_profile = 0`, i.e. Main 4:2:0
|
||||
# 8-bit, so the Vulkan pool is VK_FORMAT_G8_B8R8_2PLANE_420_UNORM and one byte
|
||||
# per sample is the whole story here. (The ten-bit scar the sibling
|
||||
# data/test-main10.p010.sha256 header records — P010's ten bits sit in the HIGH
|
||||
# end of each 16-bit word, NOT yuv420p10le's low end — does not arise for this
|
||||
# vector, but it is the first thing to check if an AV1 Main-10 golden is ever
|
||||
# added beside this one.)
|
||||
#
|
||||
# NO FILM GRAIN. `film_grain_params_present = 0` in the sequence header, so the
|
||||
# golden is grain-free and the Vulkan decode profile this is compared against is
|
||||
# the film-grain-DISABLED one (`Av1ProfileKey::film_grain == false`; grain
|
||||
# synthesis is part of the Vulkan decode PROFILE, not a per-frame toggle). A
|
||||
# vector that gained grain would need its own golden AND a device that offers the
|
||||
# grain-enabled profile — it would not merely change these hashes.
|
||||
#
|
||||
# 250, NOT 274. The vector is 250 temporal units carrying 274 coded frames: 24
|
||||
# units carry two frames each, and those 24 extras are HIDDEN frames (decoded,
|
||||
# referenced later, never shown — the vector contains no `show_existing_frame` at
|
||||
# all, so they are never displayed by any route). The rung delivers DISPLAYED
|
||||
# frames, one per `dpb.outputs` id, so the golden is 250 entries in display order
|
||||
# and NOT one per coded frame. pf-bitstream's
|
||||
# `the_whole_vendored_vector_plans_and_the_frame_count_is_the_parsers` pins all
|
||||
# four numbers (250 / 274 / 24 / 250) on CPU, and `gpu_parity.rs`'s
|
||||
# `av1_goldens_and_the_ivf_split_agree_with_the_planner` re-derives the 250 from
|
||||
# the planner beside this file's line count so the two can never drift apart
|
||||
# silently.
|
||||
#
|
||||
# Generated 2026-08-06 from libavcodec's SOFTWARE decoder (AV1 decoding is exactly
|
||||
# specified — every conformant decoder is bit-identical), and CROSS-CHECKED between
|
||||
# two independent builds on two architectures whose 28,800,000-byte raw outputs are
|
||||
# byte-identical (not merely equal per frame):
|
||||
# ffmpeg 8.1.1 (Homebrew, macOS arm64, libdav1d)
|
||||
# ffmpeg 8.0.1-3ubuntu2 (Ubuntu x86_64 in the pf-lxcheck2 image via
|
||||
# `apt-get install -y ffmpeg`, libdav1d)
|
||||
#
|
||||
# ffmpeg -i crates/pf-bitstream/vendor/cros-codecs/src/codec/av1/test_data/test-25fps.ivf.av1 \
|
||||
# -f rawvideo -pix_fmt nv12 -fps_mode passthrough ref.yuv
|
||||
# # then split ref.yuv into 115200-byte frames and sha256 each
|
||||
#
|
||||
# THIRD, INDEPENDENT CORROBORATION: the vendored vector ships upstream per-frame
|
||||
# MD5s beside it (test-25fps.ivf.av1.md5, generated by cros-codecs' own gen_crcs.sh
|
||||
# with `-pix_fmt nv12 -f framehash`), produced on other machines by another
|
||||
# toolchain at another time. Re-running that command on BOTH builds above
|
||||
# reproduces all 250 of those MD5s exactly, so this golden is not just two builds
|
||||
# of one opinion — it agrees with a decode nobody here performed.
|
||||
#
|
||||
# Sibling of data/test-25fps.nv12.sha256 (H.264) and data/test-25fps-h265.nv12.sha256
|
||||
# (H.265) and consumed the same way, by the AV1 parity leg in tests/gpu_parity.rs.
|
||||
04a9916b634632172d1e21d1bf1f083305e847225d361784a1d4fb46c49d7b1f
|
||||
51172eeef06a23063aa881bc6652e43e319c86f954eb21f3cf80d31c3832f365
|
||||
9db9cc170509db22fad2436de8afea4bc3601cd645706f21a9a786491ffa4714
|
||||
f589376bb225817e337da827ff08d55aec024148691c16589ef135dc74f364d2
|
||||
c0ae27f37ccea7405e5508c768c95a96ab44f1e916625f000410f1d82a43912e
|
||||
a8cc856dd4c2b4d558871d26fb5501385ce900a9eab4f66efbc7be8f8597912b
|
||||
155a58840b546a6c65ae6bdc16523e723dcf50788b92baf813186026e7b39fb3
|
||||
48b6c37599f073ad7f6d61f65756d85d82b5b35a1fecc22cd1491f4c3366bffe
|
||||
99846bf7c69f3c709621f3c6bbbdf7d385dc5a3736c1702933ea30441c5a2352
|
||||
a2f970fc8d340e6978b327f89366660635aec6782b2845c4a5f9319cee79d340
|
||||
a3245f047ebff7cacba1baeee09f9eb34977dd578f4a4ef3e4fe40c3601c56be
|
||||
39ebba702c84e7a342792a85ff922130335d724597fd48e41f32a471fa76333d
|
||||
8540fe45480763d891673ecf99b0dc4ff23fa1f7b1f8e25c7532805141b6ed85
|
||||
88a37ad837a0778ec7465405416e06487e2aeebb1307d550a07e5f66498a0721
|
||||
9678d42aca9aaf32d7aad049988ba6ec530babfcd547da545b9d8be4a5e2763f
|
||||
1f608b7af5d30bdb8e1786af9549b27f216656cb34175f3ab1538c07fe32a91e
|
||||
29341aeeac9d91760a3e1aa45a0a91b69a8505a125e236d35476e438b2d8b5f3
|
||||
18254332b0d8455bff2edff3c6421beabdebe45ef45a1e4da6155190ffdd8c37
|
||||
e731deed91bb66157e2c51eb2ca351801205397922fe99936defe6fe53252daa
|
||||
e52cef7ee6f7cb335350743ef4acb5eb2f1e5a1bf5db030f58afd827499be877
|
||||
f46832050dec356423266289871b6e1e48e1a8551ab03e879548f7b05f679f1c
|
||||
a9505b4fad6f60176131196b37d8d435bb079090e24402b33e96d0ad18676abb
|
||||
d41c9853a927e9c215c795050a4eb017fdecf8aee3da5e92ec8a09328ebeff4a
|
||||
73dc659f8c749f92acddf83df17deb23c55b81bdaaefe5efe3444de4eab5d6d0
|
||||
51728e28c25190da1812d15bd3652a6ff6f07eb1ebffc80a15819913446d7b5c
|
||||
861cb38a660b83663dc12da2d806e00a97ea9a17d637285c6f1b66ad66fad16e
|
||||
8f05f727c2d601fee4c46230ddea131b06d252acdadb6985ec85665e4452b6ec
|
||||
cbb46e9c3ebb5de26151c90c094bf903c1b0e64827610480e891e0857454be86
|
||||
16102b7bb0a6188c5a81ca165d160086a7e1235bda2a318bab0492119aa8c494
|
||||
bfc34108b13be73ac0d8ffac1414076f5dd79bd4de686b457a95023bbb14f8ed
|
||||
6142990f104c14f0f3095f291c9566eeedf650070d722e6da143f594b80931ec
|
||||
3b7e155c3862dfa85c2326f643617578094fcc2b9dfb2d655e8cc6b014a4da63
|
||||
66e2f3d97f3ece05e56ca37159d9586f08c7034a291fc4b003bfd4195c404fef
|
||||
e1cd61fb1b9b0378027a737ab4affaa590830d71fda2c6d8e93e82af7f0e3d19
|
||||
c141b82b6f7b20b4928f715819da5c6c4576d7228202a331a1953676295498bc
|
||||
28c2b7f4b9fc9653e4ab10cf40b270b14d92e7725370cd3203f92b5ce5531458
|
||||
bb4015bd6cf672cd51eb5d30095419b2f4c37de4e299daaec423b5c4c6d3bb92
|
||||
bd898e002d41d1539510cdf0820f6757506a781db541e90f793010ca61e633be
|
||||
7a2e6dafcc16aab285e11f763871f8d06f653aa437d5a874347fa4f3e2902036
|
||||
52389449d67fc312137ab62db2ebfd3a6550eab707bd03c04754b2b69ea0c68a
|
||||
c9e44fd4cc038e9ebaad163effdd7e1fa1fb564a7226680418744266eb7beb13
|
||||
cc66d3cc8a0ac53b360695ea9f3851a218f659a2bac5a75569c65fb663a2963a
|
||||
32bc728bd387325bfc383ff5266e790f59d10cd79f1379da7292e4b5313fac26
|
||||
50901e7012c4549cea01c9039568a002b26f0d4861acdda6e1353363fc907b2e
|
||||
d7d1c8c9f3d8533119164292583ef7f06490ebfc42f39bfa04100eb13f8dc572
|
||||
e861a7eec4472574229a22819f4c5312570eb671babf394b5d71250fbebbd963
|
||||
ae4f62b2cb9454d16729cc475fc77717d7863dde2b90295983ee83b7c23945dc
|
||||
cc1f95cba3fdf71afe9f4a17f185aea83059091cfeb217dcd83764a02537c98e
|
||||
2f01b1a190556fec2760c1bc6b9abd94839e332b6b229178aa8caaad809a76a9
|
||||
e7214ac73576798426c38f27936f5222a8f12ff046e4bef4fd7db56765b5cc1b
|
||||
cf6c88ec0eac4461d28a3544af15271a960ccc19573ef6cb300cfb9800112f88
|
||||
559d96b0d160a10581969136b4abc0d653a39ee45b03fbe1dcb04b5a98d245d8
|
||||
15631d150c3215a1fabcf3bf0035314d898a546a2b6a4b2255957202c55bca71
|
||||
44bfe0041bf7b233e2c9f39023d617b85ffd227b1922d06eee3e3d158db94426
|
||||
c520a61755e0f5e0fd78d2791431b220e131a8b7a095b5a29fed2ff66bf3ef4e
|
||||
c9b176c0d8fb92056f6d29e64a0e6f899b646bc5ea397ca09bffde796314d790
|
||||
feb41aaa9c0afa2cd36ae45318b70118aec59ac8ef694bef014d224fd4d9970f
|
||||
9f00fb66da1f277f88ba31610203e78d9cecdc750bfaf4f773b29a36c3d156b5
|
||||
225728f96f584f7a019d2a47b4cc104332d92892ae1f0ff1db2a384f11f8e6e3
|
||||
ffaccd50aaed93791506e4db411673303804e3bffd6dce69f1ab0ac9527c26fa
|
||||
dd889709e85b4f655365111efb007fe05564548898cbdb4c9328c24508a82eb0
|
||||
00c2f8613d319adb52e3fcc59014de4204aac015f549e01013c40f6a74b3af9c
|
||||
84f4f2a6b403a2c255979368d0e5ba4840e1647e3bc20c57f88506701c3adab7
|
||||
27bd83a287a03570572f1d70f823841802dccac6e92a718dd29d321b36bf4882
|
||||
5829642335c09a3d86f280a674e7d559957f40a9327917e5864f8dba5a30819f
|
||||
235aa9abe1c41aca79f4a052821a20b3687c6aad26a8921bee251d84645be5bc
|
||||
83ea45720f61cd77e6998465929e338a5bba0d94b188441570d76d8b5cd7fc42
|
||||
612d69a24cd95badafdc4315e1992f08170a212e1a8d20dbe0f54e73142d64aa
|
||||
5130f8cd836552a00e5f296b03bfe511dc3469ec8484b23fe31f787b445aaad0
|
||||
b8e25ead0212886654eb57e4949f3e00c3995360019a8270448031648949f7a2
|
||||
b370f67f98a36a98b0080beabdac6b0503646e6971f02c971e1de506d414a4c9
|
||||
af95d0cd2bb7191275681e284fb85003831b10884c610e84c55860a2497169d2
|
||||
f4a1db148944256991a51531620fb107637f2e6ef298ef100a2dde3694c7c7c2
|
||||
a1717c8df1ece7821c7202afafa951c184c097f4c0231105a1380ff9cffd3d72
|
||||
695261e9aadc259d2a05926b8591235c35939ef89ec4de0e9dcc858988ff7d95
|
||||
cdbbc29d212ce0f66ad991615b2fb01d7d3c9c2caa761a9e11f744ed2a4c0115
|
||||
e29fa67c173cbc04bf0a3a6d6fbfb2a6c6208fd71f386b8fca5c6adc0adea614
|
||||
481b12556e2a0c019a3dae5cece32af4089e077106cd672ceb79630c76f5190d
|
||||
848f032dd30b84a12383381a2e689c034377630458651d27f5d3ab946d632389
|
||||
7a9a535f138d5a966edb4f27d59faabfbe39d33d92e19c99d1ce58d3b0391d95
|
||||
7ccc23f5ee19dbac8be8c6a49145fac7b03d6f3dd19496a4236961098ded7920
|
||||
bb4c430d52662df7f2ffaef738c067d489073231834d40b819d58e38c6a44cb4
|
||||
0204f817e8e6891657bfcec7b830c244dc9dc028ed753529cc021709a6ac49c7
|
||||
4484238b52c1775e3a5436976b00d83f5398e8be0d019c491b383f169e343754
|
||||
f78c775cf0da2138af3174b9e36335bdf689af55288002ef64fc5a0cfdce05ea
|
||||
ad2934c43eb8e088c1f5f51518fa0254e418b941d1c7218f4611246e0981023c
|
||||
d83c3eefe9437fdebdb111401a3622ca6c33a36490079b7a77dea14e04cb01fa
|
||||
0b3a39e42415f1181f47f6a9df7e2083a234499a0eecdeb7a073a1421beda8a3
|
||||
1ba58406e0e81a9d283b414f3e0cf4c143d2d2b0dbccd6378d1973af864cfedc
|
||||
2097812bf91b0cad050ddf2a62cae3eb6713f191bed44d52c1ec594706365268
|
||||
90bb9b77ad84af847c7915702755b2e28805f1597d65bb04aac805a7135f4468
|
||||
c3a7b4d956edce520afaca9510f206982380617f1328f7b37e94aeb3f6827a5c
|
||||
ccc19ee8a386a1fdadf674a87ce519ed490d98b6dd071810f3286a91cf97fd61
|
||||
a0dfdd093e1bebc1c3735bd166b9f63bb52373aae85e3ed7cef0207826ed4dfb
|
||||
0319dc031150b2f2773743291ca5d9a91f756e0f6224dd6e4da7eb4d88c1bd40
|
||||
0c2edd8861e6d3397983c3bdff9dc13d2cae4522197db10401379a8d13898f38
|
||||
3393e5f4b352df95d11ad00b70dfd96802c25ea23b9d710623c175a40ad54afc
|
||||
1c2e35c9db9c862ba64ff1c7b5086b7178e347c5c68e39c916ddad03c55b9418
|
||||
95bbab9230052ba329b42fc4ab2bbd4db437e9cd65bb137b0ef604ec196335f4
|
||||
b00716a8f666a443345c03b302a90b6f56cbeac378c03c9b70d92c15f038b4f7
|
||||
c9f818bf8f49e67945410573eade3b7efb66cbda16eee10d678ff7cb9bce4b0c
|
||||
28aed1c9e7b11922920b407f5b2cf2c7df363e9f793b3961a0142691cb5a20d1
|
||||
8dd178787e662d7aade20307db2b089c09f930788ae1a0bf53ff085060c5e8d2
|
||||
48336f5699128d39f91dd9de306ebbdf05ff9f8c0e5ed94692fb75454940b388
|
||||
6c67cf9ac541919e8330f736922738f38c63adae4b19f3049f76c724bf5acfbe
|
||||
56b3569f6c2e7b52b3d87633e1295dd1ea18907e536875643ac07cb262434de0
|
||||
316979d5edd9f59ae6d46e75f527a099686ce8e7474009b2962c514479ea8c58
|
||||
c24ec25ab6b37e3115ce9e529f6e696270fea37971565543eb93e780da09d905
|
||||
9c42b6ef210397982cf52cd2915a687d39e99495cf1276643f270644d2bff2ed
|
||||
99135c0c59e8e12487b77325af4151be2cc37a77f06b7912ec3d20bc5526ddb3
|
||||
82eff222c286981bf376b18ea879cbf48db197d2fc5e5c2143b8cd6c1122c001
|
||||
19bf0af707de5933b49d84595e6e9d0c312ef2479f8eda74cf838099328de201
|
||||
bbc769d24c42c68fe33d0a3d0cca2ded31091f9430434f66f74d731a8740ecd6
|
||||
f7fafac3d6f77b639528bc29222a3ef495ffa2281ff83071edd6978849138e00
|
||||
0f3fda4e040e6ea9431eb659cfb31780afe6b59606dd839e6a1ba42e63e90ccd
|
||||
822d68d0a791832aab0cf32ee5db1681d6061bff00ead8803c1cb3beb38b47e2
|
||||
3861804dd6152ebf876be7e24868b7e8f38ae4c4f8a35de9cc4db2caff680ade
|
||||
fc0827486353b291004ef921f859fed4e158b570dda3c00006f3f654a9c94d46
|
||||
88c08db2b7aac161c53c53e10b1a7955c9f34ff7fdb63bdaa018231638d53c80
|
||||
e652c08d1ef97bfc4036d24013079e4536b0ab897ce71fee9cdd1aacf873cfe7
|
||||
9aa766897bd50812c5ad944f1cbbf85abef921dc177bcbbdb8d5af395729ebbd
|
||||
d5aa42f129cf03c4404f63875d6500dae74f142b53f90c493f65f4ea6cd52e29
|
||||
f046d91a8520f79b82b6c8127eccc10bdfa8dc33c23de610cb88e685a46dcc9d
|
||||
2e5795b34aaca15982aa068acfbf61d6a91adec487e8e58579d04c03504df010
|
||||
1feb946111faaf40be12cec1e8d9e3749241a529978fb16d84a024f8363f14a0
|
||||
58a18c5b6c68cd7e966404622e9176c6ba03d7a2b351327dd32114d24eedab6c
|
||||
a6b592f74134be10420ee0d26a80ce61d6a22b8c858b1f03551f127d469eeb35
|
||||
f0aeb9eff65e9a45937d6089ece2d75e2cf08d9723869dd629a85b83f1daf858
|
||||
3311498180e377211ebd282c3daea28675d8616c560d233aeb2990f4df3a3157
|
||||
bad41313546387ae30b83aad7aedc854fa2761fb18db2f62b8b4f060c84a6bac
|
||||
444ef76a1b0fc74a410b5bce137d05e073a4461ca10f29406ae47bdcbd6f0b9d
|
||||
9b45c7c24a19132a9fb2f9833801319ec6fca92dab61c18ca6c8eb6c52b5cbb5
|
||||
88f4546ddedf5363d5d267db4a5659986f59c0f559563b11b164fb2e67156467
|
||||
a3597d945ee641d1016f42b02db41f93e12a41d49008103119a479fc1a1d64d3
|
||||
79c3e3c8aed31550eb0d2fb8762aa0350f7aaeb107fe5c2eac577b39c2cd8d72
|
||||
ff483d3e8ba9af42c26233ef67e4d755d64245a68d580c2c1903cbbb9746dfb5
|
||||
8572121897bd614f3efa03ef6767f3e21695f2cf48b2476560dfaea16c281762
|
||||
fbd251b057a3156b32d9d5565da046dadc8ac51892d9c77b74851b31c7c08053
|
||||
f34de916154676ceda653796b53fb2fe9f892094759dd3939279b4c7d512f9a0
|
||||
eaccd92e77fcc29f32f202d93e7dae85382397098377e23051f9e0dbba31b34d
|
||||
fe83002ddfdaedc72986f35f7703cff06a67a4ffa034b7aedddd394b731dccfc
|
||||
cd23524ffe622557dc33e36b40af70d8726e124bcfe2e26d54d4e2c3b3b19417
|
||||
5a858bbe9916be0e0c5e1460ed6e7bb227a6c4a939005dba78a0f6f3a4029215
|
||||
e04e68822bc3a196507df77f4a206918438eddef2404e608cfa5c7ee8922c57b
|
||||
c3c8f51b5b2b2374316c9b2ac39c0d9464f01a220c5d8c5ff6b455aaaf45ccde
|
||||
146cd43a435e663d6eaf36975e5bb2ea5019891aeaaa762fffada09df61c848b
|
||||
d2d4b3246c184c3b219abfbd48634107aa0aa426fb190c23aae3f088867371e6
|
||||
be86426ad5e44ab73ed6f058625ab920836b532b9634aca82a4a1343342372b2
|
||||
34114cecdd6017b74adb6701f9622bc7fba27aa3410c2373479ba5e0540d267d
|
||||
35017238c99551d827d7dacbef35ed6076630049b165ae83180eb71d81e7823e
|
||||
058069a3afb141b4158815205f6a74ff1c94318cd4177abbe0c540e5ce70653d
|
||||
d6b3d6d433ed65df97f9c33a761a31507e5d98db07c632c052518efc1956a099
|
||||
e3c8746bbf9ae6f2c620bbcfb07e854d6e75741350df8cf37b57699c000c687c
|
||||
73d5cf36367ba4e506e2ad34891202da22fe77980ebc4b5d4a4f081f5616726c
|
||||
abdf3028d25ba750b9295bd1aea2686a15c8f8ba464eb60710b40053279e1bb6
|
||||
bc324cc77981f01c72f620c5cc1dd85334f781eafdb8a89b012d8e08216b62a4
|
||||
5108ff93f95752c540859a23c31822107398a38f7629f9805cde7d0e826471d6
|
||||
0b82dc51c686bd6c062ee38ba028d2abb5d7bd3fe66611294d281b3a2afe581e
|
||||
efad4f5d6f244eeabaaef316640b123b591297bbc84c3b3fade3fc18d8a35df9
|
||||
b034a1c107559c8f444e1791dc5e3c73664d9f05c945b3ed0e237c94fee5cabd
|
||||
f6423105f012a404af935e7a7385095579fe417a27b932372bc357d01bd0ec5e
|
||||
ef421cd426de02948ff7cf456e9a69f3665e6be7d1a3f0ea42821e62a3faa44d
|
||||
8c5f37b28e980057d5f168514f4031283c1bc45e0f5d28ef97640507156e4999
|
||||
a40ffe4ad42e22f4e79ab1d4565848957bb4526d8dd68016796c86eb7006c4a4
|
||||
7c193d2d343bfdc36dae5e3f49b2f73b1c77d9b41a1493fb88d1df5a8ed2f95c
|
||||
7b316fb7c9be2138fd04cc55ed2ecd4158e46239062f2890d3262dbb73996f86
|
||||
511cc99acfd3c66096d01e1225d3eab1f9ff1a14fcc84a5e8606813da3a4d5bb
|
||||
2f3799815a546dced31bdf70749462fdc81642142b393f893856afe40f1ca764
|
||||
1bb241bcbd388fe6ca5c6d6070d0526dac14abb6569dec2eb036cfe3bba694bd
|
||||
4267b825cb3a692eda0d1bd75d48f6d1f74de5c256434192c9ae27220d552ab8
|
||||
294a8e9277ae256c7e519515deaef45a2c41ce223864c9468db54c8e44233045
|
||||
ce0842add29867140313f607508076ae9fd4b7f3ed7117a0c969cdfdcda47aca
|
||||
15b4e0600d0cef8591e53681c92aedaaec419b202094533935cf52fd3419282d
|
||||
2a2f74a4d066a09c26677867345d64edf41dc5f419e0fec3b2f4618708164585
|
||||
9852a0524833dc3e60d68924558ad424b9d6fd498351b926db7c3b9501b79e97
|
||||
6fd26a7b2c5fded3688130e003560a8ddd58d0f9bc2906ec1b679842d1cf16c5
|
||||
7bf43d1d54f813057713c66ed1ac69060c26ee9dbfa10de7bee70f1272e4c4cd
|
||||
ae3dd2cfb8d7ebf28fbee064b9ee95e8ebc555e479c48a6b64e1e453f4951bf7
|
||||
93850ae80180f782c311c182d21207991df3db3b17d9f76af910ac655ae5c1fa
|
||||
e8dc4065580d4e764de7efc30498824cd2e1a3bc32db46211c8f8128182d41d7
|
||||
d154299c13772040f936fc48ac1c84001baad3ba97f42c935f34e49eb5f647c1
|
||||
daf8fe6bb8f25a1c4e07829fd7bd282ece7ac184c4671465036c102814c03ddf
|
||||
fc73ca6be0515992d070467b0f099c075f733d85d3d2202e333c20dcde11e5cf
|
||||
f1d624e05f1d4bbd19595e52d33c6ee9cf655bd7a830078a5d989f88d5e7fcff
|
||||
9457a3d05a76811600fae446c1391963aa411ac2ce4e53784af7b5a688625fdc
|
||||
b153bd9cccfd998ca9bfe291fe12f3c234fdfa342e4124c3575d1e35443ea669
|
||||
274f46071fda32c474cddbd234b837a0aacb3c57638eb6d5079cf1ffbea1651c
|
||||
874d47f84440c15fdd0c37d2c20d471e1675de27671208e007c8507ea0d7a313
|
||||
77c47246000c7168b87adeca3b5c5d80e8a5e9d723086a73ed99a14d01ac272c
|
||||
ec77435e3a8469c287bffecfac6c0e08505a467da7f4b6d09ab3248f1c1d47a8
|
||||
013c86ff9eed9007d8d44fd15fd4b050958e7737a65add691c126cbba7ad10f7
|
||||
b7adacbe6bbb4a33f0346215f67566fe4e6adcd43c221bcdf6cc112b07323396
|
||||
d37d05cebb4b4145b1934555a064069659c8a6c91854c5e207ff4e065c4df4ce
|
||||
6c377b3e07d0b16a73ff58ea7cb50fc50c49aa38cccc7dba6f8c14db16139655
|
||||
1988b08348836747be71e0828dd0ef306c437043eda757eb5baa0cf3af860dc0
|
||||
f49648f9d652552863ac10227bbfe5c29d43c8c7e2dd436d30cca8adcdcaa40f
|
||||
c8e47679b7d12b5335e24f86ee4f4a37d9bfae6ff70c99b92cba16960169fe82
|
||||
e1c353d3fd564e699359b4d5046b26a5e26323192a7a9b43be685ffbc60959ef
|
||||
c87c2abb582a2969f68b46a98815f4e0bc8d003fc2fec32a529126fb07e63be0
|
||||
d88be32ee96a5b0eb436042b3ad8017c48abf3d1fdf8cda290fb3ba3fcae5618
|
||||
cab86bce00ce96e259056298408e2bf51026801b3985be7187b927ba1433e5b1
|
||||
93b729c6995d3576c286de166850681592876dfa30bd7d2cd979aa510de00964
|
||||
cd610cb405436263464bf116632a2dd71ae379efe1f38d66238308c82941a8e9
|
||||
1925666c067fada0dc9dcb9019198829071229aa7ffadd6aca98c1b918a88a3c
|
||||
c975916319c5b80581908e88483d301c977f2b463a71f2554597bcb0e133924d
|
||||
2f28cc1210626c92c5720c9013ad4e610383911f5a8f8786dd7ae85e70ae11db
|
||||
9c826990635030face0b4016d139bab00be992debbf2cc5ae17d55ba6c3c2bc8
|
||||
7710fdb6e25f11880b021afe45244f128df67b750f634598e3902edb184dd2f7
|
||||
59d739493f8d60822d323b4236dc38073e3c231b5c1ce90ef2b2744afe503c3d
|
||||
e8b7bcf58d95f3d7ff5b26ea7c5bf82583f4cfe752569ae97904c668dd2880cc
|
||||
b537e54d9368c2a70ae6bd37c16d7d55476c1aa25247b191db34c03723d7cc89
|
||||
9d5b75f00298e2d088f4a47b8422c31f793e3c7be0093ba696c9234e213b1f45
|
||||
70cab03ae1fb6d1d8d4f090dab142ed3bb8079b924564513cd65ad2d1ff50ebe
|
||||
2699bb193449fb1b85afc49e0504cd84cab903b4329a15fd3cb423ec27eca250
|
||||
5f23520761f8ac1b09e9da8dd3cc85753f12921320b76e3bb534e8f375d71e17
|
||||
746f5bd533e793d2604bebd0e1514bec7865e92c58ea1abe554e30b27abdaf0f
|
||||
d9bd51b37c524ef653c108be5a4c5e38c4bf16da7328a0e3f88a520298975bf5
|
||||
45a7ea96e52ab5344bb5b9b69088123861b4f29d94b918b3fc895280a258022b
|
||||
29c2b08cae2f068b818792e27b5ec01e2fded550a531ecbc9385db925e9269b6
|
||||
7b6169487d371b695f2e3837cc2a2e4d6fe52a6bc6f6949ee9ee36212de897c3
|
||||
021b8dd7a86360029f7028571c9e3226057a61909b67b1471610d354eb49e270
|
||||
18c55b4d764148e3052f1232d068a542697b30f0b522d5e544ffd6e41fb62314
|
||||
f374d94bb22cf9cfdb7ace086f4e9c4e9e30bdcfab544eaefa2d23c1a39995d4
|
||||
ddb1802bc2645c54f916ba144fd96141f1705fab084ff645e11ecdabe4788d48
|
||||
5cde86b5f8a25c323774e8ba27a5cb4158f1bad5da8b75d9a530f476ddd9cb72
|
||||
197389c50e97878feaf583df1d5ef5b0f48154207c40dba89c6a72f9ed367924
|
||||
5459967d1c6b9f1dfa95e7b4564907fa1e9f42a31d77856402fb0af27c81e5aa
|
||||
647006388c6fa8f6a69b432172b31ee56835b53ba9a4d2a8d7646eba8d699424
|
||||
6d4e9ef701148328c0ba14ac0a62b41ae96babe384542731ff8b7c5ef46fe63c
|
||||
b6d3de098262e4d24011095345bf3bac97433401a1d96f0233b79c7e48ba55a7
|
||||
f4a81172ff9454e221b669c822ab3b5f76cdd8e62d3fa923e42eaaa58e60e6b2
|
||||
053ee6f10d5d37d451656c6e25c33110296f02bd5e2461ea710cd049006b224b
|
||||
b2ac14a086577e381e80865a66cce66300b72e961a1d7b8a817b9f4f5189d71d
|
||||
ceebdccc9606c9d6d59f0b8b27b8f7da95107d27864e9e1fef6d4d5379039e7a
|
||||
431569f73856c9478390635049ef07fc73a7b9ad2f0e58e71b806068e93fd844
|
||||
be4474bcf98e5112f903d54799dec8f6e8b738a6e9d901e6cbe2c1e99f886c7a
|
||||
9be9d34ab68928b3fccb05e1863fcd2a8302400b748f3d4de0411c614fca0673
|
||||
de433edf968140c2f32a4ad3cedb5153cc4e65a7d846cee1d621ef7cd5e1ce25
|
||||
1fc5889fa4fc6aa5111cb3c816a75feea5fd7159a0090d950a50935045cb2de2
|
||||
df8ad0344cc492f244ef63288517116db1404f5bac56f362ee214ff41062f6ba
|
||||
4c7a7ad1a5ebe5c7fa2c075a95b13e67418ddd3ee95c72fbca66b36150daeb63
|
||||
07c6d9e6128d4844d7665e3a12d8d9d67a104cc4bb0a7a14a34c8355aebaa486
|
||||
5583161f61a665f7d03ceca5b878621942659303c856245887bca36adab7f834
|
||||
59687559c0c38a27cc79c6d72e77d238569427a040d992d28c22a70d34dd918b
|
||||
a3485384bb20822e4b0e67f95764cd765884912149c2cd3b89d5072023044b08
|
||||
21b3d28c3324e7c2e97106e8459eb8bc56946a326fd584049e8ff93007efc368
|
||||
207bba7349318b67dc97af31762acb76a8718ef4749dbbbe51240abea6d03c72
|
||||
c60a83c60c28e3b173a6a2e4548ce11e4760f99ea740b1900697d946136d86bd
|
||||
36c83d820c97b467810e84727782fbc64b355a0692acac00621bb8817dd08080
|
||||
2917e0ad1745f53b79359df1be6886138d54219e01ddcca28525d6187ef7cf40
|
||||
@@ -1,4 +1,4 @@
|
||||
//! GPU frame-hash parity tests (WP-D) — the two decode legs are `#[ignore]`d
|
||||
//! GPU frame-hash parity tests (WP-D) — the decode legs are `#[ignore]`d
|
||||
//! because they need real Vulkan Video hardware; the coherence guards at the
|
||||
//! bottom of this file are not, and run in ordinary CI.
|
||||
//!
|
||||
@@ -8,35 +8,54 @@
|
||||
//! cargo test -p pf-vkdecode --test gpu_parity -- --ignored --nocapture
|
||||
//! ```
|
||||
//!
|
||||
//! (RADV boxes additionally need `RADV_PERFTEST=video_decode`; multi-GPU boxes
|
||||
//! (RADV boxes additionally need `RADV_PERFTEST=video_decode` — for AV1 as much as
|
||||
//! for the other two, and without it `bring_up` reports "no physical device with
|
||||
//! VK_KHR_video_decode_av1", which reads like missing silicon; multi-GPU boxes
|
||||
//! pin the vendor with `PF_VKD_SMOKE_VENDOR=0x1002` / `0x10de`, same knob as
|
||||
//! the smoke tests. Device bring-up lives in `tests/common/mod.rs`.)
|
||||
//!
|
||||
//! What they prove: H.264 and H.265 decoding are both exactly specified — every
|
||||
//! What they prove: H.264, H.265 and AV1 decoding are all exactly specified — every
|
||||
//! conformant decoder must produce bit-identical output — so the vendored 25fps
|
||||
//! vector of each codec is decoded through [`VkH264Decoder`] / [`VkH265Decoder`],
|
||||
//! vector of each codec is decoded through [`VkH264Decoder`] / [`VkH265Decoder`] /
|
||||
//! [`VkAv1Decoder`],
|
||||
//! every output frame's NV12 planes are read back (`vkCmdCopyImageToBuffer` on the
|
||||
//! graphics queue — GPU→CPU is fine in a test; the pool grows TRANSFER_SRC via the
|
||||
//! decoders' `PF_VKD_TEST_READBACK` hook), cropped to the display region,
|
||||
//! SHA-256-hashed in DISPLAY order and compared against goldens from libavcodec's
|
||||
//! SOFTWARE decoder (the reference implementation — provenance in
|
||||
//! `data/test-25fps.nv12.sha256` and `data/test-25fps-h265.nv12.sha256`). ALL
|
||||
//! `data/test-25fps.nv12.sha256`, `data/test-25fps-h265.nv12.sha256` and
|
||||
//! `data/test-25fps-av1.nv12.sha256`). ALL
|
||||
//! frames are collected, including the tail `flush` delivers, and the frame count
|
||||
//! must match libavcodec's too.
|
||||
//!
|
||||
//! Both legs run ONE body ([`collect_hashes`]) over `common::TestDecoder`, so the
|
||||
//! H.265 leg cannot quietly test something weaker than the H.264 one. A box that
|
||||
//! decodes only one codec runs that leg and reports the other as "no physical
|
||||
//! device with VK_KHR_video_decode_…", which is a fact about the box.
|
||||
//! Every leg runs ONE body ([`collect_hashes`]) over `common::TestDecoder`, so the
|
||||
//! H.265 and AV1 legs cannot quietly test something weaker than the H.264 one. A box
|
||||
//! that decodes only some of the three runs those legs and reports the rest as "no
|
||||
//! physical device with VK_KHR_video_decode_…", which is a fact about the box —
|
||||
//! and on today's fleet AV1 is the one most likely to say so.
|
||||
//!
|
||||
//! Each codec runs that body TWICE: once over the vendored vector as it sits,
|
||||
//! and once over the same vector rewritten to FOUR-byte start codes, which is
|
||||
//! The two Annex-B codecs run that body TWICE: once over the vendored vector as it
|
||||
//! sits, and once over the same vector rewritten to FOUR-byte start codes, which is
|
||||
//! what the real host emits on 100% of access units in both codecs (1514/1514
|
||||
//! H.264 and 1133/1133 HEVC, measured off the M0 NVENC corpus). Prefix width
|
||||
//! carries no information, so both runs must reproduce the same goldens —
|
||||
//! and submitting the four-byte form to the driver unchanged is precisely the
|
||||
//! defect that made HEVC unplayable on every driver tested. Until these legs
|
||||
//! existed no parity vector exercised the form that actually ships.
|
||||
//! existed no parity vector exercised the form that actually ships. **AV1 has no
|
||||
//! such twin and needs none**: OBUs are length-delimited, so there is no start-code
|
||||
//! prefix for a driver to mis-skip and no second framing to test (see
|
||||
//! `common::split_av1_aus`). Its absence is deliberate.
|
||||
//!
|
||||
//! # Why the AV1 leg exists at all
|
||||
//!
|
||||
//! Because until it did, the AV1 rung had no pixel evidence whatsoever. An
|
||||
//! adversarial review of the conversion found four defects — per-frame flags left
|
||||
//! unset on all 274 frames, a units error in `LoopRestorationSize`, per-reference
|
||||
//! info describing the wrong picture, and zeroed film-grain fields — and every one
|
||||
//! of them would have shown as a hash mismatch on frame 0 or shortly after, while
|
||||
//! NONE of them failed clippy or the crate's unit tests. Type-checking a struct
|
||||
//! conversion cannot tell you the struct describes the right picture; only the
|
||||
//! pixels can.
|
||||
//!
|
||||
//! The readback follows the presenter's exact frame contract: wait the frame's
|
||||
//! timeline `value`, round-trip the layout, signal `value + 1` in the SAME
|
||||
@@ -56,6 +75,7 @@ use common::TestDecoder;
|
||||
use pf_vkdecode::DecodeStatus;
|
||||
use pf_vkdecode::DecodedVkFrame;
|
||||
use pf_vkdecode::NoopQueueLock;
|
||||
use pf_vkdecode::VkAv1Decoder;
|
||||
use pf_vkdecode::VkH264Decoder;
|
||||
use pf_vkdecode::VkH265Decoder;
|
||||
use sha2::Digest;
|
||||
@@ -67,6 +87,12 @@ const GOLDENS_H264: &str = include_str!("data/test-25fps.nv12.sha256");
|
||||
/// The H.265 twin, cross-checked between two independent FFmpeg builds (header).
|
||||
const GOLDENS_H265: &str = include_str!("data/test-25fps-h265.nv12.sha256");
|
||||
|
||||
/// The AV1 twin: 250 DISPLAYED frames of a 274-coded-frame vector, cross-checked
|
||||
/// between two independent FFmpeg builds on two architectures AND against the
|
||||
/// per-frame MD5s cros-codecs vendored beside the vector (full provenance in the
|
||||
/// file's header — it is the only golden here with a third-party corroboration).
|
||||
const GOLDENS_AV1: &str = include_str!("data/test-25fps-av1.nv12.sha256");
|
||||
|
||||
/// The ten-bit vector and its goldens. No hardware leg in this file consumes them
|
||||
/// yet — the D3D11VA rung is where the ten-bit parity leg currently runs — but the
|
||||
/// files live here, beside the other goldens, so the guard that keeps them honest
|
||||
@@ -87,15 +113,39 @@ const DISPLAY_H264: (u32, u32) = (320, 240);
|
||||
/// parameter instead of reading one global pair.
|
||||
const DISPLAY_H265: (u32, u32) = (320, 240);
|
||||
|
||||
/// Both vectors' picture format: 8-bit 4:2:0. H.264 is NV12 by envelope
|
||||
/// (`derive_caps` wants nothing else), H.265 Main resolves to it from the SPS —
|
||||
/// The AV1 vector's display region — its `render_width` x `render_height`, AV1's
|
||||
/// answer to a conformance window, and what [`DecodedVkFrame::crop`] carries on this
|
||||
/// rung. Equal to the coded (post-superres) size for this vector, which
|
||||
/// [`av1_goldens_and_the_ivf_split_agree_with_the_planner`] pins rather than assumes:
|
||||
/// a re-synced vector whose render region shrank would make the readback crop a
|
||||
/// region the goldens never hashed.
|
||||
const DISPLAY_AV1: (u32, u32) = (320, 240);
|
||||
|
||||
/// All three vectors' picture format: 8-bit 4:2:0. H.264 is NV12 by envelope
|
||||
/// (`derive_caps` wants nothing else), H.265 Main resolves to it from the SPS and
|
||||
/// AV1 Main (`seq_profile = 0`, `high_bitdepth = 0`) from the sequence header —
|
||||
/// and [`DecodedVkFrame::format`] exists precisely so a pool misconfigured to
|
||||
/// P010 fails loudly instead of hashing differently.
|
||||
const EXPECTED_FORMAT: vk::Format = pf_vkdecode::NV12;
|
||||
|
||||
/// Every vendored 25fps vector, in both codecs, is 250 display frames.
|
||||
/// Every vendored 25fps vector, in all three codecs, is 250 DISPLAY frames.
|
||||
///
|
||||
/// For H.264 and H.265 that is also one per access unit. For AV1 it is emphatically
|
||||
/// not: its 250 temporal units carry [`AV1_CODED_FRAME_COUNT`] coded frames, 24 of
|
||||
/// which are HIDDEN — decoded, referenced by later frames, never shown (the vector
|
||||
/// uses no `show_existing_frame`, so they are displayed by no route at all). The
|
||||
/// rung delivers one frame per `dpb.outputs` id, so 250 is the number the goldens
|
||||
/// carry and the number the parity leg must compare.
|
||||
const FRAME_COUNT: usize = 250;
|
||||
|
||||
/// The AV1 vector's CODED frame count — 24 more than [`FRAME_COUNT`].
|
||||
///
|
||||
/// Asserted by the CPU guard so the display/coded distinction stays a measured fact
|
||||
/// rather than a comment: if a re-sync ever made these two numbers equal, the vector
|
||||
/// would have lost its hidden-frame coverage (the exact thing that makes AV1's
|
||||
/// multi-frame temporal units worth testing) while every hash still matched.
|
||||
const AV1_CODED_FRAME_COUNT: usize = 274;
|
||||
|
||||
/// The golden file's hash lines (comments and blanks skipped).
|
||||
fn golden_hashes(file: &'static str) -> Vec<&'static str> {
|
||||
file.lines()
|
||||
@@ -104,6 +154,50 @@ fn golden_hashes(file: &'static str) -> Vec<&'static str> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Refuse a golden set that could make a parity verdict vacuous.
|
||||
///
|
||||
/// Three ways a comparison can "pass" while proving nothing, all closed here:
|
||||
///
|
||||
/// - **an empty or short set** — [`assert_bit_identical`] compares `hashes` against
|
||||
/// `goldens` pairwise and asserts the lengths match, so a file that lost its
|
||||
/// entries to a bad regeneration would agree with a decoder that delivered
|
||||
/// nothing. Pinning the count against a constant the CPU guards also re-derive
|
||||
/// from the planner closes that.
|
||||
/// - **junk that is not a digest** — a truncated or re-formatted line can never
|
||||
/// equal a real hash, but a file of blank-looking lines could quietly become a
|
||||
/// comparison of nothing.
|
||||
/// - **all entries identical** — the one that matters most on a video codec. If
|
||||
/// every golden were the same digest, a decoder emitting one frozen frame 250
|
||||
/// times would pass, which is precisely the failure mode a broken reference
|
||||
/// conversion produces. All four golden sets here are fully distinct (250/250
|
||||
/// H.264, 250/250 H.265, 250/250 AV1, 50/50 Main 10), so requiring full
|
||||
/// distinctness is not a weak bound.
|
||||
fn assert_goldens_are_a_real_set(goldens: &[&str], expected: usize, path: &str) {
|
||||
assert_eq!(
|
||||
goldens.len(),
|
||||
expected,
|
||||
"{path} must carry one hash per display frame"
|
||||
);
|
||||
assert!(
|
||||
goldens
|
||||
.iter()
|
||||
.all(|line| line.len() == 64 && line.bytes().all(|b| b.is_ascii_hexdigit())),
|
||||
"{path}: every golden line is a bare lowercase SHA-256 hex digest"
|
||||
);
|
||||
let distinct = goldens
|
||||
.iter()
|
||||
.collect::<std::collections::HashSet<_>>()
|
||||
.len();
|
||||
assert_eq!(
|
||||
distinct,
|
||||
goldens.len(),
|
||||
"{path}: {distinct} of {} goldens are distinct — a set with repeats (and \
|
||||
above all a set that is ALL one digest) would let a decoder that froze on \
|
||||
a single frame pass parity",
|
||||
goldens.len()
|
||||
);
|
||||
}
|
||||
|
||||
fn sha256_hex(data: &[u8]) -> String {
|
||||
use std::fmt::Write as _;
|
||||
sha2::Sha256::digest(data)
|
||||
@@ -486,7 +580,16 @@ fn consume_frame(
|
||||
}
|
||||
|
||||
/// Decode every AU, hash every delivered frame in display order, including the
|
||||
/// tail `flush` hands back. One body for both codecs.
|
||||
/// tail `flush` hands back. One body for all three codecs.
|
||||
///
|
||||
/// The flush tail is where the codecs legitimately differ and the body deliberately
|
||||
/// does not: H.264 and H.265 can hold pictures back for reorder, so their planners'
|
||||
/// `flush` releases a tail. AV1's planner has no `flush` at all — a shown frame is
|
||||
/// output by the very temporal unit that decodes it — so `VkAv1Decoder::flush` frees
|
||||
/// the hidden pictures' images and hands back nothing. Draining afterwards is
|
||||
/// therefore a no-op for AV1 rather than a special case, and running the identical
|
||||
/// body means an AV1 rung that ever DID strand a shown frame would be caught by the
|
||||
/// frame-count assertion instead of hidden by a codec-specific shortcut.
|
||||
fn collect_hashes(
|
||||
decoder: &mut impl TestDecoder,
|
||||
readback: &Readback,
|
||||
@@ -532,21 +635,28 @@ fn assert_bit_identical(hashes: &[String], goldens: &[&str], codec: &str) {
|
||||
goldens.len()
|
||||
);
|
||||
let mut mismatches = 0usize;
|
||||
let mut first_divergence: Option<usize> = None;
|
||||
for (index, (got, want)) in hashes.iter().zip(goldens.iter()).enumerate() {
|
||||
if got.as_str() != *want {
|
||||
if mismatches < 10 {
|
||||
eprintln!("frame {index}: MISMATCH\n ours: {got}\n golden: {want}");
|
||||
}
|
||||
first_divergence.get_or_insert(index);
|
||||
mismatches += 1;
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
mismatches,
|
||||
0,
|
||||
"{codec}: {mismatches}/{} frames diverge from libavcodec (first 10 printed \
|
||||
above; frame 0 is intra-only — if IT mismatches, suspect readback \
|
||||
geometry (pitch/crop) or intra decode; later-only mismatches point at \
|
||||
inter prediction / DPB management)",
|
||||
// The FIRST divergent index is the whole diagnostic: everything after it may be
|
||||
// downstream of that one frame through prediction and the DPB, so a report that
|
||||
// only counted mismatches would bury the one number that localises the defect.
|
||||
assert!(
|
||||
first_divergence.is_none(),
|
||||
"{codec}: FIRST DIVERGENT FRAME = {} ({mismatches}/{} frames diverge from \
|
||||
libavcodec; up to 10 printed above). Frame 0 is intra-only — if IT is the \
|
||||
first, suspect readback geometry (pitch/crop), the picture format, or intra \
|
||||
decode / the per-frame parameter conversion; a first divergence LATER points \
|
||||
at inter prediction, per-reference info or DPB management, and the frames \
|
||||
after it are probably just downstream of it.",
|
||||
first_divergence.unwrap_or_default(),
|
||||
hashes.len()
|
||||
);
|
||||
eprintln!(
|
||||
@@ -766,6 +876,115 @@ fn h265_four_byte_start_codes_decode_bit_identically() {
|
||||
);
|
||||
}
|
||||
|
||||
/// The AV1 twin of [`h265_parity_run`].
|
||||
///
|
||||
/// Concrete where the H.265 one is parameterised, because AV1 has exactly one
|
||||
/// vendored vector and one shape (Main 4:2:0 8-bit, no film grain, 320x240); the
|
||||
/// facts it hard-codes are re-derived from the planner, without a GPU, by
|
||||
/// [`av1_goldens_and_the_ivf_split_agree_with_the_planner`], so a re-synced vector
|
||||
/// of another shape fails in ordinary CI with the reason rather than on the fleet as
|
||||
/// a confusing probe refusal. A second AV1 vector (Main 10, or one that uses
|
||||
/// `show_existing_frame`) is the point at which this should grow the same parameters
|
||||
/// the H.265 body carries — not before.
|
||||
fn av1_parity_run(aus: &[&[u8]], label: &str) {
|
||||
// As the other legs: one codec at a time on the device, and the `set_var` below
|
||||
// happens only under this lock (see `common::gpu_lock`).
|
||||
let _gpu = common::gpu_lock();
|
||||
|
||||
std::env::set_var("PF_VKD_TEST_READBACK", "1");
|
||||
|
||||
let goldens = golden_hashes(GOLDENS_AV1);
|
||||
// Non-vacuity, before any hardware is touched: the right number of entries, all
|
||||
// real digests, all distinct (see the helper's docs — a frozen-frame decoder
|
||||
// must not be able to pass this leg).
|
||||
assert_goldens_are_a_real_set(&goldens, FRAME_COUNT, "data/test-25fps-av1.nv12.sha256");
|
||||
// …and the leg must actually be fed something. An IVF whose packets failed to
|
||||
// parse would hand `collect_hashes` an empty AU list, which delivers no frames
|
||||
// and would then fail as a frame-count mismatch that reads like a decoder defect.
|
||||
assert_eq!(
|
||||
aus.len(),
|
||||
FRAME_COUNT,
|
||||
"{label}: the vector must split into {FRAME_COUNT} temporal units"
|
||||
);
|
||||
|
||||
let setup = common::bring_up(&common::Request {
|
||||
codec: common::AV1,
|
||||
// As the other parity legs: the readback records on the graphics queue, so a
|
||||
// device without a graphics family is skipped rather than defaulted.
|
||||
graphics: common::Graphics::Required,
|
||||
report_families: true,
|
||||
});
|
||||
let handles = setup.handles();
|
||||
|
||||
let hashes = {
|
||||
// SAFETY: as the H.264/H.265 legs — `setup` outlives this block (destroyed
|
||||
// below, after the decoder and readback drop at the block's end), it was
|
||||
// created with the AV1 decode extension + timeline/sync2 features, and its
|
||||
// queue fields name the families/queues it created.
|
||||
let mut decoder = unsafe { VkAv1Decoder::new(&handles, Box::new(NoopQueueLock)) }
|
||||
.expect("wrap the device");
|
||||
// The construction-time shape gate, on the vector's own facts: 4:2:0, 8-bit,
|
||||
// and NO film grain. The third argument is the load-bearing one — grain
|
||||
// synthesis is part of the Vulkan decode PROFILE, so a box that offers only
|
||||
// the grain-enabled profile (or only the disabled one) refuses HERE with a
|
||||
// caps reason instead of failing at the first temporal unit.
|
||||
decoder
|
||||
.probe_stream_support(1, 8, false)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!("{label}: the box must host AV1 Main 4:2:0 8-bit, no film grain — {e:?}");
|
||||
});
|
||||
// SAFETY: as the other legs — live instance/device, queue 0 of `graphics_qf`
|
||||
// was created by the bring-up; destroyed at the end of this block.
|
||||
let readback = unsafe {
|
||||
Readback::new(
|
||||
&setup.instance,
|
||||
setup.pd,
|
||||
&setup.device,
|
||||
setup.graphics_qf,
|
||||
DISPLAY_AV1,
|
||||
EXPECTED_FORMAT,
|
||||
)
|
||||
};
|
||||
let hashes = collect_hashes(&mut decoder, &readback, aus);
|
||||
// SAFETY: every readback was fence-waited inside `read_nv12`; nothing else
|
||||
// references its handles.
|
||||
unsafe { readback.destroy() };
|
||||
hashes
|
||||
};
|
||||
|
||||
// SAFETY: as the other legs — the decoder is gone (its Drop drained the queue and
|
||||
// destroyed its session/pools) and the readback's handles are destroyed.
|
||||
unsafe { setup.destroy() };
|
||||
|
||||
assert_bit_identical(&hashes, &goldens, label);
|
||||
}
|
||||
|
||||
/// The AV1 rung's first pixel evidence.
|
||||
///
|
||||
/// 250 temporal units in, 250 DISPLAYED frames out (the 24 hidden frames the vector
|
||||
/// also codes are decoded, referenced and never shown — module docs), each read back
|
||||
/// as tightly packed NV12 over its `render_width` x `render_height` region and
|
||||
/// compared against libavcodec's software decode.
|
||||
///
|
||||
/// What a failure looks like, and where to point it:
|
||||
/// - **frame 0** — the sequence header or the per-frame parameter conversion:
|
||||
/// `StdVideoAV1SequenceHeader`, the eight per-frame sub-blocks (tile info,
|
||||
/// quantisation, segmentation, loop filter, CDEF, loop restoration, global motion,
|
||||
/// film grain), the tile-group ranges, or the readback geometry. AV1 puts in the
|
||||
/// frame header what H.26x puts in parameter sets, so a single wrong field here
|
||||
/// damages every frame.
|
||||
/// - **frame 1** — the first frame with a reference. Per-reference info, the
|
||||
/// reference-NAME → DPB-slot table, or `ref_frame_idx` ordering.
|
||||
/// - **later, then everything after** — DPB slot management, `refresh_frame_flags`,
|
||||
/// or the hidden frames: a run that is clean until roughly the first multi-frame
|
||||
/// temporal unit and wrong thereafter is the signature of the hidden ALTREF being
|
||||
/// stored wrong or not at all.
|
||||
#[test]
|
||||
#[ignore = "needs a Vulkan Video AV1 decode device (fleet boxes; see module docs)"]
|
||||
fn av1_every_frame_hashes_bit_identical_to_libavcodec() {
|
||||
av1_parity_run(&common::split_av1_aus(common::TEST_25FPS_AV1), "AV1");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CPU coherence guards — NOT `#[ignore]`d.
|
||||
//
|
||||
@@ -776,7 +995,10 @@ fn h265_four_byte_start_codes_decode_bit_identically() {
|
||||
// hardware round trip to disprove.
|
||||
//
|
||||
// Each guard pins the whole chain the parity verdict rests on: the AU split, the
|
||||
// planner's output count, and the golden line count — with NO GPU involved.
|
||||
// planner's output count, the vector's shape and the golden set — with NO GPU
|
||||
// involved. And they are what make the verdicts non-vacuous: a comparison of zero
|
||||
// frames, or of 250 copies of one digest, would otherwise "pass" on any hardware
|
||||
// (see [`assert_goldens_are_a_real_set`]).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
@@ -784,17 +1006,7 @@ fn h265_goldens_and_au_split_agree_with_the_planner() {
|
||||
use pf_bitstream::h265::H265Planner;
|
||||
|
||||
let goldens = golden_hashes(GOLDENS_H265);
|
||||
assert_eq!(
|
||||
goldens.len(),
|
||||
FRAME_COUNT,
|
||||
"data/test-25fps-h265.nv12.sha256 must carry one hash per display frame"
|
||||
);
|
||||
assert!(
|
||||
goldens
|
||||
.iter()
|
||||
.all(|line| line.len() == 64 && line.bytes().all(|b| b.is_ascii_hexdigit())),
|
||||
"every golden line is a bare lowercase SHA-256 hex digest"
|
||||
);
|
||||
assert_goldens_are_a_real_set(&goldens, FRAME_COUNT, "data/test-25fps-h265.nv12.sha256");
|
||||
|
||||
// The AU split the parity leg feeds the decoder. `common::split_h265_aus` is
|
||||
// the copy of pf-bitstream's private splitter, and it keys on HEVC's 2-byte
|
||||
@@ -886,17 +1098,7 @@ fn h264_goldens_and_au_split_agree_with_the_planner() {
|
||||
use pf_bitstream::h264::H264Planner;
|
||||
|
||||
let goldens = golden_hashes(GOLDENS_H264);
|
||||
assert_eq!(
|
||||
goldens.len(),
|
||||
FRAME_COUNT,
|
||||
"data/test-25fps.nv12.sha256 must carry one hash per display frame"
|
||||
);
|
||||
assert!(
|
||||
goldens
|
||||
.iter()
|
||||
.all(|line| line.len() == 64 && line.bytes().all(|b| b.is_ascii_hexdigit())),
|
||||
"every golden line is a bare lowercase SHA-256 hex digest"
|
||||
);
|
||||
assert_goldens_are_a_real_set(&goldens, FRAME_COUNT, "data/test-25fps.nv12.sha256");
|
||||
|
||||
let aus = common::split_h264_aus(common::TEST_25FPS_H264);
|
||||
assert_eq!(
|
||||
@@ -927,17 +1129,7 @@ fn the_main10_vector_is_ten_bit_and_agrees_with_its_goldens() {
|
||||
use pf_bitstream::h265::H265Planner;
|
||||
|
||||
let goldens = golden_hashes(GOLDENS_MAIN10);
|
||||
assert_eq!(
|
||||
goldens.len(),
|
||||
MAIN10_FRAME_COUNT,
|
||||
"data/test-main10.p010.sha256 must carry one hash per display frame"
|
||||
);
|
||||
assert!(
|
||||
goldens
|
||||
.iter()
|
||||
.all(|line| line.len() == 64 && line.bytes().all(|b| b.is_ascii_hexdigit())),
|
||||
"every golden line is a bare lowercase SHA-256 hex digest"
|
||||
);
|
||||
assert_goldens_are_a_real_set(&goldens, MAIN10_FRAME_COUNT, "data/test-main10.p010.sha256");
|
||||
|
||||
let aus = common::split_h265_aus(TEST_MAIN10_H265);
|
||||
assert_eq!(
|
||||
@@ -984,6 +1176,145 @@ fn the_main10_vector_is_ten_bit_and_agrees_with_its_goldens() {
|
||||
);
|
||||
}
|
||||
|
||||
/// The AV1 leg's whole chain, with no GPU: the golden set, the IVF split, the shape
|
||||
/// the leg hard-codes, and — the one that matters — that **250 goldens is the
|
||||
/// DISPLAY count of a 274-frame vector**, re-derived from the planner rather than
|
||||
/// asserted from a comment.
|
||||
///
|
||||
/// Every number here is a way the fleet run could otherwise fail for a reason that
|
||||
/// is not the decoder:
|
||||
///
|
||||
/// - a golden file regenerated per CODED frame would carry 274 hashes and the leg
|
||||
/// would report a frame-count mismatch that reads exactly like dropped frames;
|
||||
/// - an IVF reader that lost packets would feed a short AU list and the leg would
|
||||
/// report the same thing;
|
||||
/// - a re-synced vector at another bit depth, sampling, or with film grain would
|
||||
/// make `probe_stream_support(1, 8, false)` probe the WRONG Vulkan profile and the
|
||||
/// readback expect the wrong format, which on hardware surfaces as a caps refusal
|
||||
/// or half a hashed picture;
|
||||
/// - and if the 24 hidden frames ever disappeared, the leg would still pass while
|
||||
/// having quietly stopped exercising multi-frame temporal units at all — the one
|
||||
/// thing AV1 has that neither H.26x vector does.
|
||||
#[test]
|
||||
fn av1_goldens_and_the_ivf_split_agree_with_the_planner() {
|
||||
use pf_bitstream::av1::Av1Planner;
|
||||
|
||||
let goldens = golden_hashes(GOLDENS_AV1);
|
||||
assert_goldens_are_a_real_set(&goldens, FRAME_COUNT, "data/test-25fps-av1.nv12.sha256");
|
||||
|
||||
// The AU split the parity leg feeds the decoder: one IVF packet per temporal
|
||||
// unit. AV1 carries no start codes, so this is the container's framing rather
|
||||
// than something a scan could get subtly wrong — but a truncated or re-muxed
|
||||
// vector would still shorten it silently.
|
||||
let aus = common::split_av1_aus(common::TEST_25FPS_AV1);
|
||||
assert_eq!(
|
||||
aus.len(),
|
||||
FRAME_COUNT,
|
||||
"the vendored AV1 vector is {FRAME_COUNT} temporal units"
|
||||
);
|
||||
assert!(
|
||||
aus.iter().all(|au| !au.is_empty()),
|
||||
"no temporal unit is empty — an IVF reader that returned empty packets would \
|
||||
make the parity leg decode nothing and blame the decoder"
|
||||
);
|
||||
|
||||
// Walk the CPU planner over the same temporal units. It is the authority on how
|
||||
// many frames the GPU leg can possibly deliver: the decoder builds exactly one
|
||||
// delivered frame per `dpb.outputs` id, and AV1's planner has no `flush` tail.
|
||||
let mut planner = Av1Planner::new();
|
||||
let mut outputs = 0usize;
|
||||
let mut coded_frames = 0usize;
|
||||
let mut multi_frame_units = 0usize;
|
||||
let mut show_existing = 0usize;
|
||||
let mut warnings = 0usize;
|
||||
for (index, au) in aus.iter().enumerate() {
|
||||
let plans = planner.plan_au(au).unwrap_or_else(|e| {
|
||||
panic!("temporal unit {index}: the clean vector must plan without errors, got {e:?}")
|
||||
});
|
||||
if plans.len() > 1 {
|
||||
multi_frame_units += 1;
|
||||
}
|
||||
for plan in &plans {
|
||||
coded_frames += 1;
|
||||
outputs += plan.dpb.outputs.len();
|
||||
warnings += plan.warnings.len();
|
||||
// A `show_existing_frame` decodes nothing and stores nothing.
|
||||
if plan.dpb.stored.is_none() {
|
||||
show_existing += 1;
|
||||
}
|
||||
// Pin the picture shape both AV1 legs hard-code. They call
|
||||
// `probe_stream_support(1, 8, false)` and assert an NV12 output format;
|
||||
// a re-synced Main-10, 4:4:4 or film-grain vector would make both
|
||||
// silently probe and expect the WRONG Vulkan decode profile on the
|
||||
// fleet — grain synthesis is part of the PROFILE, not a per-frame
|
||||
// toggle, so a grain-bearing vector is a different device requirement,
|
||||
// not merely different pixels.
|
||||
assert_eq!(
|
||||
(
|
||||
plan.picture.chroma_format_idc,
|
||||
plan.picture.bit_depth,
|
||||
plan.sequence.film_grain_params_present,
|
||||
),
|
||||
(1, 8, false),
|
||||
"frame {coded_frames} (temporal unit {index}): the vendored AV1 vector \
|
||||
must stay Main 4:2:0 8-bit with no film grain"
|
||||
);
|
||||
if coded_frames == 1 {
|
||||
assert!(plan.picture.is_key, "the vector opens on a key frame");
|
||||
// What `Readback` crops to, and what the goldens hash.
|
||||
assert_eq!(
|
||||
(plan.picture.render_width, plan.picture.render_height),
|
||||
DISPLAY_AV1,
|
||||
"the display (render) region the readback asserts against"
|
||||
);
|
||||
// …and what the pool allocates. Equal to the render region here, so
|
||||
// the vector needs no AV1 conformance-window equivalent — the golden
|
||||
// header's claim.
|
||||
assert_eq!(
|
||||
(plan.picture.upscaled_width, plan.picture.frame_height),
|
||||
DISPLAY_AV1,
|
||||
"the decoded (post-superres) picture IS the display region for \
|
||||
this vector — coded size and render size coincide"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
outputs,
|
||||
goldens.len(),
|
||||
"the planner outputs {outputs} pictures but the goldens carry {} hashes — the \
|
||||
parity leg's frame-count assertion would fail on hardware for a reason that \
|
||||
has nothing to do with the GPU",
|
||||
goldens.len()
|
||||
);
|
||||
assert_eq!(
|
||||
coded_frames,
|
||||
AV1_CODED_FRAME_COUNT,
|
||||
"the vendored AV1 vector codes {AV1_CODED_FRAME_COUNT} frames; {} of them are \
|
||||
hidden, which is why the goldens are {FRAME_COUNT} and not {coded_frames}",
|
||||
AV1_CODED_FRAME_COUNT - FRAME_COUNT
|
||||
);
|
||||
assert_eq!(
|
||||
multi_frame_units, 24,
|
||||
"24 temporal units carry two frames each — the hidden ALTREFs, and the only \
|
||||
reason AV1's `plan_au` returns a vector at all. If this reaches 0 the parity \
|
||||
leg has stopped exercising multi-frame temporal units while still passing"
|
||||
);
|
||||
assert_eq!(
|
||||
show_existing, 0,
|
||||
"this vector uses no `show_existing_frame`; if that ever changes, frames start \
|
||||
being displayed by a route the decoder handles differently and the display \
|
||||
order the goldens assume needs rederiving"
|
||||
);
|
||||
assert_eq!(
|
||||
warnings, 0,
|
||||
"a clean conformance vector must plan without concealment — any warning here \
|
||||
means the parity leg would be hashing concealed pixels against a clean \
|
||||
reference"
|
||||
);
|
||||
}
|
||||
|
||||
/// Count Annex-B start codes in `stream` as `(total, three_byte)`.
|
||||
///
|
||||
/// Emulation prevention guarantees `00 00 01` cannot occur inside a NAL payload,
|
||||
|
||||
@@ -11,15 +11,18 @@
|
||||
//! - a Vulkan 1.3 loader on the library path (`libvulkan.so.1` / `vulkan-1.dll`);
|
||||
//! - a physical device advertising `VK_KHR_video_queue`,
|
||||
//! `VK_KHR_video_decode_queue` and the leg's codec extension
|
||||
//! (`VK_KHR_video_decode_h264` / `VK_KHR_video_decode_h265`), with a queue
|
||||
//! (`VK_KHR_video_decode_h264` / `VK_KHR_video_decode_h265` /
|
||||
//! `VK_KHR_video_decode_av1`), with a queue
|
||||
//! family carrying `VIDEO_DECODE_KHR` ops for that codec;
|
||||
//! - `timelineSemaphore` + `synchronization2` feature support (Vulkan 1.3 core).
|
||||
//! - `timelineSemaphore` + `synchronization2` feature support (Vulkan 1.3 core);
|
||||
//! - on RADV, `RADV_PERFTEST=video_decode` in the environment — for AV1 exactly as
|
||||
//! for the other two, and without it the AV1 leg reports missing silicon it has.
|
||||
//!
|
||||
//! One leg per codec, running the SAME body ([`smoke`]) over the vendored 25fps
|
||||
//! vector of that codec — a box that decodes only one of the two runs that leg and
|
||||
//! reports the other as "no physical device with VK_KHR_video_decode_…", which is
|
||||
//! a fact about the box rather than a failure. Device bring-up lives in
|
||||
//! `tests/common/mod.rs`.
|
||||
//! vector of that codec — a box that decodes only some of the three runs those legs
|
||||
//! and reports the rest as "no physical device with VK_KHR_video_decode_…", which is
|
||||
//! a fact about the box rather than a failure (AV1 is the one most likely to say so
|
||||
//! on today's fleet). Device bring-up lives in `tests/common/mod.rs`.
|
||||
//!
|
||||
//! What they prove: device wrap → caps query/derivation on REAL caps → session +
|
||||
//! parameters creation → the decoupled picture pool → 48 AUs of the vendored
|
||||
@@ -45,11 +48,13 @@ use common::TestDecoder;
|
||||
use pf_vkdecode::DecodeStatus;
|
||||
use pf_vkdecode::DecodedVkFrame;
|
||||
use pf_vkdecode::NoopQueueLock;
|
||||
use pf_vkdecode::VkAv1Decoder;
|
||||
use pf_vkdecode::VkH264Decoder;
|
||||
use pf_vkdecode::VkH265Decoder;
|
||||
|
||||
/// AUs fed: far past either vector's DPB depth (`max_dpb_frames = 7` for the
|
||||
/// H.264 clip), so DPB slots re-activate onto fresh pool images repeatedly.
|
||||
/// AUs fed: far past every vector's DPB depth (`max_dpb_frames = 7` for the
|
||||
/// H.264 clip, eight reference slots for AV1), so DPB slots re-activate onto fresh
|
||||
/// pool images repeatedly.
|
||||
const AUS: usize = 48;
|
||||
/// The REAL client's consumption shape: the consumer holds four delivered frames
|
||||
/// and releases only the oldest beyond that (its channels + preroll + in-flight
|
||||
@@ -77,8 +82,8 @@ struct Geometry {
|
||||
/// Decode [`AUS`] access units while holding [`CLIENT_HOLD`] frames, asserting the
|
||||
/// decode verdict of every frame before its release.
|
||||
///
|
||||
/// One body for both codecs (over `common::TestDecoder`) so "the H.265 leg proves
|
||||
/// what the H.264 leg proves" is structural rather than a claim about two copies.
|
||||
/// One body for all three codecs (over `common::TestDecoder`) so "the AV1 leg proves
|
||||
/// what the H.264 leg proves" is structural rather than a claim about three copies.
|
||||
fn smoke(decoder: &mut impl TestDecoder, aus: &[&[u8]], geometry: &Geometry) {
|
||||
// The smoke legs exist to prove the PRODUCTION pool arrangement survives 48
|
||||
// AUs at the client's hold depth. `PF_VKD_TEST_READBACK` adds TRANSFER_SRC to
|
||||
@@ -131,7 +136,7 @@ fn smoke(decoder: &mut impl TestDecoder, aus: &[&[u8]], geometry: &Geometry) {
|
||||
}
|
||||
// A pool built for the wrong picture format decodes and then
|
||||
// renders with the wrong maths (`DecodedVkFrame::format` docs);
|
||||
// both vectors are 8-bit 4:2:0, so both must land on NV12.
|
||||
// all three vectors are 8-bit 4:2:0, so all three must land on NV12.
|
||||
assert_eq!(
|
||||
frame.format,
|
||||
pf_vkdecode::NV12,
|
||||
@@ -267,13 +272,67 @@ fn h265_decodes_48_aus_holding_four_frames_like_the_real_client() {
|
||||
unsafe { setup.destroy() };
|
||||
}
|
||||
|
||||
/// The AV1 leg — the rung's first hardware evidence of ANY kind.
|
||||
///
|
||||
/// The same 48 access units at the same client hold depth, but AV1 loads the pool
|
||||
/// harder than either H.26x leg does and that is the point of running it: the first
|
||||
/// 48 temporal units carry 53 coded frames to show 48 (the number
|
||||
/// [`the_delivery_floor_is_under_what_the_planners_emit_from_the_first_48_aus`]
|
||||
/// prints), each hidden frame keeps a pool image resident as a reference while
|
||||
/// nothing displays it, and eight reference slots re-activate against that. A pool
|
||||
/// sized as if one access unit meant one picture starves exactly here — which is this
|
||||
/// leg's whole job, since `gpu_parity`'s AV1 leg would report the same starvation as a
|
||||
/// decode failure with a less obvious cause.
|
||||
#[test]
|
||||
#[ignore = "needs a Vulkan Video AV1 decode device (fleet boxes; see module docs)"]
|
||||
fn av1_decodes_48_aus_holding_four_frames_like_the_real_client() {
|
||||
// One codec at a time on the device (see `common::gpu_lock`).
|
||||
let _gpu = common::gpu_lock();
|
||||
|
||||
let setup = common::bring_up(&common::Request {
|
||||
codec: common::AV1,
|
||||
graphics: common::Graphics::DecodeFamilyIsFine,
|
||||
report_families: true,
|
||||
});
|
||||
let handles = setup.handles();
|
||||
{
|
||||
// SAFETY: as the H.264 leg — `setup` outlives this block and was created
|
||||
// with the AV1 decode extension + timeline/sync2 features.
|
||||
let mut decoder = unsafe { VkAv1Decoder::new(&handles, Box::new(NoopQueueLock)) }
|
||||
.expect("wrap the device");
|
||||
// The construction-time shape gate on the vector's own facts (Main, 4:2:0,
|
||||
// 8-bit, NO film grain → NV12). The film-grain argument is the one that has
|
||||
// no H.26x counterpart: grain synthesis is part of the Vulkan decode PROFILE,
|
||||
// so a box offering only the grain-enabled profile refuses HERE with a caps
|
||||
// reason rather than at the first temporal unit.
|
||||
decoder.probe_stream_support(1, 8, false).expect(
|
||||
"the box must host AV1 Main 4:2:0 8-bit without film grain (the vector's shape)",
|
||||
);
|
||||
smoke(
|
||||
&mut decoder,
|
||||
&common::split_av1_aus(common::TEST_25FPS_AV1),
|
||||
&Geometry {
|
||||
display: (320, 240),
|
||||
// As HEVC: no hardware evidence for AV1's `pictureAccessGranularity`
|
||||
// on any fleet box yet, so the leg prints what it allocates rather
|
||||
// than asserting a number nobody has observed. AV1's decode extent is
|
||||
// the POST-superres width, which is another reason not to guess.
|
||||
exact_coded: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
// SAFETY: as the H.264 leg — the decoder is gone and nothing else references
|
||||
// the setup's handles.
|
||||
unsafe { setup.destroy() };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CPU coherence guard — NOT `#[ignore]`d.
|
||||
// CPU coherence guards — NOT `#[ignore]`d.
|
||||
//
|
||||
// The legs above only run on the fleet, so [`MIN_DELIVERED`] would otherwise be a
|
||||
// number copied from the H.264 leg and never checked against the H.265 vector's
|
||||
// own reorder depth. It is the CPU planner that decides how many of the first
|
||||
// [`AUS`] pictures can possibly be delivered — the decoder builds exactly one
|
||||
// number copied from the H.264 leg and never checked against the H.265 or AV1
|
||||
// vector's own reorder depth. It is the CPU planner that decides how many of the
|
||||
// first [`AUS`] pictures can possibly be delivered — the decoder builds exactly one
|
||||
// frame per `dpb.outputs` id — so the floor is checkable here, without a GPU, and
|
||||
// a re-synced vector that reorders more deeply fails HERE instead of looking like
|
||||
// a pool-starvation bug on hardware.
|
||||
@@ -313,7 +372,30 @@ fn the_delivery_floor_is_under_what_the_planners_emit_from_the_first_48_aus() {
|
||||
})
|
||||
.sum::<usize>()
|
||||
};
|
||||
eprintln!("outputs from the first {AUS} AUs: h264={h264} h265={h265}");
|
||||
// AV1 needs the extra fold: one temporal unit can plan SEVERAL frames, so the
|
||||
// outputs of a unit are the outputs of all of its plans — and counting one plan
|
||||
// per unit is exactly how a reader would under-count here.
|
||||
let (av1, av1_frames) = {
|
||||
let mut planner = pf_bitstream::av1::Av1Planner::new();
|
||||
let mut outputs = 0usize;
|
||||
let mut frames = 0usize;
|
||||
for (index, au) in common::split_av1_aus(common::TEST_25FPS_AV1)
|
||||
.iter()
|
||||
.take(AUS)
|
||||
.enumerate()
|
||||
{
|
||||
let plans = planner
|
||||
.plan_au(au)
|
||||
.unwrap_or_else(|e| panic!("AV1 temporal unit {index} must plan, got {e:?}"));
|
||||
frames += plans.len();
|
||||
outputs += plans.iter().map(|p| p.dpb.outputs.len()).sum::<usize>();
|
||||
}
|
||||
(outputs, frames)
|
||||
};
|
||||
eprintln!(
|
||||
"outputs from the first {AUS} AUs: h264={h264} h265={h265} av1={av1} \
|
||||
(av1 decoded {av1_frames} frames to show {av1} — the hidden ones)"
|
||||
);
|
||||
// No `flush` here on purpose: the smoke legs do not flush either, so the
|
||||
// planner's un-flushed output count is exactly the frame budget they have.
|
||||
assert!(
|
||||
@@ -326,4 +408,19 @@ fn the_delivery_floor_is_under_what_the_planners_emit_from_the_first_48_aus() {
|
||||
"the H.265 leg asserts >= {MIN_DELIVERED} delivered but the planner only \
|
||||
outputs {h265} pictures from the first {AUS} AUs"
|
||||
);
|
||||
assert!(
|
||||
av1 >= MIN_DELIVERED,
|
||||
"the AV1 leg asserts >= {MIN_DELIVERED} delivered but the planner only \
|
||||
outputs {av1} pictures from the first {AUS} temporal units"
|
||||
);
|
||||
// The AV1 leg's load is not the same as the other two's, and the assertion above
|
||||
// cannot see the difference: the pool must hold the hidden frames as well as the
|
||||
// shown ones. If these ever became equal, the leg would have stopped exercising
|
||||
// multi-frame temporal units — the one pool pressure AV1 has that H.26x has not —
|
||||
// while still passing everything above.
|
||||
assert!(
|
||||
av1_frames > av1,
|
||||
"the first {AUS} AV1 temporal units must decode MORE frames ({av1_frames}) \
|
||||
than they show ({av1}); equal counts mean the hidden-frame coverage is gone"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user