use crate::encoder::EncoderConfig;
use crate::types::PixelFormat;
const ENCODE_THROUGHPUT_MAX_MPIXELS: f64 = 40.0;
const ENCODE_THROUGHPUT_TYP_MPIXELS: f64 = 15.0;
const ENCODE_THROUGHPUT_MIN_MPIXELS: f64 = 8.0;
const DECODE_THROUGHPUT_MAX_MPIXELS: f64 = 120.0;
const DECODE_THROUGHPUT_TYP_MPIXELS: f64 = 80.0;
const DECODE_THROUGHPUT_MIN_MPIXELS: f64 = 40.0;
const ENCODE_MEMORY_MIN_MULT: f64 = 0.9;
const ENCODE_MEMORY_TYP_MULT: f64 = 1.0;
const ENCODE_MEMORY_MAX_MULT: f64 = 1.3;
const DECODE_MEMORY_MIN_MULT: f64 = 1.0;
const DECODE_MEMORY_TYP_MULT: f64 = 1.0;
const DECODE_MEMORY_MAX_MULT: f64 = 1.1;
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct EncodeEstimate {
pub peak_memory_bytes_min: u64,
pub peak_memory_bytes: u64,
pub peak_memory_bytes_max: u64,
pub allocations: u32,
pub time_ms_min: f32,
pub time_ms: f32,
pub time_ms_max: f32,
pub output_bytes: u64,
pub input_bytes: u64,
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct DecodeEstimate {
pub peak_memory_bytes_min: u64,
pub peak_memory_bytes: u64,
pub peak_memory_bytes_max: u64,
pub allocations: u32,
pub time_ms_min: f32,
pub time_ms: f32,
pub time_ms_max: f32,
pub output_bytes: u64,
}
#[must_use]
pub fn estimate_encode(width: u32, height: u32, config: &EncoderConfig) -> EncodeEstimate {
let pixels = (width as u64) * (height as u64);
let base_memory = config.estimate_memory(width, height) as u64;
let peak_memory_bytes_min = (base_memory as f64 * ENCODE_MEMORY_MIN_MULT) as u64;
let peak_memory_bytes = (base_memory as f64 * ENCODE_MEMORY_TYP_MULT) as u64;
let peak_memory_bytes_max = (base_memory as f64 * ENCODE_MEMORY_MAX_MULT) as u64;
let pixels_f = pixels as f64;
let prog_factor = if config.is_progressive() { 0.7 } else { 1.0 };
let time_ms_min = (pixels_f / (ENCODE_THROUGHPUT_MAX_MPIXELS * prog_factor * 1000.0)) as f32;
let time_ms = (pixels_f / (ENCODE_THROUGHPUT_TYP_MPIXELS * prog_factor * 1000.0)) as f32;
let time_ms_max = (pixels_f / (ENCODE_THROUGHPUT_MIN_MPIXELS * prog_factor * 1000.0)) as f32;
let input_bytes = pixels * 3; let output_bytes = input_bytes / 10;
let allocations = 25;
EncodeEstimate {
peak_memory_bytes_min,
peak_memory_bytes,
peak_memory_bytes_max,
allocations,
time_ms_min,
time_ms,
time_ms_max,
output_bytes,
input_bytes,
}
}
#[must_use]
pub fn estimate_encode_ceiling(width: u32, height: u32, config: &EncoderConfig) -> EncodeEstimate {
let mut est = estimate_encode(width, height, config);
let ceiling = config.estimate_memory_ceiling(width, height) as u64;
est.peak_memory_bytes_min = ceiling;
est.peak_memory_bytes = ceiling;
est.peak_memory_bytes_max = ceiling;
est
}
#[must_use]
pub fn estimate_decode(width: u32, height: u32, format: PixelFormat) -> DecodeEstimate {
let output_bpp = format.bytes_per_pixel() as u8;
let w = width as usize;
let h = height as usize;
let pixels = (width as u64) * (height as u64);
let output_bytes = pixels * (output_bpp as u64);
let mcu_cols = (w + 7) / 8;
let strip_width = mcu_cols * 8;
let strip_height = 8;
let strip_size = strip_width * strip_height;
let strip_total = strip_size * 2 * 3;
let rgb_size = w * h * 3;
let streaming_total = strip_total + rgb_size;
let blocks_per_component = mcu_cols * ((h + 7) / 8);
let coeff_storage = blocks_per_component * 130 * 3;
let base_memory = streaming_total.max(coeff_storage + rgb_size) as u64;
let peak_memory_bytes_min = (base_memory as f64 * DECODE_MEMORY_MIN_MULT) as u64;
let peak_memory_bytes = (base_memory as f64 * DECODE_MEMORY_TYP_MULT) as u64;
let peak_memory_bytes_max = (base_memory as f64 * DECODE_MEMORY_MAX_MULT) as u64;
let pixels_f = pixels as f64;
let time_ms_min = (pixels_f / (DECODE_THROUGHPUT_MAX_MPIXELS * 1000.0)) as f32;
let time_ms = (pixels_f / (DECODE_THROUGHPUT_TYP_MPIXELS * 1000.0)) as f32;
let time_ms_max = (pixels_f / (DECODE_THROUGHPUT_MIN_MPIXELS * 1000.0)) as f32;
let allocations = 15;
DecodeEstimate {
peak_memory_bytes_min,
peak_memory_bytes,
peak_memory_bytes_max,
allocations,
time_ms_min,
time_ms,
time_ms_max,
output_bytes,
}
}
#[must_use]
pub fn estimate_decode_streaming(width: u32, height: u32) -> DecodeEstimate {
let w = width as usize;
let h = height as usize;
let pixels = (width as u64) * (height as u64);
let mcu_cols = (w + 7) / 8;
let strip_width = mcu_cols * 8;
let strip_height = 8;
let strip_size = strip_width * strip_height;
let strip_total = strip_size * 2 * 3;
let rgb_size = w * h * 3;
let base_memory = (strip_total + rgb_size) as u64;
let peak_memory_bytes_min = base_memory;
let peak_memory_bytes = base_memory;
let peak_memory_bytes_max = (base_memory as f64 * 1.05) as u64;
let pixels_f = pixels as f64;
let time_ms_min = (pixels_f / (DECODE_THROUGHPUT_MAX_MPIXELS * 1000.0)) as f32;
let time_ms = (pixels_f / (DECODE_THROUGHPUT_TYP_MPIXELS * 1000.0)) as f32;
let time_ms_max = (pixels_f / (DECODE_THROUGHPUT_MIN_MPIXELS * 1000.0)) as f32;
let allocations = 10;
let output_bytes = pixels * 3;
DecodeEstimate {
peak_memory_bytes_min,
peak_memory_bytes,
peak_memory_bytes_max,
allocations,
time_ms_min,
time_ms,
time_ms_max,
output_bytes,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::encoder::ChromaSubsampling;
use crate::types::PixelFormat;
#[test]
fn encode_estimate_scales_with_size() {
let config = EncoderConfig::ycbcr(85, ChromaSubsampling::Quarter);
let small = estimate_encode(256, 256, &config);
let large = estimate_encode(512, 512, &config);
let ratio = large.peak_memory_bytes as f64 / small.peak_memory_bytes as f64;
assert!(ratio > 2.5 && ratio < 6.0, "Ratio was {}", ratio);
}
#[test]
fn decode_estimate_scales_with_size() {
let small = estimate_decode(256, 256, PixelFormat::Rgb);
let large = estimate_decode(512, 512, PixelFormat::Rgb);
let ratio = large.peak_memory_bytes as f64 / small.peak_memory_bytes as f64;
assert!(ratio > 2.5 && ratio < 6.0, "Ratio was {}", ratio);
}
#[test]
fn time_ranges_are_ordered() {
let config = EncoderConfig::ycbcr(85, ChromaSubsampling::Quarter);
let enc = estimate_encode(1024, 1024, &config);
assert!(enc.time_ms_min < enc.time_ms);
assert!(enc.time_ms < enc.time_ms_max);
let dec = estimate_decode(1024, 1024, PixelFormat::Rgb);
assert!(dec.time_ms_min < dec.time_ms);
assert!(dec.time_ms < dec.time_ms_max);
}
#[test]
fn memory_ranges_are_ordered() {
let config = EncoderConfig::ycbcr(85, ChromaSubsampling::Quarter);
let enc = estimate_encode(1024, 1024, &config);
assert!(enc.peak_memory_bytes_min <= enc.peak_memory_bytes);
assert!(enc.peak_memory_bytes <= enc.peak_memory_bytes_max);
let dec = estimate_decode(1024, 1024, PixelFormat::Rgb);
assert!(dec.peak_memory_bytes_min <= dec.peak_memory_bytes);
assert!(dec.peak_memory_bytes <= dec.peak_memory_bytes_max);
}
#[test]
fn ceiling_is_at_least_typical() {
let config = EncoderConfig::ycbcr(85, ChromaSubsampling::Quarter);
let typical = estimate_encode(1024, 1024, &config);
let ceiling = estimate_encode_ceiling(1024, 1024, &config);
assert!(ceiling.peak_memory_bytes >= typical.peak_memory_bytes);
}
#[test]
fn streaming_decode_uses_less_memory() {
let full = estimate_decode(1024, 1024, PixelFormat::Rgb);
let streaming = estimate_decode_streaming(1024, 1024);
assert!(streaming.peak_memory_bytes <= full.peak_memory_bytes);
}
#[test]
fn progressive_is_slower() {
let baseline = EncoderConfig::ycbcr(85, ChromaSubsampling::Quarter).progressive(false);
let progressive = EncoderConfig::ycbcr(85, ChromaSubsampling::Quarter);
let base_est = estimate_encode(1024, 1024, &baseline);
let prog_est = estimate_encode(1024, 1024, &progressive);
assert!(prog_est.time_ms > base_est.time_ms);
}
}