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