profiler.rs (75890B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 //! # Overlay profiler 6 //! 7 //! ## Profiler UI string syntax 8 //! 9 //! Comma-separated list of of tokens with trailing and leading spaces trimmed. 10 //! Each tokens can be: 11 //! - A counter name with an optional prefix. The name corresponds to the displayed name (see the 12 //! counters vector below. 13 //! - By default (no prefix) the counter is shown as average + max over half a second. 14 //! - With a '#' prefix the counter is shown as a graph. 15 //! - With a '*' prefix the counter is shown as a change indicator. 16 //! - Some special counters such as GPU time queries have specific visualizations ignoring prefixes. 17 //! - A preset name to append the preset to the UI (see PROFILER_PRESETS). 18 //! - An empty token to insert a bit of vertical space. 19 //! - A '|' token to start a new column. 20 //! - A '_' token to start a new row. 21 22 use api::{ColorF, ColorU, RenderCommandInfo}; 23 #[cfg(feature = "debugger")] 24 use api::debugger::{ProfileCounterUpdate, ProfileCounterId}; 25 use glyph_rasterizer::profiler::GlyphRasterizeProfiler; 26 use crate::renderer::DebugRenderer; 27 use crate::device::query::GpuTimer; 28 use euclid::{Point2D, Rect, Size2D, vec2, default}; 29 use crate::internal_types::FastHashMap; 30 use crate::renderer::{FullFrameStats, init::wr_has_been_initialized}; 31 use api::units::DeviceIntSize; 32 use std::collections::vec_deque::VecDeque; 33 use std::fmt::{Write, Debug}; 34 use std::f32; 35 use std::ops::Range; 36 use std::time::Duration; 37 38 macro_rules! set_text { 39 ($dst:expr, $($arg:tt)*) => { 40 $dst.clear(); 41 write!($dst, $($arg)*).unwrap(); 42 }; 43 } 44 45 const GRAPH_WIDTH: f32 = 1024.0; 46 const GRAPH_HEIGHT: f32 = 320.0; 47 const GRAPH_PADDING: f32 = 8.0; 48 const GRAPH_FRAME_HEIGHT: f32 = 16.0; 49 const PROFILE_SPACING: f32 = 15.0; 50 const PROFILE_PADDING: f32 = 10.0; 51 const BACKGROUND_COLOR: ColorU = ColorU { r: 20, g: 20, b: 20, a: 220 }; 52 53 const ONE_SECOND_NS: u64 = 1_000_000_000; 54 55 /// Profiler UI string presets. Defined in the profiler UI string syntax, can contain other presets. 56 static PROFILER_PRESETS: &'static[(&'static str, &'static str)] = &[ 57 // Default view, doesn't show everything, but still shows quite a bit. 58 (&"Default", &"FPS,|,Slow indicators,_,Time graphs,|,Frame times, ,Transaction times, ,Frame stats, ,Memory, ,Interners,_,GPU time queries,_,Paint phase graph"), 59 // Smaller, less intrusive overview 60 (&"Compact", &"FPS, ,Frame times, ,Frame stats"), 61 // Even less intrusive, only slow transactions and frame indicators. 62 (&"Slow indicators", &"*Slow transaction,*Slow frame"), 63 64 // Counters: 65 66 // Timing information for per layout transaction stages. 67 (&"Transaction times", &"DisplayList,Scene building,Content send,API send"), 68 // Timing information for per-frame stages. 69 (&"Frame times", &"Frame CPU total,Frame building,Visibility,Prepare,Batching,Glyph resolve,Texture cache update,Shader build time,Renderer,GPU"), 70 // Stats about the content of the frame. 71 (&"Frame stats", &"Primitives,Visible primitives,Draw calls,Vertices,Color passes,Alpha passes,Rendered picture tiles,Rasterized glyphs"), 72 // Texture cache allocation stats. 73 (&"Texture cache stats", &"Atlas textures mem, Standalone textures mem, Picture tiles mem, Render targets mem, Depth targets mem, Atlas items mem, 74 Texture cache standalone pressure, Texture cache eviction count, Texture cache youngest evicted, , 75 Atlas RGBA8 linear pixels, Atlas RGBA8 glyphs pixels, Atlas A8 glyphs pixels, Atlas A8 pixels, Atlas A16 pixels, Atlas RGBA8 nearest pixels, 76 Atlas RGBA8 linear textures, Atlas RGBA8 glyphs textures, Atlas A8 glyphs textures, Atlas A8 textures, Atlas A16 textures, Atlas RGBA8 nearest textures, 77 Atlas RGBA8 linear pressure, Atlas RGBA8 glyphs pressure, Atlas A8 glyphs pressure, Atlas A8 pressure, Atlas A16 pressure, Atlas RGBA8 nearest pressure," 78 ), 79 // Graphs to investigate driver overhead of texture cache updates. 80 (&"Texture upload perf", &"#Texture cache update,#Texture cache upload, ,#Staging CPU allocation,#Staging GPU allocation,#Staging CPU copy,#Staging GPU copy,#Upload time, ,#Upload copy batches,#Rasterized glyphs, ,#Cache texture creation,#Cache texture deletion"), 81 82 // Graphs: 83 84 // Graph overview of time spent in WebRender's main stages. 85 (&"Time graphs", &"#DisplayList,#Scene building,#Blob rasterization, ,#Frame CPU total,#Frame building,#Renderer,#Texture cache update, ,#GPU,"), 86 // Useful when investigating render backend bottlenecks. 87 (&"Backend graphs", &"#Frame building, #Visibility, #Prepare, #Batching, #Glyph resolve"), 88 // Useful when investigating renderer bottlenecks. 89 (&"Renderer graphs", &"#Rendered picture tiles,#Draw calls,#Rasterized glyphs,#Texture uploads,#Texture uploads mem, ,#Texture cache update,#Renderer,"), 90 91 // Misc: 92 93 (&"GPU Memory", &"External image mem, Atlas textures mem, Standalone textures mem, Picture tiles mem, Render targets mem, Depth targets mem, Atlas items mem, GPU cache mem, GPU buffer mem, GPU total mem"), 94 (&"CPU Memory", &"Image templates, Image templates mem, Font templates,Font templates mem, DisplayList mem"), 95 (&"Memory", &"$CPU,CPU Memory, ,$GPU,GPU Memory"), 96 (&"Interners", "Interned primitives,Interned clips,Interned pictures,Interned text runs,Interned normal borders,Interned image borders,Interned images,Interned YUV images,Interned line decorations,Interned linear gradients,Interned radial gradients,Interned conic gradients,Interned filter data,Interned backdrop renders, Interned backdrop captures"), 97 // Gpu sampler queries (need the pref gfx.webrender.debug.gpu-sampler-queries). 98 (&"GPU samplers", &"Alpha targets samplers,Transparent pass samplers,Opaque pass samplers,Total samplers"), 99 100 (&"Render reasons", &"Reason scene, Reason animated property, Reason resource update, Reason async image, Reason clear resources, Reason APZ, Reason resize, Reason widget, Reason cache flush, Reason snapshot, Reason resource hook, Reason config change, Reason content sync, Reason flush, On vsync, Reason testing, Reason other"), 101 102 (&"Slow frame breakdown", &"Total slow frames CPU, Total slow frames GPU, Slow: frame build, Slow: upload, Slow: render, Slow: draw calls, Slow: targets, Slow: blobs, Slow: after scene, Slow scroll frames"), 103 104 (&"Compositor", &"Compositor surface underlays,Compositor surface overlays,Compositor surface blits"), 105 (&"Video", &"FPS,_,#Rendered picture tiles,_,Compositor"), 106 ]; 107 108 fn find_preset(name: &str) -> Option<&'static str> { 109 for preset in PROFILER_PRESETS { 110 if preset.0 == name { 111 return Some(preset.1); 112 } 113 } 114 115 None 116 } 117 118 // The indices here must match the PROFILE_COUNTERS array (checked at runtime). 119 pub const FRAME_BUILDING_TIME: usize = 0; 120 pub const FRAME_VISIBILITY_TIME: usize = 1; 121 pub const FRAME_PREPARE_TIME: usize = 2; 122 pub const FRAME_BATCHING_TIME: usize = 3; 123 124 pub const RENDERER_TIME: usize = 4; 125 pub const TOTAL_FRAME_CPU_TIME: usize = 5; 126 pub const GPU_TIME: usize = 6; 127 128 pub const CONTENT_SEND_TIME: usize = 7; 129 pub const API_SEND_TIME: usize = 8; 130 131 pub const DISPLAY_LIST_BUILD_TIME: usize = 9; 132 pub const DISPLAY_LIST_MEM: usize = 10; 133 134 pub const SCENE_BUILD_TIME: usize = 11; 135 136 pub const SLOW_FRAME: usize = 12; 137 pub const SLOW_TXN: usize = 13; 138 139 pub const FRAME_TIME: usize = 14; 140 141 pub const TEXTURE_UPLOADS: usize = 15; 142 pub const TEXTURE_UPLOADS_MEM: usize = 16; 143 pub const TEXTURE_CACHE_UPDATE_TIME: usize = 17; 144 pub const CPU_TEXTURE_ALLOCATION_TIME: usize = 18; 145 pub const STAGING_TEXTURE_ALLOCATION_TIME: usize = 19; 146 pub const UPLOAD_CPU_COPY_TIME: usize = 20; 147 pub const UPLOAD_GPU_COPY_TIME: usize = 21; 148 pub const UPLOAD_TIME: usize = 22; 149 pub const UPLOAD_NUM_COPY_BATCHES: usize = 23; 150 pub const TOTAL_UPLOAD_TIME: usize = 24; 151 pub const CREATE_CACHE_TEXTURE_TIME: usize = 25; 152 pub const DELETE_CACHE_TEXTURE_TIME: usize = 26; 153 154 pub const RASTERIZED_BLOBS: usize = 27; 155 pub const RASTERIZED_BLOB_TILES: usize = 28; 156 pub const RASTERIZED_BLOBS_PX: usize = 29; 157 pub const BLOB_RASTERIZATION_TIME: usize = 30; 158 159 pub const RASTERIZED_GLYPHS: usize = 31; 160 pub const GLYPH_RESOLVE_TIME: usize = 32; 161 162 pub const DRAW_CALLS: usize = 33; 163 pub const VERTICES: usize = 34; 164 pub const PRIMITIVES: usize = 35; 165 pub const VISIBLE_PRIMITIVES: usize = 36; 166 167 pub const USED_TARGETS: usize = 37; 168 pub const CREATED_TARGETS: usize = 38; 169 pub const PICTURE_CACHE_SLICES: usize = 39; 170 171 pub const COLOR_PASSES: usize = 40; 172 pub const ALPHA_PASSES: usize = 41; 173 pub const PICTURE_TILES: usize = 42; 174 pub const RENDERED_PICTURE_TILES: usize = 43; 175 176 pub const FONT_TEMPLATES: usize = 44; 177 pub const FONT_TEMPLATES_MEM: usize = 45; 178 pub const IMAGE_TEMPLATES: usize = 46; 179 pub const IMAGE_TEMPLATES_MEM: usize = 47; 180 181 // Atlas items represents the area occupied by items in the cache textures. 182 // The actual texture memory allocated is ATLAS_TEXTURES_MEM. 183 pub const ATLAS_ITEMS_MEM: usize = 48; 184 pub const ATLAS_A8_PIXELS: usize = 49; 185 pub const ATLAS_A8_TEXTURES: usize = 50; 186 pub const ATLAS_A16_PIXELS: usize = 51; 187 pub const ATLAS_A16_TEXTURES: usize = 52; 188 pub const ATLAS_RGBA8_LINEAR_PIXELS: usize = 53; 189 pub const ATLAS_RGBA8_LINEAR_TEXTURES: usize = 54; 190 pub const ATLAS_RGBA8_NEAREST_PIXELS: usize = 55; 191 pub const ATLAS_RGBA8_NEAREST_TEXTURES: usize = 56; 192 pub const ATLAS_RGBA8_GLYPHS_PIXELS: usize = 57; 193 pub const ATLAS_RGBA8_GLYPHS_TEXTURES: usize = 58; 194 pub const ATLAS_A8_GLYPHS_PIXELS: usize = 59; 195 pub const ATLAS_A8_GLYPHS_TEXTURES: usize = 60; 196 pub const ATLAS_COLOR8_LINEAR_PRESSURE: usize = 61; 197 pub const ATLAS_COLOR8_NEAREST_PRESSURE: usize = 62; 198 pub const ATLAS_COLOR8_GLYPHS_PRESSURE: usize = 63; 199 pub const ATLAS_ALPHA8_PRESSURE: usize = 64; 200 pub const ATLAS_ALPHA8_GLYPHS_PRESSURE: usize = 65; 201 pub const ATLAS_ALPHA16_PRESSURE: usize = 66; 202 pub const ATLAS_STANDALONE_PRESSURE: usize = 67; 203 204 pub const TEXTURE_CACHE_EVICTION_COUNT: usize = 68; 205 pub const TEXTURE_CACHE_YOUNGEST_EVICTION: usize = 69; 206 pub const EXTERNAL_IMAGE_BYTES: usize = 70; 207 pub const ATLAS_TEXTURES_MEM: usize = 71; 208 pub const STANDALONE_TEXTURES_MEM: usize = 72; 209 pub const PICTURE_TILES_MEM: usize = 73; 210 pub const RENDER_TARGET_MEM: usize = 74; 211 212 pub const ALPHA_TARGETS_SAMPLERS: usize = 75; 213 pub const TRANSPARENT_PASS_SAMPLERS: usize = 76; 214 pub const OPAQUE_PASS_SAMPLERS: usize = 77; 215 pub const TOTAL_SAMPLERS: usize = 78; 216 217 pub const INTERNED_PRIMITIVES: usize = 79; 218 pub const INTERNED_CLIPS: usize = 80; 219 pub const INTERNED_TEXT_RUNS: usize = 81; 220 pub const INTERNED_NORMAL_BORDERS: usize = 82; 221 pub const INTERNED_IMAGE_BORDERS: usize = 83; 222 pub const INTERNED_IMAGES: usize = 84; 223 pub const INTERNED_YUV_IMAGES: usize = 85; 224 pub const INTERNED_LINE_DECORATIONS: usize = 86; 225 pub const INTERNED_LINEAR_GRADIENTS: usize = 87; 226 pub const INTERNED_RADIAL_GRADIENTS: usize = 88; 227 pub const INTERNED_CONIC_GRADIENTS: usize = 89; 228 pub const INTERNED_PICTURES: usize = 90; 229 pub const INTERNED_FILTER_DATA: usize = 91; 230 pub const INTERNED_BACKDROP_CAPTURES: usize = 92; 231 pub const INTERNED_BACKDROP_RENDERS: usize = 93; 232 pub const INTERNED_POLYGONS: usize = 94; 233 pub const INTERNED_BOX_SHADOWS: usize = 95; 234 pub const DEPTH_TARGETS_MEM: usize = 96; 235 236 pub const SHADER_BUILD_TIME: usize = 97; 237 238 pub const RENDER_REASON_FIRST: usize = 98; 239 pub const RENDER_REASON_SCENE: usize = 99; 240 pub const RENDER_REASON_ANIMATED_PROPERTY: usize = 100; 241 pub const RENDER_REASON_RESOURCE_UPDATE: usize = 101; 242 pub const RENDER_REASON_ASYNC_IMAGE: usize = 102; 243 pub const RENDER_REASON_CLEAR_RESOURCES: usize = 103; 244 pub const RENDER_REASON_APZ: usize = 104; 245 pub const RENDER_REASON_RESIZE: usize = 105; 246 pub const RENDER_REASON_WIDGET: usize = 106; 247 pub const RENDER_REASON_TEXTURE_CACHE_FLUSH: usize = 107; 248 pub const RENDER_REASON_SNAPSHOT: usize = 108; 249 pub const RENDER_REASON_POST_RESOURCE_UPDATE_HOOKS: usize = 109; 250 pub const RENDER_REASON_CONFIG_CHANGE: usize = 110; 251 pub const RENDER_REASON_CONTENT_SYNC: usize = 111; 252 pub const RENDER_REASON_FLUSH: usize = 112; 253 pub const RENDER_REASON_TESTING: usize = 113; 254 pub const RENDER_REASON_OTHER: usize = 114; 255 pub const RENDER_REASON_VSYNC: usize = 115; 256 257 pub const TEXTURES_CREATED: usize = 116; 258 pub const TEXTURES_DELETED: usize = 117; 259 260 pub const SLOW_FRAME_CPU_COUNT: usize = 118; 261 pub const SLOW_FRAME_GPU_COUNT: usize = 119; 262 pub const SLOW_FRAME_BUILD_COUNT: usize = 120; 263 pub const SLOW_UPLOAD_COUNT: usize = 121; 264 pub const SLOW_RENDER_COUNT: usize = 122; 265 pub const SLOW_DRAW_CALLS_COUNT: usize = 123; 266 pub const SLOW_TARGETS_COUNT: usize = 124; 267 pub const SLOW_BLOB_COUNT: usize = 125; 268 pub const SLOW_SCROLL_AFTER_SCENE_COUNT: usize = 126; 269 270 pub const GPU_BUFFER_MEM: usize = 127; 271 pub const GPU_TOTAL_MEM: usize = 128; 272 273 pub const FRAME_SEND_TIME: usize = 129; 274 pub const UPDATE_DOCUMENT_TIME: usize = 130; 275 276 pub const COMPOSITOR_SURFACE_UNDERLAYS: usize = 131; 277 pub const COMPOSITOR_SURFACE_OVERLAYS: usize = 132; 278 pub const COMPOSITOR_SURFACE_BLITS: usize = 133; 279 280 pub const NUM_PROFILER_EVENTS: usize = 134; 281 282 pub struct Profiler { 283 counters: Vec<Counter>, 284 gpu_frames: ProfilerFrameCollection, 285 frame_stats: ProfilerFrameCollection, 286 slow_scroll_frames: ProfilerFrameCollection, 287 288 start: u64, 289 avg_over_period: u64, 290 num_graph_samples: usize, 291 slow_cpu_frame_threshold: f32, 292 293 // For FPS computation. Updated in update(). 294 frame_timestamps_within_last_second: Vec<u64>, 295 296 /// Total number of slow frames on the CPU. 297 slow_frame_cpu_count: u64, 298 /// Total number of slow frames on the GPU. 299 slow_frame_gpu_count: u64, 300 /// Slow frames dominated by frame building. 301 slow_frame_build_count: u64, 302 /// Slow frames dominated by draw call submission. 303 slow_render_count: u64, 304 /// Slow frames dominated by texture uploads. 305 slow_upload_count: u64, 306 /// Slow renders with a high number of draw calls. 307 slow_draw_calls_count: u64, 308 /// Slow renders with a high number of render targets. 309 slow_targets_count: u64, 310 /// Slow uploads with a high number of blob tiles. 311 slow_blob_count: u64, 312 /// Slow scrolling or animation frame after a scene build. 313 slow_scroll_after_scene_count: u64, 314 315 ui: Vec<Item>, 316 } 317 318 impl Profiler { 319 pub fn new() -> Self { 320 321 fn float(name: &'static str, unit: &'static str, index: usize, expected: Expected<f64>) -> CounterDescriptor { 322 CounterDescriptor { name, unit, show_as: ShowAs::Float, index, expected } 323 } 324 325 fn int(name: &'static str, unit: &'static str, index: usize, expected: Expected<i64>) -> CounterDescriptor { 326 CounterDescriptor { name, unit, show_as: ShowAs::Int, index, expected: expected.into_float() } 327 } 328 329 // Not in the list below: 330 // - "GPU time queries" shows the details of the GPU time queries if selected as a graph. 331 // - "GPU cache bars" shows some info about the GPU cache. 332 333 // TODO: This should be a global variable but to keep things readable we need to be able to 334 // use match in const fn which isn't supported by the current rustc version in gecko's build 335 // system. 336 let profile_counters = &[ 337 float("Frame building", "ms", FRAME_BUILDING_TIME, expected(0.0..6.0).avg(0.0..3.0)), 338 float("Visibility", "ms", FRAME_VISIBILITY_TIME, expected(0.0..3.0).avg(0.0..2.0)), 339 float("Prepare", "ms", FRAME_PREPARE_TIME, expected(0.0..3.0).avg(0.0..2.0)), 340 float("Batching", "ms", FRAME_BATCHING_TIME, expected(0.0..3.0).avg(0.0..2.0)), 341 342 float("Renderer", "ms", RENDERER_TIME, expected(0.0..8.0).avg(0.0..5.0)), 343 float("Frame CPU total", "ms", TOTAL_FRAME_CPU_TIME, expected(0.0..15.0).avg(0.0..6.0)), 344 float("GPU", "ms", GPU_TIME, expected(0.0..15.0).avg(0.0..8.0)), 345 346 float("Content send", "ms", CONTENT_SEND_TIME, expected(0.0..1.0).avg(0.0..1.0)), 347 float("API send", "ms", API_SEND_TIME, expected(0.0..1.0).avg(0.0..0.4)), 348 float("DisplayList", "ms", DISPLAY_LIST_BUILD_TIME, expected(0.0..5.0).avg(0.0..3.0)), 349 float("DisplayList mem", "MB", DISPLAY_LIST_MEM, expected(0.0..20.0)), 350 float("Scene building", "ms", SCENE_BUILD_TIME, expected(0.0..4.0).avg(0.0..3.0)), 351 352 float("Slow frame", "", SLOW_FRAME, expected(0.0..0.0)), 353 float("Slow transaction", "", SLOW_TXN, expected(0.0..0.0)), 354 355 float("Frame", "ms", FRAME_TIME, Expected::none()), 356 357 int("Texture uploads", "", TEXTURE_UPLOADS, expected(0..10)), 358 float("Texture uploads mem", "MB", TEXTURE_UPLOADS_MEM, expected(0.0..10.0)), 359 float("Texture cache update", "ms", TEXTURE_CACHE_UPDATE_TIME, expected(0.0..3.0)), 360 float("Staging CPU allocation", "ms", CPU_TEXTURE_ALLOCATION_TIME, Expected::none()), 361 float("Staging GPU allocation", "ms", STAGING_TEXTURE_ALLOCATION_TIME, Expected::none()), 362 float("Staging CPU copy", "ms", UPLOAD_CPU_COPY_TIME, Expected::none()), 363 float("Staging GPU copy", "ms", UPLOAD_GPU_COPY_TIME, Expected::none()), 364 float("Upload time", "ms", UPLOAD_TIME, Expected::none()), 365 int("Upload copy batches", "", UPLOAD_NUM_COPY_BATCHES, Expected::none()), 366 float("Texture cache upload", "ms", TOTAL_UPLOAD_TIME, expected(0.0..5.0)), 367 float("Cache texture creation", "ms", CREATE_CACHE_TEXTURE_TIME, expected(0.0..2.0)), 368 float("Cache texture deletion", "ms", DELETE_CACHE_TEXTURE_TIME, expected(0.0..1.0)), 369 370 int("Rasterized blobs", "", RASTERIZED_BLOBS, expected(0..15)), 371 int("Rasterized blob tiles", "", RASTERIZED_BLOB_TILES, expected(0..15)), 372 int("Rasterized blob pixels", "px", RASTERIZED_BLOBS_PX, expected(0..300_000)), 373 float("Blob rasterization", "ms", BLOB_RASTERIZATION_TIME, expected(0.0..8.0)), 374 375 int("Rasterized glyphs", "", RASTERIZED_GLYPHS, expected(0..15)), 376 float("Glyph resolve", "ms", GLYPH_RESOLVE_TIME, expected(0.0..4.0)), 377 378 int("Draw calls", "", DRAW_CALLS, expected(1..120).avg(1..90)), 379 int("Vertices", "", VERTICES, expected(10..5000)), 380 int("Primitives", "", PRIMITIVES, expected(10..5000)), 381 int("Visible primitives", "", VISIBLE_PRIMITIVES, expected(1..5000)), 382 383 int("Used targets", "", USED_TARGETS, expected(1..4)), 384 int("Created targets", "", CREATED_TARGETS, expected(0..3)), 385 int("Picture cache slices", "", PICTURE_CACHE_SLICES, expected(0..5)), 386 387 int("Color passes", "", COLOR_PASSES, expected(1..4)), 388 int("Alpha passes", "", ALPHA_PASSES, expected(0..3)), 389 int("Picture tiles", "", PICTURE_TILES, expected(0..15)), 390 int("Rendered picture tiles", "", RENDERED_PICTURE_TILES, expected(0..5)), 391 392 int("Font templates", "", FONT_TEMPLATES, expected(0..40)), 393 float("Font templates mem", "MB", FONT_TEMPLATES_MEM, expected(0.0..20.0)), 394 int("Image templates", "", IMAGE_TEMPLATES, expected(0..100)), 395 float("Image templates mem", "MB", IMAGE_TEMPLATES_MEM, expected(0.0..50.0)), 396 397 float("Atlas items mem", "MB", ATLAS_ITEMS_MEM, expected(0.0..100.0)), 398 int("Atlas A8 pixels", "px", ATLAS_A8_PIXELS, expected(0..1_000_000)), 399 int("Atlas A8 textures", "", ATLAS_A8_TEXTURES, expected(0..2)), 400 int("Atlas A16 pixels", "px", ATLAS_A16_PIXELS, expected(0..260_000)), 401 int("Atlas A16 textures", "", ATLAS_A16_TEXTURES, expected(0..2)), 402 int("Atlas RGBA8 linear pixels", "px", ATLAS_RGBA8_LINEAR_PIXELS, expected(0..8_000_000)), 403 int("Atlas RGBA8 linear textures", "", ATLAS_RGBA8_LINEAR_TEXTURES, expected(0..3)), 404 int("Atlas RGBA8 nearest pixels", "px", ATLAS_RGBA8_NEAREST_PIXELS, expected(0..260_000)), 405 int("Atlas RGBA8 nearest textures", "", ATLAS_RGBA8_NEAREST_TEXTURES, expected(0..2)), 406 int("Atlas RGBA8 glyphs pixels", "px", ATLAS_RGBA8_GLYPHS_PIXELS, expected(0..4_000_000)), 407 int("Atlas RGBA8 glyphs textures", "", ATLAS_RGBA8_GLYPHS_TEXTURES, expected(0..2)), 408 int("Atlas A8 glyphs pixels", "px", ATLAS_A8_GLYPHS_PIXELS, expected(0..4_000_000)), 409 int("Atlas A8 glyphs textures", "", ATLAS_A8_GLYPHS_TEXTURES, expected(0..2)), 410 float("Atlas RGBA8 linear pressure", "", ATLAS_COLOR8_LINEAR_PRESSURE, expected(0.0..1.0)), 411 float("Atlas RGBA8 nearest pressure", "", ATLAS_COLOR8_NEAREST_PRESSURE, expected(0.0..1.0)), 412 float("Atlas RGBA8 glyphs pressure", "", ATLAS_COLOR8_GLYPHS_PRESSURE, expected(0.0..1.0)), 413 float("Atlas A8 pressure", "", ATLAS_ALPHA8_PRESSURE, expected(0.0..1.0)), 414 float("Atlas A8 glyphs pressure", "", ATLAS_ALPHA8_GLYPHS_PRESSURE, expected(0.0..1.0)), 415 float("Atlas A16 pressure", "", ATLAS_ALPHA16_PRESSURE, expected(0.0..1.0)), 416 float("Texture cache standalone pressure", "", ATLAS_STANDALONE_PRESSURE, expected(0.0..1.0)), 417 418 int("Texture cache eviction count", "items", TEXTURE_CACHE_EVICTION_COUNT, Expected::none()), 419 int("Texture cache youngest evicted", "frames", TEXTURE_CACHE_YOUNGEST_EVICTION, Expected::none()), 420 float("External image mem", "MB", EXTERNAL_IMAGE_BYTES, Expected::none()), 421 float("Atlas textures mem", "MB", ATLAS_TEXTURES_MEM, Expected::none()), 422 float("Standalone textures mem", "MB", STANDALONE_TEXTURES_MEM, Expected::none()), 423 float("Picture tiles mem", "MB", PICTURE_TILES_MEM, expected(0.0..150.0)), 424 float("Render targets mem", "MB", RENDER_TARGET_MEM, Expected::none()), 425 426 float("Alpha targets samplers", "%", ALPHA_TARGETS_SAMPLERS, Expected::none()), 427 float("Transparent pass samplers", "%", TRANSPARENT_PASS_SAMPLERS, Expected::none()), 428 float("Opaque pass samplers", "%", OPAQUE_PASS_SAMPLERS, Expected::none()), 429 float("Total samplers", "%", TOTAL_SAMPLERS, Expected::none()), 430 431 int("Interned primitives", "", INTERNED_PRIMITIVES, Expected::none()), 432 int("Interned clips", "", INTERNED_CLIPS, Expected::none()), 433 int("Interned text runs", "", INTERNED_TEXT_RUNS, Expected::none()), 434 int("Interned normal borders", "", INTERNED_NORMAL_BORDERS, Expected::none()), 435 int("Interned image borders", "", INTERNED_IMAGE_BORDERS, Expected::none()), 436 int("Interned images", "", INTERNED_IMAGES, Expected::none()), 437 int("Interned YUV images", "", INTERNED_YUV_IMAGES, Expected::none()), 438 int("Interned line decorations", "", INTERNED_LINE_DECORATIONS, Expected::none()), 439 int("Interned linear gradients", "", INTERNED_LINEAR_GRADIENTS, Expected::none()), 440 int("Interned radial gradients", "", INTERNED_RADIAL_GRADIENTS, Expected::none()), 441 int("Interned conic gradients", "", INTERNED_CONIC_GRADIENTS, Expected::none()), 442 int("Interned pictures", "", INTERNED_PICTURES, Expected::none()), 443 int("Interned filter data", "", INTERNED_FILTER_DATA, Expected::none()), 444 int("Interned backdrop captures", "", INTERNED_BACKDROP_CAPTURES, Expected::none()), 445 int("Interned backdrop renders", "", INTERNED_BACKDROP_RENDERS, Expected::none()), 446 int("Interned polygons", "", INTERNED_POLYGONS, Expected::none()), 447 int("Interned box-shadows", "", INTERNED_BOX_SHADOWS, Expected::none()), 448 449 float("Depth targets mem", "MB", DEPTH_TARGETS_MEM, Expected::none()), 450 float("Shader build time", "ms", SHADER_BUILD_TIME, Expected::none()), 451 // We use the expected range to highlight render reasons that are happening. 452 float("Reason First", "", RENDER_REASON_FIRST, expected(0.0..0.01)), 453 float("Reason scene", "", RENDER_REASON_SCENE, expected(0.0..0.01)), 454 float("Reason animated property", "", RENDER_REASON_ANIMATED_PROPERTY, expected(0.0..0.01)), 455 float("Reason resource update", "", RENDER_REASON_RESOURCE_UPDATE, expected(0.0..0.01)), 456 float("Reason async image", "", RENDER_REASON_ASYNC_IMAGE, expected(0.0..0.01)), 457 float("Reason clear resources", "", RENDER_REASON_CLEAR_RESOURCES, expected(0.0..0.01)), 458 float("Reason APZ", "", RENDER_REASON_APZ, expected(0.0..0.01)), 459 float("Reason resize", "", RENDER_REASON_RESIZE, expected(0.0..0.01)), 460 float("Reason widget", "", RENDER_REASON_WIDGET, expected(0.0..0.01)), 461 float("Reason cache flush", "", RENDER_REASON_TEXTURE_CACHE_FLUSH, expected(0.0..0.01)), 462 float("Reason snapshot", "", RENDER_REASON_SNAPSHOT, expected(0.0..0.01)), 463 float("Reason resource hook", "", RENDER_REASON_POST_RESOURCE_UPDATE_HOOKS, expected(0.0..0.01)), 464 float("Reason config change", "", RENDER_REASON_CONFIG_CHANGE, expected(0.0..0.01)), 465 float("Reason content sync", "", RENDER_REASON_CONTENT_SYNC, expected(0.0..0.01)), 466 float("Reason flush", "", RENDER_REASON_FLUSH, expected(0.0..0.01)), 467 float("Reason testing", "", RENDER_REASON_TESTING, expected(0.0..0.01)), 468 float("Reason other", "", RENDER_REASON_OTHER, expected(0.0..0.01)), 469 float("On vsync", "", RENDER_REASON_VSYNC, expected(0.0..0.01)), 470 471 int("Textures created", "", TEXTURES_CREATED, expected(0..5)), 472 int("Textures deleted", "", TEXTURES_DELETED, Expected::none()), 473 474 int("Total slow frames CPU", "", SLOW_FRAME_CPU_COUNT, Expected::none()), 475 int("Total slow frames GPU", "", SLOW_FRAME_GPU_COUNT, Expected::none()), 476 int("Slow: frame build", "%", SLOW_FRAME_BUILD_COUNT, Expected::none()), 477 int("Slow: upload", "%", SLOW_UPLOAD_COUNT, Expected::none()), 478 int("Slow: render", "%", SLOW_RENDER_COUNT, Expected::none()), 479 int("Slow: draw calls", "%", SLOW_DRAW_CALLS_COUNT, Expected::none()), 480 int("Slow: targets", "%", SLOW_TARGETS_COUNT, Expected::none()), 481 int("Slow: blobs", "%", SLOW_BLOB_COUNT, Expected::none()), 482 int("Slow: after scene", "%", SLOW_SCROLL_AFTER_SCENE_COUNT, Expected::none()), 483 484 float("GPU buffer mem", "MB", GPU_BUFFER_MEM, Expected::none()), 485 float("GPU total mem", "MB", GPU_TOTAL_MEM, Expected::none()), 486 487 float("Frame send", "ms", FRAME_SEND_TIME, Expected::none()), 488 float("Update document", "ms", UPDATE_DOCUMENT_TIME, Expected::none()), 489 490 int("Compositor surface underlays", "", COMPOSITOR_SURFACE_UNDERLAYS, Expected::none()), 491 int("Compositor surface overlays", "", COMPOSITOR_SURFACE_OVERLAYS, Expected::none()), 492 int("Compositor surface blits", "", COMPOSITOR_SURFACE_BLITS, Expected::none()), 493 ]; 494 495 let mut counters = Vec::with_capacity(profile_counters.len()); 496 497 for (idx, descriptor) in profile_counters.iter().enumerate() { 498 debug_assert_eq!(descriptor.index, idx); 499 counters.push(Counter::new(descriptor)); 500 } 501 502 Profiler { 503 gpu_frames: ProfilerFrameCollection::new(), 504 frame_stats: ProfilerFrameCollection::new(), 505 slow_scroll_frames: ProfilerFrameCollection::new(), 506 507 counters, 508 start: zeitstempel::now(), 509 avg_over_period: ONE_SECOND_NS / 2, 510 slow_cpu_frame_threshold: 10.0, 511 512 num_graph_samples: 500, // Would it be useful to control this via a pref? 513 frame_timestamps_within_last_second: Vec::new(), 514 515 slow_frame_cpu_count: 0, 516 slow_frame_gpu_count: 0, 517 slow_frame_build_count: 0, 518 slow_render_count: 0, 519 slow_upload_count: 0, 520 slow_draw_calls_count: 0, 521 slow_targets_count: 0, 522 slow_blob_count: 0, 523 slow_scroll_after_scene_count: 0, 524 525 ui: Vec::new(), 526 } 527 } 528 529 pub fn set_parameter(&mut self, param: &api::Parameter) { 530 match param { 531 api::Parameter::Float(api::FloatParameter::SlowCpuFrameThreshold, threshold) => { 532 self.slow_cpu_frame_threshold = *threshold; 533 } 534 _ => {} 535 } 536 } 537 538 /// Sum a few counters and if the total amount is larger than a threshold, update 539 /// a specific counter. 540 /// 541 /// This is useful to monitor slow frame and slow transactions. 542 fn update_slow_event(&mut self, dst_counter: usize, counters: &[usize], threshold: f64) -> bool { 543 let mut total = 0.0; 544 for &counter in counters { 545 if self.counters[counter].value.is_finite() { 546 total += self.counters[counter].value; 547 } 548 } 549 550 if total > threshold { 551 self.counters[dst_counter].set(total); 552 return true; 553 } 554 555 false 556 } 557 558 fn classify_slow_cpu_frame(&mut self) { 559 let is_apz = self.counters[RENDER_REASON_ANIMATED_PROPERTY].value > 0.5 560 || self.counters[RENDER_REASON_APZ].value > 0.5; 561 if !is_apz { 562 // Only consider slow frames affecting scrolling for now. 563 return; 564 } 565 566 let frame = CpuFrameTimings::new(&self.counters); 567 self.slow_scroll_frames.push(frame.to_profiler_frame()); 568 569 if self.counters[RENDER_REASON_SCENE].value > 0.5 { 570 self.slow_scroll_after_scene_count += 1; 571 } 572 573 let frame_build = self.counters[FRAME_BUILDING_TIME].value; 574 let uploads = self.counters[TEXTURE_CACHE_UPDATE_TIME].value; 575 let renderer = self.counters[RENDERER_TIME].value - uploads; 576 let mut reasons = [ 577 (frame_build, &mut self.slow_frame_build_count, SLOW_FRAME_BUILD_COUNT,), 578 (renderer, &mut self.slow_render_count, SLOW_RENDER_COUNT,), 579 (uploads, &mut self.slow_upload_count, SLOW_UPLOAD_COUNT,), 580 ]; 581 582 reasons.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); 583 584 *reasons[0].1 += 1; 585 let reason = reasons[0].2; 586 std::mem::drop(reasons); 587 588 self.slow_frame_cpu_count += 1; 589 590 if reason == SLOW_RENDER_COUNT { 591 let draw_calls = self.counters[DRAW_CALLS].value; 592 if draw_calls > 200.0 { 593 self.slow_draw_calls_count += 1; 594 } 595 596 let render_passes = self.counters[COLOR_PASSES].value + self.counters[ALPHA_PASSES].value; 597 if render_passes > 20.0 { 598 self.slow_targets_count += 1; 599 } 600 } 601 602 if reason == SLOW_UPLOAD_COUNT { 603 let count = self.counters[TEXTURE_UPLOADS].value; 604 let blob_tiles = self.counters[RASTERIZED_BLOB_TILES].value; 605 // This is an approximation: we rasterize blobs for the whole displayport and 606 // only upload blob tiles for the current viewport. That said, the presence of 607 // a high number of blob tiles compared to the total number of uploads is still 608 // a good indication that blob images are the likely cause of the slow upload 609 // time, or at least contributing to it to a large extent. 610 if blob_tiles > count * 0.5 { 611 self.slow_blob_count += 1; 612 } 613 } 614 } 615 616 #[cfg(feature = "debugger")] 617 pub fn collect_updates_for_debugger(&self) -> Vec<ProfileCounterUpdate> { 618 let mut updates = Vec::new(); 619 620 for (i, counter) in self.counters.iter().enumerate() { 621 if let Some(value) = counter.get() { 622 updates.push(ProfileCounterUpdate { 623 id: ProfileCounterId(i), 624 value, 625 }); 626 } 627 } 628 629 updates 630 } 631 632 // Call at the end of every frame, after setting the counter values and before drawing the counters. 633 pub fn update(&mut self) { 634 let now = zeitstempel::now(); 635 let update_avg = (now - self.start) > self.avg_over_period; 636 if update_avg { 637 self.start = now; 638 } 639 let one_second_ago = now - ONE_SECOND_NS; 640 self.frame_timestamps_within_last_second.retain(|t| *t > one_second_ago); 641 self.frame_timestamps_within_last_second.push(now); 642 643 let slow_cpu = self.update_slow_event( 644 SLOW_FRAME, 645 &[TOTAL_FRAME_CPU_TIME], 646 self.slow_cpu_frame_threshold as f64, 647 ); 648 self.update_slow_event( 649 SLOW_TXN, 650 &[DISPLAY_LIST_BUILD_TIME, CONTENT_SEND_TIME, SCENE_BUILD_TIME], 651 80.0 652 ); 653 654 if slow_cpu { 655 self.classify_slow_cpu_frame(); 656 } 657 658 let div = 100.0 / self.slow_frame_cpu_count as f64; 659 self.counters[SLOW_FRAME_CPU_COUNT].set(self.slow_frame_cpu_count as f64); 660 self.counters[SLOW_FRAME_GPU_COUNT].set(self.slow_frame_gpu_count as f64); 661 self.counters[SLOW_FRAME_BUILD_COUNT].set(self.slow_frame_build_count as f64 * div); 662 self.counters[SLOW_RENDER_COUNT].set(self.slow_render_count as f64 * div); 663 self.counters[SLOW_UPLOAD_COUNT].set(self.slow_upload_count as f64 * div); 664 self.counters[SLOW_DRAW_CALLS_COUNT].set(self.slow_draw_calls_count as f64 * div); 665 self.counters[SLOW_TARGETS_COUNT].set(self.slow_targets_count as f64 * div); 666 self.counters[SLOW_BLOB_COUNT].set(self.slow_blob_count as f64 * div); 667 self.counters[SLOW_SCROLL_AFTER_SCENE_COUNT].set(self.slow_scroll_after_scene_count as f64 * div); 668 669 self.update_total_gpu_mem(); 670 671 for counter in &mut self.counters { 672 counter.update(update_avg); 673 } 674 } 675 676 pub fn update_frame_stats(&mut self, stats: FullFrameStats) { 677 if stats.gecko_display_list_time != 0.0 { 678 self.frame_stats.push(stats.into()); 679 } 680 } 681 682 pub fn update_total_gpu_mem(&mut self) { 683 let mut total = 0.0; 684 for counter in [ 685 EXTERNAL_IMAGE_BYTES, 686 ATLAS_TEXTURES_MEM, 687 STANDALONE_TEXTURES_MEM, 688 PICTURE_TILES_MEM, 689 RENDER_TARGET_MEM, 690 DEPTH_TARGETS_MEM, 691 ATLAS_ITEMS_MEM, 692 GPU_BUFFER_MEM, 693 ] { 694 if let Some(val) = self.counters[counter].get() { 695 total += val; 696 } 697 } 698 self.counters[GPU_TOTAL_MEM].set(total); 699 } 700 701 pub fn set_gpu_time_queries(&mut self, gpu_queries: Vec<GpuTimer>) { 702 let mut gpu_time_ns = 0; 703 for sample in &gpu_queries { 704 gpu_time_ns += sample.time_ns; 705 } 706 707 self.gpu_frames.push(ProfilerFrame { 708 total_time: gpu_time_ns, 709 samples: gpu_queries 710 }); 711 712 let gpu_time = ns_to_ms(gpu_time_ns); 713 self.counters[GPU_TIME].set_f64(gpu_time); 714 if gpu_time > 12.0 { 715 self.slow_frame_gpu_count += 1; 716 } 717 } 718 719 // Find the index of a counter by its name. 720 pub fn index_of(&self, name: &str) -> Option<usize> { 721 self.counters.iter().position(|counter| counter.name == name) 722 } 723 724 // Define the profiler UI, see comment about the syntax at the top of this file. 725 pub fn set_ui(&mut self, names: &str) { 726 let mut selection = Vec::new(); 727 728 self.append_to_ui(&mut selection, names); 729 730 if selection == self.ui { 731 return; 732 } 733 734 for counter in &mut self.counters { 735 counter.disable_graph(); 736 } 737 738 for item in &selection { 739 if let Item::Graph(idx) = item { 740 self.counters[*idx].enable_graph(self.num_graph_samples); 741 } 742 } 743 744 self.ui = selection; 745 } 746 747 fn append_to_ui(&mut self, selection: &mut Vec<Item>, names: &str) { 748 // Group successive counters together. 749 fn flush_counters(counters: &mut Vec<usize>, selection: &mut Vec<Item>) { 750 if !counters.is_empty() { 751 selection.push(Item::Counters(std::mem::take(counters))) 752 } 753 } 754 755 let mut counters = Vec::new(); 756 757 for name in names.split(",") { 758 let name = name.trim(); 759 let is_graph = name.starts_with("#"); 760 let is_indicator = name.starts_with("*"); 761 let is_string = name.starts_with("$"); 762 let name = if is_graph || is_indicator { 763 &name[1..] 764 } else { 765 name 766 }; 767 // See comment about the ui string syntax at the top of this file. 768 match name { 769 "" => { 770 flush_counters(&mut counters, selection); 771 selection.push(Item::Space); 772 } 773 "|" => { 774 flush_counters(&mut counters, selection); 775 selection.push(Item::Column); 776 } 777 "_" => { 778 flush_counters(&mut counters, selection); 779 selection.push(Item::Row); 780 } 781 "FPS" => { 782 flush_counters(&mut counters, selection); 783 selection.push(Item::Fps); 784 } 785 "GPU time queries" => { 786 flush_counters(&mut counters, selection); 787 selection.push(Item::GpuTimeQueries); 788 } 789 "Paint phase graph" => { 790 flush_counters(&mut counters, selection); 791 selection.push(Item::PaintPhaseGraph); 792 } 793 "Slow scroll frames" => { 794 flush_counters(&mut counters, selection); 795 selection.push(Item::SlowScrollFrames); 796 } 797 _ => { 798 if is_string { 799 selection.push(Item::Text(name[1..].into())); 800 } else if let Some(idx) = self.index_of(name) { 801 if is_graph { 802 flush_counters(&mut counters, selection); 803 selection.push(Item::Graph(idx)); 804 } else if is_indicator { 805 flush_counters(&mut counters, selection); 806 selection.push(Item::ChangeIndicator(idx)); 807 } else { 808 counters.push(idx); 809 } 810 } else if let Some(preset_str) = find_preset(name) { 811 flush_counters(&mut counters, selection); 812 self.append_to_ui(selection, preset_str); 813 } else { 814 selection.push(Item::Text(format!("Unknown counter: {}", name))); 815 } 816 } 817 } 818 } 819 820 flush_counters(&mut counters, selection); 821 } 822 823 pub fn set_counters(&mut self, counters: &mut TransactionProfile) { 824 for (id, evt) in counters.events.iter_mut().enumerate() { 825 if let Event::Value(val) = *evt { 826 self.counters[id].set(val); 827 } 828 *evt = Event::None; 829 } 830 } 831 832 #[cfg(feature = "debugger")] 833 pub fn counters(&self) -> &[Counter] { 834 &self.counters 835 } 836 837 fn draw_counters( 838 counters: &[Counter], 839 selected: &[usize], 840 mut x: f32, mut y: f32, 841 text_buffer: &mut String, 842 debug_renderer: &mut DebugRenderer, 843 ) -> default::Rect<f32> { 844 let line_height = debug_renderer.line_height(); 845 846 x += PROFILE_PADDING; 847 y += PROFILE_PADDING; 848 let origin = default::Point2D::new(x, y); 849 y += line_height * 0.5; 850 851 let mut total_rect = Rect::zero(); 852 853 let mut color_index = 0; 854 let colors = [ 855 // Regular values, 856 ColorU::new(255, 255, 255, 255), 857 ColorU::new(255, 255, 0, 255), 858 // Unexpected values, 859 ColorU::new(255, 80, 0, 255), 860 ColorU::new(255, 0, 0, 255), 861 ]; 862 863 for idx in selected { 864 // If The index is invalid, add some vertical space. 865 let counter = &counters[*idx]; 866 867 let rect = debug_renderer.add_text( 868 x, y, 869 counter.name, 870 colors[color_index], 871 None, 872 ); 873 color_index = (color_index + 1) % 2; 874 875 total_rect = total_rect.union(&rect); 876 y += line_height; 877 } 878 879 color_index = 0; 880 x = total_rect.max_x() + 60.0; 881 y = origin.y + line_height * 0.5; 882 883 for idx in selected { 884 let counter = &counters[*idx]; 885 let expected_offset = if counter.has_unexpected_avg_max() { 2 } else { 0 }; 886 887 counter.write_value(text_buffer); 888 889 let rect = debug_renderer.add_text( 890 x, 891 y, 892 &text_buffer, 893 colors[color_index + expected_offset], 894 None, 895 ); 896 color_index = (color_index + 1) % 2; 897 898 total_rect = total_rect.union(&rect); 899 y += line_height; 900 } 901 902 total_rect = total_rect 903 .union(&Rect { origin, size: Size2D::new(1.0, 1.0) }) 904 .inflate(PROFILE_PADDING, PROFILE_PADDING); 905 906 debug_renderer.add_quad( 907 total_rect.min_x(), 908 total_rect.min_y(), 909 total_rect.max_x(), 910 total_rect.max_y(), 911 BACKGROUND_COLOR, 912 BACKGROUND_COLOR, 913 ); 914 915 total_rect 916 } 917 918 fn draw_graph( 919 counter: &Counter, 920 x: f32, 921 y: f32, 922 text_buffer: &mut String, 923 debug_renderer: &mut DebugRenderer, 924 ) -> default::Rect<f32> { 925 let graph = counter.graph.as_ref().unwrap(); 926 927 let max_samples = graph.values.capacity() as f32; 928 929 let size = Size2D::new(max_samples, 100.0); 930 let line_height = debug_renderer.line_height(); 931 let graph_rect = Rect::new(Point2D::new(x + PROFILE_PADDING, y + PROFILE_PADDING), size); 932 let mut rect = graph_rect.inflate(PROFILE_PADDING, PROFILE_PADDING); 933 934 let stats = graph.stats(); 935 936 let text_color = ColorU::new(255, 255, 0, 255); 937 let text_origin = rect.origin + vec2(rect.size.width, 25.0); 938 set_text!(text_buffer, "{} ({})", counter.name, counter.unit); 939 debug_renderer.add_text( 940 text_origin.x, 941 text_origin.y, 942 if counter.unit == "" { counter.name } else { text_buffer }, 943 ColorU::new(0, 255, 0, 255), 944 None, 945 ); 946 947 set_text!(text_buffer, "Samples: {}", stats.samples); 948 949 debug_renderer.add_text( 950 text_origin.x, 951 text_origin.y + line_height, 952 text_buffer, 953 text_color, 954 None, 955 ); 956 957 if stats.samples > 0 { 958 set_text!(text_buffer, "Min: {:.2} {}", stats.min, counter.unit); 959 debug_renderer.add_text( 960 text_origin.x, 961 text_origin.y + line_height * 2.0, 962 text_buffer, 963 text_color, 964 None, 965 ); 966 967 set_text!(text_buffer, "Avg: {:.2} {}", stats.avg, counter.unit); 968 debug_renderer.add_text( 969 text_origin.x, 970 text_origin.y + line_height * 3.0, 971 text_buffer, 972 text_color, 973 None, 974 ); 975 976 set_text!(text_buffer, "Max: {:.2} {}", stats.max, counter.unit); 977 debug_renderer.add_text( 978 text_origin.x, 979 text_origin.y + line_height * 4.0, 980 text_buffer, 981 text_color, 982 None, 983 ); 984 } 985 986 rect.size.width += 220.0; 987 debug_renderer.add_quad( 988 rect.min_x(), 989 rect.min_y(), 990 rect.max_x(), 991 rect.max_y(), 992 BACKGROUND_COLOR, 993 BACKGROUND_COLOR, 994 ); 995 996 let bx1 = graph_rect.max_x(); 997 let by1 = graph_rect.max_y(); 998 999 let w = graph_rect.size.width / max_samples; 1000 let h = graph_rect.size.height; 1001 1002 let color_t0 = ColorU::new(0, 255, 0, 255); 1003 let color_b0 = ColorU::new(0, 180, 0, 255); 1004 1005 let color_t2 = ColorU::new(255, 0, 0, 255); 1006 let color_b2 = ColorU::new(180, 0, 0, 255); 1007 1008 if stats.max > 0.0 { 1009 for (index, sample) in graph.values.iter().enumerate() { 1010 if !sample.is_finite() { 1011 // NAN means no sample this frame. 1012 continue; 1013 } 1014 let sample = *sample as f32; 1015 let x1 = bx1 - index as f32 * w; 1016 let x0 = x1 - w; 1017 1018 let y0 = by1 - (sample / stats.max as f32) as f32 * h; 1019 let y1 = by1; 1020 1021 let (color_top, color_bottom) = if counter.is_unexpected_value(sample as f64) { 1022 (color_t2, color_b2) 1023 } else { 1024 (color_t0, color_b0) 1025 }; 1026 1027 debug_renderer.add_quad(x0, y0, x1, y1, color_top, color_bottom); 1028 } 1029 } 1030 1031 rect 1032 } 1033 1034 1035 fn draw_change_indicator( 1036 counter: &Counter, 1037 x: f32, y: f32, 1038 debug_renderer: &mut DebugRenderer 1039 ) -> default::Rect<f32> { 1040 let height = 10.0; 1041 let width = 20.0; 1042 1043 // Draw the indicator red instead of blue if is is not within expected ranges. 1044 let color = if counter.has_unexpected_value() || counter.has_unexpected_avg_max() { 1045 ColorU::new(255, 20, 20, 255) 1046 } else { 1047 ColorU::new(0, 100, 250, 255) 1048 }; 1049 1050 let tx = counter.change_indicator as f32 * width; 1051 debug_renderer.add_quad( 1052 x, 1053 y, 1054 x + 15.0 * width, 1055 y + height, 1056 ColorU::new(0, 0, 0, 150), 1057 ColorU::new(0, 0, 0, 150), 1058 ); 1059 1060 debug_renderer.add_quad( 1061 x + tx, 1062 y, 1063 x + tx + width, 1064 y + height, 1065 color, 1066 ColorU::new(25, 25, 25, 255), 1067 ); 1068 1069 Rect { 1070 origin: Point2D::new(x, y), 1071 size: Size2D::new(15.0 * width + 20.0, height), 1072 } 1073 } 1074 1075 // Draws a frame graph for a given frame collection. 1076 fn draw_frame_graph( 1077 frame_collection: &ProfilerFrameCollection, 1078 x: f32, y: f32, 1079 debug_renderer: &mut DebugRenderer, 1080 ) -> default::Rect<f32> { 1081 let mut has_data = false; 1082 for frame in &frame_collection.frames { 1083 if !frame.samples.is_empty() { 1084 has_data = true; 1085 break; 1086 } 1087 } 1088 1089 if !has_data { 1090 return Rect::zero(); 1091 } 1092 1093 let graph_rect = Rect::new( 1094 Point2D::new(x + GRAPH_PADDING, y + GRAPH_PADDING), 1095 Size2D::new(GRAPH_WIDTH, GRAPH_HEIGHT), 1096 ); 1097 let bounding_rect = graph_rect.inflate(GRAPH_PADDING, GRAPH_PADDING); 1098 1099 debug_renderer.add_quad( 1100 bounding_rect.origin.x, 1101 bounding_rect.origin.y, 1102 bounding_rect.origin.x + bounding_rect.size.width, 1103 bounding_rect.origin.y + bounding_rect.size.height, 1104 BACKGROUND_COLOR, 1105 BACKGROUND_COLOR, 1106 ); 1107 1108 let w = graph_rect.size.width; 1109 let mut y0 = graph_rect.origin.y; 1110 1111 let mut max_time = frame_collection.frames 1112 .iter() 1113 .max_by_key(|f| f.total_time) 1114 .unwrap() 1115 .total_time as f32; 1116 1117 // If the max time is lower than 16ms, fix the scale 1118 // at 16ms so that the graph is easier to interpret. 1119 let baseline_ns = 16_000_000.0; // 16ms 1120 max_time = max_time.max(baseline_ns); 1121 1122 let mut tags_present = FastHashMap::default(); 1123 1124 for frame in &frame_collection.frames { 1125 let y1 = y0 + GRAPH_FRAME_HEIGHT; 1126 1127 let mut current_ns = 0; 1128 for sample in &frame.samples { 1129 let x0 = graph_rect.origin.x + w * current_ns as f32 / max_time; 1130 current_ns += sample.time_ns; 1131 let x1 = graph_rect.origin.x + w * current_ns as f32 / max_time; 1132 let mut bottom_color = sample.tag.color; 1133 bottom_color.a *= 0.5; 1134 1135 debug_renderer.add_quad( 1136 x0, 1137 y0, 1138 x1, 1139 y1, 1140 sample.tag.color.into(), 1141 bottom_color.into(), 1142 ); 1143 1144 tags_present.insert(sample.tag.label, sample.tag.color); 1145 } 1146 1147 y0 = y1; 1148 } 1149 1150 let mut tags_present: Vec<_> = tags_present.iter().collect(); 1151 tags_present.sort_by_key(|item| item.0); 1152 1153 // If the max time is higher than 16ms, show a vertical line at the 1154 // 16ms mark. 1155 if max_time > baseline_ns { 1156 let x = graph_rect.origin.x + w * baseline_ns as f32 / max_time; 1157 let height = frame_collection.frames.len() as f32 * GRAPH_FRAME_HEIGHT; 1158 1159 debug_renderer.add_quad( 1160 x, 1161 graph_rect.origin.y, 1162 x + 4.0, 1163 graph_rect.origin.y + height, 1164 ColorU::new(120, 00, 00, 150), 1165 ColorU::new(120, 00, 00, 100), 1166 ); 1167 } 1168 1169 1170 // Add a legend to see which color correspond to what primitive. 1171 const LEGEND_SIZE: f32 = 20.0; 1172 const PADDED_LEGEND_SIZE: f32 = 25.0; 1173 if !tags_present.is_empty() { 1174 debug_renderer.add_quad( 1175 bounding_rect.max_x() + GRAPH_PADDING, 1176 bounding_rect.origin.y, 1177 bounding_rect.max_x() + GRAPH_PADDING + 200.0, 1178 bounding_rect.origin.y + tags_present.len() as f32 * PADDED_LEGEND_SIZE + GRAPH_PADDING, 1179 BACKGROUND_COLOR, 1180 BACKGROUND_COLOR, 1181 ); 1182 } 1183 1184 for (i, (label, &color)) in tags_present.iter().enumerate() { 1185 let x0 = bounding_rect.origin.x + bounding_rect.size.width + GRAPH_PADDING * 2.0; 1186 let y0 = bounding_rect.origin.y + GRAPH_PADDING + i as f32 * PADDED_LEGEND_SIZE; 1187 1188 debug_renderer.add_quad( 1189 x0, y0, x0 + LEGEND_SIZE, y0 + LEGEND_SIZE, 1190 color.into(), 1191 color.into(), 1192 ); 1193 1194 debug_renderer.add_text( 1195 x0 + PADDED_LEGEND_SIZE, 1196 y0 + LEGEND_SIZE * 0.75, 1197 label, 1198 ColorU::new(255, 255, 0, 255), 1199 None, 1200 ); 1201 } 1202 1203 bounding_rect 1204 } 1205 1206 pub fn draw_profile( 1207 &mut self, 1208 _frame_index: u64, 1209 debug_renderer: &mut DebugRenderer, 1210 device_size: DeviceIntSize, 1211 ) { 1212 let x_start = 20.0; 1213 let mut y_start = 150.0; 1214 let default_column_width = 400.0; 1215 1216 // set_text!(..) into this string instead of using format!(..) to avoid 1217 // unnecessary allocations. 1218 let mut text_buffer = String::with_capacity(32); 1219 1220 let mut column_width = default_column_width; 1221 let mut max_y = y_start; 1222 1223 let mut x = x_start; 1224 let mut y = y_start; 1225 1226 for elt in &self.ui { 1227 let rect = match elt { 1228 Item::Counters(indices) => { 1229 Profiler::draw_counters(&self.counters, &indices, x, y, &mut text_buffer, debug_renderer) 1230 } 1231 Item::Graph(idx) => { 1232 Profiler::draw_graph(&self.counters[*idx], x, y, &mut text_buffer, debug_renderer) 1233 } 1234 Item::ChangeIndicator(idx) => { 1235 Profiler::draw_change_indicator(&self.counters[*idx], x, y, debug_renderer) 1236 } 1237 Item::GpuTimeQueries => { 1238 Profiler::draw_frame_graph(&self.gpu_frames, x, y, debug_renderer) 1239 } 1240 Item::PaintPhaseGraph => { 1241 Profiler::draw_frame_graph(&self.frame_stats, x, y, debug_renderer) 1242 } 1243 Item::SlowScrollFrames => { 1244 Profiler::draw_frame_graph(&self.slow_scroll_frames, x, y, debug_renderer) 1245 } 1246 Item::Text(text) => { 1247 let p = 10.0; 1248 let mut rect = debug_renderer.add_text( 1249 x + p, 1250 y + p, 1251 &text, 1252 ColorU::new(255, 255, 255, 255), 1253 None, 1254 ); 1255 rect = rect.inflate(p, p); 1256 1257 debug_renderer.add_quad( 1258 rect.origin.x, 1259 rect.origin.y, 1260 rect.max_x(), 1261 rect.max_y(), 1262 BACKGROUND_COLOR, 1263 BACKGROUND_COLOR, 1264 ); 1265 1266 rect 1267 } 1268 Item::Fps => { 1269 let fps = self.frame_timestamps_within_last_second.len(); 1270 set_text!(&mut text_buffer, "{} fps", fps); 1271 let mut rect = debug_renderer.add_text( 1272 x + PROFILE_PADDING, 1273 y + PROFILE_PADDING + 5.0, 1274 &text_buffer, 1275 ColorU::new(255, 255, 255, 255), 1276 None, 1277 ); 1278 rect = rect.inflate(PROFILE_PADDING, PROFILE_PADDING); 1279 1280 debug_renderer.add_quad( 1281 rect.min_x(), 1282 rect.min_y(), 1283 rect.max_x(), 1284 rect.max_y(), 1285 BACKGROUND_COLOR, 1286 BACKGROUND_COLOR, 1287 ); 1288 1289 rect 1290 } 1291 Item::Space => { 1292 Rect { origin: Point2D::new(x, y), size: Size2D::new(0.0, PROFILE_SPACING) } 1293 } 1294 Item::Column => { 1295 max_y = max_y.max(y); 1296 x += column_width + PROFILE_SPACING; 1297 y = y_start; 1298 column_width = default_column_width; 1299 1300 continue; 1301 } 1302 Item::Row => { 1303 max_y = max_y.max(y); 1304 y_start = max_y + PROFILE_SPACING; 1305 y = y_start; 1306 x = x_start; 1307 column_width = default_column_width; 1308 1309 continue; 1310 } 1311 }; 1312 1313 column_width = column_width.max(rect.size.width); 1314 y = rect.max_y(); 1315 1316 if y > device_size.height as f32 - 100.0 { 1317 max_y = max_y.max(y); 1318 x += column_width + PROFILE_SPACING; 1319 y = y_start; 1320 column_width = default_column_width; 1321 } 1322 } 1323 } 1324 1325 #[cfg(feature = "capture")] 1326 pub fn dump_stats(&self, sink: &mut dyn std::io::Write) -> std::io::Result<()> { 1327 for counter in &self.counters { 1328 if counter.value.is_finite() { 1329 writeln!(sink, "{} {:?}{}", counter.name, counter.value, counter.unit)?; 1330 } 1331 } 1332 1333 Ok(()) 1334 } 1335 } 1336 1337 /// Defines the interface for hooking up an external profiler to WR. 1338 pub trait ProfilerHooks : Send + Sync { 1339 /// Register a thread with the profiler. 1340 fn register_thread(&self, thread_name: &str); 1341 1342 /// Unregister a thread with the profiler. 1343 fn unregister_thread(&self); 1344 1345 /// Called at the beginning of a profile scope. 1346 fn begin_marker(&self, label: &str); 1347 1348 /// Called at the end of a profile scope. 1349 fn end_marker(&self, label: &str); 1350 1351 /// Called to mark an event happening. 1352 fn event_marker(&self, label: &str); 1353 1354 /// Called with a duration to indicate a text marker that just ended. Text 1355 /// markers allow different types of entries to be recorded on the same row 1356 /// in the timeline, by adding labels to the entry. 1357 /// 1358 /// This variant is also useful when the caller only wants to record events 1359 /// longer than a certain threshold, and thus they don't know in advance 1360 /// whether the event will qualify. 1361 fn add_text_marker(&self, label: &str, text: &str, duration: Duration); 1362 1363 /// Returns true if the current thread is being profiled. 1364 fn thread_is_being_profiled(&self) -> bool; 1365 } 1366 1367 /// The current global profiler callbacks, if set by embedder. 1368 pub static mut PROFILER_HOOKS: Option<&'static dyn ProfilerHooks> = None; 1369 1370 /// Set the profiler callbacks, or None to disable the profiler. 1371 /// This function must only ever be called before any WR instances 1372 /// have been created, or the hooks will not be set. 1373 pub fn set_profiler_hooks(hooks: Option<&'static dyn ProfilerHooks>) { 1374 if !wr_has_been_initialized() { 1375 unsafe { 1376 PROFILER_HOOKS = hooks; 1377 } 1378 } 1379 } 1380 1381 /// A simple RAII style struct to manage a profile scope. 1382 pub struct ProfileScope { 1383 name: &'static str, 1384 } 1385 1386 1387 /// Register a thread with the Gecko Profiler. 1388 pub fn register_thread(thread_name: &str) { 1389 unsafe { 1390 if let Some(ref hooks) = PROFILER_HOOKS { 1391 hooks.register_thread(thread_name); 1392 } 1393 } 1394 } 1395 1396 1397 /// Unregister a thread with the Gecko Profiler. 1398 pub fn unregister_thread() { 1399 unsafe { 1400 if let Some(ref hooks) = PROFILER_HOOKS { 1401 hooks.unregister_thread(); 1402 } 1403 } 1404 } 1405 1406 /// Records a marker of the given duration that just ended. 1407 pub fn add_text_marker(label: &str, text: &str, duration: Duration) { 1408 unsafe { 1409 if let Some(ref hooks) = PROFILER_HOOKS { 1410 hooks.add_text_marker(label, text, duration); 1411 } 1412 } 1413 } 1414 1415 /// Records a marker of the given duration that just ended. 1416 pub fn add_event_marker(label: &str) { 1417 unsafe { 1418 if let Some(ref hooks) = PROFILER_HOOKS { 1419 hooks.event_marker(label); 1420 } 1421 } 1422 } 1423 1424 /// Returns true if the current thread is being profiled. 1425 pub fn thread_is_being_profiled() -> bool { 1426 unsafe { 1427 PROFILER_HOOKS.map_or(false, |h| h.thread_is_being_profiled()) 1428 } 1429 } 1430 1431 impl ProfileScope { 1432 /// Begin a new profile scope 1433 pub fn new(name: &'static str) -> Self { 1434 unsafe { 1435 if let Some(ref hooks) = PROFILER_HOOKS { 1436 hooks.begin_marker(name); 1437 } 1438 } 1439 1440 ProfileScope { 1441 name, 1442 } 1443 } 1444 } 1445 1446 impl Drop for ProfileScope { 1447 fn drop(&mut self) { 1448 unsafe { 1449 if let Some(ref hooks) = PROFILER_HOOKS { 1450 hooks.end_marker(self.name); 1451 } 1452 } 1453 } 1454 } 1455 1456 /// A helper macro to define profile scopes. 1457 macro_rules! profile_marker { 1458 ($string:expr) => { 1459 let _scope = $crate::profiler::ProfileScope::new($string); 1460 }; 1461 } 1462 1463 #[derive(Debug, Clone)] 1464 pub struct GpuProfileTag { 1465 pub label: &'static str, 1466 pub color: ColorF, 1467 } 1468 1469 /// Ranges of expected value for a profile counter. 1470 #[derive(Clone, Debug)] 1471 pub struct Expected<T> { 1472 pub range: Option<Range<T>>, 1473 pub avg: Option<Range<T>>, 1474 } 1475 1476 impl<T> Expected<T> { 1477 const fn none() -> Self { 1478 Expected { 1479 range: None, 1480 avg: None, 1481 } 1482 } 1483 } 1484 1485 const fn expected<T>(range: Range<T>) -> Expected<T> { 1486 Expected { 1487 range: Some(range), 1488 avg: None, 1489 } 1490 } 1491 1492 impl Expected<f64> { 1493 const fn avg(mut self, avg: Range<f64>) -> Self { 1494 self.avg = Some(avg); 1495 self 1496 } 1497 } 1498 1499 impl Expected<i64> { 1500 const fn avg(mut self, avg: Range<i64>) -> Self { 1501 self.avg = Some(avg); 1502 self 1503 } 1504 1505 fn into_float(self) -> Expected<f64> { 1506 Expected { 1507 range: match self.range { 1508 Some(r) => Some(r.start as f64 .. r.end as f64), 1509 None => None, 1510 }, 1511 avg: match self.avg { 1512 Some(r) => Some(r.start as f64 .. r.end as f64), 1513 None => None, 1514 }, 1515 } 1516 } 1517 } 1518 1519 pub struct CounterDescriptor { 1520 pub name: &'static str, 1521 pub unit: &'static str, 1522 pub index: usize, 1523 pub show_as: ShowAs, 1524 pub expected: Expected<f64>, 1525 } 1526 1527 #[derive(Debug)] 1528 pub struct Counter { 1529 pub name: &'static str, 1530 pub unit: &'static str, 1531 pub show_as: ShowAs, 1532 pub expected: Expected<f64>, 1533 1534 /// 1535 value: f64, 1536 /// Number of samples in the current time slice. 1537 num_samples: u64, 1538 /// Sum of the values recorded during the current time slice. 1539 sum: f64, 1540 /// The max value in in-progress time slice. 1541 next_max: f64, 1542 /// The max value of the previous time slice (displayed). 1543 max: f64, 1544 /// The average value of the previous time slice (displayed). 1545 avg: f64, 1546 /// Incremented when the counter changes. 1547 change_indicator: u8, 1548 1549 graph: Option<Graph>, 1550 } 1551 1552 impl Counter { 1553 pub fn new(descriptor: &CounterDescriptor) -> Self { 1554 Counter { 1555 name: descriptor.name, 1556 unit: descriptor.unit, 1557 show_as: descriptor.show_as, 1558 expected: descriptor.expected.clone(), 1559 value: std::f64::NAN, 1560 num_samples: 0, 1561 sum: 0.0, 1562 next_max: 0.0, 1563 max: 0.0, 1564 avg: 0.0, 1565 change_indicator: 0, 1566 graph: None, 1567 } 1568 } 1569 pub fn set_f64(&mut self, val: f64) { 1570 self.value = val; 1571 } 1572 1573 pub fn set<T>(&mut self, val: T) where T: Into<f64> { 1574 self.set_f64(val.into()); 1575 } 1576 1577 pub fn get(&self) -> Option<f64> { 1578 if self.value.is_finite() { 1579 Some(self.value) 1580 } else { 1581 None 1582 } 1583 } 1584 1585 pub fn write_value(&self, output: &mut String) { 1586 match self.show_as { 1587 ShowAs::Float => { 1588 set_text!(output, "{:.2} {} (max: {:.2})", self.avg, self.unit, self.max); 1589 } 1590 ShowAs::Int => { 1591 set_text!(output, "{:.0} {} (max: {:.0})", self.avg.round(), self.unit, self.max.round()); 1592 } 1593 } 1594 } 1595 1596 pub fn enable_graph(&mut self, max_samples: usize) { 1597 if self.graph.is_some() { 1598 return; 1599 } 1600 1601 self.graph = Some(Graph::new(max_samples)); 1602 } 1603 1604 pub fn disable_graph(&mut self) { 1605 self.graph = None; 1606 } 1607 1608 pub fn is_unexpected_value(&self, value: f64) -> bool { 1609 if let Some(range) = &self.expected.range { 1610 return value.is_finite() && value >= range.end; 1611 } 1612 1613 false 1614 } 1615 1616 pub fn has_unexpected_value(&self) -> bool { 1617 self.is_unexpected_value(self.value) 1618 } 1619 1620 pub fn has_unexpected_avg_max(&self) -> bool { 1621 if let Some(range) = &self.expected.range { 1622 if self.max.is_finite() && self.max >= range.end { 1623 return true; 1624 } 1625 } 1626 1627 if let Some(range) = &self.expected.avg { 1628 if self.avg < range.start || self.avg >= range.end { 1629 return true; 1630 } 1631 } 1632 1633 false 1634 } 1635 1636 fn update(&mut self, update_avg: bool) { 1637 let updated = self.value.is_finite(); 1638 if updated { 1639 self.next_max = self.next_max.max(self.value); 1640 self.sum += self.value; 1641 self.num_samples += 1; 1642 self.change_indicator = (self.change_indicator + 1) % 15; 1643 } 1644 1645 if let Some(graph) = &mut self.graph { 1646 graph.set(self.value); 1647 } 1648 1649 self.value = std::f64::NAN; 1650 1651 if update_avg { 1652 if self.num_samples > 0 { 1653 self.avg = self.sum / self.num_samples as f64; 1654 self.max = self.next_max; 1655 } else { 1656 // There has been no sample in the averaging window, just show zero. 1657 self.avg = 0.0; 1658 self.max = 0.0; 1659 } 1660 self.sum = 0.0; 1661 self.num_samples = 0; 1662 self.next_max = std::f64::MIN; 1663 } 1664 } 1665 } 1666 1667 #[derive(Copy, Clone, Debug)] 1668 pub enum Event { 1669 Start(u64), 1670 Value(f64), 1671 None, 1672 } 1673 1674 // std::convert::From/TryFrom can't deal with integer to f64 so we roll our own... 1675 pub trait EventValue { 1676 fn into_f64(self) -> f64; 1677 } 1678 1679 impl EventValue for f64 { fn into_f64(self) -> f64 { self } } 1680 impl EventValue for f32 { fn into_f64(self) -> f64 { self as f64 } } 1681 impl EventValue for u32 { fn into_f64(self) -> f64 { self as f64 } } 1682 impl EventValue for i32 { fn into_f64(self) -> f64 { self as f64 } } 1683 impl EventValue for u64 { fn into_f64(self) -> f64 { self as f64 } } 1684 impl EventValue for usize { fn into_f64(self) -> f64 { self as f64 } } 1685 1686 /// A container for profiling information that moves along the rendering pipeline 1687 /// and is handed off to the profiler at the end. 1688 pub struct TransactionProfile { 1689 pub events: Vec<Event>, 1690 } 1691 1692 impl TransactionProfile { 1693 pub fn new() -> Self { 1694 TransactionProfile { 1695 events: vec![Event::None; NUM_PROFILER_EVENTS], 1696 } 1697 } 1698 1699 pub fn start_time(&mut self, id: usize) { 1700 let ns = zeitstempel::now(); 1701 self.events[id] = Event::Start(ns); 1702 } 1703 1704 pub fn end_time(&mut self, id: usize) -> f64 { 1705 self.end_time_if_started(id).unwrap() 1706 } 1707 1708 /// Similar to end_time, but doesn't panic if not matched with start_time. 1709 pub fn end_time_if_started(&mut self, id: usize) -> Option<f64> { 1710 if let Event::Start(start) = self.events[id] { 1711 let now = zeitstempel::now(); 1712 let time_ns = now - start; 1713 1714 let time_ms = ns_to_ms(time_ns); 1715 self.events[id] = Event::Value(time_ms); 1716 1717 Some(time_ms) 1718 } else { 1719 None 1720 } 1721 } 1722 1723 pub fn set<T>(&mut self, id: usize, value: T) where T: EventValue { 1724 self.set_f64(id, value.into_f64()); 1725 } 1726 1727 1728 pub fn set_f64(&mut self, id: usize, value: f64) { 1729 self.events[id] = Event::Value(value); 1730 } 1731 1732 pub fn get(&self, id: usize) -> Option<f64> { 1733 if let Event::Value(val) = self.events[id] { 1734 Some(val) 1735 } else { 1736 None 1737 } 1738 } 1739 1740 pub fn get_or(&self, id: usize, or: f64) -> f64 { 1741 self.get(id).unwrap_or(or) 1742 } 1743 1744 pub fn add<T>(&mut self, id: usize, n: T) where T: EventValue { 1745 let n = n.into_f64(); 1746 1747 let evt = &mut self.events[id]; 1748 1749 let val = match *evt { 1750 Event::Value(v) => v + n, 1751 Event::None => n, 1752 Event::Start(..) => { panic!(); } 1753 }; 1754 1755 *evt = Event::Value(val); 1756 } 1757 1758 pub fn inc(&mut self, id: usize) { 1759 self.add(id, 1.0); 1760 } 1761 1762 pub fn take(&mut self) -> Self { 1763 TransactionProfile { 1764 events: std::mem::take(&mut self.events), 1765 } 1766 } 1767 1768 pub fn take_and_reset(&mut self) -> Self { 1769 let events = std::mem::take(&mut self.events); 1770 1771 *self = TransactionProfile::new(); 1772 1773 TransactionProfile { events } 1774 } 1775 1776 pub fn merge(&mut self, other: &mut Self) { 1777 for i in 0..self.events.len() { 1778 match (self.events[i], other.events[i]) { 1779 (Event::Value(v1), Event::Value(v2)) => { 1780 self.events[i] = Event::Value(v1.max(v2)); 1781 } 1782 (Event::Value(_), _) => {} 1783 (_, Event::Value(v2)) => { 1784 self.events[i] = Event::Value(v2); 1785 } 1786 (Event::None, evt) => { 1787 self.events[i] = evt; 1788 } 1789 (Event::Start(s1), Event::Start(s2)) => { 1790 self.events[i] = Event::Start(s1.max(s2)); 1791 } 1792 _=> {} 1793 } 1794 other.events[i] = Event::None; 1795 } 1796 } 1797 1798 pub fn clear(&mut self) { 1799 for evt in &mut self.events { 1800 *evt = Event::None; 1801 } 1802 } 1803 } 1804 1805 impl GlyphRasterizeProfiler for TransactionProfile { 1806 fn start_time(&mut self) { 1807 let id = GLYPH_RESOLVE_TIME; 1808 let ns = zeitstempel::now(); 1809 self.events[id] = Event::Start(ns); 1810 } 1811 1812 fn end_time(&mut self) -> f64 { 1813 let id = GLYPH_RESOLVE_TIME; 1814 self.end_time_if_started(id).unwrap() 1815 } 1816 1817 fn set(&mut self, value: f64) { 1818 let id = RASTERIZED_GLYPHS; 1819 self.set_f64(id, value); 1820 } 1821 } 1822 1823 #[derive(Debug)] 1824 pub struct GraphStats { 1825 pub min: f64, 1826 pub avg: f64, 1827 pub max: f64, 1828 pub sum: f64, 1829 pub samples: usize, 1830 } 1831 1832 #[derive(Debug)] 1833 pub struct Graph { 1834 values: VecDeque<f64>, 1835 } 1836 1837 impl Graph { 1838 fn new(max_samples: usize) -> Self { 1839 let mut values = VecDeque::new(); 1840 values.reserve(max_samples); 1841 1842 Graph { values } 1843 } 1844 1845 fn set(&mut self, val: f64) { 1846 if self.values.len() == self.values.capacity() { 1847 self.values.pop_back(); 1848 } 1849 self.values.push_front(val); 1850 } 1851 1852 pub fn stats(&self) -> GraphStats { 1853 let mut stats = GraphStats { 1854 min: f64::MAX, 1855 avg: 0.0, 1856 max: -f64::MAX, 1857 sum: 0.0, 1858 samples: 0, 1859 }; 1860 1861 let mut samples = 0; 1862 for value in &self.values { 1863 if value.is_finite() { 1864 stats.min = stats.min.min(*value); 1865 stats.max = stats.max.max(*value); 1866 stats.sum += *value; 1867 samples += 1; 1868 } 1869 } 1870 1871 if samples > 0 { 1872 stats.avg = stats.sum / samples as f64; 1873 stats.samples = samples; 1874 } 1875 1876 stats 1877 } 1878 } 1879 1880 #[derive(Copy, Clone, Debug, PartialEq, Eq)] 1881 pub enum ShowAs { 1882 Float, 1883 Int, 1884 } 1885 1886 struct ProfilerFrame { 1887 total_time: u64, 1888 samples: Vec<GpuTimer>, 1889 } 1890 1891 struct ProfilerFrameCollection { 1892 frames: VecDeque<ProfilerFrame>, 1893 } 1894 1895 impl ProfilerFrameCollection { 1896 fn new() -> Self { 1897 ProfilerFrameCollection { 1898 frames: VecDeque::new(), 1899 } 1900 } 1901 1902 fn push(&mut self, frame: ProfilerFrame) { 1903 if self.frames.len() == 20 { 1904 self.frames.pop_back(); 1905 } 1906 self.frames.push_front(frame); 1907 } 1908 } 1909 1910 impl From<FullFrameStats> for ProfilerFrame { 1911 fn from(stats: FullFrameStats) -> ProfilerFrame { 1912 let new_sample = |time, label, color| -> GpuTimer { 1913 let tag = GpuProfileTag { 1914 label, 1915 color 1916 }; 1917 1918 let time_ns = ms_to_ns(time); 1919 1920 GpuTimer { 1921 tag, time_ns 1922 } 1923 }; 1924 1925 let samples = vec![ 1926 new_sample(stats.gecko_display_list_time, "Gecko DL", ColorF { r: 0.0, g: 1.0, b: 0.0, a: 1.0 }), 1927 new_sample(stats.wr_display_list_time, "WR DL", ColorF { r: 0.0, g: 1.0, b: 1.0, a: 1.0 }), 1928 new_sample(stats.scene_build_time, "Scene Build", ColorF { r: 1.0, g: 0.0, b: 1.0, a: 1.0 }), 1929 new_sample(stats.frame_build_time, "Frame Build", ColorF { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }), 1930 ]; 1931 1932 ProfilerFrame { 1933 total_time: ms_to_ns(stats.total()), 1934 samples 1935 } 1936 } 1937 } 1938 1939 pub struct CpuFrameTimings { 1940 pub total: f64, 1941 pub api_send: f64, 1942 pub update_document: f64, 1943 pub visibility: f64, 1944 pub prepare: f64, 1945 pub glyph_resolve: f64, 1946 pub batching: f64, 1947 pub frame_building_other: f64, 1948 pub frame_send: f64, 1949 pub uploads: f64, 1950 pub draw_calls: f64, 1951 pub unknown: f64, 1952 } 1953 1954 impl CpuFrameTimings { 1955 pub fn new(counters: &[Counter]) -> Self { 1956 let total = counters[TOTAL_FRAME_CPU_TIME].get().unwrap_or(0.0); 1957 let api_send = counters[API_SEND_TIME].get().unwrap_or(0.0); 1958 let visibility = counters[FRAME_VISIBILITY_TIME].get().unwrap_or(0.0); 1959 let prepare = counters[FRAME_PREPARE_TIME].get().unwrap_or(0.0); 1960 let glyph_resolve = counters[GLYPH_RESOLVE_TIME].get().unwrap_or(0.0); 1961 let batching = counters[FRAME_BATCHING_TIME].get().unwrap_or(0.0); 1962 let frame_send = counters[FRAME_SEND_TIME].get().unwrap_or(0.0); 1963 let renderer = counters[RENDERER_TIME].get().unwrap_or(0.0); 1964 let uploads = counters[TEXTURE_CACHE_UPDATE_TIME].get().unwrap_or(0.0); 1965 let frame_build = visibility + prepare + glyph_resolve + batching; 1966 let update_document = counters[UPDATE_DOCUMENT_TIME].get().unwrap_or(0.0) - frame_build; 1967 let draw_calls = renderer - uploads; 1968 let unknown = (total - (api_send + update_document + frame_build + frame_send + renderer)).max(0.0); 1969 let frame_building_other = (counters[FRAME_BUILDING_TIME].get().unwrap_or(0.0) - frame_build).max(0.0); 1970 1971 CpuFrameTimings { 1972 total, 1973 api_send, 1974 update_document, 1975 visibility, 1976 prepare, 1977 glyph_resolve, 1978 batching, 1979 frame_building_other, 1980 frame_send, 1981 uploads, 1982 draw_calls, 1983 unknown, 1984 } 1985 } 1986 1987 fn to_profiler_frame(&self) -> ProfilerFrame { 1988 fn sample(time_ms: f64, label: &'static str, color: ColorF) -> GpuTimer { 1989 let time_ns = ms_to_ns(time_ms); 1990 GpuTimer { 1991 time_ns, 1992 tag: GpuProfileTag { label, color }, 1993 } 1994 } 1995 1996 ProfilerFrame { 1997 total_time: ms_to_ns(self.total), 1998 // Number the label so that they are displayed in order. 1999 samples: vec![ 2000 // Compositor -> frame building 2001 sample(self.api_send, "01. send", ColorF { r: 0.5, g: 0.5, b: 0.5, a: 1.0 }), 2002 // Frame building 2003 sample(self.update_document, "02. update document", ColorF { r: 0.2, g: 0.2, b: 0.7, a: 1.0 }), 2004 sample(self.visibility, "03. visibility", ColorF { r: 0.0, g: 0.5, b: 0.9, a: 1.0 }), 2005 sample(self.prepare, "04. prepare", ColorF { r: 0.0, g: 0.4, b: 0.3, a: 1.0 }), 2006 sample(self.glyph_resolve, "05. glyph resolve", ColorF { r: 0.0, g: 0.7, b: 0.4, a: 1.0 }), 2007 sample(self.batching, "06. batching", ColorF { r: 0.2, g: 0.3, b: 0.7, a: 1.0 }), 2008 sample(self.frame_building_other, "07. frame build (other)", ColorF { r: 0.1, g: 0.7, b: 0.7, a: 1.0 }), 2009 // Frame building -> renderer 2010 sample(self.frame_send, "08. frame send", ColorF { r: 1.0, g: 0.8, b: 0.8, a: 1.0 }), 2011 // Renderer 2012 sample(self.uploads, "09. texture uploads", ColorF { r: 0.8, g: 0.0, b: 0.3, a: 1.0 }), 2013 sample(self.draw_calls, "10. draw calls", ColorF { r: 1.0, g: 0.5, b: 0.0, a: 1.0 }), 2014 // Unaccounted time 2015 sample(self.unknown, "11. unknown", ColorF { r: 0.3, g: 0.3, b: 0.3, a: 1.0 }), 2016 ], 2017 } 2018 } 2019 } 2020 2021 pub fn ns_to_ms(ns: u64) -> f64 { 2022 ns as f64 / 1_000_000.0 2023 } 2024 2025 pub fn ms_to_ns(ms: f64) -> u64 { 2026 (ms * 1_000_000.0) as u64 2027 } 2028 2029 pub fn bytes_to_mb(bytes: usize) -> f64 { 2030 bytes as f64 / 1_000_000.0 2031 } 2032 2033 #[derive(Debug, PartialEq)] 2034 enum Item { 2035 Counters(Vec<usize>), 2036 Graph(usize), 2037 ChangeIndicator(usize), 2038 Fps, 2039 GpuTimeQueries, 2040 PaintPhaseGraph, 2041 SlowScrollFrames, 2042 Text(String), 2043 Space, 2044 Column, 2045 Row, 2046 } 2047 2048 pub struct RenderCommandLog { 2049 items: Vec<RenderCommandInfo>, 2050 current_shader: &'static str, 2051 } 2052 2053 impl RenderCommandLog { 2054 pub fn new() -> Self { 2055 RenderCommandLog { 2056 items: Vec::new(), 2057 current_shader: "", 2058 } 2059 } 2060 2061 pub fn get(&self) -> &[RenderCommandInfo] { 2062 &self.items 2063 } 2064 2065 pub fn clear(&mut self) { 2066 self.current_shader = ""; 2067 self.items.clear(); 2068 } 2069 2070 pub fn set_shader(&mut self, shader: &'static str) { 2071 self.current_shader = shader; 2072 } 2073 2074 pub fn begin_render_target(&mut self, label: &str, size: DeviceIntSize) { 2075 self.items.push(RenderCommandInfo::RenderTarget { kind: label.into(), size }) 2076 } 2077 2078 pub fn draw(&mut self, instances: u32) { 2079 self.items.push(RenderCommandInfo::DrawCall { 2080 shader: self.current_shader.into(), 2081 instances, 2082 }); 2083 } 2084 }