blob: f8e088472cfde26da838e018da76386f7d9e0249 [file] [log] [blame]
use avm_analyzer_common::AvmStreamInfo;
use avm_stats::{FrameStatistic, HeatmapSettings, PlaneType, Sample, StatsSettings};
use base64::{engine::general_purpose::URL_SAFE, Engine as _};
use egui::{pos2, vec2, Color32, Pos2, Rect, Stroke};
use log::info;
use serde::{Deserialize, Serialize};
use web_time::Instant;
use crate::{
stream::{Stream, StreamSource},
views::{SelectedObject, SelectedObjectKind, ViewMode},
};
const GITLAB_ROOT: &str = "https://gitlab.com/AOMediaCodec/avm/-/blob/research-v6.0.0";
const DEFAULT_STREAMS_URL: &str = "/streams";
const DEFAULT_WORLD_BOUNDS_WIDTH: f32 = 1280.0;
const DEFAULT_WORLD_BOUNDS_HEIGHT: f32 = 720.0;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct OverlayStyle {
pub transform_unit_stroke: Stroke,
pub coding_unit_stroke: Stroke,
pub superblock_stroke: Stroke,
pub highlighted_object_stroke: Stroke,
pub selected_object_stroke: Stroke,
pub mode_name_color: Color32,
pub pixel_viewer_text_color: Color32,
pub enable_text_shadows: bool,
}
impl Default for OverlayStyle {
fn default() -> Self {
Self {
transform_unit_stroke: Stroke::new(1.0, Color32::DARK_RED),
coding_unit_stroke: Stroke::new(1.0, Color32::RED),
superblock_stroke: Stroke::new(1.0, Color32::BLUE),
highlighted_object_stroke: Stroke::new(4.0, Color32::LIGHT_YELLOW),
selected_object_stroke: Stroke::new(3.0, Color32::YELLOW),
mode_name_color: Color32::LIGHT_GREEN,
pixel_viewer_text_color: Color32::LIGHT_GREEN,
enable_text_shadows: true,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct StyleSettings {
pub overlay: OverlayStyle,
}
#[allow(clippy::derivable_impls)]
impl Default for StyleSettings {
fn default() -> Self {
Self {
overlay: OverlayStyle::default(),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
pub enum FrameSortOrder {
Decode,
Display,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PersistentSettings {
pub avm_source_url: String,
pub style: StyleSettings,
pub apply_cache_strategy: bool,
pub cache_strategy_limit: usize,
pub update_sharable_url: bool,
pub frame_sort_order: FrameSortOrder,
}
impl Default for PersistentSettings {
fn default() -> Self {
Self {
avm_source_url: GITLAB_ROOT.into(),
style: StyleSettings::default(),
apply_cache_strategy: false,
cache_strategy_limit: 10,
update_sharable_url: false,
frame_sort_order: FrameSortOrder::Decode,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
pub enum CoeffViewSelect {
Quantized,
Dequantized,
DequantValue,
}
impl CoeffViewSelect {
pub fn name(&self) -> &str {
match self {
Self::Quantized => "Quantized Coefficients",
Self::Dequantized => "Dequantized Coefficients",
Self::DequantValue => "Inverse Quantization Matrix",
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
pub enum MotionFieldColoring {
RefFrames,
PastFuture,
Monochrome,
}
impl MotionFieldColoring {
pub fn name(&self) -> &str {
match self {
Self::RefFrames => "Reference frames",
Self::PastFuture => "Past vs Future",
Self::Monochrome => "Monochrome",
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct MotionFieldSettings {
pub show: bool,
pub show_origin: bool,
pub normalize: bool,
/// In 4x4 units.
pub granularity: usize,
pub auto_granularity: bool,
pub coloring: MotionFieldColoring,
pub scale: f32,
}
impl Default for MotionFieldSettings {
fn default() -> Self {
Self {
show: false,
show_origin: true,
normalize: true,
granularity: 1,
auto_granularity: true,
coloring: MotionFieldColoring::RefFrames,
scale: 1.0,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
pub enum DistortionView {
Original,
Reconstruction,
Distortion,
}
impl DistortionView {
pub fn name(&self) -> &str {
match self {
Self::Original => "Source (pre-encode)",
Self::Reconstruction => "Reconstruction",
Self::Distortion => "Distortion",
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct SharableSettings {
pub view_mode: ViewMode,
pub current_plane: PlaneType,
pub show_relative_delta: bool,
pub show_overlay: bool,
pub show_yuv: bool,
pub motion_field: MotionFieldSettings,
pub selected_stat: FrameStatistic,
pub stats_settings: StatsSettings,
pub symbol_info_filter: String,
pub symbol_info_show_tags: bool,
pub world_bounds: Rect,
pub show_pixel_viewer: bool,
pub show_coeffs_viewer: bool,
pub pixel_viewer_bounds: Rect,
pub coeffs_viewer_bounds: Rect,
pub coeff_view_select: CoeffViewSelect,
pub show_heatmap_legend: bool,
pub heatmap_settings: HeatmapSettings,
pub heatmap_histogram_log_scale: bool,
pub show_remote_streams: bool,
pub streams_url: String,
// The following fields are used only during serialization. Otherwise their settings or state equivalent is used.
pub selected_object_kind: Option<SelectedObjectKind>,
pub selected_stream: Option<AvmStreamInfo>,
pub selected_frame: usize,
}
impl Default for SharableSettings {
fn default() -> Self {
Self {
current_plane: PlaneType::Rgb,
show_relative_delta: false,
show_overlay: true,
show_yuv: true,
motion_field: MotionFieldSettings::default(),
view_mode: ViewMode::CodingFlow,
stats_settings: StatsSettings::default(),
selected_stat: FrameStatistic::BlockSizes,
symbol_info_filter: "".into(),
symbol_info_show_tags: true,
world_bounds: Rect::from_min_size(
Pos2::ZERO,
vec2(DEFAULT_WORLD_BOUNDS_WIDTH, DEFAULT_WORLD_BOUNDS_HEIGHT),
),
show_pixel_viewer: false,
show_coeffs_viewer: false,
pixel_viewer_bounds: Rect::from_min_max(pos2(0.0, 0.0), pos2(4.0, 4.0)),
coeffs_viewer_bounds: Rect::from_min_max(pos2(0.0, 0.0), pos2(4.0, 4.0)),
coeff_view_select: CoeffViewSelect::Dequantized,
show_heatmap_legend: true,
heatmap_settings: HeatmapSettings::default(),
heatmap_histogram_log_scale: false,
show_remote_streams: true,
streams_url: DEFAULT_STREAMS_URL.into(),
selected_object_kind: None,
selected_stream: None,
selected_frame: 0,
}
}
}
impl SharableSettings {
pub fn encode(&self) -> String {
let bytes = bincode::serialize(self).unwrap();
let compressed = weezl::encode::Encoder::new(weezl::BitOrder::Lsb, 8)
.encode(&bytes)
.unwrap();
URL_SAFE.encode(compressed)
}
pub fn decode(settings_str: Option<&str>) -> Option<Self> {
if let Some(settings_str) = settings_str {
if let Ok(bytes) = URL_SAFE.decode(settings_str) {
if let Ok(uncompressed) = weezl::decode::Decoder::new(weezl::BitOrder::Lsb, 8).decode(&bytes) {
if let Ok(sharable) = bincode::deserialize::<SharableSettings>(&uncompressed) {
return Some(sharable);
}
}
}
}
None
}
}
pub struct PlaybackSettings {
pub playback_fps: f32,
pub show_loaded_frames_only: bool,
pub playback_loop: bool,
pub playback_running: bool,
pub current_frame_display_instant: Instant,
}
impl Default for PlaybackSettings {
fn default() -> Self {
Self {
playback_fps: 30.0,
show_loaded_frames_only: true,
playback_loop: true,
playback_running: false,
current_frame_display_instant: Instant::now(),
}
}
}
pub struct Settings {
pub show_stream_select: bool,
pub show_decode_progress: bool,
pub scroll_to_index: Option<usize>,
pub cached_stat_data: Option<Vec<Sample>>,
pub show_performance_window: bool,
pub show_settings_window: bool,
pub persistent: PersistentSettings,
pub sharable: SharableSettings,
pub selected_object: Option<SelectedObject>,
/// The first object that was selected when double clicked to navigate up the hierarchy.
pub selected_object_leaf: Option<SelectedObjectKind>,
pub have_world_bounds_from_shared_settings: bool,
pub playback: PlaybackSettings,
}
#[allow(clippy::derivable_impls)]
impl Default for Settings {
fn default() -> Self {
Self {
show_stream_select: false,
show_decode_progress: false,
scroll_to_index: None,
cached_stat_data: None,
show_performance_window: false,
show_settings_window: false,
persistent: PersistentSettings::default(),
sharable: SharableSettings::default(),
selected_object: None,
selected_object_leaf: None,
have_world_bounds_from_shared_settings: false,
playback: PlaybackSettings::default(),
}
}
}
impl Settings {
pub fn apply_saved_settings_string(&mut self, settings: Option<String>) {
if let Some(settings) = settings {
if let Ok(persistent) = serde_json::from_str::<PersistentSettings>(&settings) {
info!("Restoring saved settings: {persistent:?}");
self.persistent = persistent;
}
}
}
pub fn from_shared_settings_string(settings_str: Option<&str>) -> Self {
if let Some(sharable) = SharableSettings::decode(settings_str) {
info!("Loaded shared settings: {sharable:?}");
return Self {
sharable,
have_world_bounds_from_shared_settings: true,
..Default::default()
};
}
Self::default()
}
pub fn create_shared_settings(&self, stream: &Option<Stream>) -> SharableSettings {
let mut shared = self.sharable.clone();
shared.selected_frame = 0;
shared.selected_stream = None;
shared.selected_object_kind = None;
if let Some(stream) = stream {
// Only save state for HTTP streams.
if matches!(stream.source, StreamSource::Http(_)) {
shared.selected_frame = stream.current_frame_index;
// TODO(comc): Can be simplified with serde skip.
shared.selected_stream = Some(AvmStreamInfo {
thumbnail_png: None,
..stream.stream_info.clone()
});
if let Some(selected_object) = &self.selected_object {
shared.selected_object_kind = Some(selected_object.kind.clone());
}
}
}
shared
}
}