Skip to content

Commit af91fa9

Browse files
committed
Fix puffin integration memory leak
1 parent f7cee67 commit af91fa9

2 files changed

Lines changed: 39 additions & 12 deletions

File tree

examples/demo.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ struct State {
6060
window: Option<Arc<winit::window::Window>>,
6161
gfx_state: Option<GfxState>,
6262
latest_profiler_results: Option<Vec<GpuTimerQueryResult>>,
63+
#[cfg(feature = "puffin")]
64+
puffin_scope_cache: wgpu_profiler::puffin::PuffinScopeCache,
6365
}
6466

6567
struct GfxState {
@@ -264,6 +266,7 @@ impl ApplicationHandler<()> for State {
264266
let mut gpu_profiler = PUFFIN_GPU_PROFILER.lock().unwrap();
265267
wgpu_profiler::puffin::output_frame_to_puffin(
266268
&mut gpu_profiler,
269+
&mut self.puffin_scope_cache,
267270
self.latest_profiler_results.as_deref().unwrap_or_default(),
268271
);
269272
gpu_profiler.new_frame();

src/puffin.rs

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
1-
use puffin::{GlobalProfiler, NanoSecond, ScopeDetails, StreamInfo, ThreadInfo};
1+
use std::collections::HashMap;
2+
3+
use puffin::{GlobalProfiler, NanoSecond, ScopeDetails, ScopeId, StreamInfo, ThreadInfo};
24

35
use crate::GpuTimerQueryResult;
46

7+
/// Cache for registered puffin scope IDs to avoid memory leaks.
8+
#[derive(Default)]
9+
pub struct PuffinScopeCache {
10+
scope_ids: HashMap<String, ScopeId>,
11+
}
12+
513
/// Visualize the query results in a `puffin::GlobalProfiler`.
6-
pub fn output_frame_to_puffin(profiler: &mut GlobalProfiler, query_result: &[GpuTimerQueryResult]) {
14+
pub fn output_frame_to_puffin(
15+
profiler: &mut GlobalProfiler,
16+
cache: &mut PuffinScopeCache,
17+
query_result: &[GpuTimerQueryResult],
18+
) {
719
let mut stream_info = StreamInfo::default();
8-
collect_stream_info_recursive(profiler, &mut stream_info, query_result, 0);
20+
build_stream_info(profiler, cache, &mut stream_info, query_result, 0);
921

1022
profiler.report_user_scopes(
1123
ThreadInfo {
@@ -16,29 +28,41 @@ pub fn output_frame_to_puffin(profiler: &mut GlobalProfiler, query_result: &[Gpu
1628
);
1729
}
1830

19-
fn collect_stream_info_recursive(
31+
fn build_stream_info(
2032
profiler: &mut GlobalProfiler,
33+
cache: &mut PuffinScopeCache,
2134
stream_info: &mut StreamInfo,
2235
query_result: &[GpuTimerQueryResult],
2336
depth: usize,
2437
) {
25-
let details: Vec<_> = query_result
26-
.iter()
27-
.map(|query| ScopeDetails::from_scope_name(query.label.clone()))
28-
.collect();
29-
let ids = profiler.register_user_scopes(&details);
30-
for (query, id) in query_result.iter().zip(ids) {
38+
for query in query_result {
39+
// Use get() first to avoid cloning the label on cache hits.
40+
let id = if let Some(&id) = cache.scope_ids.get(&query.label) {
41+
id
42+
} else {
43+
let details = [ScopeDetails::from_scope_name(query.label.clone())];
44+
let id = profiler.register_user_scopes(&details)[0];
45+
cache.scope_ids.insert(query.label.clone(), id);
46+
id
47+
};
48+
3149
if let Some(time) = &query.time {
3250
let start = (time.start * 1e9) as NanoSecond;
3351
let end = (time.end * 1e9) as NanoSecond;
3452

3553
stream_info.depth = stream_info.depth.max(depth);
3654
stream_info.num_scopes += 1;
3755
stream_info.range_ns.0 = stream_info.range_ns.0.min(start);
38-
stream_info.range_ns.1 = stream_info.range_ns.0.max(end);
56+
stream_info.range_ns.1 = stream_info.range_ns.1.max(end);
3957

4058
let (offset, _) = stream_info.stream.begin_scope(|| start, id, "");
41-
collect_stream_info_recursive(profiler, stream_info, &query.nested_queries, depth + 1);
59+
build_stream_info(
60+
profiler,
61+
cache,
62+
stream_info,
63+
&query.nested_queries,
64+
depth + 1,
65+
);
4266
stream_info.stream.end_scope(offset, end as NanoSecond);
4367
}
4468
}

0 commit comments

Comments
 (0)