Step 5 + 6: split two bench binaries into directory form with sibling
helper modules. Cargo auto-discovers src/bin/<name>/main.rs as binary
<name>; no Cargo.toml change needed.
vaapi_import_bench (973 LOC) -> 6 files:
- main.rs main() + mod declarations
- stats.rs BenchArgs + PipelineMode + FrameStats + impl
- software.rs SoftwareEncoder + SwsContext (with Drop) + create_*
+ encode_yuv_frame + finish_encoder
- pipeline_cpu.rs run_cpu_pipeline
- pipeline_gpu.rs import_frame + build_gpu_filter_graph + run_gpu_pipeline
- util.rs output_for_mode + print_detailed_results + print_comparison
sw_encode_bench (547 LOC) -> 2 files:
- main.rs main() + mod declarations (main is ~480 LOC and stays
intact per Oracle/Momis risk note on function
decomposition)
- stats.rs BenchArgs + FrameStats + impl + pix_fmt helper
Both main.rs files use #[path = "../common/mod.rs"] mod common; to keep
sharing src/bin/common/mod.rs (path adjusted for the new directory depth).
DEVATION NOTE on visibility:
The original single-file binaries accessed struct fields across what
became module boundaries (70+ accesses, e.g. encoder.yuv_frame in
run_cpu_pipeline, sws_ctx.0 in run_gpu_pipeline, stats.frames_encoded
in main, stats.mmap_us in main). Rule 2 forbids widening visibility on
struct fields. After 2 build attempts confirmed there is no way to
perform the specified split without widening, the minimum necessary
pub(crate) was applied to:
- vaapi_import_bench/stats.rs: BenchArgs fields, PipelineMode (type
only), FrameStats fields, FrameStats::{avg_ms, avg_total_ms,
achieved_fps, theoretical_fps}
- vaapi_import_bench/software.rs: SoftwareEncoder fields (enc_video,
octx, yuv_frame, codec_name), SwsContext.0, all four functions
- vaapi_import_bench/pipeline_*.rs: run_cpu_pipeline, run_gpu_pipeline,
import_frame (build_gpu_filter_graph kept private)
- vaapi_import_bench/util.rs: output_for_mode, print_detailed_results,
print_comparison
- sw_encode_bench/stats.rs: BenchArgs fields, FrameStats fields,
FrameStats::avg_ms, pix_fmt
No pub (truly public) was used anywhere. All widening is to pub(crate),
keeping these symbols private outside the binary crate.
Verification (all green):
- cargo build --bins / cargo build --release --bins
- cargo test (79 lib + 3 integration = 82 pass, 1 ignored — unchanged)
- cargo clippy --all-targets -- -D warnings
- cargo fmt --check
- --help smoke test on both binaries
108 lines
3.5 KiB
Rust
108 lines
3.5 KiB
Rust
use std::path::Path;
|
|
|
|
use crate::stats::{FrameStats, PipelineMode};
|
|
|
|
pub(crate) fn output_for_mode(base: &str, mode: PipelineMode, split: bool) -> String {
|
|
if !split || base.contains("null") {
|
|
return base.to_string();
|
|
}
|
|
|
|
let path = Path::new(base);
|
|
let suffix = match mode {
|
|
PipelineMode::Cpu => "cpu",
|
|
PipelineMode::Gpu => "gpu",
|
|
PipelineMode::Both => unreachable!(),
|
|
};
|
|
let file_name = path.file_name().and_then(|s| s.to_str()).unwrap_or(base);
|
|
let split_name = if let Some((stem, ext)) = file_name.rsplit_once('.') {
|
|
format!("{stem}.{suffix}.{ext}")
|
|
} else {
|
|
format!("{file_name}.{suffix}")
|
|
};
|
|
path.with_file_name(split_name)
|
|
.to_string_lossy()
|
|
.into_owned()
|
|
}
|
|
|
|
pub(crate) fn print_detailed_results(
|
|
label: &str,
|
|
stats: &FrameStats,
|
|
src_width: u32,
|
|
src_height: u32,
|
|
enc_width: u32,
|
|
enc_height: u32,
|
|
) {
|
|
println!();
|
|
println!("=== {label} Pipeline Results ===");
|
|
println!("Capture resolution: {}x{}", src_width, src_height);
|
|
println!("Encode resolution: {}x{}", enc_width, enc_height);
|
|
println!("Frames encoded: {}", stats.frames_encoded);
|
|
println!("Total time: {:.2}s", stats.elapsed_secs);
|
|
println!("Output: {}", stats.output_path);
|
|
if stats.import_failures > 0 {
|
|
println!("Import failures: {}", stats.import_failures);
|
|
}
|
|
println!(
|
|
"import avg: {:.2} ms/frame",
|
|
FrameStats::avg_ms(&stats.import_us)
|
|
);
|
|
if !stats.filter_us.is_empty() {
|
|
println!(
|
|
"filter avg: {:.2} ms/frame",
|
|
FrameStats::avg_ms(&stats.filter_us)
|
|
);
|
|
}
|
|
println!(
|
|
"transfer avg: {:.2} ms/frame",
|
|
FrameStats::avg_ms(&stats.transfer_us)
|
|
);
|
|
if !stats.scale_us.is_empty() {
|
|
println!(
|
|
"scale avg: {:.2} ms/frame",
|
|
FrameStats::avg_ms(&stats.scale_us)
|
|
);
|
|
}
|
|
if !stats.format_us.is_empty() {
|
|
println!(
|
|
"format avg: {:.2} ms/frame",
|
|
FrameStats::avg_ms(&stats.format_us)
|
|
);
|
|
}
|
|
println!(
|
|
"encode ({}): {:.2} ms/frame",
|
|
stats.codec_name,
|
|
FrameStats::avg_ms(&stats.encode_us)
|
|
);
|
|
println!("total avg: {:.2} ms/frame", stats.avg_total_ms());
|
|
println!("achieved FPS: {:.1}", stats.achieved_fps());
|
|
println!("max theoretical: {:.1} FPS", stats.theoretical_fps());
|
|
}
|
|
|
|
pub(crate) fn print_comparison(cpu: Option<&FrameStats>, gpu: Option<&FrameStats>) {
|
|
println!();
|
|
println!("=== Pipeline Comparison ===");
|
|
if let Some(s) = cpu {
|
|
println!(
|
|
"CPU: import={:.2}ms transfer={:.2}ms scale={:.2}ms encode={:.2}ms total={:.2}ms ({:.1} FPS)",
|
|
FrameStats::avg_ms(&s.import_us),
|
|
FrameStats::avg_ms(&s.transfer_us),
|
|
FrameStats::avg_ms(&s.scale_us),
|
|
FrameStats::avg_ms(&s.encode_us),
|
|
s.avg_total_ms(),
|
|
s.theoretical_fps(),
|
|
);
|
|
}
|
|
if let Some(s) = gpu {
|
|
println!(
|
|
"GPU: import={:.2}ms filter={:.2}ms transfer={:.2}ms format={:.2}ms encode={:.2}ms total={:.2}ms ({:.1} FPS)",
|
|
FrameStats::avg_ms(&s.import_us),
|
|
FrameStats::avg_ms(&s.filter_us),
|
|
FrameStats::avg_ms(&s.transfer_us),
|
|
FrameStats::avg_ms(&s.format_us),
|
|
FrameStats::avg_ms(&s.encode_us),
|
|
s.avg_total_ms(),
|
|
s.theoretical_fps(),
|
|
);
|
|
}
|
|
}
|