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