webrender/
profiler.rs

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