feat(vkdecode): M7's Vulkan CPU half — AV1 into the Std structures

The sequence header and the picture info, converted for
VK_KHR_video_decode_av1. Same shape as the H.264 and H.265 conversions, and
the same ownership contract: boxed backing beside the Std struct that points
at it, movable wrapper, no mutation, not Clone.

AV1 puts almost the whole frame header in the PICTURE info rather than in a
parameter set, so StdVideoDecodeAV1PictureInfo carries eight pointers to
per-frame blocks — tile info, quantisation, segmentation, loop filter, CDEF,
loop restoration, global motion, film grain — and the tile info carries four
more arrays of its own. Session parameters, by contrast, hold exactly one
sequence header. That asymmetry is why params_av1 is the small module here
and pic_av1 the large one.

The plan now carries the parsed frame header whole. The client needs a
digest — size, depth, colour, keyframe — but a backend needs nearly all of
the header, so AuPlan carries it the way its H.264 and H.265 siblings carry
their activated parameter sets: a backend builds from exactly what was
parsed, never by re-reading the access unit.

referenceNameSlotIndices holds DPB SLOT indices, not positions in the
reference list, and that is the HEVC RPS defect's exact shape in a narrower
place. Measured rather than argued: over the vendored vector the two
readings disagree 566 times across 274 frames, and the test fails if they
ever stop disagreeing, because then it would no longer be able to tell the
conventions apart.

Two places where transcription would have been wrong, both caught by the
types and then by asking the spec:

The parser's film-grain point arrays are 16 entries where the Std ones are
14 (luma) and 10 (chroma) — the spec's own maxima. The counts are validated
against the Std capacity and the copy is bounded by them; a stream declaring
more is refused, because a decoder handed fewer scaling points than the
stream declared synthesises different grain.

`coded_denom` is the superres denominator less SUPERRES_DENOM_MIN and only
meaningful where superres is in use, and `UsesLr` is derived — no frame
header codes it — from whether any plane's restoration type is not NONE.

Film grain rides only where the sequence enables it AND the frame applies
it, with the apply_grain flag set from whether a block is attached, so the
flag and the pointer cannot disagree.

Gates: macOS fmt/clippy/345 tests, container clippy -D warnings over six
crates, 800 tests, workspace check.
This commit is contained in:
2026-08-06 18:00:59 +02:00
parent 7f83ec6c2f
commit 83cfabda89
4 changed files with 849 additions and 1 deletions
+23 -1
View File
@@ -38,7 +38,6 @@ use std::ops::Range;
use std::rc::Rc;
use cros_codecs::codec::av1::parser::FrameHeaderObu;
use cros_codecs::codec::av1::parser::FrameType;
use cros_codecs::codec::av1::parser::ObuAction;
use cros_codecs::codec::av1::parser::ParsedObu;
use cros_codecs::codec::av1::parser::Parser;
@@ -46,6 +45,13 @@ use cros_codecs::codec::av1::parser::SequenceHeaderObu;
use crate::h264::ColourDescription;
/// The parsed types a backend conversion names, re-exported so each names them
/// through this module rather than reaching into the vendored crate — the same
/// courtesy [`crate::h264`] does with its `Sps`/`Pps`.
pub use cros_codecs::codec::av1::parser::FrameHeaderObu as ParsedFrameHeader;
pub use cros_codecs::codec::av1::parser::FrameType;
pub use cros_codecs::codec::av1::parser::SequenceHeaderObu as ParsedSequenceHeader;
/// A stable identity for a decoded picture, the same currency the other two planners
/// deal in: the backends key their surface tables by it and never by slot index.
pub type PicId = u64;
@@ -133,6 +139,17 @@ pub struct AuPlan {
pub dpb_refs: Vec<RefPic>,
pub warnings: Vec<PlanWarning>,
pub sequence: Rc<SequenceHeaderObu>,
/// The frame header this plan was built from, whole.
///
/// [`Self::picture`] is the digest the CLIENT needs — size, depth, colour,
/// keyframe — while a hardware backend needs nearly all of the header:
/// AV1 puts tile info, quantisation, segmentation, loop filter, CDEF, loop
/// restoration, global motion and film grain in the per-frame header rather
/// than in a parameter set, and every one of them reaches the driver. Carried
/// whole for the same reason the H.264 and H.265 plans carry their activated
/// SPS/PPS: a backend must build its structures from exactly what was parsed,
/// never by re-reading the access unit.
pub header: Rc<FrameHeaderObu>,
}
/// Concealment signals: planning continues, the session layer requests recovery.
@@ -342,6 +359,9 @@ impl Av1Planner {
tiles: Vec<TilePlan>,
mut warnings: Vec<PlanWarning>,
) -> Result<AuPlan, PlanError> {
// Shared with the plan: the backends need the whole header and there is no
// reason for each to own a copy of a struct this size.
let header = Rc::new(header);
let dpb_refs = self.dpb_refs();
// `show_existing_frame` decodes nothing: it displays a slot's contents.
@@ -374,6 +394,7 @@ impl Av1Planner {
removed,
},
dpb_refs,
header: header.clone(),
warnings,
sequence,
});
@@ -425,6 +446,7 @@ impl Av1Planner {
removed,
},
dpb_refs,
header: header.clone(),
warnings,
sequence,
})