├── .gitignore ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── build.rs ├── rustfmt.toml ├── src ├── alloc.rs ├── bevy.rs ├── color.rs ├── frame.rs ├── future.rs ├── lib.rs ├── plot.rs ├── tracing.rs ├── wgpu.rs └── zone.rs └── tests └── tests.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | 4 | .idea 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tracy_full" 3 | version = "1.11.0" 4 | authors = ["SparkyPotato"] 5 | license = "MIT" 6 | edition = "2021" 7 | 8 | readme = "README.md" 9 | repository = "https://github.com/SparkyPotato/tracy_full" 10 | documentation = "https://docs.rs/tracy_full" 11 | description = "Fully featured bindings for the Tracy profiler" 12 | 13 | [[test]] 14 | name = "tests" 15 | path = "tests/tests.rs" 16 | harness = false 17 | 18 | [features] 19 | # Default features that tracy uses. 20 | default = ["system-tracing", "context-switch-tracing", "sampling", "code-transfer", "broadcast"] 21 | 22 | # Enable nightly-only features and optimizations. 23 | unstable = [] 24 | # Enable the nightly Allocator API features. 25 | allocator_api = ["unstable"] 26 | 27 | # Bevy support. 28 | bevy = ["bevy_ecs", "futures"] 29 | # Tracing support. 30 | tracing = ["dep:tracing", "tracing-subscriber"] 31 | # WGPU support. 32 | wgpu = ["dep:wgpu", "futures-lite"] 33 | 34 | # Enable the capture of profiling data. Disabled by default. 35 | enable = ["sys/enable"] 36 | # Expose manual initialization and shutdown functions. These must be called before any other tracy functions. 37 | manual-init = ["sys/manual-lifetime"] 38 | # Enable support for fibers, coroutines, and async/await. 39 | futures = ["sys/fibers"] 40 | 41 | # Enable the capture of system-level details, if possible. 42 | system-tracing = ["sys/system-tracing"] 43 | # Enable the capture of context-switch data. 44 | context-switch-tracing = ["sys/context-switch-tracing"] 45 | # Enable sampling of call-stacks. 46 | sampling = ["sys/sampling"] 47 | # Enable the capture of machine code data. 48 | code-transfer = ["sys/code-transfer"] 49 | 50 | # Enable the broadcast of profiling data on the network. 51 | broadcast = ["sys/broadcast"] 52 | # Connect only to profiles running on the same machine. 53 | only-localhost = ["sys/only-localhost"] 54 | # Connect through IPv4 only. 55 | only-ipv4 = ["sys/only-ipv4"] 56 | 57 | # Enable support for low-resolution timers. 58 | timer-fallback = ["sys/timer-fallback"] 59 | # Enable support for profiling on demand. This has a minor performance penalty. 60 | ondemand = ["sys/ondemand"] 61 | 62 | [build-dependencies] 63 | rustc_version = "0.4" 64 | 65 | [dependencies] 66 | sys = { package = "tracy-client-sys", version = "0.24.3", default-features = false } 67 | 68 | bevy_ecs = { version = "0.15", optional = true } 69 | futures-lite = { version = "2.0", optional = true } 70 | tracing = { version = "0.1", optional = true } 71 | tracing-subscriber = { version = "0.3", optional = true } 72 | wgpu = { version = "24", optional = true, default-features = false } 73 | 74 | [target.'cfg(not(unstable))'.dependencies] 75 | once_cell = "1.10.0" 76 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Complete Rust bindings for the [Tracy](https://github.com/wolfpld/tracy) profiler. 2 | 3 | ## Getting Started 4 | 5 | Just add the following to your `Cargo.toml`: 6 | ```toml 7 | [dependencies.tracy] 8 | package = "tracy_full" 9 | version = "1.10.0" 10 | ``` 11 | 12 | To enable profiling for a build, add the `enable` feature: 13 | ```toml 14 | [dependencies.tracy] 15 | ... 16 | features = ["enable"] 17 | ``` 18 | 19 | ## Features 20 | 21 | ### Allocation Tracking 22 | ```rust 23 | #[global_allocator] 24 | static ALLOC: tracy::GlobalAllocator = tracy::GlobalAllocator::new(); 25 | ``` 26 | This tracks all allocations, using the default `System` allocator for allocations. 27 | 28 | For a custom allocator: 29 | ```rust 30 | #[global_allocator] 31 | static ALLOC: tracy::GlobalAllocator = tracy::GlobalAllocator::new_with(MyAlloc::new()); 32 | ``` 33 | 34 | Tracy also supports tracking custom allocators using the `allocator_api` feature: 35 | ```toml 36 | [dependencies.tracy] 37 | ... 38 | features = ["allocator_api"] 39 | ``` 40 | ```rust 41 | let alloc = TrackedAllocator::new(alloc, tracy::c_str!("TrackedAllocator")); 42 | ``` 43 | This creates a memory pool named `TrackedAllocator` in Tracy. 44 | 45 | All the allocators have a `*Sampled` variant that samples the callstack on each allocation. 46 | 47 | ### Frame Marks 48 | Mark the end of the main frame using: 49 | ```rust 50 | use tracy::frame; 51 | 52 | frame!(); 53 | ``` 54 | 55 | Mark the end of a sub-frame using: 56 | ```rust 57 | frame!("Name"); 58 | ``` 59 | 60 | Mark the scope of a discontinuous frame using: 61 | ```rust 62 | frame!(discontinuous "Name"); 63 | ``` 64 | 65 | #### The difference between frame types 66 | The main frame what is usually thought of as a 'frame'. 67 | It is usually placed after the swapchain present call on the main thread. 68 | 69 | Sub-frames are parts of the main frame, for example, input gathering, physics, and rendering: 70 | ```rust 71 | loop { 72 | // Gather input 73 | frame!("Input"); 74 | // Process input 75 | frame!("Processing"); 76 | // Render 77 | frame!("Render"); 78 | 79 | swapchain.present(); 80 | frame!(); 81 | } 82 | ``` 83 | 84 | Discontinuous frames are frames that are not in sync with the frame on the main thread. 85 | This can be things like async asset loading on different threads. 86 | 87 | ### Plotting 88 | You can plot graphs in Tracy: 89 | ```rust 90 | use tracy::plotter; 91 | 92 | let plotter = plotter!("MyGraph"); 93 | plotter.value(1.0); 94 | plotter.value(2.0); 95 | ``` 96 | 97 | ### Zones 98 | ```rust 99 | use tracy::zone; 100 | 101 | zone!(); // Zone with no name 102 | zone!("MyZone"); // Zone with name "MyZone" 103 | zone!(tracy::color::RED); // Zone with color red 104 | zone!("MyZone", true); // Zone with name "MyZone", and enabled with a runtime expression. 105 | zone!(tracy::color::RED, true); // Zone with color red, and enabled with a runtime expression. 106 | zone!("MyZone", tracy::color::RED, true); // Zone with name "MyZone", color red, and enabled with a runtime expression. 107 | ``` 108 | All zones profile from creation to the end of the enclosed scope. 109 | 110 | ## Extra features 111 | 112 | ### Future support 113 | Futures can be represented as fibers in Tracy. The `futures` feature must be enabled. 114 | 115 | ```toml 116 | [dependencies.tracy] 117 | ... 118 | features = ["enable", "futures"] 119 | ``` 120 | ```rust 121 | use tracy::future; 122 | 123 | trace_future!(async_function(), "Async Function").await; 124 | ``` 125 | 126 | ### Unstable 127 | The `unstable` feature allows for optimizations that require a nightly compiler. 128 | 129 | ```toml 130 | [dependencies.tracy] 131 | ... 132 | features = ["enable", "unstable"] 133 | ``` 134 | 135 | ## External Library Integration 136 | 137 | ### `bevy` 138 | Enable the `bevy` feature to be able to profile Bevy systems. 139 | ```toml 140 | [dependencies.tracy] 141 | ... 142 | features = ["enable", "bevy"] 143 | ``` 144 | 145 | ```rust 146 | use tracy::bevy::timeline; 147 | 148 | App::new().add_system(timeline(my_system)).run(); 149 | ``` 150 | 151 | This creates a separate fiber for the system in the tracy timeline. 152 | 153 | ### `tracing` 154 | Enable the `tracing` feature to be able to profile tracing spans. 155 | ```toml 156 | [dependencies.tracy] 157 | ... 158 | features = ["enable", "tracing"] 159 | ``` 160 | 161 | ```rust 162 | use tracy::tracing::TracyLayer; 163 | 164 | tracing::subscriber::set_global_default( 165 | tracing_subscriber::registry().with(TracyLayer) 166 | ); 167 | ``` 168 | 169 | ### `wgpu` 170 | Enable the `wgpu` feature to be able to profile wgpu command encoders and render/compute passes. 171 | ```toml 172 | [dependencies.tracy] 173 | ... 174 | features = ["enable", "wgpu"] 175 | ``` 176 | 177 | ```rust 178 | use tracy::wgpu::ProfileContext; 179 | 180 | let mut profile_context = ProfileContext::with_name("Name", &adapter, &device, &queue, buffered_frames); 181 | ``` 182 | `buffered_frames`: the number of frames of profiling data you want the profiler to buffer. 183 | Note that you must synchronize the host and device accordingly, or else the call to `end_frame` will panic. 184 | 185 | You also need to have one `ProfileContext` per host thread. 186 | 187 | You can create a profiled command encoder: 188 | ```rust 189 | use tracy::{wgpu_command_encoder, wgpu_render_pass, wgpu_compute_pass}; 190 | 191 | let mut command_encoder = wgpu_command_encoder!(device, profile_context, desc); 192 | { 193 | let render_pass = wgpu_render_pass!(command_encoder, desc) 194 | } 195 | 196 | { 197 | let compute_pass = wgpu_compute_pass!(command_encoder, desc) 198 | } 199 | ``` 200 | 201 | At the end of each frame, you must call `end_frame`: 202 | ```rust 203 | profile_context.end_frame(&device, &queue); 204 | ``` 205 | This uploads the profiling data to Tracy. 206 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use rustc_version::{version_meta, Channel}; 2 | 3 | fn main() { 4 | if version_meta().unwrap().channel == Channel::Nightly { 5 | println!(r#"cargo:rustc-cfg=feature="unstable""#); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | binop_separator = "Front" 2 | blank_lines_lower_bound = 0 3 | blank_lines_upper_bound = 1 4 | combine_control_expr = true 5 | comment_width = 120 6 | condense_wildcard_suffixes = true 7 | empty_item_single_line = true 8 | fn_params_layout = "Compressed" 9 | fn_single_line = true 10 | force_explicit_abi = true 11 | format_code_in_doc_comments = true 12 | format_macro_matchers = true 13 | format_macro_bodies = true 14 | format_strings = true 15 | hard_tabs = true 16 | imports_indent = "Block" 17 | imports_granularity = "Crate" 18 | imports_layout = "HorizontalVertical" 19 | indent_style = "Block" 20 | match_block_trailing_comma = true 21 | max_width = 120 22 | merge_derives = true 23 | newline_style = "Native" 24 | normalize_comments = true 25 | normalize_doc_attributes = true 26 | reorder_impl_items = true 27 | reorder_imports = true 28 | group_imports = "StdExternalCrate" 29 | trailing_semicolon = true 30 | use_field_init_shorthand = true 31 | use_try_shorthand = true 32 | wrap_comments = true 33 | 34 | -------------------------------------------------------------------------------- /src/alloc.rs: -------------------------------------------------------------------------------- 1 | //! Allocation profiling. 2 | 3 | #[cfg(feature = "allocator_api")] 4 | use std::alloc::{AllocError, Allocator}; 5 | use std::{ 6 | alloc::{GlobalAlloc, Layout, System}, 7 | ffi::CStr, 8 | ptr::NonNull, 9 | }; 10 | 11 | use crate::clamp_callstack_depth; 12 | 13 | /// Create an allocator that is tracked by tracy. 14 | #[cfg(feature = "allocator_api")] 15 | #[macro_export] 16 | macro_rules! tracked_allocator { 17 | ($name:literal, $alloc:expr) => { 18 | $crate::alloc::TrackedAllocator::new($alloc, $crate::c_str!($name)) 19 | }; 20 | 21 | ($name:literal, $alloc:expr, $depth:expr) => { 22 | $crate::alloc::TrackedAllocatorSampled::new($alloc, $crate::c_str!($name), $depth) 23 | }; 24 | } 25 | 26 | /// A wrapper around an allocator that tracy tracks as a memory pool. 27 | #[cfg(feature = "allocator_api")] 28 | pub struct TrackedAllocator<'a, T> { 29 | inner: T, 30 | #[cfg(feature = "enable")] 31 | name: &'a CStr, 32 | } 33 | 34 | #[cfg(feature = "allocator_api")] 35 | impl<'a, T: Allocator> TrackedAllocator<'a, T> { 36 | #[inline(always)] 37 | pub const fn new(inner: T, name: &'a CStr) -> Self { 38 | Self { 39 | inner, 40 | #[cfg(feature = "enable")] 41 | name, 42 | } 43 | } 44 | } 45 | 46 | #[cfg(feature = "allocator_api")] 47 | unsafe impl Allocator for TrackedAllocator<'_, T> { 48 | fn allocate(&self, layout: Layout) -> Result, AllocError> { 49 | #[cfg(feature = "enable")] 50 | { 51 | self.inner.allocate(layout).map(|value| unsafe { 52 | sys::___tracy_emit_memory_alloc_named(value.as_ptr() as _, value.len(), 0, self.name.as_ptr()); 53 | value 54 | }) 55 | } 56 | 57 | #[cfg(not(feature = "enable"))] 58 | self.inner.allocate(layout) 59 | } 60 | 61 | fn allocate_zeroed(&self, layout: Layout) -> Result, AllocError> { 62 | #[cfg(feature = "enable")] 63 | { 64 | self.inner.allocate_zeroed(layout).map(|value| unsafe { 65 | sys::___tracy_emit_memory_alloc_named(value.as_ptr() as _, value.len(), 0, self.name.as_ptr()); 66 | value 67 | }) 68 | } 69 | 70 | #[cfg(not(feature = "enable"))] 71 | self.inner.allocate_zeroed(layout) 72 | } 73 | 74 | unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { 75 | #[cfg(feature = "enable")] 76 | sys::___tracy_emit_memory_free_named(ptr.as_ptr() as _, 0, self.name.as_ptr()); 77 | self.inner.deallocate(ptr, layout); 78 | } 79 | 80 | unsafe fn grow( 81 | &self, ptr: NonNull, old_layout: Layout, new_layout: Layout, 82 | ) -> Result, AllocError> { 83 | #[cfg(feature = "enable")] 84 | { 85 | sys::___tracy_emit_memory_free_named(ptr.as_ptr() as _, 0, self.name.as_ptr()); 86 | self.inner.grow(ptr, old_layout, new_layout).map(|value| { 87 | sys::___tracy_emit_memory_alloc_named(value.as_ptr() as _, value.len(), 0, self.name.as_ptr()); 88 | value 89 | }) 90 | } 91 | 92 | #[cfg(not(feature = "enable"))] 93 | self.inner.grow(ptr, old_layout, new_layout) 94 | } 95 | 96 | unsafe fn grow_zeroed( 97 | &self, ptr: NonNull, old_layout: Layout, new_layout: Layout, 98 | ) -> Result, AllocError> { 99 | #[cfg(feature = "enable")] 100 | { 101 | sys::___tracy_emit_memory_free_named(ptr.as_ptr() as _, 0, self.name.as_ptr()); 102 | self.inner.grow_zeroed(ptr, old_layout, new_layout).map(|value| { 103 | sys::___tracy_emit_memory_alloc_named(value.as_ptr() as _, value.len(), 0, self.name.as_ptr()); 104 | value 105 | }) 106 | } 107 | 108 | #[cfg(not(feature = "enable"))] 109 | self.inner.grow_zeroed(ptr, old_layout, new_layout) 110 | } 111 | 112 | unsafe fn shrink( 113 | &self, ptr: NonNull, old_layout: Layout, new_layout: Layout, 114 | ) -> Result, AllocError> { 115 | #[cfg(feature = "enable")] 116 | { 117 | sys::___tracy_emit_memory_free_named(ptr.as_ptr() as _, 0, self.name.as_ptr()); 118 | self.inner.shrink(ptr, old_layout, new_layout).map(|value| { 119 | sys::___tracy_emit_memory_alloc_named(value.as_ptr() as _, value.len(), 0, self.name.as_ptr()); 120 | value 121 | }) 122 | } 123 | 124 | #[cfg(not(feature = "enable"))] 125 | self.inner.shrink(ptr, old_layout, new_layout) 126 | } 127 | } 128 | 129 | /// A wrapper around an allocator that tracy tracks as a memory pool, that also samples the callstack on every 130 | /// allocation. 131 | #[cfg(feature = "allocator_api")] 132 | pub struct TrackedAllocatorSampled { 133 | inner: T, 134 | #[cfg(feature = "enable")] 135 | name: &'static CStr, 136 | #[cfg(feature = "enable")] 137 | depth: i32, 138 | } 139 | 140 | #[cfg(feature = "allocator_api")] 141 | impl TrackedAllocatorSampled { 142 | #[inline(always)] 143 | pub const fn new(inner: T, name: &'static CStr, depth: u32) -> Self { 144 | Self { 145 | inner, 146 | #[cfg(feature = "enable")] 147 | name, 148 | #[cfg(feature = "enable")] 149 | depth: clamp_callstack_depth(depth) as _, 150 | } 151 | } 152 | } 153 | 154 | #[cfg(feature = "allocator_api")] 155 | unsafe impl Allocator for TrackedAllocatorSampled { 156 | fn allocate(&self, layout: Layout) -> Result, AllocError> { 157 | #[cfg(feature = "enable")] 158 | { 159 | self.inner.allocate(layout).map(|value| unsafe { 160 | sys::___tracy_emit_memory_alloc_callstack_named( 161 | value.as_ptr() as _, 162 | value.len(), 163 | self.depth, 164 | 0, 165 | self.name.as_ptr(), 166 | ); 167 | value 168 | }) 169 | } 170 | 171 | #[cfg(not(feature = "enable"))] 172 | self.inner.allocate(layout) 173 | } 174 | 175 | fn allocate_zeroed(&self, layout: Layout) -> Result, AllocError> { 176 | #[cfg(feature = "enable")] 177 | { 178 | self.inner.allocate_zeroed(layout).map(|value| unsafe { 179 | sys::___tracy_emit_memory_alloc_callstack_named( 180 | value.as_ptr() as _, 181 | value.len(), 182 | self.depth, 183 | 0, 184 | self.name.as_ptr(), 185 | ); 186 | value 187 | }) 188 | } 189 | 190 | #[cfg(not(feature = "enable"))] 191 | self.inner.allocate_zeroed(layout) 192 | } 193 | 194 | unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { 195 | #[cfg(feature = "enable")] 196 | sys::___tracy_emit_memory_free_callstack_named(ptr.as_ptr() as _, self.depth, 0, self.name.as_ptr()); 197 | self.inner.deallocate(ptr, layout); 198 | } 199 | 200 | unsafe fn grow( 201 | &self, ptr: NonNull, old_layout: Layout, new_layout: Layout, 202 | ) -> Result, AllocError> { 203 | #[cfg(feature = "enable")] 204 | { 205 | sys::___tracy_emit_memory_free_callstack_named(ptr.as_ptr() as _, self.depth, 0, self.name.as_ptr()); 206 | self.inner.grow(ptr, old_layout, new_layout).map(|value| { 207 | sys::___tracy_emit_memory_alloc_callstack_named( 208 | value.as_ptr() as _, 209 | value.len(), 210 | self.depth, 211 | 0, 212 | self.name.as_ptr(), 213 | ); 214 | value 215 | }) 216 | } 217 | 218 | #[cfg(not(feature = "enable"))] 219 | self.inner.grow(ptr, old_layout, new_layout) 220 | } 221 | 222 | unsafe fn grow_zeroed( 223 | &self, ptr: NonNull, old_layout: Layout, new_layout: Layout, 224 | ) -> Result, AllocError> { 225 | #[cfg(feature = "enable")] 226 | { 227 | sys::___tracy_emit_memory_free_callstack_named(ptr.as_ptr() as _, self.depth, 0, self.name.as_ptr()); 228 | self.inner.grow_zeroed(ptr, old_layout, new_layout).map(|value| { 229 | sys::___tracy_emit_memory_alloc_callstack_named( 230 | value.as_ptr() as _, 231 | value.len(), 232 | self.depth, 233 | 0, 234 | self.name.as_ptr(), 235 | ); 236 | value 237 | }) 238 | } 239 | 240 | #[cfg(not(feature = "enable"))] 241 | self.inner.grow_zeroed(ptr, old_layout, new_layout) 242 | } 243 | 244 | unsafe fn shrink( 245 | &self, ptr: NonNull, old_layout: Layout, new_layout: Layout, 246 | ) -> Result, AllocError> { 247 | #[cfg(feature = "enable")] 248 | { 249 | sys::___tracy_emit_memory_free_callstack_named(ptr.as_ptr() as _, self.depth, 0, self.name.as_ptr()); 250 | self.inner.shrink(ptr, old_layout, new_layout).map(|value| { 251 | sys::___tracy_emit_memory_alloc_callstack_named( 252 | value.as_ptr() as _, 253 | value.len(), 254 | self.depth, 255 | 0, 256 | self.name.as_ptr(), 257 | ); 258 | value 259 | }) 260 | } 261 | 262 | #[cfg(not(feature = "enable"))] 263 | self.inner.shrink(ptr, old_layout, new_layout) 264 | } 265 | } 266 | 267 | /// A tracked global allocator. 268 | pub struct GlobalAllocator { 269 | inner: T, 270 | } 271 | 272 | impl GlobalAllocator { 273 | #[inline(always)] 274 | pub const fn new() -> Self { Self::new_with(System) } 275 | } 276 | 277 | impl GlobalAllocator { 278 | #[inline(always)] 279 | pub const fn new_with(inner: T) -> Self { Self { inner } } 280 | } 281 | 282 | impl Default for GlobalAllocator { 283 | #[inline(always)] 284 | fn default() -> Self { Self::new() } 285 | } 286 | 287 | unsafe impl GlobalAlloc for GlobalAllocator { 288 | #[inline(always)] 289 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 290 | let value = self.inner.alloc(layout); 291 | #[cfg(feature = "enable")] 292 | sys::___tracy_emit_memory_alloc(value as _, layout.size(), 0); 293 | value 294 | } 295 | 296 | #[inline(always)] 297 | unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { 298 | #[cfg(feature = "enable")] 299 | sys::___tracy_emit_memory_free(ptr as _, 0); 300 | self.inner.dealloc(ptr, layout); 301 | } 302 | 303 | #[inline(always)] 304 | unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { 305 | let value = self.inner.alloc_zeroed(layout); 306 | #[cfg(feature = "enable")] 307 | sys::___tracy_emit_memory_alloc(value as _, layout.size(), 0); 308 | value 309 | } 310 | 311 | #[inline(always)] 312 | unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { 313 | #[cfg(feature = "enable")] 314 | sys::___tracy_emit_memory_free(ptr as _, 0); 315 | let value = self.inner.realloc(ptr, layout, new_size); 316 | #[cfg(feature = "enable")] 317 | sys::___tracy_emit_memory_alloc(value as _, new_size, 0); 318 | value 319 | } 320 | } 321 | 322 | /// A tracked global allocator that samples the callstack on every allocation. 323 | pub struct GlobalAllocatorSampled { 324 | inner: T, 325 | #[cfg(feature = "enable")] 326 | depth: i32, 327 | } 328 | 329 | impl GlobalAllocatorSampled { 330 | #[inline(always)] 331 | pub const fn new(depth: u32) -> Self { Self::new_with(System, depth) } 332 | } 333 | 334 | impl GlobalAllocatorSampled { 335 | #[inline(always)] 336 | pub const fn new_with(inner: T, depth: u32) -> Self { 337 | Self { 338 | inner, 339 | #[cfg(feature = "enable")] 340 | depth: clamp_callstack_depth(depth) as _, 341 | } 342 | } 343 | } 344 | 345 | unsafe impl GlobalAlloc for GlobalAllocatorSampled { 346 | #[inline(always)] 347 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 348 | let value = self.inner.alloc(layout); 349 | #[cfg(feature = "enable")] 350 | sys::___tracy_emit_memory_alloc_callstack(value as _, layout.size(), self.depth, 0); 351 | value 352 | } 353 | 354 | #[inline(always)] 355 | unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { 356 | #[cfg(feature = "enable")] 357 | sys::___tracy_emit_memory_free_callstack(ptr as _, self.depth, 0); 358 | self.inner.dealloc(ptr, layout); 359 | } 360 | 361 | #[inline(always)] 362 | unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { 363 | let value = self.inner.alloc_zeroed(layout); 364 | #[cfg(feature = "enable")] 365 | sys::___tracy_emit_memory_alloc_callstack(value as _, layout.size(), self.depth, 0); 366 | value 367 | } 368 | 369 | #[inline(always)] 370 | unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { 371 | #[cfg(feature = "enable")] 372 | sys::___tracy_emit_memory_free_callstack(ptr as _, self.depth, 0); 373 | let value = self.inner.realloc(ptr, layout, new_size); 374 | #[cfg(feature = "enable")] 375 | sys::___tracy_emit_memory_alloc_callstack(value as _, new_size, self.depth, 0); 376 | value 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /src/bevy.rs: -------------------------------------------------------------------------------- 1 | //! Bevy related profiling. 2 | 3 | use std::{any::TypeId, borrow::Cow, ffi::CString}; 4 | 5 | use bevy_ecs::{ 6 | archetype::ArchetypeComponentId, 7 | component::{ComponentId, Tick}, 8 | prelude::World, 9 | query::Access, 10 | system::{IntoSystem, System, SystemInput}, 11 | world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld}, 12 | }; 13 | 14 | /// Create a system that appears as a separate fiber in the profiler. 15 | #[inline(always)] 16 | pub fn timeline>(sys: T) -> SystemWrapper { 17 | let sys = T::into_system(sys); 18 | SystemWrapper { 19 | name: CString::new::>(match sys.name() { 20 | Cow::Borrowed(b) => b.into(), 21 | Cow::Owned(o) => o.into(), 22 | }) 23 | .expect("System name must not have null bytes"), 24 | inner: sys, 25 | } 26 | } 27 | 28 | /// A wrapper around a system that appears as a separate fiber in the profiler. 29 | pub struct SystemWrapper { 30 | inner: T, 31 | name: CString, 32 | } 33 | 34 | impl System for SystemWrapper 35 | where 36 | T: System, 37 | { 38 | type In = T::In; 39 | type Out = T::Out; 40 | 41 | #[inline(always)] 42 | fn name(&self) -> Cow<'static, str> { self.inner.name() } 43 | 44 | #[inline(always)] 45 | fn component_access(&self) -> &Access { self.inner.component_access() } 46 | 47 | #[inline(always)] 48 | fn archetype_component_access(&self) -> &Access { self.inner.archetype_component_access() } 49 | 50 | #[inline(always)] 51 | fn is_send(&self) -> bool { self.inner.is_send() } 52 | 53 | #[inline(always)] 54 | unsafe fn run_unsafe(&mut self, input: ::Inner<'_>, world: UnsafeWorldCell) -> Self::Out { 55 | #[cfg(feature = "enable")] 56 | sys::___tracy_fiber_enter(self.name.as_ptr()); 57 | let out = self.inner.run_unsafe(input, world); 58 | #[cfg(feature = "enable")] 59 | sys::___tracy_fiber_leave(); 60 | out 61 | } 62 | 63 | #[inline(always)] 64 | fn run(&mut self, input: ::Inner<'_>, world: &mut World) -> Self::Out { 65 | self.inner.run(input, world) 66 | } 67 | 68 | #[inline(always)] 69 | fn initialize(&mut self, _world: &mut World) { self.inner.initialize(_world) } 70 | 71 | #[inline(always)] 72 | fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) { 73 | self.inner.update_archetype_component_access(world) 74 | } 75 | 76 | #[inline(always)] 77 | fn check_change_tick(&mut self, change_tick: Tick) { self.inner.check_change_tick(change_tick) } 78 | 79 | #[inline(always)] 80 | fn is_exclusive(&self) -> bool { self.inner.is_exclusive() } 81 | 82 | fn type_id(&self) -> TypeId { self.inner.type_id() } 83 | 84 | fn has_deferred(&self) -> bool { self.inner.has_deferred() } 85 | 86 | fn apply_deferred(&mut self, world: &mut World) { self.inner.apply_deferred(world) } 87 | 88 | fn get_last_run(&self) -> Tick { self.inner.get_last_run() } 89 | 90 | fn set_last_run(&mut self, last_run: Tick) { self.inner.set_last_run(last_run) } 91 | 92 | fn queue_deferred(&mut self, world: DeferredWorld) { self.inner.queue_deferred(world) } 93 | 94 | unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool { 95 | self.inner.validate_param_unsafe(world) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/color.rs: -------------------------------------------------------------------------------- 1 | //! Tracy colors. 2 | 3 | #[repr(transparent)] 4 | pub struct Color(u32); 5 | 6 | impl Color { 7 | pub const BLACK: Color = Color::new(0, 0, 1); 8 | pub const BLUE: Color = Color::new(0, 0, 255); 9 | pub const CYAN: Color = Color::new(0, 255, 255); 10 | pub const GREEN: Color = Color::new(0, 255, 0); 11 | pub const MAGENTA: Color = Color::new(255, 0, 255); 12 | pub const RED: Color = Color::new(255, 0, 0); 13 | pub const WHITE: Color = Color::new(255, 255, 255); 14 | pub const YELLOW: Color = Color::new(255, 255, 0); 15 | } 16 | 17 | impl Color { 18 | #[inline(always)] 19 | pub const fn new(r: u8, g: u8, b: u8) -> Color { Color((r as u32) << 16 | (g as u32) << 8 | (b as u32) << 0) } 20 | 21 | #[inline(always)] 22 | pub const fn none() -> Color { Color(0) } 23 | 24 | #[inline(always)] 25 | pub const fn to_u32(&self) -> u32 { self.0 } 26 | } 27 | 28 | impl Into for Color { 29 | #[inline(always)] 30 | fn into(self) -> u32 { self.0 } 31 | } 32 | -------------------------------------------------------------------------------- /src/frame.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CStr, marker::PhantomData}; 2 | 3 | /// Macro to make working with frame marks easier. 4 | /// 5 | /// # Example 6 | /// ``` 7 | /// # use tracy_full::frame; 8 | /// 9 | /// // End of main continuous frame. 10 | /// frame!(); 11 | /// 12 | /// // End of secondary continuous frame. 13 | /// frame!("Secondary Frame"); 14 | /// 15 | /// // Discontinuous frame in the current scope. 16 | /// frame!(discontinuous "Discontinuous Frame"); 17 | /// ``` 18 | #[macro_export] 19 | macro_rules! frame { 20 | () => { 21 | $crate::frame::frame(); 22 | }; 23 | 24 | ($name:literal $(,)?) => { 25 | $crate::frame::named_frame($crate::c_str!($name)); 26 | }; 27 | 28 | (discontinuous $name:literal $(,)?) => { 29 | let _frame = $crate::frame::discontinuous_frame($crate::c_str!($name)); 30 | }; 31 | } 32 | 33 | /// The processing of the main continuous frame has ended. 34 | /// 35 | /// A 'continuous frame' is some work that repeats continuously for the duration of the program. 36 | #[inline(always)] 37 | pub fn frame() { 38 | #[cfg(feature = "enable")] 39 | unsafe { 40 | sys::___tracy_emit_frame_mark(std::ptr::null()); 41 | } 42 | } 43 | 44 | /// The processing of a secondary continuous frame has ended. 45 | /// 46 | /// A 'continuous frame' is some work that repeats continuously for the duration of the program. 47 | #[inline(always)] 48 | pub fn named_frame(name: &'static CStr) { 49 | #[cfg(feature = "enable")] 50 | unsafe { 51 | sys::___tracy_emit_frame_mark(name.as_ptr()); 52 | } 53 | } 54 | 55 | /// Start a discontinuous frame. The frame ends when the returned object is dropped. 56 | /// 57 | /// A 'discontinuous frame' is some work that runs periodically, with gaps between executions. 58 | #[inline(always)] 59 | pub fn discontinuous_frame(name: &'static CStr) -> DiscontinuousFrame { 60 | #[cfg(feature = "enable")] 61 | unsafe { 62 | sys::___tracy_emit_frame_mark_start(name.as_ptr()); 63 | DiscontinuousFrame { 64 | unsend: PhantomData, 65 | name, 66 | } 67 | } 68 | #[cfg(not(feature = "enable"))] 69 | DiscontinuousFrame { 70 | unsend: PhantomData, 71 | name: (), 72 | } 73 | } 74 | 75 | /// A discontinuous frame. 76 | pub struct DiscontinuousFrame { 77 | unsend: PhantomData<*mut ()>, 78 | #[cfg(feature = "enable")] 79 | name: &'static CStr, 80 | #[cfg(not(feature = "enable"))] 81 | name: (), 82 | } 83 | 84 | impl Drop for DiscontinuousFrame { 85 | #[inline(always)] 86 | fn drop(&mut self) { 87 | #[cfg(feature = "enable")] 88 | unsafe { 89 | sys::___tracy_emit_frame_mark_end(self.name.as_ptr()); 90 | } 91 | } 92 | } 93 | 94 | #[repr(C)] 95 | pub struct Pixel { 96 | pub r: u8, 97 | pub g: u8, 98 | pub b: u8, 99 | pub a: u8, 100 | } 101 | 102 | /// An image sent to the profiler. 103 | pub struct Image<'a> { 104 | /// The pixels of the image. 105 | pub data: &'a [Pixel], 106 | /// The width of the image. 107 | pub width: u16, 108 | /// The height of the image. 109 | pub height: u16, 110 | /// The number of frames that passed between being rendered and sent to the profiler. 111 | pub lag: u8, 112 | /// If the image is flipped. 113 | pub flip: bool, 114 | } 115 | 116 | /// Send an image to the profiler. 117 | /// 118 | /// The image is attached to the frame that is currently being processed: before a continuous frame mark, or inside a 119 | /// discontinuous frame. 120 | #[inline(always)] 121 | pub fn frame_image(image: Image) { 122 | #[cfg(feature = "enable")] 123 | unsafe { 124 | sys::___tracy_emit_frame_image( 125 | image.data.as_ptr() as *const _, 126 | image.width, 127 | image.height, 128 | image.lag, 129 | image.flip as _, 130 | ); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/future.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::CStr, 3 | future::Future, 4 | marker::PhantomData, 5 | pin::Pin, 6 | task::{Context, Poll}, 7 | }; 8 | 9 | /// Create a profiled future that appears as a fiber. 10 | #[macro_export] 11 | macro_rules! trace_future { 12 | ($name:literal, $future:expr) => { 13 | $crate::future::FutureWrapper::new($crate::c_str!($name), $future) 14 | }; 15 | } 16 | 17 | /// A wrapper for a future that appears a separate fiber. 18 | pub struct FutureWrapper<'a, T> { 19 | #[cfg(feature = "enable")] 20 | name: &'a CStr, 21 | #[cfg(not(feature = "enable"))] 22 | phantom: PhantomData<&'a ()>, 23 | inner: T, 24 | } 25 | 26 | impl<'a, T> FutureWrapper<'a, T> { 27 | #[inline(always)] 28 | pub const fn new(name: &'a CStr, inner: T) -> Self { 29 | Self { 30 | #[cfg(feature = "enable")] 31 | name, 32 | #[cfg(not(feature = "enable"))] 33 | phantom: PhantomData, 34 | inner, 35 | } 36 | } 37 | } 38 | 39 | impl Future for FutureWrapper<'_, T> { 40 | type Output = T::Output; 41 | 42 | #[inline(always)] 43 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 44 | #[cfg(feature = "enable")] 45 | unsafe { 46 | sys::___tracy_fiber_enter(self.name.as_ptr()); 47 | 48 | let this = self.get_unchecked_mut(); 49 | let inner = Pin::new_unchecked(&mut this.inner); 50 | let val = inner.poll(cx); 51 | 52 | sys::___tracy_fiber_enter(this.name.as_ptr()); 53 | sys::___tracy_fiber_leave(); 54 | val 55 | } 56 | 57 | #[cfg(not(feature = "enable"))] 58 | unsafe { 59 | self.map_unchecked_mut(|this| &mut this.inner).poll(cx) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(incomplete_features)] 3 | #![allow(unused_imports)] 4 | #![allow(unused_mut)] 5 | #![allow(unused_variables)] 6 | #![cfg_attr(feature = "allocator_api", feature(allocator_api))] 7 | #![cfg_attr(feature = "unstable", feature(const_type_name))] 8 | #![cfg_attr(feature = "unstable", feature(generic_const_exprs))] 9 | 10 | use std::{error::Error, ffi::CString}; 11 | 12 | #[doc(hidden)] 13 | pub use once_cell; 14 | 15 | pub mod alloc; 16 | #[cfg(feature = "bevy")] 17 | pub mod bevy; 18 | pub mod color; 19 | pub mod frame; 20 | #[cfg(feature = "futures")] 21 | pub mod future; 22 | pub mod plot; 23 | #[cfg(feature = "tracing")] 24 | pub mod tracing; 25 | #[cfg(feature = "wgpu")] 26 | pub mod wgpu; 27 | pub mod zone; 28 | 29 | /// Initialize the tracy profiler. Must be called before any other Tracy functions. 30 | #[cfg(feature = "manual-init")] 31 | pub unsafe fn startup_tracy() { 32 | #[cfg(feature = "enable")] 33 | sys::___tracy_startup_profiler(); 34 | } 35 | 36 | /// Shutdown the tracy profiler. Any other Tracy functions must not be called after this. 37 | #[cfg(feature = "manual-init")] 38 | pub unsafe fn shutdown_tracy() { 39 | #[cfg(feature = "enable")] 40 | sys::___tracy_shutdown_profiler(); 41 | } 42 | 43 | /// Set the current thread's name. Panics if the name contains interior nulls. 44 | #[inline(always)] 45 | pub fn set_thread_name(name: T) 46 | where 47 | T: TryInto, 48 | T::Error: Error, 49 | { 50 | #[cfg(feature = "enable")] 51 | unsafe { 52 | let cstr = name.try_into().expect("name is not a valid string"); 53 | sys::___tracy_set_thread_name(cstr.as_ptr()); 54 | } 55 | } 56 | 57 | /// Clamp a requested callstack depth to the maximum supported by tracy (62). 58 | #[inline(always)] 59 | pub const fn clamp_callstack_depth(depth: u32) -> u32 { 60 | if depth < 62 { 61 | depth 62 | } else { 63 | 62 64 | } 65 | } 66 | 67 | /// Create a `&'static CStr` from a string literal. 68 | #[macro_export] 69 | macro_rules! c_str { 70 | ($str:literal) => { 71 | unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(concat!($str, "\0").as_bytes()) } 72 | }; 73 | } 74 | -------------------------------------------------------------------------------- /src/plot.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CStr, marker::PhantomData}; 2 | 3 | /// Create a plotter. 4 | #[macro_export] 5 | macro_rules! plotter { 6 | ($name:literal) => { 7 | $crate::plot::Plotter::new($crate::c_str!($name)) 8 | }; 9 | } 10 | 11 | /// A plotter. 12 | pub struct Plotter<'a> { 13 | #[cfg(feature = "enable")] 14 | name: &'a CStr, 15 | #[cfg(not(feature = "enable"))] 16 | name: PhantomData<&'a ()>, 17 | } 18 | 19 | impl<'a> Plotter<'a> { 20 | #[inline(always)] 21 | pub const fn new(name: &'a CStr) -> Self { 22 | Self { 23 | #[cfg(feature = "enable")] 24 | name, 25 | #[cfg(not(feature = "enable"))] 26 | name: PhantomData, 27 | } 28 | } 29 | 30 | /// Emit a value for the plotter. 31 | #[inline(always)] 32 | pub fn value(&self, value: f64) { 33 | #[cfg(feature = "enable")] 34 | unsafe { 35 | sys::___tracy_emit_plot(self.name.as_ptr(), value); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/tracing.rs: -------------------------------------------------------------------------------- 1 | use std::{any::TypeId, borrow::Cow, cell::UnsafeCell, num::NonZeroU64}; 2 | 3 | use tracing::{span::Attributes, Id, Subscriber}; 4 | use tracing_subscriber::{ 5 | fmt::{format::DefaultFields, FormatFields, FormattedFields}, 6 | layer::Context, 7 | registry::LookupSpan, 8 | Layer, 9 | }; 10 | 11 | thread_local! { 12 | static STACK: UnsafeCell> = UnsafeCell::new(Vec::new()); 13 | } 14 | 15 | /// A tracing layer that tracks spans. 16 | pub struct TracyLayer; 17 | 18 | impl Layer for TracyLayer 19 | where 20 | S: Subscriber, 21 | S: for<'a> LookupSpan<'a>, 22 | { 23 | fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) { 24 | #[cfg(feature = "enable")] 25 | { 26 | if let Some(span) = ctx.span(id) { 27 | let mut extensions = span.extensions_mut(); 28 | 29 | if extensions.get_mut::>().is_none() { 30 | let mut fields = FormattedFields::::new(String::with_capacity(64)); 31 | 32 | if DefaultFields::default() 33 | .format_fields(fields.as_writer(), attrs) 34 | .is_ok() 35 | { 36 | extensions.insert(fields); 37 | } 38 | } 39 | } 40 | } 41 | } 42 | 43 | fn on_enter(&self, id: &Id, ctx: Context<'_, S>) { 44 | #[cfg(feature = "enable")] 45 | { 46 | let Some(span) = ctx.span(id) else { 47 | return; 48 | }; 49 | let meta = span.metadata(); 50 | let file = meta.file().unwrap_or(""); 51 | let module = meta.module_path().unwrap_or(""); 52 | let name: Cow = if let Some(fields) = span.extensions().get::>() { 53 | if fields.fields.as_str().is_empty() { 54 | meta.name().into() 55 | } else { 56 | format!("{}{{{}}}", meta.name(), fields.fields.as_str()).into() 57 | } 58 | } else { 59 | meta.name().into() 60 | }; 61 | 62 | unsafe { 63 | let srcloc = sys::___tracy_alloc_srcloc_name( 64 | meta.line().unwrap_or(0), 65 | file.as_ptr() as _, 66 | file.len(), 67 | module.as_ptr() as _, 68 | module.len(), 69 | name.as_ptr() as _, 70 | name.len(), 71 | 0, 72 | ); 73 | 74 | let ctx = sys::___tracy_emit_zone_begin_alloc(srcloc, 1); 75 | 76 | STACK.with(|stack| { 77 | let stack = &mut *stack.get(); 78 | stack.push(ctx.id); 79 | }) 80 | } 81 | } 82 | } 83 | 84 | fn on_exit(&self, id: &Id, ctx: Context<'_, S>) { 85 | #[cfg(feature = "enable")] 86 | { 87 | STACK.with(|stack| unsafe { 88 | let stack = &mut *stack.get(); 89 | sys::___tracy_emit_zone_end(sys::___tracy_c_zone_context { 90 | id: stack.pop().unwrap(), 91 | active: 1, 92 | }) 93 | }); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/wgpu.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | future::Future, 3 | marker::PhantomData, 4 | mem::{ManuallyDrop, MaybeUninit}, 5 | ops::{Deref, DerefMut}, 6 | pin::Pin, 7 | sync::atomic::{AtomicU8, Ordering}, 8 | time::{Duration, Instant}, 9 | }; 10 | 11 | use futures_lite::{ 12 | future::{block_on, poll_once}, 13 | FutureExt, 14 | }; 15 | use wgpu::{ 16 | Adapter, 17 | Backend, 18 | Buffer, 19 | BufferAsyncError, 20 | BufferDescriptor, 21 | BufferUsages, 22 | CommandBuffer, 23 | CommandEncoder, 24 | CommandEncoderDescriptor, 25 | ComputePass, 26 | ComputePassDescriptor, 27 | Device, 28 | Maintain, 29 | MapMode, 30 | QuerySet, 31 | QuerySetDescriptor, 32 | QueryType, 33 | Queue, 34 | RenderPass, 35 | RenderPassDescriptor, 36 | SubmissionIndex, 37 | QUERY_SET_MAX_QUERIES, 38 | }; 39 | 40 | /// Create a profiled command encoder. 41 | #[macro_export] 42 | macro_rules! wgpu_command_encoder { 43 | ($device:expr, $profiler:expr, $desc:expr $(,)?) => {{ 44 | struct S; 45 | let s = ::std::any::type_name::(); 46 | $profiler.create_command_encoder(&$device, &$desc, line!(), file!(), &s[..s.len() - 3]) 47 | }}; 48 | } 49 | 50 | /// Create a profiled render pass from a profiled command encoder. 51 | #[macro_export] 52 | macro_rules! wgpu_render_pass { 53 | ($encoder:expr, $desc:expr) => {{ 54 | struct S; 55 | let s = ::std::any::type_name::(); 56 | $encoder.begin_render_pass(&$desc, line!(), file!(), &s[..s.len() - 3]) 57 | }}; 58 | } 59 | 60 | /// Create a profiled compute pass from a profiled command encoder. 61 | #[macro_export] 62 | macro_rules! wgpu_compute_pass { 63 | ($encoder:expr, $desc:expr) => {{ 64 | struct S; 65 | let s = ::std::any::type_name::(); 66 | $encoder.begin_compute_pass(&$desc, line!(), file!(), &s[..s.len() - 3]) 67 | }}; 68 | } 69 | 70 | #[cfg(feature = "enable")] 71 | static CONTEXTS: AtomicU8 = AtomicU8::new(0); 72 | 73 | #[cfg(feature = "enable")] 74 | fn get_next_context() -> u8 { 75 | let next = CONTEXTS.fetch_add(1, Ordering::Relaxed); 76 | if next == 255 { 77 | panic!("Too many contexts"); 78 | } 79 | 80 | next 81 | } 82 | 83 | #[cfg(feature = "enable")] 84 | struct QueryPool { 85 | resolve: Buffer, 86 | readback: Buffer, 87 | query: QuerySet, 88 | used_queries: u16, 89 | base_query_id: u16, 90 | } 91 | 92 | #[cfg(feature = "enable")] 93 | impl QueryPool { 94 | const QUERY_POOL_SIZE: u16 = 128; 95 | 96 | pub fn new(device: &Device, allocated_query_ids: &mut u16) -> Self { 97 | let ret = Self { 98 | resolve: device.create_buffer(&BufferDescriptor { 99 | label: Some("Tracy Resolve Buffer"), 100 | size: 8 * Self::QUERY_POOL_SIZE as u64, 101 | usage: BufferUsages::COPY_SRC | BufferUsages::QUERY_RESOLVE, 102 | mapped_at_creation: false, 103 | }), 104 | readback: device.create_buffer(&BufferDescriptor { 105 | label: Some("Tracy Readback Buffer"), 106 | size: 8 * Self::QUERY_POOL_SIZE as u64, 107 | usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ, 108 | mapped_at_creation: false, 109 | }), 110 | query: device.create_query_set(&QuerySetDescriptor { 111 | label: Some("Tracy Query Set"), 112 | ty: QueryType::Timestamp, 113 | count: Self::QUERY_POOL_SIZE as _, 114 | }), 115 | used_queries: 0, 116 | base_query_id: *allocated_query_ids, 117 | }; 118 | *allocated_query_ids += Self::QUERY_POOL_SIZE; 119 | ret 120 | } 121 | 122 | pub fn write_query(&mut self, pass: &mut T) -> (u16, bool) { 123 | let id = self.base_query_id + self.used_queries; 124 | pass.write_timestamp(&self.query, self.used_queries as _); 125 | self.used_queries += 1; 126 | (id, self.used_queries == Self::QUERY_POOL_SIZE) 127 | } 128 | 129 | pub fn reset(&mut self) { 130 | self.used_queries = 0; 131 | self.readback.unmap(); 132 | } 133 | } 134 | 135 | #[cfg(feature = "enable")] 136 | struct FrameInFlight { 137 | pools: Vec, 138 | curr_pool: usize, 139 | map_submission: Option, 140 | } 141 | 142 | #[cfg(feature = "enable")] 143 | impl FrameInFlight { 144 | fn new() -> Self { 145 | Self { 146 | pools: Vec::new(), 147 | curr_pool: 0, 148 | map_submission: None, 149 | } 150 | } 151 | 152 | fn get_pool(&mut self, device: &Device, allocated_query_ids: &mut u16) -> &mut QueryPool { 153 | if self.pools.len() == self.curr_pool { 154 | self.pools.push(QueryPool::new(device, allocated_query_ids)); 155 | } 156 | 157 | &mut self.pools[self.curr_pool] 158 | } 159 | } 160 | 161 | /// A context for profiling the GPU. 162 | pub struct ProfileContext { 163 | #[cfg(feature = "enable")] 164 | context: u8, 165 | #[cfg(feature = "enable")] 166 | frames: Vec, 167 | #[cfg(feature = "enable")] 168 | curr_frame: usize, 169 | #[cfg(feature = "enable")] 170 | allocated_query_ids: u16, 171 | #[cfg(feature = "enable")] 172 | enabled: bool, 173 | #[cfg(feature = "enable")] 174 | last_sync: Instant, 175 | #[cfg(feature = "enable")] 176 | resync_interval: Duration, 177 | 178 | #[cfg(not(feature = "enable"))] 179 | _context: (), 180 | } 181 | 182 | impl ProfileContext { 183 | /// Device needs `Features::TIMESTAMP_QUERY` enabled. 184 | /// `buffered_frames` is the number of frames to buffer before uploading the data to Tracy. 185 | /// `resync_interval` is the interval at which to force a full CPU-GPU sync to prevent drift. 186 | pub fn new( 187 | adapter: &Adapter, device: &Device, queue: &Queue, buffered_frames: u32, resync_interval: Duration, 188 | ) -> Self { 189 | Self::with_enabled(adapter, device, queue, buffered_frames, resync_interval, true) 190 | } 191 | 192 | pub fn with_name( 193 | name: &str, adapter: &Adapter, device: &Device, queue: &Queue, buffered_frames: u32, resync_interval: Duration, 194 | ) -> Self { 195 | Self::with_enabled_and_name(name, adapter, device, queue, buffered_frames, resync_interval, true) 196 | } 197 | 198 | pub fn with_enabled( 199 | adapter: &Adapter, device: &Device, queue: &Queue, buffered_frames: u32, resync_interval: Duration, 200 | enabled: bool, 201 | ) -> Self { 202 | #[cfg(feature = "enable")] 203 | { 204 | let context = get_next_context(); 205 | let mut allocated_query_ids = 0; 206 | 207 | let frames = if enabled { 208 | let mut frames: Vec<_> = std::iter::repeat_with(|| FrameInFlight::new()) 209 | .take(buffered_frames as _) 210 | .collect(); 211 | 212 | let period = queue.get_timestamp_period(); 213 | let gpu_time = Self::sync_frame(&mut allocated_query_ids, &mut frames[0], device, queue); 214 | 215 | let mut type_ = match adapter.get_info().backend { 216 | Backend::Empty => 0, 217 | Backend::Gl => 1, 218 | Backend::Vulkan => 2, 219 | Backend::Dx12 => 4, 220 | Backend::Metal => 5, 221 | Backend::BrowserWebGpu => 6, 222 | }; 223 | 224 | unsafe { 225 | sys::___tracy_emit_gpu_new_context_serial(sys::___tracy_gpu_new_context_data { 226 | gpuTime: gpu_time, 227 | period, 228 | context, 229 | flags: 0, 230 | type_, 231 | }); 232 | } 233 | 234 | frames 235 | } else { 236 | Vec::new() 237 | }; 238 | 239 | Self { 240 | context, 241 | frames, 242 | curr_frame: 0, 243 | enabled, 244 | allocated_query_ids, 245 | last_sync: Instant::now() - resync_interval, 246 | resync_interval, 247 | } 248 | } 249 | 250 | #[cfg(not(feature = "enable"))] 251 | { 252 | Self { _context: () } 253 | } 254 | } 255 | 256 | pub fn with_enabled_and_name( 257 | name: &str, adapter: &Adapter, device: &Device, queue: &Queue, buffered_frames: u32, resync_interval: Duration, 258 | enabled: bool, 259 | ) -> Self { 260 | let this = Self::with_enabled(adapter, device, queue, buffered_frames, resync_interval, true); 261 | 262 | #[cfg(feature = "enable")] 263 | unsafe { 264 | sys::___tracy_emit_gpu_context_name_serial(sys::___tracy_gpu_context_name_data { 265 | context: this.context, 266 | name: name.as_ptr() as _, 267 | len: name.len() as _, 268 | }); 269 | } 270 | 271 | this 272 | } 273 | 274 | /// Create a profiled command encoder. 275 | pub fn create_command_encoder<'a>( 276 | &'a mut self, device: &'a Device, desc: &CommandEncoderDescriptor, line: u32, file: &str, function: &str, 277 | ) -> EncoderProfiler<'a> { 278 | #[cfg(feature = "enable")] 279 | { 280 | let mut inner = device.create_command_encoder(desc); 281 | self.begin_zone(device, &mut inner, desc.label, line, file, function); 282 | EncoderProfiler { 283 | inner, 284 | context: self, 285 | device, 286 | } 287 | } 288 | 289 | #[cfg(not(feature = "enable"))] 290 | EncoderProfiler { 291 | inner: device.create_command_encoder(desc), 292 | context: PhantomData, 293 | } 294 | } 295 | 296 | /// End a frame, uploading the data to Tracy, while also synchronizing for `buffered_frames` frames. 297 | pub fn end_frame(&mut self, device: &Device, queue: &Queue) { 298 | #[cfg(feature = "enable")] 299 | if self.enabled { 300 | Self::resolve_frame(&mut self.frames[self.curr_frame], device, queue); 301 | if self.last_sync.elapsed() >= self.resync_interval { 302 | // Force a full sync to prevent drift. 303 | for _ in 0..self.frames.len() { 304 | self.curr_frame = (self.curr_frame + 1) % self.frames.len(); 305 | Self::readback_frame(self.context, &mut self.frames[self.curr_frame], device); 306 | } 307 | 308 | let gpu_time = Self::sync_frame(&mut self.allocated_query_ids, &mut self.frames[0], device, queue); 309 | unsafe { 310 | sys::___tracy_emit_gpu_time_sync_serial(sys::___tracy_gpu_time_sync_data { 311 | gpuTime: gpu_time, 312 | context: self.context, 313 | }); 314 | } 315 | 316 | self.curr_frame = 0; 317 | self.last_sync = Instant::now(); 318 | } else { 319 | self.curr_frame = (self.curr_frame + 1) % self.frames.len(); 320 | Self::readback_frame(self.context, &mut self.frames[self.curr_frame], device); 321 | } 322 | } 323 | } 324 | 325 | #[cfg(feature = "enable")] 326 | fn sync_frame(allocated_query_ids: &mut u16, frame: &mut FrameInFlight, device: &Device, queue: &Queue) -> i64 { 327 | let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor { 328 | label: Some("Initialize Profiler"), 329 | }); 330 | let pool = frame.get_pool(device, allocated_query_ids); 331 | encoder.write_timestamp(&pool.query, 0); 332 | encoder.resolve_query_set(&pool.query, 0..1, &pool.resolve, 0); 333 | encoder.copy_buffer_to_buffer(&pool.resolve, 0, &pool.readback, 0, 8); 334 | queue.submit([encoder.finish()]); 335 | let slice = pool.readback.slice(0..8); 336 | slice.map_async(MapMode::Read, |_| {}); 337 | device.poll(Maintain::Wait); 338 | 339 | let gpu_time = i64::from_le_bytes(slice.get_mapped_range()[0..8].try_into().unwrap()); 340 | pool.reset(); 341 | gpu_time 342 | } 343 | 344 | #[cfg(feature = "enable")] 345 | fn resolve_frame(frame: &mut FrameInFlight, device: &Device, queue: &Queue) { 346 | let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor { 347 | label: Some("Tracy Query Resolve"), 348 | }); 349 | for pool in &mut frame.pools { 350 | if pool.used_queries == 0 { 351 | break; 352 | } 353 | 354 | encoder.resolve_query_set(&pool.query, 0..(pool.used_queries as u32), &pool.resolve, 0); 355 | encoder.copy_buffer_to_buffer(&pool.resolve, 0, &pool.readback, 0, pool.used_queries as u64 * 8); 356 | } 357 | frame.map_submission = Some(queue.submit([encoder.finish()])); 358 | for pool in &mut frame.pools { 359 | if pool.used_queries == 0 { 360 | break; 361 | } 362 | 363 | let slice = pool.readback.slice(..(pool.used_queries as u64 * 8)); 364 | slice.map_async(MapMode::Read, |_| {}); 365 | } 366 | } 367 | 368 | #[cfg(feature = "enable")] 369 | fn readback_frame(context: u8, frame: &mut FrameInFlight, device: &Device) { 370 | if let Some(map_submission) = &frame.map_submission { 371 | device.poll(Maintain::WaitForSubmissionIndex(map_submission.to_owned())); 372 | } 373 | 374 | for pool in &mut frame.pools { 375 | if pool.used_queries == 0 { 376 | break; 377 | } 378 | 379 | let slice = pool.readback.slice(..(pool.used_queries as u64 * 8)); 380 | { 381 | let view = slice.get_mapped_range(); 382 | for i in 0..pool.used_queries { 383 | let query_id = pool.base_query_id + i; 384 | let view_base = i as usize * 8; 385 | let gpu_time = i64::from_le_bytes(view[view_base..view_base + 8].try_into().unwrap()); 386 | 387 | unsafe { 388 | sys::___tracy_emit_gpu_time_serial(sys::___tracy_gpu_time_data { 389 | gpuTime: gpu_time, 390 | queryId: query_id, 391 | context, 392 | }); 393 | } 394 | } 395 | } 396 | 397 | pool.reset(); 398 | } 399 | } 400 | 401 | #[cfg(feature = "enable")] 402 | fn begin_zone( 403 | &mut self, device: &Device, pass: &mut T, name: Option<&str>, line: u32, file: &str, function: &str, 404 | ) { 405 | if self.enabled { 406 | unsafe { 407 | let srcloc = match name { 408 | Some(label) => sys::___tracy_alloc_srcloc_name( 409 | line, 410 | file.as_ptr() as _, 411 | file.len(), 412 | function.as_ptr() as _, 413 | function.len(), 414 | label.as_ptr() as _, 415 | label.len(), 416 | 0, 417 | ), 418 | None => sys::___tracy_alloc_srcloc( 419 | line, 420 | file.as_ptr() as _, 421 | file.len(), 422 | function.as_ptr() as _, 423 | function.len(), 424 | 0, 425 | ), 426 | }; 427 | 428 | let frame = &mut self.frames[self.curr_frame]; 429 | let pool = frame.get_pool(device, &mut self.allocated_query_ids); 430 | let (query_id, need_new_pool) = pool.write_query(pass); 431 | if need_new_pool { 432 | frame.curr_pool += 1; 433 | } 434 | 435 | sys::___tracy_emit_gpu_zone_begin_alloc_serial(sys::___tracy_gpu_zone_begin_data { 436 | srcloc, 437 | queryId: query_id, 438 | context: self.context, 439 | }); 440 | } 441 | } 442 | } 443 | 444 | #[cfg(feature = "enable")] 445 | fn end_zone(&mut self, device: &Device, pass: &mut T) { 446 | if self.enabled { 447 | let frame = &mut self.frames[self.curr_frame]; 448 | let pool = frame.get_pool(device, &mut self.allocated_query_ids); 449 | let (query_id, need_new_pool) = pool.write_query(pass); 450 | if need_new_pool { 451 | frame.curr_pool += 1; 452 | } 453 | 454 | unsafe { 455 | sys::___tracy_emit_gpu_zone_end_serial(sys::___tracy_gpu_zone_end_data { 456 | queryId: query_id, 457 | context: self.context, 458 | }); 459 | } 460 | } 461 | } 462 | } 463 | 464 | pub trait Pass { 465 | type This<'a>: Pass; 466 | 467 | fn write_timestamp(&mut self, set: &QuerySet, index: u32); 468 | 469 | fn forget_lifetime(self) -> Self::This<'static>; 470 | } 471 | 472 | pub struct PassProfiler<'a, T: Pass> { 473 | inner: T, 474 | #[cfg(feature = "enable")] 475 | context: &'a mut ProfileContext, 476 | #[cfg(feature = "enable")] 477 | device: &'a Device, 478 | #[cfg(not(feature = "enable"))] 479 | context: PhantomData<&'a mut ()>, 480 | } 481 | 482 | impl Drop for PassProfiler<'_, T> { 483 | fn drop(&mut self) { 484 | #[cfg(feature = "enable")] 485 | self.context.end_zone(&self.device, &mut self.inner); 486 | } 487 | } 488 | 489 | impl Deref for PassProfiler<'_, T> { 490 | type Target = T; 491 | 492 | fn deref(&self) -> &Self::Target { &self.inner } 493 | } 494 | 495 | impl DerefMut for PassProfiler<'_, T> { 496 | fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } 497 | } 498 | 499 | impl<'a, T: Pass> PassProfiler<'a, T> { 500 | pub fn forget_lifetime(self) -> PassProfiler<'a, T::This<'static>> { 501 | let mut m = MaybeUninit::new(self); 502 | let m = m.as_mut_ptr(); 503 | 504 | unsafe { 505 | PassProfiler { 506 | inner: (&raw mut (*m).inner).read().forget_lifetime(), 507 | #[cfg(feature = "enable")] 508 | context: (&raw mut (*m).context).read(), 509 | #[cfg(feature = "enable")] 510 | device: (&raw mut (*m).device).read(), 511 | #[cfg(not(feature = "enable"))] 512 | context: PhantomData, 513 | } 514 | } 515 | } 516 | } 517 | 518 | pub struct EncoderProfiler<'a> { 519 | inner: CommandEncoder, 520 | #[cfg(feature = "enable")] 521 | context: &'a mut ProfileContext, 522 | #[cfg(feature = "enable")] 523 | device: &'a Device, 524 | #[cfg(not(feature = "enable"))] 525 | context: PhantomData<&'a mut ()>, 526 | } 527 | 528 | impl EncoderProfiler<'_> { 529 | /// Begin a profiled render pass. 530 | pub fn begin_render_pass<'a>( 531 | &'a mut self, desc: &RenderPassDescriptor<'_>, line: u32, file: &str, function: &str, 532 | ) -> PassProfiler<'a, RenderPass<'a>> { 533 | #[cfg(feature = "enable")] 534 | { 535 | let mut inner = self.inner.begin_render_pass(desc); 536 | self.context 537 | .begin_zone(&self.device, &mut inner, desc.label, line, file, function); 538 | PassProfiler { 539 | inner, 540 | context: self.context, 541 | device: self.device, 542 | } 543 | } 544 | 545 | #[cfg(not(feature = "enable"))] 546 | PassProfiler { 547 | inner: self.inner.begin_render_pass(desc), 548 | context: PhantomData, 549 | } 550 | } 551 | 552 | /// Begin a profiled compute pass. 553 | pub fn begin_compute_pass<'a>( 554 | &'a mut self, desc: &ComputePassDescriptor<'_>, line: u32, file: &str, function: &str, 555 | ) -> PassProfiler<'a, ComputePass<'a>> { 556 | #[cfg(feature = "enable")] 557 | { 558 | let mut inner = self.inner.begin_compute_pass(desc); 559 | self.context 560 | .begin_zone(&self.device, &mut inner, desc.label, line, file, function); 561 | PassProfiler { 562 | inner, 563 | context: self.context, 564 | device: self.device, 565 | } 566 | } 567 | 568 | #[cfg(not(feature = "enable"))] 569 | PassProfiler { 570 | inner: self.inner.begin_compute_pass(desc), 571 | context: PhantomData, 572 | } 573 | } 574 | 575 | /// Finish the profiled encoder. 576 | pub fn finish(mut self) -> CommandBuffer { 577 | #[cfg(feature = "enable")] 578 | self.context.end_zone(&self.device, &mut self.inner); 579 | self.inner.finish() 580 | } 581 | } 582 | 583 | impl Deref for EncoderProfiler<'_> { 584 | type Target = CommandEncoder; 585 | 586 | fn deref(&self) -> &Self::Target { &self.inner } 587 | } 588 | 589 | impl DerefMut for EncoderProfiler<'_> { 590 | fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } 591 | } 592 | 593 | impl Pass for CommandEncoder { 594 | type This<'a> = CommandEncoder; 595 | 596 | fn write_timestamp(&mut self, set: &QuerySet, index: u32) { self.write_timestamp(set, index); } 597 | 598 | fn forget_lifetime(self) -> Self::This<'static> { self } 599 | } 600 | 601 | impl Pass for RenderPass<'_> { 602 | type This<'a> = RenderPass<'a>; 603 | 604 | fn write_timestamp(&mut self, set: &QuerySet, index: u32) { self.write_timestamp(set, index); } 605 | 606 | fn forget_lifetime(self) -> Self::This<'static> { self.forget_lifetime() } 607 | } 608 | 609 | impl Pass for ComputePass<'_> { 610 | type This<'a> = ComputePass<'a>; 611 | 612 | fn write_timestamp(&mut self, set: &QuerySet, index: u32) { self.write_timestamp(set, index); } 613 | 614 | fn forget_lifetime(self) -> Self::This<'static> { self.forget_lifetime() } 615 | } 616 | -------------------------------------------------------------------------------- /src/zone.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{CStr, CString}, 3 | marker::PhantomData, 4 | }; 5 | 6 | use crate::color::Color; 7 | 8 | /// Mark a zone in the current scope. 9 | #[macro_export] 10 | macro_rules! zone { 11 | () => { 12 | let loc = $crate::get_location!(); 13 | let _zone = $crate::zone::zone(loc, true); 14 | }; 15 | 16 | ($name:literal $(,)?) => { 17 | let loc = $crate::get_location!($name); 18 | let _zone = $crate::zone::zone(loc, true); 19 | }; 20 | 21 | ($color:expr $(,)?) => { 22 | let loc = $crate::get_location!($color); 23 | let _zone = $crate::zone::zone(loc, true); 24 | }; 25 | 26 | ($name:literal, $enabled:expr $(,)?) => { 27 | let loc = $crate::get_location!($name); 28 | let _zone = $crate::zone::zone(loc, $enabled); 29 | }; 30 | 31 | ($color:expr, $enabled:expr $(,)?) => { 32 | let loc = $crate::get_location!($color); 33 | let _zone = $crate::zone::zone(loc, $enabled); 34 | }; 35 | 36 | ($name:literal, $color:expr, $enabled:expr $(,)?) => { 37 | let loc = $crate::get_location!($name, $color); 38 | let _zone = $crate::zone::zone(loc, $enabled); 39 | }; 40 | } 41 | 42 | /// Mark a zone in the current scope, sampling the callstack. 43 | #[macro_export] 44 | macro_rules! zone_sample { 45 | ($depth:literal) => { 46 | let _zone = $crate::zone::zone_sample($crate::get_location!(), $crate::clamp_callstack_depth($depth), true); 47 | }; 48 | 49 | ($name:literal, $depth:literal, $enabled:expr $(,)?) => { 50 | let _zone = $crate::zone::zone_sample( 51 | $crate::get_location!($name), 52 | $crate::clamp_callstack_depth($depth), 53 | $enabled, 54 | ); 55 | }; 56 | 57 | ($color:expr, $depth:literal, $enabled:expr $(,)?) => { 58 | let _zone = $crate::zone::zone_sample( 59 | $crate::get_location!($color), 60 | $crate::clamp_callstack_depth($depth), 61 | $enabled, 62 | ); 63 | }; 64 | 65 | ($name:literal, $color:expr, $depth:literal, $enabled:expr $(,)?) => { 66 | let _zone = $crate::zone::zone_sample( 67 | $crate::get_location!($name, $color), 68 | $crate::clamp_callstack_depth($depth), 69 | $enabled, 70 | ); 71 | }; 72 | } 73 | 74 | /// Create a zone. 75 | #[inline(always)] 76 | pub fn zone(loc: &'static ZoneLocation, active: bool) -> Zone { 77 | #[cfg(feature = "enable")] 78 | unsafe { 79 | Zone { 80 | unsend: PhantomData, 81 | ctx: sys::___tracy_emit_zone_begin(&loc.loc, active as _), 82 | } 83 | } 84 | 85 | #[cfg(not(feature = "enable"))] 86 | Zone { 87 | unsend: PhantomData, 88 | ctx: (), 89 | } 90 | } 91 | 92 | /// Create a callstack sampled zone. 93 | #[inline(always)] 94 | #[cfg(feature = "enable")] 95 | pub fn zone_sample(loc: &'static ZoneLocation, depth: u32, active: bool) -> Zone { 96 | #[cfg(feature = "enable")] 97 | unsafe { 98 | Zone { 99 | unsend: PhantomData, 100 | ctx: sys::___tracy_emit_zone_begin_callstack(&loc.loc, depth as _, active as _), 101 | } 102 | } 103 | 104 | #[cfg(not(feature = "enable"))] 105 | Zone { 106 | unsend: PhantomData, 107 | ctx: (), 108 | } 109 | } 110 | 111 | pub struct Zone { 112 | unsend: PhantomData<*mut ()>, 113 | #[cfg(feature = "enable")] 114 | ctx: sys::___tracy_c_zone_context, 115 | #[cfg(not(feature = "enable"))] 116 | ctx: (), 117 | } 118 | 119 | #[cfg(feature = "enable")] 120 | impl Drop for Zone { 121 | #[inline(always)] 122 | fn drop(&mut self) { 123 | unsafe { 124 | sys::___tracy_emit_zone_end(self.ctx); 125 | } 126 | } 127 | } 128 | 129 | #[doc(hidden)] 130 | #[cfg(feature = "unstable")] 131 | pub const fn get_function_name_from_local_type() -> [u8; std::any::type_name::().len() - (TY + 1)] 132 | where 133 | [(); std::any::type_name::().len() - (TY + 1)]:, 134 | { 135 | let mut name = [0; std::any::type_name::().len() - (TY + 1)]; 136 | unsafe { 137 | std::ptr::copy_nonoverlapping(std::any::type_name::().as_ptr(), name.as_mut_ptr(), name.len() - 1); 138 | name 139 | } 140 | } 141 | 142 | pub struct ZoneLocation { 143 | #[cfg(feature = "enable")] 144 | loc: sys::___tracy_source_location_data, 145 | #[cfg(all(feature = "enable", not(feature = "unstable")))] 146 | function: CString, 147 | #[cfg(not(feature = "enable"))] 148 | pub loc: (), 149 | } 150 | 151 | unsafe impl Send for ZoneLocation {} 152 | unsafe impl Sync for ZoneLocation {} 153 | 154 | #[cfg(all(feature = "enable", feature = "unstable"))] 155 | impl ZoneLocation { 156 | pub const fn from_function_file_line(function: &'static CStr, file: &'static CStr, line: u32) -> Self { 157 | Self { 158 | loc: sys::___tracy_source_location_data { 159 | name: std::ptr::null(), 160 | function: function.as_ptr(), 161 | file: file.as_ptr(), 162 | line, 163 | color: Color::none().to_u32(), 164 | }, 165 | } 166 | } 167 | 168 | pub const fn from_name_function_file_line( 169 | name: &'static CStr, function: &'static CStr, file: &'static CStr, line: u32, 170 | ) -> Self { 171 | Self { 172 | loc: sys::___tracy_source_location_data { 173 | name: name.as_ptr(), 174 | function: function.as_ptr(), 175 | file: file.as_ptr(), 176 | line, 177 | color: Color::none().to_u32(), 178 | }, 179 | } 180 | } 181 | 182 | pub const fn from_function_file_line_color( 183 | function: &'static CStr, file: &'static CStr, line: u32, color: Color, 184 | ) -> Self { 185 | Self { 186 | loc: sys::___tracy_source_location_data { 187 | name: std::ptr::null(), 188 | function: function.as_ptr(), 189 | file: file.as_ptr(), 190 | line, 191 | color: color.to_u32(), 192 | }, 193 | } 194 | } 195 | 196 | pub const fn from_name_function_file_line_color( 197 | name: &'static CStr, function: &'static CStr, file: &'static CStr, line: u32, color: Color, 198 | ) -> Self { 199 | Self { 200 | loc: sys::___tracy_source_location_data { 201 | name: name.as_ptr(), 202 | function: function.as_ptr(), 203 | file: file.as_ptr(), 204 | line, 205 | color: color.to_u32(), 206 | }, 207 | } 208 | } 209 | } 210 | 211 | #[cfg(all(feature = "enable", not(feature = "unstable")))] 212 | impl ZoneLocation { 213 | pub fn from_function_file_line(function: CString, file: &'static CStr, line: u32) -> Self { 214 | Self { 215 | loc: sys::___tracy_source_location_data { 216 | name: std::ptr::null(), 217 | function: function.as_ptr(), 218 | file: file.as_ptr(), 219 | line, 220 | color: Color::none().to_u32(), 221 | }, 222 | function, 223 | } 224 | } 225 | 226 | pub fn from_name_function_file_line( 227 | name: &'static CStr, function: CString, file: &'static CStr, line: u32, 228 | ) -> Self { 229 | Self { 230 | loc: sys::___tracy_source_location_data { 231 | name: name.as_ptr(), 232 | function: function.as_ptr(), 233 | file: file.as_ptr(), 234 | line, 235 | color: Color::none().to_u32(), 236 | }, 237 | function, 238 | } 239 | } 240 | 241 | pub fn from_function_file_line_color(function: CString, file: &'static CStr, line: u32, color: Color) -> Self { 242 | Self { 243 | loc: sys::___tracy_source_location_data { 244 | name: std::ptr::null(), 245 | function: function.as_ptr(), 246 | file: file.as_ptr(), 247 | line, 248 | color: color.to_u32(), 249 | }, 250 | function, 251 | } 252 | } 253 | 254 | pub fn from_name_function_file_line_color( 255 | name: &'static CStr, function: CString, file: &'static CStr, line: u32, color: Color, 256 | ) -> Self { 257 | Self { 258 | loc: sys::___tracy_source_location_data { 259 | name: name.as_ptr(), 260 | function: function.as_ptr(), 261 | file: file.as_ptr(), 262 | line, 263 | color: color.to_u32(), 264 | }, 265 | function, 266 | } 267 | } 268 | } 269 | 270 | /// Get a zone location. 271 | #[cfg(all(feature = "enable", feature = "unstable"))] 272 | #[macro_export] 273 | macro_rules! get_location { 274 | () => {{ 275 | { 276 | struct S; 277 | static FUNCTION: &[u8] = &$crate::zone::get_function_name_from_local_type::(); 278 | static LOC: $crate::zone::ZoneLocation = $crate::zone::ZoneLocation::from_function_file_line( 279 | unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(FUNCTION) }, 280 | unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) }, 281 | line!(), 282 | ); 283 | &LOC 284 | } 285 | }}; 286 | 287 | ($name:literal $(,)?) => {{ 288 | struct S; 289 | static FUNCTION: &[u8] = &$crate::zone::get_function_name_from_local_type::(); 290 | static LOC: $crate::zone::ZoneLocation = $crate::zone::ZoneLocation::from_name_function_file_line( 291 | $crate::c_str!($name), 292 | unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(FUNCTION) }, 293 | unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) }, 294 | line!(), 295 | ); 296 | &LOC 297 | }}; 298 | 299 | ($color:expr $(,)?) => {{ 300 | struct S; 301 | static FUNCTION: &[u8] = &$crate::zone::get_function_name_from_local_type::(); 302 | static LOC: $crate::zone::ZoneLocation = $crate::zone::ZoneLocation::from_function_file_line_color( 303 | unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(FUNCTION) }, 304 | unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) }, 305 | line!(), 306 | $color, 307 | ); 308 | &LOC 309 | }}; 310 | 311 | ($name:literal, $color:expr $(,)?) => {{ 312 | struct S; 313 | static FUNCTION: &[u8] = &$crate::zone::get_function_name_from_local_type::(); 314 | static LOC: $crate::zone::ZoneLocation = $crate::zone::ZoneLocation::from_name_function_file_line_color( 315 | $crate::c_str!($name), 316 | unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(FUNCTION) }, 317 | unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) }, 318 | line!(), 319 | $color, 320 | ); 321 | &LOC 322 | }}; 323 | } 324 | 325 | /// Get a zone location. 326 | #[cfg(all(feature = "enable", not(feature = "unstable")))] 327 | #[macro_export] 328 | macro_rules! get_location { 329 | () => {{ 330 | struct S; 331 | static LOC: $crate::once_cell::sync::Lazy<$crate::zone::ZoneLocation> = 332 | $crate::once_cell::sync::Lazy::new(|| { 333 | let name = ::std::any::type_name::(); 334 | let name = name[0..name.len() - 3].as_bytes().to_owned(); 335 | $crate::zone::ZoneLocation::from_function_file_line( 336 | unsafe { ::std::ffi::CString::from_vec_unchecked(name.into()) }, 337 | unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) }, 338 | line!(), 339 | ) 340 | }); 341 | 342 | &*LOC 343 | }}; 344 | 345 | ($name:literal $(,)?) => {{ 346 | struct S; 347 | static LOC: $crate::once_cell::sync::Lazy<$crate::zone::ZoneLocation> = 348 | $crate::once_cell::sync::Lazy::new(|| { 349 | let name = ::std::any::type_name::(); 350 | let name = name[0..name.len() - 3].as_bytes().to_owned(); 351 | $crate::zone::ZoneLocation::from_name_function_file_line( 352 | $crate::c_str!($name), 353 | unsafe { ::std::ffi::CString::from_vec_unchecked(name.into()) }, 354 | unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) }, 355 | line!(), 356 | ) 357 | }); 358 | 359 | &*LOC 360 | }}; 361 | 362 | ($color:expr $(,)?) => {{ 363 | struct S; 364 | static LOC: $crate::once_cell::sync::Lazy<$crate::zone::ZoneLocation> = 365 | $crate::once_cell::sync::Lazy::new(|| { 366 | let name = ::std::any::type_name::(); 367 | let name = name[0..name.len() - 3].as_bytes().to_owned(); 368 | $crate::zone::ZoneLocation::from_function_file_line_color( 369 | unsafe { ::std::ffi::CString::from_vec_unchecked(name.into()) }, 370 | unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) }, 371 | line!(), 372 | $color, 373 | ) 374 | }); 375 | 376 | &*LOC 377 | }}; 378 | 379 | ($name:literal, $color:expr $(,)?) => {{ 380 | struct S; 381 | static FN_NAME: $crate::once_cell::sync::Lazy<$crate::zone::ZoneLocation> = 382 | $crate::once_cell::sync::Lazy::new(|| { 383 | let name = ::std::any::type_name::(); 384 | let name = name[0..name.len() - 3].as_bytes().to_owned(); 385 | $crate::zone::ZoneLocation::from_name_function_file_line_color( 386 | $crate::c_str!($name), 387 | unsafe { ::std::ffi::CString::from_vec_unchecked(name.into()) }, 388 | unsafe { ::std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(file!(), "\0").as_bytes()) }, 389 | line!(), 390 | $color, 391 | ) 392 | }); 393 | 394 | &*LOC 395 | }}; 396 | } 397 | 398 | /// Get a zone location. 399 | #[cfg(not(feature = "enable"))] 400 | #[macro_export] 401 | macro_rules! get_location { 402 | () => {{ 403 | &$crate::zone::ZoneLocation { loc: () } 404 | }}; 405 | 406 | ($name:literal) => {{ 407 | &$crate::zone::ZoneLocation { loc: () } 408 | }}; 409 | 410 | ($color:expr) => {{ 411 | &$crate::zone::ZoneLocation { loc: () } 412 | }}; 413 | 414 | ($name:literal, $color:expr $(,)?) => {{ 415 | $crate::zone::ZoneLocation { loc: () } 416 | }}; 417 | } 418 | -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | use tracy_full as tracy; 2 | 3 | fn main() { 4 | for i in 0..10000 { 5 | tracy::frame!("secondary"); 6 | 7 | tracy::frame!(); 8 | 9 | if i % 2 == 0 { 10 | tracy::frame!(discontinuous "discontinuous"); 11 | 12 | tracy::zone!("hi", true); 13 | } 14 | } 15 | } 16 | --------------------------------------------------------------------------------