1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//! Debug helpers and logging flags.
use std::{
collections::HashMap,
sync::{LazyLock, Mutex},
};
/// Renderer logging flags.
#[derive(Clone, Debug, Default)]
pub struct AwsmRendererLogging {
/// How much per-frame work should open a `tracing` span.
///
/// Each span enter/exit on the web routes through
/// `tracing_web::performance_layer`, which calls
/// `performance.mark()` and `performance.measure()` across the
/// wasm↔JS boundary. On mobile the per-call cost is large enough
/// that letting every sub-pass open a span dominates frame time.
/// We therefore
/// gate at the call site so a span is never even *created*
/// unless the tier permits it.
pub render_timings: RenderTimings,
}
/// How much render-side tracing to emit per frame.
///
/// Ordering matters: each tier is a strict superset of the
/// previous one. Comparing with `>=` is the canonical way to test
/// at a span site.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum RenderTimings {
/// No render-timing spans at all. The crate-level default —
/// matches the prior `render_timings: bool = false` behavior so
/// any embedder that constructs `AwsmRendererLogging::default()`
/// pays zero tracing cost. Frontends explicitly opt in to a
/// non-`Off` tier; see `crates/web-shared/src/perf.rs` for the
/// `?trace=…` URL-param wiring.
#[default]
Off,
/// Just the outermost `"Render"` span — one
/// `performance.mark` plus one `performance.measure` per
/// frame. This is what the shipping web build runs by default:
/// it tells you frame time (and lets the DevTools performance
/// panel show a single bar per frame) while costing essentially
/// nothing.
Frame,
/// Every render pass, GPU write, hook, and renderer-internal
/// stage opens its own span. This is what you want when
/// diagnosing why a frame is slow; it's far too chatty to run
/// in shipping builds on mobile.
SubFrame,
}
impl RenderTimings {
/// True when *any* render-timing span should be created.
/// Equivalent to `!= Off`.
pub fn enabled(self) -> bool {
self != RenderTimings::Off
}
/// True when sub-frame spans (passes, GPU writes, hooks, …)
/// should be created. Equivalent to `>= SubFrame`.
pub fn sub_frame(self) -> bool {
self >= RenderTimings::SubFrame
}
/// Parse the value of a `?trace=…` URL parameter.
///
/// Accepts (case-insensitive): `off`, `none`, `frame`,
/// `sub-frame`, `subframe`, `sub_frame`. Returns `None` for
/// anything else so callers can fall back to a build-time
/// default.
pub fn parse(s: &str) -> Option<Self> {
match s.trim().to_ascii_lowercase().as_str() {
"off" | "none" | "0" => Some(Self::Off),
"frame" | "1" => Some(Self::Frame),
"sub-frame" | "subframe" | "sub_frame" | "2" => Some(Self::SubFrame),
_ => None,
}
}
}
/// Debug ID reserved for renderable tracking.
pub const DEBUG_ID_RENDERABLE: u32 = u32::MAX - 1;
static DEBUG_TRANSACTION_ID: LazyLock<Mutex<HashMap<u32, u64>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
static DEBUG_UNIQUE_STRING: LazyLock<Mutex<HashMap<u32, String>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
// returns the old value
fn bump_transaction_count(id: u32) -> u64 {
let mut lock = DEBUG_TRANSACTION_ID.lock().unwrap();
let value = lock.entry(id).or_insert(0);
let curr = *value;
*value += 1;
curr
}
/// Runs a closure only once per debug ID.
pub fn debug_once(id: u32, f: impl FnOnce()) {
let transaction_count = bump_transaction_count(id);
if transaction_count == 0 {
f();
}
}
/// Runs a closure up to `n` times per debug ID.
pub fn debug_n(id: u32, n: u64, f: impl FnOnce()) {
let transaction_count = bump_transaction_count(id);
if transaction_count < n {
f();
}
}
/// Runs a closure if the input string changes for the debug ID.
pub fn debug_unique_string(id: u32, input: &str, f: impl FnOnce()) {
bump_transaction_count(id);
let mut lock = DEBUG_UNIQUE_STRING.lock().unwrap();
if let Some(value) = lock.get(&id) {
if value == input {
return; // already set
}
}
f();
lock.insert(id, input.to_string());
}