1use 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
54static PROFILER_PRESETS: &'static[(&'static str, &'static str)] = &[
56 (&"Default", &"FPS,|,Slow indicators,_,Time graphs,|,Frame times, ,Transaction times, ,Frame stats, ,Memory, ,Interners,_,GPU time queries,_,Paint phase graph"),
58 (&"Compact", &"FPS, ,Frame times, ,Frame stats"),
60 (&"Slow indicators", &"*Slow transaction,*Slow frame"),
62
63 (&"Transaction times", &"DisplayList,Scene building,Content send,API send"),
67 (&"Frame times", &"Frame CPU total,Frame building,Visibility,Prepare,Batching,Glyph resolve,Texture cache update,Renderer,GPU"),
69 (&"Frame stats", &"Primitives,Visible primitives,Draw calls,Vertices,Color passes,Alpha passes,Rendered picture tiles,Rasterized glyphs"),
71 (&"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 (&"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 (&"Time graphs", &"#DisplayList,#Scene building,#Blob rasterization, ,#Frame CPU total,#Frame building,#Renderer,#Texture cache update, ,#GPU,"),
85 (&"Backend graphs", &"#Frame building, #Visibility, #Prepare, #Batching, #Glyph resolve"),
87 (&"Renderer graphs", &"#Rendered picture tiles,#Draw calls,#Rasterized glyphs,#Texture uploads,#Texture uploads mem, ,#Texture cache update,#Renderer,"),
89
90 (&"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 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
108pub 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
178pub 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 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 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, frame_timestamps_within_last_second: Vec::new(),
405 ui: Vec::new(),
406 }
407 }
408
409 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 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 pub fn index_of(&self, name: &str) -> Option<usize> {
475 self.counters.iter().position(|counter| counter.name == name)
476 }
477
478 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 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 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 ColorU::new(255, 255, 255, 255),
607 ColorU::new(255, 255, 0, 255),
608 ColorU::new(255, 80, 0, 255),
610 ColorU::new(255, 0, 0, 255),
611 ];
612
613 for idx in selected {
614 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 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 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 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 let baseline_ns = 16_000_000.0; 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 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 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 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
1178pub trait ProfilerHooks : Send + Sync {
1180 fn register_thread(&self, thread_name: &str);
1182
1183 fn unregister_thread(&self);
1185
1186 fn begin_marker(&self, label: &CStr);
1189
1190 fn end_marker(&self, label: &CStr);
1193
1194 fn event_marker(&self, label: &CStr);
1197
1198 fn add_text_marker(&self, label: &CStr, text: &str, duration: Duration);
1206
1207 fn thread_is_being_profiled(&self) -> bool;
1209}
1210
1211pub static mut PROFILER_HOOKS: Option<&'static dyn ProfilerHooks> = None;
1213
1214pub 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
1225pub struct ProfileScope {
1227 name: &'static CStr,
1228}
1229
1230
1231pub 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
1241pub fn unregister_thread() {
1243 unsafe {
1244 if let Some(ref hooks) = PROFILER_HOOKS {
1245 hooks.unregister_thread();
1246 }
1247 }
1248}
1249
1250pub 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
1259pub 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
1268pub 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 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
1300macro_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#[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 value: f64,
1380 num_samples: u64,
1382 sum: f64,
1384 next_max: f64,
1386 max: f64,
1388 avg: f64,
1390 change_indicator: u8,
1392
1393 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
1516pub 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
1528pub 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 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