blob: d342661b50fa0edfa4487154eb1d63800330aed8 [file] [log] [blame]
use avm_stats::{
CodingUnitKind, CodingUnitLocator, Frame, FrameError, PartitionLocator, Plane, ProtoEnumMapping, Spatial,
SuperblockLocator, SymbolContext, TransformUnitLocator, MISSING_SYMBOL_INFO, MOTION_VECTOR_PRECISION,
};
use egui::emath::Rect;
use serde::{Deserialize, Serialize};
#[derive(Debug)]
pub enum CachedInfo<T> {
Missing,
Calculated(T),
}
#[derive(Clone, Debug)]
pub struct SymbolEntry {
pub func: String,
pub bits: f32,
pub tags: Vec<String>,
pub file: String,
pub line: i32,
pub value: i32,
}
impl SymbolEntry {
pub fn new(ctx: SymbolContext) -> Self {
let bits = ctx.symbol.bits;
let value = ctx.symbol.value;
let sym_info = ctx.info.unwrap_or(&MISSING_SYMBOL_INFO);
let func: String = sym_info.source_function.clone();
let file = sym_info.source_file.clone();
let line = sym_info.source_line;
let tags = sym_info.tags.clone();
Self {
func,
bits,
tags,
file,
line,
value,
}
}
}
#[derive(Debug)]
pub struct FieldEntry {
pub name: String,
pub value: String,
}
impl FieldEntry {
fn new(name: String, value: String) -> Self {
Self { name, value }
}
}
#[derive(Debug)]
pub struct SelectedObjectInfo {
pub fields: Vec<FieldEntry>,
pub symbols: Vec<SymbolEntry>,
}
impl SelectedObjectInfo {
fn get_symbols_coding_unit(locator: &CodingUnitLocator, frame: &Frame) -> Vec<SymbolEntry> {
let Some(ctx) = locator.try_resolve(frame) else {
return Vec::new();
};
let symbols: Vec<SymbolEntry> = ctx.iter_symbols().map(|sym| SymbolEntry::new(sym)).collect();
symbols
}
fn get_symbols_partition(locator: &PartitionLocator, frame: &Frame) -> Vec<SymbolEntry> {
let Some(ctx) = locator.try_resolve(frame) else {
return Vec::new();
};
let symbols: Vec<SymbolEntry> = ctx.iter_symbols().map(|sym| SymbolEntry::new(sym)).collect();
symbols
}
fn get_symbols_superblock(locator: &SuperblockLocator, frame: &Frame) -> Vec<SymbolEntry> {
let Some(ctx) = locator.try_resolve(frame) else {
return Vec::new();
};
let symbols: Vec<SymbolEntry> = ctx.iter_symbols(None).map(|sym| SymbolEntry::new(sym)).collect();
symbols
}
fn get_symbols_transform_unit(_locator: &TransformUnitLocator, _frame: &Frame) -> Vec<SymbolEntry> {
// TODO(comc): Transform units don't currently have symbol info tracked.
Vec::new()
}
fn calculate_coding_unit(locator: &CodingUnitLocator, frame: &Frame) -> Result<Vec<FieldEntry>, FrameError> {
let ctx = locator
.try_resolve(frame)
.ok_or(FrameError::Internal("Invalid coding unit locator".into()))?;
let coding_unit = ctx.coding_unit;
let mut fields = Vec::new();
fields.push(FieldEntry::new("Type".into(), "Coding block".into()));
let width = coding_unit.width();
let height = coding_unit.height();
fields.push(FieldEntry::new("Width".into(), width.to_string()));
fields.push(FieldEntry::new("Height".into(), height.to_string()));
let x = coding_unit.x();
let y = coding_unit.y();
fields.push(FieldEntry::new("Position".into(), format!("(x={}, y={})", x, y)));
if coding_unit.has_luma()? {
let mode_name = coding_unit.lookup_mode_name(frame)?;
let mode_is_directional = mode_name.ends_with("_PRED");
fields.push(FieldEntry::new("Prediction Mode".into(), mode_name));
// TODO(comc): Make this a method.
if mode_is_directional {
if let Some(delta) = coding_unit.luma_mode_angle_delta(frame) {
fields.push(FieldEntry::new("Angle delta".into(), delta.to_string()));
}
} else {
let motion_mode = coding_unit.get_prediction_mode()?.motion_mode;
if let Ok(motion_mode_name) = frame.enum_lookup(ProtoEnumMapping::MotionMode, motion_mode) {
// TODO(comc): Make this a method.
let is_compound = motion_mode_name.contains('_');
fields.push(FieldEntry::new("Motion mode".into(), motion_mode_name));
if let Ok(mv_prec_name) = coding_unit.lookup_motion_vector_precision_name(frame) {
fields.push(FieldEntry::new("MV precision".into(), mv_prec_name));
}
let num_mvs = if is_compound { 2 } else { 1 };
for i in 0..num_mvs {
if let Some(mv) = coding_unit.get_prediction_mode()?.motion_vectors.get(i as usize) {
let ref_frame = mv.ref_frame;
if ref_frame == -1 {
continue;
}
let dx = mv.dx as f32 / MOTION_VECTOR_PRECISION;
let dy = mv.dy as f32 / MOTION_VECTOR_PRECISION;
let mut order_hint = mv.ref_frame_order_hint.to_string();
if mv.ref_frame_is_tip {
order_hint = "TIP".to_string();
}
fields.push(FieldEntry::new(
"Motion vector".into(),
format!("{} ({}): dx={}, dy={}", ref_frame, order_hint, dx, dy),
));
}
}
}
}
}
if coding_unit.has_chroma()? {
let uv_mode = coding_unit.lookup_uv_mode_name(frame)?;
if uv_mode != "UV_MODE_INVALID" {
fields.push(FieldEntry::new("UV Prediction Mode".into(), uv_mode));
if let Some(delta) = coding_unit.chroma_mode_angle_delta(frame) {
fields.push(FieldEntry::new("UV angle delta".into(), delta.to_string()));
}
}
}
// TODO(comc): Disambiguate skip mode vs skip txfm.
fields.push(FieldEntry::new("Skip Mode".into(), coding_unit.skip.to_string()));
fields.push(FieldEntry::new(
"Use Intra BC".into(),
coding_unit.get_prediction_mode()?.use_intrabc.to_string(),
));
fields.push(FieldEntry::new("QIndex".into(), coding_unit.qindex.to_string()));
let bits = ctx.total_bits();
fields.push(FieldEntry::new("Bits".into(), bits.to_string()));
let num_transform_units = coding_unit.transform_planes[0].transform_units.len();
fields.push(FieldEntry::new(
"Transform units".into(),
num_transform_units.to_string(),
));
Ok(fields)
}
fn calculate_transform_unit(locator: &TransformUnitLocator, frame: &Frame) -> Result<Vec<FieldEntry>, FrameError> {
let ctx = locator
.try_resolve(frame)
.ok_or(FrameError::Internal("Invalid transform unit locator".into()))?;
let transform_unit = ctx.transform_unit;
let mut fields = Vec::new();
let width = transform_unit.width();
let height = transform_unit.height();
fields.push(FieldEntry::new("Type".into(), "Transform block".into()));
fields.push(FieldEntry::new("Width".into(), width.to_string()));
fields.push(FieldEntry::new("Height".into(), height.to_string()));
let x = transform_unit.x();
let y = transform_unit.y();
fields.push(FieldEntry::new("Position".into(), format!("(x={}, y={})", x, y)));
let tx_type = transform_unit.primary_tx_type_or_skip(frame);
fields.push(FieldEntry::new("TX type".into(), tx_type));
Ok(fields)
}
fn calculate_partition(locator: &PartitionLocator, frame: &Frame) -> Result<Vec<FieldEntry>, FrameError> {
let ctx = locator
.try_resolve(frame)
.ok_or(FrameError::Internal("Invalid partition locator".into()))?;
let partition = ctx.partition;
let mut fields = Vec::new();
let width = partition.width();
let height = partition.height();
fields.push(FieldEntry::new("Type".into(), "Partition block".into()));
fields.push(FieldEntry::new("Width".into(), width.to_string()));
fields.push(FieldEntry::new("Height".into(), height.to_string()));
let x = partition.x();
let y = partition.y();
fields.push(FieldEntry::new("Position".into(), format!("(x={}, y={})", x, y)));
let partition_type = partition.partition_type;
let partition_type_name = frame
.enum_lookup(ProtoEnumMapping::PartitionType, partition_type)
.unwrap_or("UNKNOWN".into());
fields.push(FieldEntry::new("Partition type".into(), partition_type_name));
Ok(fields)
}
fn calculate_superblock(locator: &SuperblockLocator, frame: &Frame) -> Result<Vec<FieldEntry>, FrameError> {
let ctx = locator
.try_resolve(frame)
.ok_or(FrameError::Internal("Invalid superblock locator".into()))?;
let superblock = ctx.superblock;
let mut fields = Vec::new();
let width = superblock.width();
let height = superblock.height();
fields.push(FieldEntry::new("Type".into(), "Superblock".into()));
fields.push(FieldEntry::new("Width".into(), width.to_string()));
fields.push(FieldEntry::new("Height".into(), height.to_string()));
let x = superblock.x();
let y = superblock.y();
fields.push(FieldEntry::new("Position".into(), format!("(x={}, y={})", x, y)));
// TODO(comc): Switch between luma and chroma partition trees here.
if let Some(partition_root) = &superblock.luma_partition_tree {
let partition_type = partition_root.partition_type;
let partition_type_name = frame
.enum_lookup(ProtoEnumMapping::PartitionType, partition_type)
.unwrap_or("UNKNOWN".into());
fields.push(FieldEntry::new("Partition type".into(), partition_type_name));
}
Ok(fields)
}
fn calculate(kind: &SelectedObjectKind, frame: &Frame) -> Result<Self, FrameError> {
let fields = match kind {
SelectedObjectKind::TransformUnit(obj) => Self::calculate_transform_unit(obj, frame)?,
SelectedObjectKind::CodingUnit(obj) => Self::calculate_coding_unit(obj, frame)?,
SelectedObjectKind::Partition(obj) => Self::calculate_partition(obj, frame)?,
SelectedObjectKind::Superblock(obj) => Self::calculate_superblock(obj, frame)?,
};
// TODO(comc): Make children selectable.
// for (i, child) in kind.get_children(frame).iter().enumerate() {
// if let Some(rect) = child.rect(frame) {
// let width = rect.width();
// let height = rect.height();
// let x = rect.left();
// let y = rect.top();
// fields.push(FieldEntry::new(
// format!("Child {i}"),
// format!("{width}x{height} at ({x},{y})"),
// ));
// }
// }
let symbols = match kind {
SelectedObjectKind::TransformUnit(obj) => Self::get_symbols_transform_unit(obj, frame),
SelectedObjectKind::CodingUnit(obj) => Self::get_symbols_coding_unit(obj, frame),
SelectedObjectKind::Partition(obj) => Self::get_symbols_partition(obj, frame),
SelectedObjectKind::Superblock(obj) => Self::get_symbols_superblock(obj, frame),
};
Ok(Self { fields, symbols })
}
}
// TODO(comc): Option to show partition blocks at user-selected depth.
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
pub enum SelectedObjectKind {
TransformUnit(TransformUnitLocator),
CodingUnit(CodingUnitLocator),
Partition(PartitionLocator),
Superblock(SuperblockLocator),
}
impl SelectedObjectKind {
pub fn rect(&self, frame: &Frame) -> Option<Rect> {
match self {
SelectedObjectKind::TransformUnit(obj) => obj.try_resolve(frame).map(|ctx| ctx.transform_unit.rect()),
SelectedObjectKind::CodingUnit(obj) => obj.try_resolve(frame).map(|ctx| ctx.coding_unit.rect()),
SelectedObjectKind::Partition(obj) => obj.try_resolve(frame).map(|ctx| ctx.partition.rect()),
SelectedObjectKind::Superblock(obj) => obj.try_resolve(frame).map(|ctx| ctx.superblock.rect()),
}
}
pub fn get_parent(&self, frame: &Frame) -> Option<SelectedObjectKind> {
match self {
SelectedObjectKind::TransformUnit(obj) => {
let ctx = obj.try_resolve(frame)?;
Some(SelectedObjectKind::CodingUnit(ctx.coding_unit_context.locator))
}
SelectedObjectKind::CodingUnit(obj) => {
let ctx = obj.try_resolve(frame)?;
let parent = ctx.find_parent_partition()?;
if parent.is_root() {
Some(SelectedObjectKind::Superblock(ctx.superblock_context.locator))
} else {
// Partition tree leaf nodes map directly to coding units. To get the actual parent, we need to go one more level up the hierarchy.
let actual_parent = parent.locator.parent().unwrap();
if actual_parent.is_root() {
Some(SelectedObjectKind::Superblock(ctx.superblock_context.locator))
} else {
Some(SelectedObjectKind::Partition(actual_parent))
}
}
}
SelectedObjectKind::Partition(obj) => {
let ctx = obj.try_resolve(frame)?;
if ctx.is_root() {
Some(SelectedObjectKind::Superblock(ctx.superblock_context.locator))
} else {
let parent = ctx.locator.parent().unwrap();
if parent.is_root() {
Some(SelectedObjectKind::Superblock(ctx.superblock_context.locator))
} else {
Some(SelectedObjectKind::Partition(ctx.locator.parent().unwrap()))
}
}
}
_ => None,
}
}
#[allow(dead_code)]
pub fn get_children(&self, frame: &Frame) -> Vec<SelectedObjectKind> {
let mut children = Vec::new();
match self {
SelectedObjectKind::TransformUnit(_obj) => {}
SelectedObjectKind::CodingUnit(obj) => {
if let Some(ctx) = obj.try_resolve(frame) {
let kind = ctx.locator.kind;
let plane = match kind {
CodingUnitKind::LumaOnly | CodingUnitKind::Shared => Plane::Y,
CodingUnitKind::ChromaOnly => Plane::U,
};
// TODO(comc): Iterate over all three planes depending on plane view settings.
children.extend(
ctx.iter_transform_units(plane)
.map(|transform_unit| SelectedObjectKind::TransformUnit(transform_unit.locator)),
)
}
}
SelectedObjectKind::Partition(obj) => {
if let Some(ctx) = obj.try_resolve(frame) {
for (i, child) in ctx.iter_direct_children().enumerate() {
if child.partition.is_leaf_node {
if let Some(coding_unit_range) = &child.partition.coding_unit_range {
let coding_unit_index = coding_unit_range.start as usize;
let locator = CodingUnitLocator::new(
ctx.superblock_context.locator,
ctx.locator.kind,
coding_unit_index,
);
children.push(SelectedObjectKind::CodingUnit(locator));
}
} else {
let mut locator = ctx.locator.clone();
locator.path_indices.push(i);
children.push(SelectedObjectKind::Partition(locator));
}
}
}
}
_ => {}
}
children
}
}
pub struct SelectedObject {
pub kind: SelectedObjectKind,
pub info: CachedInfo<Result<SelectedObjectInfo, FrameError>>,
}
impl SelectedObject {
pub fn rect(&self, frame: &Frame) -> Option<Rect> {
self.kind.rect(frame)
}
// TODO(comc): Refactor.
pub fn get_or_calculate_info(&mut self, frame: &Frame) -> &Result<SelectedObjectInfo, FrameError> {
if matches!(self.info, CachedInfo::Missing) {
self.info = CachedInfo::Calculated(SelectedObjectInfo::calculate(&self.kind, frame));
}
let CachedInfo::Calculated(info) = &self.info else {
panic!("Info is not calculated.");
};
info
}
pub fn new(kind: SelectedObjectKind) -> Self {
Self {
kind,
info: CachedInfo::Missing,
}
}
}