use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll, Waker};
use zvidlib::hevc_decode_profile as profile;
use zvidlib::hevc_decode_profile::paired;
use zvidlib::hevc_narrow_interp as narrow_interp;
use zvidlib::io::MemorySource;
use zvidlib::simd::{self, SimdIsa};
use zvidlib::{
CancellationToken, Codec, CodecProfile, ColorRange, DecodedVideoFrame, EncodedVideoSample,
HardwarePreference, Limits, Mp4Demuxer, Mp4DemuxerOptions, PixelFormat, VideoDecoderConfig,
VideoDecoderFactory, native_hevc_video_decoder_factory,
};
const DEFAULT_FRAMES: usize = 48;
const PAIR_ROUNDS: usize = 12;
fn main() {
let mut positional = Vec::new();
let mut pair = false;
let mut rounds = PAIR_ROUNDS;
let mut args = std::env::args().skip(1);
while let Some(arg) = args.next() {
match arg.as_str() {
"--pair" => pair = true,
"--rounds" => {
rounds = args
.next()
.and_then(|value| value.parse().ok())
.expect("--rounds takes a positive round count");
assert!(rounds > 0, "--rounds takes a positive round count");
}
_ => positional.push(arg),
}
}
let frames: usize = positional
.first()
.map_or(DEFAULT_FRAMES, |arg| arg.parse().expect("frame count"));
let isa = positional.get(1).map(|name| parse_isa(name));
if let Some(isa) = isa {
simd::set_override(Some(isa));
}
let active = simd::active();
let (configuration, samples) = bundled_sample();
if pair {
pair_narrow_accumulation(&configuration, &samples, frames, rounds, active);
if isa.is_some() {
simd::set_override(None);
}
return;
}
let (decoded, report) = decode_profiled(&configuration, &samples, frames, |_| {});
if isa.is_some() {
simd::set_override(None);
}
println!("# HEVC decode stage attribution");
println!();
println!(
"{decoded} frames of examples/media/BigBuckBunny.mp4, instruction set `{active:?}`, \
{:.1} ms total ({:.2} ms/frame)",
report.total().as_secs_f64() * 1e3,
report.total().as_secs_f64() * 1e3 / decoded.max(1) as f64,
);
println!();
print!("{}", report.markdown_table(decoded));
println!();
println!(
"Vectorized stages cover {:.1}% of the measured total, and {:.1}% of decode \
proper (total minus `color_convert`, which is output conversion, not decoding).",
report.vectorized_share() * 100.0,
report.vectorized_decode_share() * 100.0,
);
println!(
"Amdahl ceiling on the measured total: infinitely fast vector kernels would run \
{:.2}x faster; a uniform 2x on those stages gives {:.2}x, a uniform 4x gives {:.2}x.",
report.max_whole_frame_speedup(),
report.speedup_at(2.0),
report.speedup_at(4.0),
);
println!(
"`color_convert` alone is {:.1}% of the measured total; it is output conversion \
rather than decoding, and since issue #219 it has a vector kernel of its own.",
report.share(profile::Stage::ColorConvert) * 100.0,
);
println!(
"Serial entropy decoding (`slice_data_cabac` + `residual_cabac`) is {:.1}% of the total.",
(report.share(profile::Stage::SliceData) + report.share(profile::Stage::Residual)) * 100.0,
);
println!(
"Profiler overhead is at most {:.1} ms of that ({:.1}%, {} scopes).",
report.overhead().as_secs_f64() * 1e3,
report.overhead().as_secs_f64() / report.total().as_secs_f64() * 100.0,
report.scopes(),
);
}
fn decode_profiled(
configuration: &VideoDecoderConfig,
samples: &[EncodedVideoSample],
frames: usize,
mut each: impl FnMut(&DecodedVideoFrame),
) -> (usize, profile::Report) {
let factory = native_hevc_video_decoder_factory();
let mut decoder = factory
.create(configuration, &Limits::default())
.expect("the software HEVC decoder is constructible");
let cancellation = CancellationToken::new();
let mut warmup = 0usize;
let mut iter = samples.iter();
for encoded in iter.by_ref() {
warmup += decoder
.submit(encoded, &cancellation)
.expect("the bundled sample decodes")
.len();
if warmup >= 2 {
break;
}
}
assert!(
profile::start(),
"stage profiling needs a monotonic clock; this target has none"
);
let mut decoded = 0usize;
for encoded in iter {
let batch = decoder
.submit(encoded, &cancellation)
.expect("the bundled sample decodes");
decoded += batch.len();
for frame in &batch {
each(frame);
}
if decoded >= frames {
break;
}
}
let report = profile::finish().expect("profiling was started");
(decoded, report)
}
fn pair_narrow_accumulation(
configuration: &VideoDecoderConfig,
samples: &[EncodedVideoSample],
frames: usize,
rounds: usize,
active: SimdIsa,
) {
const ARMS: [(&str, Option<bool>); 3] = [
("wide (`i32`, pre-#404)", Some(false)),
("shipped (`narrows` decides)", None),
("control (wide again)", Some(false)),
];
let mut digests = Vec::new();
let mut counts = Vec::new();
for (_, narrow) in ARMS {
narrow_interp::set_override(narrow);
let mut digest = 0xcbf2_9ce4_8422_2325u64;
let (decoded, _) = decode_profiled(configuration, samples, frames, |frame| {
digest = fnv1a(digest, &frame.presentation_index.0.to_le_bytes());
for plane in &frame.frame.planes {
digest = fnv1a(digest, &plane.data);
}
});
digests.push(digest);
counts.push(decoded);
}
narrow_interp::set_override(None);
assert_eq!(
digests[0], digests[1],
"the narrow accumulation changed a decoded sample"
);
assert_eq!(
digests[0], digests[2],
"the decode is not reproducible between two identical arms"
);
assert!(
counts.iter().all(|&count| count == counts[0]),
"the arms decoded different frame counts: {counts:?}"
);
let decoded = counts[0];
let mut totals = vec![vec![0f64; rounds]; ARMS.len()];
let mut filters = vec![vec![0f64; rounds]; ARMS.len()];
for round in 0..rounds {
for offset in 0..ARMS.len() {
let slot = (round + offset) % ARMS.len();
narrow_interp::set_override(ARMS[slot].1);
let (count, report) = decode_profiled(configuration, samples, frames, |_| {});
let per_frame = count.max(1) as f64;
totals[slot][round] = report.total().as_secs_f64() * 1e3 / per_frame;
filters[slot][round] =
report.stage(profile::Stage::InterPredFilter).as_secs_f64() * 1e3 / per_frame;
}
}
narrow_interp::set_override(None);
println!("# HEVC narrow interpolation accumulation, whole-decode A/B");
println!();
println!(
"{decoded} frames of examples/media/BigBuckBunny.mp4, instruction set `{active:?}`, \
{rounds} interleaved rounds in one process, elementwise minimum."
);
println!();
println!("| Arm | ms/frame (min) | `inter_pred_filter` ms/frame (min) |");
println!("| --- | ---: | ---: |");
for (slot, (name, _)) in ARMS.iter().enumerate() {
println!(
"| {name} | {:.3} | {:.3} |",
minimum(&totals[slot]),
minimum(&filters[slot])
);
}
println!();
if rounds < paired::MIN_FLOOR_ROUNDS {
println!(
"**No noise floor is reported below {} rounds, and this run took {rounds}.** \
The floor is an interquartile width of the host's own round-to-round \
scatter, and at {rounds} rounds fewer than two readings fall beyond each \
end of that band, so its width is set by a single round rather than by a \
sampled spread — a floor drawn from it reads *tighter* than a longer run \
on the same host without the host having been any quieter. Re-run with \
`--rounds {}` or more.",
paired::MIN_FLOOR_ROUNDS,
paired::MIN_FLOOR_ROUNDS,
);
println!();
}
for (label, arms) in [("whole decode", &totals), ("`inter_pred_filter`", &filters)] {
let effect = paired_ratios(&arms[0], &arms[1]);
let control = paired_ratios(&arms[0], &arms[2]);
let (effect_median, effect_low, effect_high) = paired::band(&effect);
let (control_median, control_low, control_high) = paired::band(&control);
println!(
"{label}: shipped against wide **{effect_median:.4}x** \
[{effect_low:.4}, {effect_high:.4}] interquartile over {rounds} rounds"
);
println!(
" control against wide (identical code, true answer 1.0000x) \
**{control_median:.4}x** [{control_low:.4}, {control_high:.4}]"
);
let effect_size = (effect_median - 1.0).abs();
let Some(floor) = paired::floor(&control) else {
println!(
" not resolved — {rounds} rounds underwrite no floor (see above), so \
the {:.2}% effect this run reads is unbounded rather than small.",
effect_size * 100.0,
);
continue;
};
if effect_size > floor && (effect_low - 1.0) * (effect_high - 1.0) > 0.0 {
println!(
" resolved — the {:.2}% effect clears the {:.2}% floor this instrument \
reaches on this host over {rounds} rounds, and its band does not \
straddle 1.0000x.",
effect_size * 100.0,
floor * 100.0,
);
} else {
println!(
" not resolved — the effect is bounded by the {:.2}% floor this \
instrument reaches on this host over {rounds} rounds.",
floor * 100.0,
);
}
}
}
fn minimum(values: &[f64]) -> f64 {
values.iter().copied().fold(f64::INFINITY, f64::min)
}
fn paired_ratios(numerator: &[f64], denominator: &[f64]) -> Vec<f64> {
numerator
.iter()
.zip(denominator)
.map(|(&a, &b)| a / b)
.collect()
}
fn fnv1a(mut hash: u64, bytes: &[u8]) -> u64 {
for &byte in bytes {
hash ^= u64::from(byte);
hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
}
hash
}
fn parse_isa(name: &str) -> SimdIsa {
simd::available()
.iter()
.copied()
.find(|isa| format!("{isa:?}").eq_ignore_ascii_case(name))
.unwrap_or_else(|| {
panic!(
"unknown instruction set {name:?}; this host offers {:?}",
simd::available()
)
})
}
fn bundled_sample() -> (VideoDecoderConfig, Vec<EncodedVideoSample>) {
let limits = Limits::default();
let source = MemorySource::new(include_bytes!("media/BigBuckBunny.mp4").to_vec());
let movie = block_on(Mp4Demuxer::open(&source, Mp4DemuxerOptions::default()))
.expect("the bundled sample is a readable MP4");
let track = movie.track(1).expect("the bundled sample has track 1");
let dimensions = track
.dimensions
.expect("the bundled sample's video track is dimensioned");
let samples = block_on(track.to_encoded_video_samples(&source, &limits))
.expect("the bundled sample's video samples are readable");
(
VideoDecoderConfig {
codec: Codec::Hevc,
profile: CodecProfile::HevcMain,
coded_dimensions: dimensions,
output_format: PixelFormat::Rgba8,
color_range: ColorRange::Limited,
hardware: HardwarePreference::Avoid,
configuration: track.decoder_config.clone(),
},
samples,
)
}
fn block_on<T>(future: impl Future<Output = T>) -> T {
let waker = Waker::noop();
let mut context = Context::from_waker(waker);
let mut future = Box::pin(future);
loop {
match Pin::new(&mut future).poll(&mut context) {
Poll::Ready(value) => return value,
Poll::Pending => std::thread::yield_now(),
}
}
}