Simple profiler scopes for wgpu using timer queries

Overview

wgpu-profiler

Crates.io

Simple profiler scopes for wgpu using timer queries

Features

  • Easy to use profiler scopes
    • Allows nesting!
    • Can be disabled by runtime flag
    • Additionally generates debug markers
  • Internally creates pools of timer queries automatically
    • Does not need to know in advance how many queries/profiling scopes are needed
    • Caches up profiler-frames until results are available
      • No stalling of the device at any time!
  • Many profiler instances can live side by side
  • chrome trace flamegraph json export

TODO:

  • Better error messages
  • Disable via feature flag

How to use

Create a new profiler object:

use wgpu_profiler::{wgpu_profiler, GpuProfiler};
// ...
let mut profiler = GpuProfiler::new(4, adapter.get_timestamp_period()); // buffer up to 4 frames

Using scopes is easiest with the macro:

wgpu_profiler!("name of your scope", &mut profiler, &mut encoder, &device, {
  // wgpu commands go here
});

Unless you disable timer scoping (wgpu_profile will still emit debug scopes), your wgpu device needs wgpu::Features::TIMESTAMP_QUERY enabled.

Wgpu-profiler needs to insert buffer copy commands, so when you're done with an encoder and won't do any more profiling scopes on it, you need to resolve the queries:

profiler.resolve_queries(&mut encoder);

And finally, to end a profiling frame, call end_frame. This does a few checks and will let you know of something is off!

profiler.end_frame().unwrap();

Retrieving the oldest available frame and writing it out to a chrome trace file.

if let Some(profiling_data) = profiler.process_finished_frame() {
    // You usually want to write to disk only under some condition, e.g. press of a key or button
    wgpu_profiler::chrometrace::write_chrometrace(Path::new("mytrace.json"), profiling_data);
}

To get a look of it in action, check out the example project!

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Comments
  • Scope guard types

    Scope guard types

    The way the project I work on handles state transitions between e.g. pipelines while drawing with wgpu it isn't feasible to put things inside a scope macro. So I made the scope types (see below) which wrap the CommandEncoder/RenderPass and utilize Drop. I was wondering if something like these would be useful in this crate or make more sense in a separate crate or simply created by users as needed?

    pub struct Scope<'a, W: ProfilerCommandRecorder> {
        profiler: &'a mut GpuProfiler,
        wgpu_thing: &'a mut W,
    }
    
    pub struct OwningScope<'a, W: ProfilerCommandRecorder> {
        profiler: &'a mut GpuProfiler,
        wgpu_thing: W,
    }
    
    // Separate type since we can't destructure types that impl Drop :/
    pub struct ManualOwningScope<'a, W: ProfilerCommandRecorder> {
        profiler: &'a mut GpuProfiler,
        wgpu_thing: W,
    }
    
    impl<'a, W: ProfilerCommandRecorder> Scope<'a, W> {
        pub fn start(
            profiler: &'a mut GpuProfiler,
            wgpu_thing: &'a mut W,
            device: &wgpu::Device,
            label: &str,
        ) -> Self {
            profiler.begin_scope(label, wgpu_thing, device);
            Self {
                profiler,
                wgpu_thing,
            }
        }
    
        /// Starts a scope nested within this one
        pub fn scope(&mut self, device: &wgpu::Device, label: &str) -> Scope<'_, W> {
            Scope::start(self.profiler, self.wgpu_thing, device, label)
        }
    }
    
    impl<'a, W: ProfilerCommandRecorder> OwningScope<'a, W> {
        pub fn start(
            profiler: &'a mut GpuProfiler,
            mut wgpu_thing: W,
            device: &wgpu::Device,
            label: &str,
        ) -> Self {
            profiler.begin_scope(label, &mut wgpu_thing, device);
            Self {
                profiler,
                wgpu_thing,
            }
        }
    
        /// Starts a scope nested within this one
        pub fn scope(&mut self, device: &wgpu::Device, label: &str) -> Scope<'_, W> {
            Scope::start(self.profiler, &mut self.wgpu_thing, device, label)
        }
    }
    
    impl<'a, W: ProfilerCommandRecorder> ManualOwningScope<'a, W> {
        pub fn start(
            profiler: &'a mut GpuProfiler,
            mut wgpu_thing: W,
            device: &wgpu::Device,
            label: &str,
        ) -> Self {
            profiler.begin_scope(label, &mut wgpu_thing, device);
            Self {
                profiler,
                wgpu_thing,
            }
        }
    
        /// Starts a scope nested within this one
        pub fn scope(&mut self, device: &wgpu::Device, label: &str) -> Scope<'_, W> {
            Scope::start(self.profiler, &mut self.wgpu_thing, device, label)
        }
    
        /// Ends the scope allowing the extraction of owned the wgpu thing
        /// and the mutable reference to the GpuProfiler
        pub fn end_scope(mut self) -> (W, &'a mut GpuProfiler) {
            self.profiler.end_scope(&mut self.wgpu_thing);
            (self.wgpu_thing, self.profiler)
        }
    }
    impl<'a> Scope<'a, wgpu::CommandEncoder> {
        /// Start a render pass wrapped in an OwnedScope
        pub fn scoped_render_pass<'b>(
            &'b mut self,
            device: &wgpu::Device,
            label: &str,
            pass_descriptor: &wgpu::RenderPassDescriptor<'b, '_>,
        ) -> OwningScope<'b, wgpu::RenderPass> {
            let render_pass = self.wgpu_thing.begin_render_pass(pass_descriptor);
            OwningScope::start(self.profiler, render_pass, device, label)
        }
    }
    
    impl<'a> OwningScope<'a, wgpu::CommandEncoder> {
        /// Start a render pass wrapped in an OwnedScope
        pub fn scoped_render_pass<'b>(
            &'b mut self,
            device: &wgpu::Device,
            label: &str,
            pass_descriptor: &wgpu::RenderPassDescriptor<'b, '_>,
        ) -> OwningScope<'b, wgpu::RenderPass> {
            let render_pass = self.wgpu_thing.begin_render_pass(pass_descriptor);
            OwningScope::start(self.profiler, render_pass, device, label)
        }
    }
    
    impl<'a> ManualOwningScope<'a, wgpu::CommandEncoder> {
        /// Start a render pass wrapped in an OwnedScope
        pub fn scoped_render_pass<'b>(
            &'b mut self,
            device: &wgpu::Device,
            label: &str,
            pass_descriptor: &wgpu::RenderPassDescriptor<'b, '_>,
        ) -> OwningScope<'b, wgpu::RenderPass> {
            let render_pass = self.wgpu_thing.begin_render_pass(pass_descriptor);
            OwningScope::start(self.profiler, render_pass, device, label)
        }
    }
    
    // Scope
    impl<'a, W: ProfilerCommandRecorder> std::ops::Deref for Scope<'a, W> {
        type Target = W;
    
        fn deref(&self) -> &Self::Target { self.wgpu_thing }
    }
    
    impl<'a, W: ProfilerCommandRecorder> std::ops::DerefMut for Scope<'a, W> {
        fn deref_mut(&mut self) -> &mut Self::Target { self.wgpu_thing }
    }
    
    impl<'a, W: ProfilerCommandRecorder> Drop for Scope<'a, W> {
        fn drop(&mut self) { self.profiler.end_scope(self.wgpu_thing); }
    }
    
    // OwningScope
    impl<'a, W: ProfilerCommandRecorder> std::ops::Deref for OwningScope<'a, W> {
        type Target = W;
    
        fn deref(&self) -> &Self::Target { &self.wgpu_thing }
    }
    
    impl<'a, W: ProfilerCommandRecorder> std::ops::DerefMut for OwningScope<'a, W> {
        fn deref_mut(&mut self) -> &mut Self::Target { &mut self.wgpu_thing }
    }
    
    impl<'a, W: ProfilerCommandRecorder> Drop for OwningScope<'a, W> {
        fn drop(&mut self) { self.profiler.end_scope(&mut self.wgpu_thing); }
    }
    
    // ManualOwningScope
    impl<'a, W: ProfilerCommandRecorder> std::ops::Deref for ManualOwningScope<'a, W> {
        type Target = W;
    
        fn deref(&self) -> &Self::Target { &self.wgpu_thing }
    }
    
    impl<'a, W: ProfilerCommandRecorder> std::ops::DerefMut for ManualOwningScope<'a, W> {
        fn deref_mut(&mut self) -> &mut Self::Target { &mut self.wgpu_thing }
    }
    
    opened by Imberflur 7
  • Don't panic on dropped frames

    Don't panic on dropped frames

    On some lower end systems it's possible in some high pressure moments hit an edge case where a frame is dropped whilst buffers are still waiting to be mapped, causing wgpu to signal an error on the mapping callback and the profiler to panic.

    This is fixed by tracking wether the current frame was dropped or not and ignoring buffer mappings if it was dropped.

    I'm opening this PR to gather opinions on how this should be done, right now it's implemented using an AtomicBool, but it could be possibly be done trough the already existing mapped_buffers using a sentinel value like usize::MAX or another method.

    opened by JCapucho 5
  • Fix crash on dropped frame when buffer mapping hasn't completed yet

    Fix crash on dropped frame when buffer mapping hasn't completed yet

    Simplified alternative to fix in #18 and commented a bit what's happening on a frame drop (took me way too long to figure that out again). I decided to ignore the BufferAsyncError after all in the expectation that wgpu will soon be able to report aborted mappings. This should be safe since the only way to trigger this error is an unmap call (and naturally, wgpu-profile owns all of these for the buffers in question), see https://www.w3.org/TR/webgpu/#dom-gpubuffer-unmap

    However, the error code are not yet exposed in wgpu, eagerly awaiting the resolution https://github.com/gfx-rs/wgpu/pull/2939

    PR comes with a regression test :) (which gives a stable repro for the previous issue)

    opened by Wumpf 4
  • Upgrade to wgpu 0.13

    Upgrade to wgpu 0.13

    Upstream wgpu introduced some changes to how buffers are mapped, so that now instead of returning a future they expect a callback.

    This PR accommodates those changes so that the crate is ready for when 0.13 lands and for easier discoverability for those who want to use wgpu master.

    opened by JCapucho 4
  • Better docs

    Better docs

    improved what's there and added a rustdoc block with a working doctest hope it's useful (and if only for me to read up on the basic workings again next time)

    @Imberflur would you mind proof-reading this? No rush ofc, whenever you feel like it :)

    opened by Wumpf 1
  • Wgpu upgrade 0.13 followup

    Wgpu upgrade 0.13 followup

    opened by Wumpf 1
  • Update to wgpu 0.11

    Update to wgpu 0.11

    ~~This PR expands the dependency to wgpu 0.10 -> 0.11, as well as bumping the version to 0.6.2. While the examples need to break, none of the actual apis used by this library broke between 0.10 -> 0.11, so we can support both, and as such it is a non-breaking change.~~

    This is now a normal breaking change, there was a flaw in my logic.

    opened by cwfitzgerald 0
  • scoped_render_pass and scoped_compute_pass don't need `#[must_use]`

    scoped_render_pass and scoped_compute_pass don't need `#[must_use]`

    It's hard to forget to use a render pass if you need it for something and some passes used for e.g. clearing a texture can be immediately dropped. Since these functions don't wrap a borrow of an existing value it isn't possible to drop them on accident and use the original wrapped value.

    opened by Imberflur 0
  • Document required wgpu features and/or have a function that returns them.

    Document required wgpu features and/or have a function that returns them.

    Although it's kind of obvious for anyone that has tried to use them directly it would be nice to note that this requires wgpu::Features::TIMESTAMP_QUERY and it might even be convenient to have a function that returns the set of required wgpu::Features.

    opened by Imberflur 0
  • Integration with tracy_client

    Integration with tracy_client

    Now that tracy has proper C api support for GPU timestamps, it would be a great extension to this library to offer a tracy feature where the timestamps collected by wgpu-profiler are reported to tracy.

    I am planning on working on this in a while but I wanted to clear it with you before I did.

    opened by cwfitzgerald 4
Releases(v0.2)
Owner
null
Rustcraft is a simple Minecraft engine written in rust using wgpu.

Rustcraft is a simple Minecraft engine written in rust using wgpu.

Raphael Van Hoffelen 110 Dec 22, 2022
Show puffin profiler flamegraph in-game using egui

Show puffin profiler flamegraph in-game using egui puffin is an instrumentation profiler where you opt-in to profile parts of your code: fn my_functio

Emil Ernerfeldt 44 Jun 3, 2022
Self Study on developing a game engine using wgpu as the rendering API. Learning as I go.

Fabled Engine Any issues, enhancement, features, or bugs report are always welcome in Issues. The obj branch is where frequent development and up to d

Khalid 20 Jan 5, 2023
Guide for using gfx-rs's wgpu library.

Introduction What is wgpu? Wgpu is a Rust implementation of the WebGPU API spec. WebGPU is a specification published by the GPU for the Web Community

sotrh 1k Dec 29, 2022
game engine built in rust, using wgpu and probably other stuff too

horizon game engine engine for devpty games, made in 99.9% rust and 0.1% shell. this is our main project currently. the engine will be used for most i

DEVPTY 2 Apr 12, 2022
🍖A WGPU graphics pipeline, along with simple types used to marshal data to the GPU

renderling ?? This library is a collection of WGPU render pipelines. Shaders are written in GLSL. shaderc is used to compile shaders to SPIR-V. Defini

Schell Carl Scivally 5 Dec 20, 2022
🦅🦁 Fast, simple 2D text renderer for wgpu

?? glyphon ?? Fast, simple 2D text rendering for wgpu What is this? This crate provides a simple way to render 2D text with wgpu by: rasterizing glyph

Josh Groves 60 Nov 5, 2022
Safe, fully-featured bindings to the Tracy profiler

Complete Rust bindings for the Tracy profiler. Getting Started Just add the following to your Cargo.toml: [dependencies.tracy] package = "tracy_full"

Shaye Garg 12 May 6, 2023
A modern 3D/2D game engine that uses wgpu.

Harmony A modern 3D/2D game engine that uses wgpu and is designed to work out of the box with minimal effort. It uses legion for handling game/renderi

John 152 Dec 24, 2022
A curated list of wgpu code and resources.

Awesome wgpu A curated list of wgpu code and resources. PRs welcome. About wgpu https://github.com/gfx-rs/wgpu-rs matrix chat https://matrix.to/#/#wgp

Roman Frołow 283 Jan 3, 2023
Scion is a tiny 2D game library built on top of wgpu, winit and legion.

Scion is a 2D game library made in rust. Please note that this project is in its first milestones and is subject to change according to convience need

Jérémy Thulliez 143 Dec 25, 2022
Tic-Tac-Toe on the GPU, as an example application for wgpu

Tic-Tac-GPU A simple (cough cough) example on a tic-tac-toe game with wgpu. Why? Because I didn't find that many small applications which use wgpu as

multisn8 2 Oct 7, 2022
A barebones example of how to integrate OpenXR with wgpu (Vulkan-only)

wgpu-openxr-example a barebones example of how to integrate OpenXR with wgpu (Vulkan-only) It has four modes: cargo run --no-default-features: desktop

Philpax 21 Dec 15, 2022
A simple space shooter game. Runs in the terminal using characters-based UI. Fully written in Rust, using the "ruscii" library.

Thrust - a terminal shooter game Originally created as a project for the "Missing Semester" course at JKU Linz (338.006). The game is entirely written

Mathias Wöß 3 Jan 16, 2023
Using bevy and custom render pipelines in order to render many objects in a forest using chunks for performance.

bevy_efficient_forest_example Using bevy and custom render pipelines in order to render many objects in a forest using chunks for performance. Grass i

Henrik Djurestål 43 Jan 5, 2023
minesweeper-rs is a simple minesweeper game using Rust and windows-rs.

minesweeper-rs minesweeper-rs is a simple minesweeper game using Rust and windows-rs. Key Features TBA Quick Start TBA How To Contribute Contributions

Chris Ohk 15 Jun 25, 2022
A simple implementation of Conway's Game of Life using Fully homomorphic Encryption

Game of life using Fully homomorphic encryption A simple implementation of Conway's Game of Life built using Zama's concrete-boolean library. Build Ju

Florent Michel 4 Oct 3, 2022
Simple island generator written in rust using bevy engine

Getting Started Easy enough to run cargo run --release Change generation speed Find the system set that looks like this .add_system_set(

Kris 3 Apr 21, 2022
Simple retro game made using Rust bracket-lib by following "Herbert Wolverson's Hands on Rust" book.

Flappy Dragon Code from This program is a result of a tutorial i followed from Herbert Wolverson's Hands-on Rust Effective Learning through 2D Game De

Praneeth Chinthaka Ranasinghe 1 Feb 7, 2022