Skip to content

Commit

Permalink
First pass at using puffin to display the query results (#81)
Browse files Browse the repository at this point in the history
Co-authored-by: Andreas Reich <r_andreas2@web.de>
  • Loading branch information
Gonkalbell and Wumpf authored Jan 19, 2025
1 parent 0551e72 commit 0974584
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 4 deletions.
8 changes: 7 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@ jobs:
with:
target: wasm32-unknown-unknown
- run: cargo fmt -- --check

- run: cargo clippy --locked --all-targets -- -D warnings
# profiling & tracy features can't be both enabled at the same time in the demo. This is a limitation of the `profiling` crate.
- run: cargo clippy --locked --all-features -- -D warnings
- run: cargo clippy --locked --all-targets --features puffin -- -D warnings
- run: cargo clippy --locked --all-targets --features tracy -- -D warnings

- run: cargo check --locked --target wasm32-unknown-unknown
- run: cargo clippy --locked --all-features --all-targets -- -D warnings

- run: cargo doc --no-deps
env:
RUSTDOCFLAGS: '-D warnings'
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Change Log

## Unreleased
* Integration with puffin, by @gonkalbell in [#81](https://github.com/Wumpf/wgpu-profiler/pull/81)

## 0.20.0
* Update to tracy-client 0.18.0, update to thiserror 2, in [#91](https://github.com/Wumpf/wgpu-profiler/pull/91)
Expand Down
84 changes: 84 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ clippy.doc_markdown = "warn"

[features]
tracy = ["dep:tracy-client", "profiling/profile-with-tracy"]
puffin = ["dep:puffin", "profiling/profile-with-puffin"]

[lib]

Expand All @@ -24,11 +25,12 @@ thiserror = "2"
wgpu = "24.0.0"

tracy-client = { version = "0.18", optional = true }

puffin = { version = "0.19.1", optional = true }

[dev-dependencies]
futures-lite = "2"
profiling = { version = "1" }
profiling = "1"
puffin_http = "0.16.1"
tracy-client = "0.18"
wgpu = { version = "24.0.0", default-features = true }
winit = "0.30"
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Simple profiler scopes for wgpu using timer queries
* Easy to use profiler scopes
* Allows nesting!
* Can be disabled by runtime flag
* Additionally generates debug markers
* Additionally generates debug markers
* Thread-safe - can profile several command encoder/buffers in parallel
* Internally creates pools of timer queries automatically
* Does not need to know in advance how many queries/profiling scopes are needed
Expand All @@ -17,6 +17,7 @@ Simple profiler scopes for wgpu using timer queries
* Many profiler instances can live side by side
* chrome trace flamegraph json export
* Tracy integration (behind `tracy` feature flag)
* Puffin integration (behind `puffin` feature flag)

## How to use

Expand Down
32 changes: 32 additions & 0 deletions examples/demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ use winit::{
keyboard::{KeyCode, PhysicalKey},
};

#[cfg(feature = "puffin")]
// Since the timing information we get from WGPU may be several frames behind the CPU, we can't report these frames to
// the singleton returned by `puffin::GlobalProfiler::lock`. Instead, we need our own `puffin::GlobalProfiler` that we
// can be several frames behind puffin's main global profiler singleton.
static PUFFIN_GPU_PROFILER: std::sync::LazyLock<std::sync::Mutex<puffin::GlobalProfiler>> =
std::sync::LazyLock::new(|| std::sync::Mutex::new(puffin::GlobalProfiler::default()));

fn scopes_to_console_recursive(results: &[GpuTimerQueryResult], indentation: u32) {
for scope in results {
if indentation > 0 {
Expand Down Expand Up @@ -156,6 +163,7 @@ impl GfxState {
panic!("Failed to create profiler: {}", err);
}
});

#[cfg(not(feature = "tracy"))]
let profiler =
GpuProfiler::new(GpuProfilerSettings::default()).expect("Failed to create profiler");
Expand Down Expand Up @@ -255,6 +263,15 @@ impl ApplicationHandler<()> for State {
self.latest_profiler_results =
profiler.process_finished_frame(queue.get_timestamp_period());
console_output(&self.latest_profiler_results, device.features());
#[cfg(feature = "puffin")]
{
let mut gpu_profiler = PUFFIN_GPU_PROFILER.lock().unwrap();
wgpu_profiler::puffin::output_frame_to_puffin(
&mut gpu_profiler,
self.latest_profiler_results.as_deref().unwrap_or_default(),
);
gpu_profiler.new_frame();
}
}

WindowEvent::KeyboardInput {
Expand Down Expand Up @@ -387,9 +404,24 @@ fn draw(
}

fn main() {
#[cfg(feature = "tracy")]
tracy_client::Client::start();

//env_logger::init_from_env(env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "warn"));
let event_loop = EventLoop::new().unwrap();
event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);

#[cfg(feature = "puffin")]
let (_cpu_server, _gpu_server) = {
puffin::set_scopes_on(true);
let cpu_server =
puffin_http::Server::new(&format!("0.0.0.0:{}", puffin_http::DEFAULT_PORT)).unwrap();
let gpu_server = puffin_http::Server::new_custom(
&format!("0.0.0.0:{}", puffin_http::DEFAULT_PORT + 1),
|sink| PUFFIN_GPU_PROFILER.lock().unwrap().add_sink(sink),
|id| _ = PUFFIN_GPU_PROFILER.lock().unwrap().remove_sink(id),
);
(cpu_server, gpu_server)
};
let _ = event_loop.run_app(&mut State::default());
}
25 changes: 25 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,29 @@ if let Some(profiling_data) = profiler.process_finished_frame(queue.get_timestam
```
Check also the [Example](https://github.com/Wumpf/wgpu-profiler/blob/main/examples/demo.rs) where everything can be seen in action.
## Tracy integration
If you want to use [tracy](https://github.com/wolfpld/tracy) for profiling, you can enable the `tracy` feature.
This adds `wgpu_profiler::new_with_tracy_client` which will automatically report profiling data to tracy.
For details check the example code.
## Puffin integration
If you want to use [puffin](https://github.com/EmbarkStudios/puffin) for profiling, you can enable the `puffin` feature.
This adds `wgpu_profiler::puffin::output_frame_to_puffin` which makes it easy to report profiling data to a `puffin::GlobalProfiler`.
You can run the demo example with puffin by running `cargo run --example demo --features puffin`.
All CPU profiling goes to port `8585`, all GPU profiling goes to port `8586`. You can open puffin viewers for both.
```sh
puffin_viewer --url 127.0.0.1:8585
puffin_viewer --url 127.0.0.1:8586
```
For details check the example code.
# Internals
For every frame that hasn't completely finished processing yet
Expand All @@ -106,6 +129,8 @@ mod profiler;
mod profiler_command_recorder;
mod profiler_query;
mod profiler_settings;
#[cfg(feature = "puffin")]
pub mod puffin;
mod scope;
#[cfg(feature = "tracy")]
mod tracy;
Expand Down
45 changes: 45 additions & 0 deletions src/puffin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use puffin::{GlobalProfiler, NanoSecond, ScopeDetails, StreamInfo, ThreadInfo};

use crate::GpuTimerQueryResult;

/// Visualize the query results in a `puffin::GlobalProfiler`.
pub fn output_frame_to_puffin(profiler: &mut GlobalProfiler, query_result: &[GpuTimerQueryResult]) {
let mut stream_info = StreamInfo::default();
collect_stream_info_recursive(profiler, &mut stream_info, query_result, 0);

profiler.report_user_scopes(
ThreadInfo {
start_time_ns: None,
name: "GPU".to_string(),
},
&stream_info.as_stream_into_ref(),
);
}

fn collect_stream_info_recursive(
profiler: &mut GlobalProfiler,
stream_info: &mut StreamInfo,
query_result: &[GpuTimerQueryResult],
depth: usize,
) {
let details: Vec<_> = query_result
.iter()
.map(|query| ScopeDetails::from_scope_name(query.label.clone()))
.collect();
let ids = profiler.register_user_scopes(&details);
for (query, id) in query_result.iter().zip(ids) {
if let Some(time) = &query.time {
let start = (time.start * 1e9) as NanoSecond;
let end = (time.end * 1e9) as NanoSecond;

stream_info.depth = stream_info.depth.max(depth);
stream_info.num_scopes += 1;
stream_info.range_ns.0 = stream_info.range_ns.0.min(start);
stream_info.range_ns.1 = stream_info.range_ns.0.max(end);

let (offset, _) = stream_info.stream.begin_scope(|| start, id, "");
collect_stream_info_recursive(profiler, stream_info, &query.nested_queries, depth + 1);
stream_info.stream.end_scope(offset, end as NanoSecond);
}
}
}

0 comments on commit 0974584

Please sign in to comment.