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