mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
Add a setting to show time to first window draw and frames per second in status bar (#16422)
I want to showcase Zed's performance via videos, and this seemed like a good way to demonstrate it. https://github.com/user-attachments/assets/f4a5fabc-efe7-4b48-9ba5-719882fdc856 Release Notes: - On macOS, you can now set assign `performance.show_in_status_bar: true` in your settings to show the time to the first window draw on startup and then current FPS of the containing window's renderer. --------- Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com> Co-authored-by: Kirill Bulatov <kirill@zed.dev> Co-authored-by: David Soria Parra <167242713+dsp-ant@users.noreply.github.com> Co-authored-by: Danny Hua <danny.hua@hey.com>
This commit is contained in:
parent
6f93b42ecb
commit
11753914d7
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -7592,6 +7592,21 @@ version = "2.3.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "performance"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"collections",
|
||||||
|
"gpui",
|
||||||
|
"log",
|
||||||
|
"schemars",
|
||||||
|
"serde",
|
||||||
|
"settings",
|
||||||
|
"util",
|
||||||
|
"workspace",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest"
|
name = "pest"
|
||||||
version = "2.7.11"
|
version = "2.7.11"
|
||||||
@ -13877,6 +13892,7 @@ dependencies = [
|
|||||||
"outline_panel",
|
"outline_panel",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"paths",
|
"paths",
|
||||||
|
"performance",
|
||||||
"profiling",
|
"profiling",
|
||||||
"project",
|
"project",
|
||||||
"project_panel",
|
"project_panel",
|
||||||
|
@ -70,6 +70,7 @@ members = [
|
|||||||
"crates/outline",
|
"crates/outline",
|
||||||
"crates/outline_panel",
|
"crates/outline_panel",
|
||||||
"crates/paths",
|
"crates/paths",
|
||||||
|
"crates/performance",
|
||||||
"crates/picker",
|
"crates/picker",
|
||||||
"crates/prettier",
|
"crates/prettier",
|
||||||
"crates/project",
|
"crates/project",
|
||||||
@ -241,6 +242,7 @@ open_ai = { path = "crates/open_ai" }
|
|||||||
outline = { path = "crates/outline" }
|
outline = { path = "crates/outline" }
|
||||||
outline_panel = { path = "crates/outline_panel" }
|
outline_panel = { path = "crates/outline_panel" }
|
||||||
paths = { path = "crates/paths" }
|
paths = { path = "crates/paths" }
|
||||||
|
performance = { path = "crates/performance" }
|
||||||
picker = { path = "crates/picker" }
|
picker = { path = "crates/picker" }
|
||||||
plugin = { path = "crates/plugin" }
|
plugin = { path = "crates/plugin" }
|
||||||
plugin_macros = { path = "crates/plugin_macros" }
|
plugin_macros = { path = "crates/plugin_macros" }
|
||||||
|
@ -6,7 +6,7 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::{Rc, Weak},
|
rc::{Rc, Weak},
|
||||||
sync::{atomic::Ordering::SeqCst, Arc},
|
sync::{atomic::Ordering::SeqCst, Arc},
|
||||||
time::Duration,
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
@ -142,6 +142,12 @@ impl App {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets a start time for tracking time to first window draw.
|
||||||
|
pub fn measure_time_to_first_window_draw(self, start: Instant) -> Self {
|
||||||
|
self.0.borrow_mut().time_to_first_window_draw = Some(TimeToFirstWindowDraw::Pending(start));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Start the application. The provided callback will be called once the
|
/// Start the application. The provided callback will be called once the
|
||||||
/// app is fully launched.
|
/// app is fully launched.
|
||||||
pub fn run<F>(self, on_finish_launching: F)
|
pub fn run<F>(self, on_finish_launching: F)
|
||||||
@ -247,6 +253,7 @@ pub struct AppContext {
|
|||||||
pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests.
|
pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests.
|
||||||
pub(crate) propagate_event: bool,
|
pub(crate) propagate_event: bool,
|
||||||
pub(crate) prompt_builder: Option<PromptBuilder>,
|
pub(crate) prompt_builder: Option<PromptBuilder>,
|
||||||
|
pub(crate) time_to_first_window_draw: Option<TimeToFirstWindowDraw>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppContext {
|
impl AppContext {
|
||||||
@ -300,6 +307,7 @@ impl AppContext {
|
|||||||
layout_id_buffer: Default::default(),
|
layout_id_buffer: Default::default(),
|
||||||
propagate_event: true,
|
propagate_event: true,
|
||||||
prompt_builder: Some(PromptBuilder::Default),
|
prompt_builder: Some(PromptBuilder::Default),
|
||||||
|
time_to_first_window_draw: None,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1302,6 +1310,14 @@ impl AppContext {
|
|||||||
|
|
||||||
(task, is_first)
|
(task, is_first)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the time to first window draw, if available.
|
||||||
|
pub fn time_to_first_window_draw(&self) -> Option<Duration> {
|
||||||
|
match self.time_to_first_window_draw {
|
||||||
|
Some(TimeToFirstWindowDraw::Done(duration)) => Some(duration),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context for AppContext {
|
impl Context for AppContext {
|
||||||
@ -1465,6 +1481,15 @@ impl<G: Global> DerefMut for GlobalLease<G> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents the initialization duration of the application.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum TimeToFirstWindowDraw {
|
||||||
|
/// The application is still initializing, and contains the start time.
|
||||||
|
Pending(Instant),
|
||||||
|
/// The application has finished initializing, and contains the total duration.
|
||||||
|
Done(Duration),
|
||||||
|
}
|
||||||
|
|
||||||
/// Contains state associated with an active drag operation, started by dragging an element
|
/// Contains state associated with an active drag operation, started by dragging an element
|
||||||
/// within the window or by dragging into the app from the underlying platform.
|
/// within the window or by dragging into the app from the underlying platform.
|
||||||
pub struct AnyDrag {
|
pub struct AnyDrag {
|
||||||
|
@ -16,6 +16,7 @@ mod blade;
|
|||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
mod test;
|
mod test;
|
||||||
|
|
||||||
|
mod fps;
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
mod windows;
|
mod windows;
|
||||||
|
|
||||||
@ -51,6 +52,7 @@ use strum::EnumIter;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub use app_menu::*;
|
pub use app_menu::*;
|
||||||
|
pub use fps::*;
|
||||||
pub use keystroke::*;
|
pub use keystroke::*;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
@ -354,7 +356,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
|||||||
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
|
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
|
||||||
fn on_close(&self, callback: Box<dyn FnOnce()>);
|
fn on_close(&self, callback: Box<dyn FnOnce()>);
|
||||||
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
|
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
|
||||||
fn draw(&self, scene: &Scene);
|
fn draw(&self, scene: &Scene, on_complete: Option<oneshot::Sender<()>>);
|
||||||
fn completed_frame(&self) {}
|
fn completed_frame(&self) {}
|
||||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
||||||
|
|
||||||
@ -379,6 +381,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
|||||||
}
|
}
|
||||||
fn set_client_inset(&self, _inset: Pixels) {}
|
fn set_client_inset(&self, _inset: Pixels) {}
|
||||||
fn gpu_specs(&self) -> Option<GPUSpecs>;
|
fn gpu_specs(&self) -> Option<GPUSpecs>;
|
||||||
|
fn fps(&self) -> Option<f32>;
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
fn as_test(&mut self) -> Option<&mut TestWindow> {
|
fn as_test(&mut self) -> Option<&mut TestWindow> {
|
||||||
|
@ -9,6 +9,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
|
use futures::channel::oneshot;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use media::core_video::CVMetalTextureCache;
|
use media::core_video::CVMetalTextureCache;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@ -537,7 +538,12 @@ impl BladeRenderer {
|
|||||||
self.gpu.destroy_command_encoder(&mut self.command_encoder);
|
self.gpu.destroy_command_encoder(&mut self.command_encoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self, scene: &Scene) {
|
pub fn draw(
|
||||||
|
&mut self,
|
||||||
|
scene: &Scene,
|
||||||
|
// Required to compile on macOS, but not currently supported.
|
||||||
|
_on_complete: Option<oneshot::Sender<()>>,
|
||||||
|
) {
|
||||||
self.command_encoder.start();
|
self.command_encoder.start();
|
||||||
self.atlas.before_frame(&mut self.command_encoder);
|
self.atlas.before_frame(&mut self.command_encoder);
|
||||||
self.rasterize_paths(scene.paths());
|
self.rasterize_paths(scene.paths());
|
||||||
@ -766,4 +772,9 @@ impl BladeRenderer {
|
|||||||
self.wait_for_gpu();
|
self.wait_for_gpu();
|
||||||
self.last_sync_point = Some(sync_point);
|
self.last_sync_point = Some(sync_point);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Required to compile on macOS, but not currently supported.
|
||||||
|
pub fn fps(&self) -> f32 {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
94
crates/gpui/src/platform/fps.rs
Normal file
94
crates/gpui/src/platform/fps.rs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
const NANOS_PER_SEC: u64 = 1_000_000_000;
|
||||||
|
const WINDOW_SIZE: usize = 128;
|
||||||
|
|
||||||
|
/// Represents a rolling FPS (Frames Per Second) counter.
|
||||||
|
///
|
||||||
|
/// This struct provides a lock-free mechanism to measure and calculate FPS
|
||||||
|
/// continuously, updating with every frame. It uses atomic operations to
|
||||||
|
/// ensure thread-safety without the need for locks.
|
||||||
|
pub struct FpsCounter {
|
||||||
|
frame_times: [AtomicU64; WINDOW_SIZE],
|
||||||
|
head: AtomicUsize,
|
||||||
|
tail: AtomicUsize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FpsCounter {
|
||||||
|
/// Creates a new `Fps` counter.
|
||||||
|
///
|
||||||
|
/// Returns an `Arc<Fps>` for safe sharing across threads.
|
||||||
|
pub fn new() -> Arc<Self> {
|
||||||
|
Arc::new(Self {
|
||||||
|
frame_times: std::array::from_fn(|_| AtomicU64::new(0)),
|
||||||
|
head: AtomicUsize::new(0),
|
||||||
|
tail: AtomicUsize::new(0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Increments the FPS counter with a new frame timestamp.
|
||||||
|
///
|
||||||
|
/// This method updates the internal state to maintain a rolling window
|
||||||
|
/// of frame data for the last second. It uses atomic operations to
|
||||||
|
/// ensure thread-safety.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `timestamp_ns` - The timestamp of the new frame in nanoseconds.
|
||||||
|
pub fn increment(&self, timestamp_ns: u64) {
|
||||||
|
let mut head = self.head.load(Ordering::Relaxed);
|
||||||
|
let mut tail = self.tail.load(Ordering::Relaxed);
|
||||||
|
|
||||||
|
// Add new timestamp
|
||||||
|
self.frame_times[head].store(timestamp_ns, Ordering::Relaxed);
|
||||||
|
// Increment head and wrap around to 0 if it reaches WINDOW_SIZE
|
||||||
|
head = (head + 1) % WINDOW_SIZE;
|
||||||
|
self.head.store(head, Ordering::Relaxed);
|
||||||
|
|
||||||
|
// Remove old timestamps (older than 1 second)
|
||||||
|
while tail != head {
|
||||||
|
let oldest = self.frame_times[tail].load(Ordering::Relaxed);
|
||||||
|
if timestamp_ns.wrapping_sub(oldest) <= NANOS_PER_SEC {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Increment tail and wrap around to 0 if it reaches WINDOW_SIZE
|
||||||
|
tail = (tail + 1) % WINDOW_SIZE;
|
||||||
|
self.tail.store(tail, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates and returns the current FPS.
|
||||||
|
///
|
||||||
|
/// This method computes the FPS based on the frames recorded in the last second.
|
||||||
|
/// It uses atomic loads to ensure thread-safety.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The calculated FPS as a `f32`, or 0.0 if no frames have been recorded.
|
||||||
|
pub fn fps(&self) -> f32 {
|
||||||
|
let head = self.head.load(Ordering::Relaxed);
|
||||||
|
let tail = self.tail.load(Ordering::Relaxed);
|
||||||
|
|
||||||
|
if head == tail {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newest =
|
||||||
|
self.frame_times[head.wrapping_sub(1) & (WINDOW_SIZE - 1)].load(Ordering::Relaxed);
|
||||||
|
let oldest = self.frame_times[tail].load(Ordering::Relaxed);
|
||||||
|
|
||||||
|
let time_diff = newest.wrapping_sub(oldest) as f32;
|
||||||
|
if time_diff == 0.0 {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let frame_count = if head > tail {
|
||||||
|
head - tail
|
||||||
|
} else {
|
||||||
|
WINDOW_SIZE - tail + head
|
||||||
|
};
|
||||||
|
|
||||||
|
(frame_count as f32 - 1.0) * NANOS_PER_SEC as f32 / time_diff
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use blade_graphics as gpu;
|
use blade_graphics as gpu;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use futures::channel::oneshot::Receiver;
|
use futures::channel::oneshot;
|
||||||
|
|
||||||
use raw_window_handle as rwh;
|
use raw_window_handle as rwh;
|
||||||
use wayland_backend::client::ObjectId;
|
use wayland_backend::client::ObjectId;
|
||||||
@ -827,7 +827,7 @@ impl PlatformWindow for WaylandWindow {
|
|||||||
_msg: &str,
|
_msg: &str,
|
||||||
_detail: Option<&str>,
|
_detail: Option<&str>,
|
||||||
_answers: &[&str],
|
_answers: &[&str],
|
||||||
) -> Option<Receiver<usize>> {
|
) -> Option<oneshot::Receiver<usize>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -934,9 +934,9 @@ impl PlatformWindow for WaylandWindow {
|
|||||||
self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
|
self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, scene: &Scene) {
|
fn draw(&self, scene: &Scene, on_complete: Option<oneshot::Sender<()>>) {
|
||||||
let mut state = self.borrow_mut();
|
let mut state = self.borrow_mut();
|
||||||
state.renderer.draw(scene);
|
state.renderer.draw(scene, on_complete);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn completed_frame(&self) {
|
fn completed_frame(&self) {
|
||||||
@ -1009,6 +1009,10 @@ impl PlatformWindow for WaylandWindow {
|
|||||||
fn gpu_specs(&self) -> Option<GPUSpecs> {
|
fn gpu_specs(&self) -> Option<GPUSpecs> {
|
||||||
self.borrow().renderer.gpu_specs().into()
|
self.borrow().renderer.gpu_specs().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fps(&self) -> Option<f32> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_window(mut state: RefMut<WaylandWindowState>) {
|
fn update_window(mut state: RefMut<WaylandWindowState>) {
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use anyhow::Context;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
platform::blade::{BladeRenderer, BladeSurfaceConfig},
|
platform::blade::{BladeRenderer, BladeSurfaceConfig},
|
||||||
px, size, AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GPUSpecs,
|
px, size, AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GPUSpecs,
|
||||||
@ -9,7 +7,9 @@ use crate::{
|
|||||||
X11ClientStatePtr,
|
X11ClientStatePtr,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
use blade_graphics as gpu;
|
use blade_graphics as gpu;
|
||||||
|
use futures::channel::oneshot;
|
||||||
use raw_window_handle as rwh;
|
use raw_window_handle as rwh;
|
||||||
use util::{maybe, ResultExt};
|
use util::{maybe, ResultExt};
|
||||||
use x11rb::{
|
use x11rb::{
|
||||||
@ -1210,9 +1210,10 @@ impl PlatformWindow for X11Window {
|
|||||||
self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
|
self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, scene: &Scene) {
|
// TODO: on_complete not yet supported for X11 windows
|
||||||
|
fn draw(&self, scene: &Scene, on_complete: Option<oneshot::Sender<()>>) {
|
||||||
let mut inner = self.0.state.borrow_mut();
|
let mut inner = self.0.state.borrow_mut();
|
||||||
inner.renderer.draw(scene);
|
inner.renderer.draw(scene, on_complete);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
||||||
@ -1398,4 +1399,8 @@ impl PlatformWindow for X11Window {
|
|||||||
fn gpu_specs(&self) -> Option<GPUSpecs> {
|
fn gpu_specs(&self) -> Option<GPUSpecs> {
|
||||||
self.0.state.borrow().renderer.gpu_specs().into()
|
self.0.state.borrow().renderer.gpu_specs().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fps(&self) -> Option<f32> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use super::metal_atlas::MetalAtlas;
|
use super::metal_atlas::MetalAtlas;
|
||||||
use crate::{
|
use crate::{
|
||||||
point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels,
|
point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels,
|
||||||
Hsla, MonochromeSprite, PaintSurface, Path, PathId, PathVertex, PolychromeSprite,
|
FpsCounter, Hsla, MonochromeSprite, PaintSurface, Path, PathId, PathVertex, PolychromeSprite,
|
||||||
PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline,
|
PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
@ -14,6 +14,7 @@ use cocoa::{
|
|||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use core_foundation::base::TCFType;
|
use core_foundation::base::TCFType;
|
||||||
use foreign_types::ForeignType;
|
use foreign_types::ForeignType;
|
||||||
|
use futures::channel::oneshot;
|
||||||
use media::core_video::CVMetalTextureCache;
|
use media::core_video::CVMetalTextureCache;
|
||||||
use metal::{CAMetalLayer, CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
|
use metal::{CAMetalLayer, CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
|
||||||
use objc::{self, msg_send, sel, sel_impl};
|
use objc::{self, msg_send, sel, sel_impl};
|
||||||
@ -105,6 +106,7 @@ pub(crate) struct MetalRenderer {
|
|||||||
instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>,
|
instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>,
|
||||||
sprite_atlas: Arc<MetalAtlas>,
|
sprite_atlas: Arc<MetalAtlas>,
|
||||||
core_video_texture_cache: CVMetalTextureCache,
|
core_video_texture_cache: CVMetalTextureCache,
|
||||||
|
fps_counter: Arc<FpsCounter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetalRenderer {
|
impl MetalRenderer {
|
||||||
@ -250,6 +252,7 @@ impl MetalRenderer {
|
|||||||
instance_buffer_pool,
|
instance_buffer_pool,
|
||||||
sprite_atlas,
|
sprite_atlas,
|
||||||
core_video_texture_cache,
|
core_video_texture_cache,
|
||||||
|
fps_counter: FpsCounter::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,7 +295,8 @@ impl MetalRenderer {
|
|||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self, scene: &Scene) {
|
pub fn draw(&mut self, scene: &Scene, on_complete: Option<oneshot::Sender<()>>) {
|
||||||
|
let on_complete = Arc::new(Mutex::new(on_complete));
|
||||||
let layer = self.layer.clone();
|
let layer = self.layer.clone();
|
||||||
let viewport_size = layer.drawable_size();
|
let viewport_size = layer.drawable_size();
|
||||||
let viewport_size: Size<DevicePixels> = size(
|
let viewport_size: Size<DevicePixels> = size(
|
||||||
@ -319,13 +323,24 @@ impl MetalRenderer {
|
|||||||
Ok(command_buffer) => {
|
Ok(command_buffer) => {
|
||||||
let instance_buffer_pool = self.instance_buffer_pool.clone();
|
let instance_buffer_pool = self.instance_buffer_pool.clone();
|
||||||
let instance_buffer = Cell::new(Some(instance_buffer));
|
let instance_buffer = Cell::new(Some(instance_buffer));
|
||||||
let block = ConcreteBlock::new(move |_| {
|
let device = self.device.clone();
|
||||||
if let Some(instance_buffer) = instance_buffer.take() {
|
let fps_counter = self.fps_counter.clone();
|
||||||
instance_buffer_pool.lock().release(instance_buffer);
|
let completed_handler =
|
||||||
}
|
ConcreteBlock::new(move |_: &metal::CommandBufferRef| {
|
||||||
});
|
let mut cpu_timestamp = 0;
|
||||||
let block = block.copy();
|
let mut gpu_timestamp = 0;
|
||||||
command_buffer.add_completed_handler(&block);
|
device.sample_timestamps(&mut cpu_timestamp, &mut gpu_timestamp);
|
||||||
|
|
||||||
|
fps_counter.increment(gpu_timestamp);
|
||||||
|
if let Some(on_complete) = on_complete.lock().take() {
|
||||||
|
on_complete.send(()).ok();
|
||||||
|
}
|
||||||
|
if let Some(instance_buffer) = instance_buffer.take() {
|
||||||
|
instance_buffer_pool.lock().release(instance_buffer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let completed_handler = completed_handler.copy();
|
||||||
|
command_buffer.add_completed_handler(&completed_handler);
|
||||||
|
|
||||||
if self.presents_with_transaction {
|
if self.presents_with_transaction {
|
||||||
command_buffer.commit();
|
command_buffer.commit();
|
||||||
@ -1117,6 +1132,10 @@ impl MetalRenderer {
|
|||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn fps(&self) -> f32 {
|
||||||
|
self.fps_counter.fps()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_pipeline_state(
|
fn build_pipeline_state(
|
||||||
|
@ -784,14 +784,14 @@ impl PlatformWindow for MacWindow {
|
|||||||
self.0.as_ref().lock().bounds()
|
self.0.as_ref().lock().bounds()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn window_bounds(&self) -> WindowBounds {
|
|
||||||
self.0.as_ref().lock().window_bounds()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_maximized(&self) -> bool {
|
fn is_maximized(&self) -> bool {
|
||||||
self.0.as_ref().lock().is_maximized()
|
self.0.as_ref().lock().is_maximized()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn window_bounds(&self) -> WindowBounds {
|
||||||
|
self.0.as_ref().lock().window_bounds()
|
||||||
|
}
|
||||||
|
|
||||||
fn content_size(&self) -> Size<Pixels> {
|
fn content_size(&self) -> Size<Pixels> {
|
||||||
self.0.as_ref().lock().content_size()
|
self.0.as_ref().lock().content_size()
|
||||||
}
|
}
|
||||||
@ -975,8 +975,6 @@ impl PlatformWindow for MacWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_app_id(&mut self, _app_id: &str) {}
|
|
||||||
|
|
||||||
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
|
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
|
||||||
let mut this = self.0.as_ref().lock();
|
let mut this = self.0.as_ref().lock();
|
||||||
this.renderer
|
this.renderer
|
||||||
@ -1007,30 +1005,6 @@ impl PlatformWindow for MacWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_edited(&mut self, edited: bool) {
|
|
||||||
unsafe {
|
|
||||||
let window = self.0.lock().native_window;
|
|
||||||
msg_send![window, setDocumentEdited: edited as BOOL]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Changing the document edited state resets the traffic light position,
|
|
||||||
// so we have to move it again.
|
|
||||||
self.0.lock().move_traffic_light();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_character_palette(&self) {
|
|
||||||
let this = self.0.lock();
|
|
||||||
let window = this.native_window;
|
|
||||||
this.executor
|
|
||||||
.spawn(async move {
|
|
||||||
unsafe {
|
|
||||||
let app = NSApplication::sharedApplication(nil);
|
|
||||||
let _: () = msg_send![app, orderFrontCharacterPalette: window];
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn minimize(&self) {
|
fn minimize(&self) {
|
||||||
let window = self.0.lock().native_window;
|
let window = self.0.lock().native_window;
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -1107,18 +1081,48 @@ impl PlatformWindow for MacWindow {
|
|||||||
self.0.lock().appearance_changed_callback = Some(callback);
|
self.0.lock().appearance_changed_callback = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, scene: &crate::Scene) {
|
fn draw(&self, scene: &crate::Scene, on_complete: Option<oneshot::Sender<()>>) {
|
||||||
let mut this = self.0.lock();
|
let mut this = self.0.lock();
|
||||||
this.renderer.draw(scene);
|
this.renderer.draw(scene, on_complete);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
||||||
self.0.lock().renderer.sprite_atlas().clone()
|
self.0.lock().renderer.sprite_atlas().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_edited(&mut self, edited: bool) {
|
||||||
|
unsafe {
|
||||||
|
let window = self.0.lock().native_window;
|
||||||
|
msg_send![window, setDocumentEdited: edited as BOOL]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changing the document edited state resets the traffic light position,
|
||||||
|
// so we have to move it again.
|
||||||
|
self.0.lock().move_traffic_light();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_character_palette(&self) {
|
||||||
|
let this = self.0.lock();
|
||||||
|
let window = this.native_window;
|
||||||
|
this.executor
|
||||||
|
.spawn(async move {
|
||||||
|
unsafe {
|
||||||
|
let app = NSApplication::sharedApplication(nil);
|
||||||
|
let _: () = msg_send![app, orderFrontCharacterPalette: window];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_app_id(&mut self, _app_id: &str) {}
|
||||||
|
|
||||||
fn gpu_specs(&self) -> Option<crate::GPUSpecs> {
|
fn gpu_specs(&self) -> Option<crate::GPUSpecs> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fps(&self) -> Option<f32> {
|
||||||
|
Some(self.0.lock().renderer.fps())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl rwh::HasWindowHandle for MacWindow {
|
impl rwh::HasWindowHandle for MacWindow {
|
||||||
|
@ -251,7 +251,12 @@ impl PlatformWindow for TestWindow {
|
|||||||
|
|
||||||
fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {}
|
fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {}
|
||||||
|
|
||||||
fn draw(&self, _scene: &crate::Scene) {}
|
fn draw(
|
||||||
|
&self,
|
||||||
|
_scene: &crate::Scene,
|
||||||
|
_on_complete: Option<futures::channel::oneshot::Sender<()>>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
|
fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
|
||||||
self.0.lock().sprite_atlas.clone()
|
self.0.lock().sprite_atlas.clone()
|
||||||
@ -277,6 +282,10 @@ impl PlatformWindow for TestWindow {
|
|||||||
fn gpu_specs(&self) -> Option<GPUSpecs> {
|
fn gpu_specs(&self) -> Option<GPUSpecs> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fps(&self) -> Option<f32> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct TestAtlasState {
|
pub(crate) struct TestAtlasState {
|
||||||
|
@ -660,8 +660,8 @@ impl PlatformWindow for WindowsWindow {
|
|||||||
self.0.state.borrow_mut().callbacks.appearance_changed = Some(callback);
|
self.0.state.borrow_mut().callbacks.appearance_changed = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, scene: &Scene) {
|
fn draw(&self, scene: &Scene, on_complete: Option<oneshot::Sender<()>>) {
|
||||||
self.0.state.borrow_mut().renderer.draw(scene)
|
self.0.state.borrow_mut().renderer.draw(scene, on_complete)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
||||||
@ -675,6 +675,10 @@ impl PlatformWindow for WindowsWindow {
|
|||||||
fn gpu_specs(&self) -> Option<GPUSpecs> {
|
fn gpu_specs(&self) -> Option<GPUSpecs> {
|
||||||
Some(self.0.state.borrow().renderer.gpu_specs())
|
Some(self.0.state.borrow().renderer.gpu_specs())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fps(&self) -> Option<f32> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[implement(IDropTarget)]
|
#[implement(IDropTarget)]
|
||||||
|
@ -11,9 +11,9 @@ use crate::{
|
|||||||
PromptLevel, Quad, Render, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams,
|
PromptLevel, Quad, Render, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams,
|
||||||
Replay, ResizeEdge, ScaledPixels, Scene, Shadow, SharedString, Size, StrikethroughStyle, Style,
|
Replay, ResizeEdge, ScaledPixels, Scene, Shadow, SharedString, Size, StrikethroughStyle, Style,
|
||||||
SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement,
|
SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement,
|
||||||
TransformationMatrix, Underline, UnderlineStyle, View, VisualContext, WeakView,
|
TimeToFirstWindowDraw, TransformationMatrix, Underline, UnderlineStyle, View, VisualContext,
|
||||||
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations,
|
WeakView, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControls,
|
||||||
WindowOptions, WindowParams, WindowTextSystem, SUBPIXEL_VARIANTS,
|
WindowDecorations, WindowOptions, WindowParams, WindowTextSystem, SUBPIXEL_VARIANTS,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use collections::{FxHashMap, FxHashSet};
|
use collections::{FxHashMap, FxHashSet};
|
||||||
@ -544,6 +544,8 @@ pub struct Window {
|
|||||||
hovered: Rc<Cell<bool>>,
|
hovered: Rc<Cell<bool>>,
|
||||||
pub(crate) dirty: Rc<Cell<bool>>,
|
pub(crate) dirty: Rc<Cell<bool>>,
|
||||||
pub(crate) needs_present: Rc<Cell<bool>>,
|
pub(crate) needs_present: Rc<Cell<bool>>,
|
||||||
|
/// We assign this to be notified when the platform graphics backend fires the next completion callback for drawing the window.
|
||||||
|
present_completed: RefCell<Option<oneshot::Sender<()>>>,
|
||||||
pub(crate) last_input_timestamp: Rc<Cell<Instant>>,
|
pub(crate) last_input_timestamp: Rc<Cell<Instant>>,
|
||||||
pub(crate) refreshing: bool,
|
pub(crate) refreshing: bool,
|
||||||
pub(crate) draw_phase: DrawPhase,
|
pub(crate) draw_phase: DrawPhase,
|
||||||
@ -820,6 +822,7 @@ impl Window {
|
|||||||
hovered,
|
hovered,
|
||||||
dirty,
|
dirty,
|
||||||
needs_present,
|
needs_present,
|
||||||
|
present_completed: RefCell::default(),
|
||||||
last_input_timestamp,
|
last_input_timestamp,
|
||||||
refreshing: false,
|
refreshing: false,
|
||||||
draw_phase: DrawPhase::None,
|
draw_phase: DrawPhase::None,
|
||||||
@ -1489,13 +1492,29 @@ impl<'a> WindowContext<'a> {
|
|||||||
self.window.refreshing = false;
|
self.window.refreshing = false;
|
||||||
self.window.draw_phase = DrawPhase::None;
|
self.window.draw_phase = DrawPhase::None;
|
||||||
self.window.needs_present.set(true);
|
self.window.needs_present.set(true);
|
||||||
|
|
||||||
|
if let Some(TimeToFirstWindowDraw::Pending(start)) = self.app.time_to_first_window_draw {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
*self.window.present_completed.borrow_mut() = Some(tx);
|
||||||
|
self.spawn(|mut cx| async move {
|
||||||
|
rx.await.ok();
|
||||||
|
cx.update(|cx| {
|
||||||
|
let duration = start.elapsed();
|
||||||
|
cx.time_to_first_window_draw = Some(TimeToFirstWindowDraw::Done(duration));
|
||||||
|
log::info!("time to first window draw: {:?}", duration);
|
||||||
|
cx.push_effect(Effect::Refresh);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[profiling::function]
|
#[profiling::function]
|
||||||
fn present(&self) {
|
fn present(&self) {
|
||||||
|
let on_complete = self.window.present_completed.take();
|
||||||
self.window
|
self.window
|
||||||
.platform_window
|
.platform_window
|
||||||
.draw(&self.window.rendered_frame.scene);
|
.draw(&self.window.rendered_frame.scene, on_complete);
|
||||||
self.window.needs_present.set(false);
|
self.window.needs_present.set(false);
|
||||||
profiling::finish_frame!();
|
profiling::finish_frame!();
|
||||||
}
|
}
|
||||||
@ -3718,6 +3737,12 @@ impl<'a> WindowContext<'a> {
|
|||||||
pub fn gpu_specs(&self) -> Option<GPUSpecs> {
|
pub fn gpu_specs(&self) -> Option<GPUSpecs> {
|
||||||
self.window.platform_window.gpu_specs()
|
self.window.platform_window.gpu_specs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the current FPS (frames per second) of the window.
|
||||||
|
/// This is only supported on macOS currently.
|
||||||
|
pub fn fps(&self) -> Option<f32> {
|
||||||
|
self.window.platform_window.fps()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
|
36
crates/performance/Cargo.toml
Normal file
36
crates/performance/Cargo.toml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
[package]
|
||||||
|
name = "performance"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
license = "GPL-3.0-or-later"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/performance.rs"
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[features]
|
||||||
|
test-support = [
|
||||||
|
"collections/test-support",
|
||||||
|
"gpui/test-support",
|
||||||
|
"workspace/test-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow.workspace = true
|
||||||
|
gpui.workspace = true
|
||||||
|
log.workspace = true
|
||||||
|
schemars.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
settings.workspace = true
|
||||||
|
workspace.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
collections = { workspace = true, features = ["test-support"] }
|
||||||
|
gpui = { workspace = true, features = ["test-support"] }
|
||||||
|
settings = { workspace = true, features = ["test-support"] }
|
||||||
|
util = { workspace = true, features = ["test-support"] }
|
||||||
|
workspace = { workspace = true, features = ["test-support"] }
|
1
crates/performance/LICENSE-GPL
Symbolic link
1
crates/performance/LICENSE-GPL
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../LICENSE-GPL
|
189
crates/performance/src/performance.rs
Normal file
189
crates/performance/src/performance.rs
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use gpui::{
|
||||||
|
div, AppContext, InteractiveElement as _, Render, StatefulInteractiveElement as _,
|
||||||
|
Subscription, ViewContext, VisualContext,
|
||||||
|
};
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use settings::{Settings, SettingsSources, SettingsStore};
|
||||||
|
use workspace::{
|
||||||
|
ui::{Label, LabelCommon, LabelSize, Tooltip},
|
||||||
|
ItemHandle, StatusItemView, Workspace,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SHOW_STARTUP_TIME_DURATION: std::time::Duration = std::time::Duration::from_secs(5);
|
||||||
|
|
||||||
|
pub fn init(cx: &mut AppContext) {
|
||||||
|
PerformanceSettings::register(cx);
|
||||||
|
|
||||||
|
let mut enabled = PerformanceSettings::get_global(cx)
|
||||||
|
.show_in_status_bar
|
||||||
|
.unwrap_or(false);
|
||||||
|
let start_time = Instant::now();
|
||||||
|
let mut _observe_workspaces = toggle_status_bar_items(enabled, start_time, cx);
|
||||||
|
|
||||||
|
cx.observe_global::<SettingsStore>(move |cx| {
|
||||||
|
let new_value = PerformanceSettings::get_global(cx)
|
||||||
|
.show_in_status_bar
|
||||||
|
.unwrap_or(false);
|
||||||
|
if new_value != enabled {
|
||||||
|
enabled = new_value;
|
||||||
|
_observe_workspaces = toggle_status_bar_items(enabled, start_time, cx);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggle_status_bar_items(
|
||||||
|
enabled: bool,
|
||||||
|
start_time: Instant,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<Subscription> {
|
||||||
|
for window in cx.windows() {
|
||||||
|
if let Some(workspace) = window.downcast::<Workspace>() {
|
||||||
|
workspace
|
||||||
|
.update(cx, |workspace, cx| {
|
||||||
|
toggle_status_bar_item(workspace, enabled, start_time, cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if enabled {
|
||||||
|
log::info!("performance metrics display enabled");
|
||||||
|
Some(cx.observe_new_views::<Workspace>(move |workspace, cx| {
|
||||||
|
toggle_status_bar_item(workspace, true, start_time, cx);
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
log::info!("performance metrics display disabled");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PerformanceStatusBarItem {
|
||||||
|
display_mode: DisplayMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
enum DisplayMode {
|
||||||
|
StartupTime,
|
||||||
|
Fps,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PerformanceStatusBarItem {
|
||||||
|
fn new(start_time: Instant, cx: &mut ViewContext<Self>) -> Self {
|
||||||
|
let now = Instant::now();
|
||||||
|
let display_mode = if now < start_time + SHOW_STARTUP_TIME_DURATION {
|
||||||
|
DisplayMode::StartupTime
|
||||||
|
} else {
|
||||||
|
DisplayMode::Fps
|
||||||
|
};
|
||||||
|
|
||||||
|
let this = Self { display_mode };
|
||||||
|
|
||||||
|
if let DisplayMode::StartupTime = display_mode {
|
||||||
|
cx.spawn(|this, mut cx| async move {
|
||||||
|
let now = Instant::now();
|
||||||
|
let remaining_duration =
|
||||||
|
(start_time + SHOW_STARTUP_TIME_DURATION).saturating_duration_since(now);
|
||||||
|
cx.background_executor().timer(remaining_duration).await;
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.display_mode = DisplayMode::Fps;
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for PerformanceStatusBarItem {
|
||||||
|
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
|
||||||
|
let text = match self.display_mode {
|
||||||
|
DisplayMode::StartupTime => cx
|
||||||
|
.time_to_first_window_draw()
|
||||||
|
.map_or("Pending".to_string(), |duration| {
|
||||||
|
format!("{}ms", duration.as_millis())
|
||||||
|
}),
|
||||||
|
DisplayMode::Fps => cx.fps().map_or("".to_string(), |fps| {
|
||||||
|
format!("{:3} FPS", fps.round() as u32)
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
use gpui::ParentElement;
|
||||||
|
let display_mode = self.display_mode;
|
||||||
|
div()
|
||||||
|
.id("performance status")
|
||||||
|
.child(Label::new(text).size(LabelSize::Small))
|
||||||
|
.tooltip(move |cx| match display_mode {
|
||||||
|
DisplayMode::StartupTime => Tooltip::text("Time to first window draw", cx),
|
||||||
|
DisplayMode::Fps => cx
|
||||||
|
.new_view(|cx| {
|
||||||
|
let tooltip = Tooltip::new("Current FPS");
|
||||||
|
if let Some(time_to_first) = cx.time_to_first_window_draw() {
|
||||||
|
tooltip.meta(format!(
|
||||||
|
"Time to first window draw: {}ms",
|
||||||
|
time_to_first.as_millis()
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
tooltip
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatusItemView for PerformanceStatusBarItem {
|
||||||
|
fn set_active_pane_item(
|
||||||
|
&mut self,
|
||||||
|
_active_pane_item: Option<&dyn ItemHandle>,
|
||||||
|
_cx: &mut gpui::ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
// This is not currently used.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggle_status_bar_item(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
enabled: bool,
|
||||||
|
start_time: Instant,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) {
|
||||||
|
if enabled {
|
||||||
|
workspace.status_bar().update(cx, |bar, cx| {
|
||||||
|
bar.add_right_item(
|
||||||
|
cx.new_view(|cx| PerformanceStatusBarItem::new(start_time, cx)),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
workspace.status_bar().update(cx, |bar, cx| {
|
||||||
|
bar.remove_items_of_type::<PerformanceStatusBarItem>(cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration of the display of performance details.
|
||||||
|
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||||
|
pub struct PerformanceSettings {
|
||||||
|
/// Display the time to first window draw and frame rate in the status bar.
|
||||||
|
///
|
||||||
|
/// Default: false
|
||||||
|
pub show_in_status_bar: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Settings for PerformanceSettings {
|
||||||
|
const KEY: Option<&'static str> = Some("performance");
|
||||||
|
|
||||||
|
type FileContent = Self;
|
||||||
|
|
||||||
|
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
|
||||||
|
sources.json_merge()
|
||||||
|
}
|
||||||
|
}
|
@ -153,6 +153,17 @@ impl StatusBar {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn remove_items_of_type<T>(&mut self, cx: &mut ViewContext<Self>)
|
||||||
|
where
|
||||||
|
T: 'static + StatusItemView,
|
||||||
|
{
|
||||||
|
self.left_items
|
||||||
|
.retain(|item| item.item_type() != TypeId::of::<T>());
|
||||||
|
self.right_items
|
||||||
|
.retain(|item| item.item_type() != TypeId::of::<T>());
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_right_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
|
pub fn add_right_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
|
||||||
where
|
where
|
||||||
T: 'static + StatusItemView,
|
T: 'static + StatusItemView,
|
||||||
|
@ -38,7 +38,7 @@ use gpui::{
|
|||||||
ResizeEdge, Size, Stateful, Subscription, Task, Tiling, View, WeakView, WindowBounds,
|
ResizeEdge, Size, Stateful, Subscription, Task, Tiling, View, WeakView, WindowBounds,
|
||||||
WindowHandle, WindowId, WindowOptions,
|
WindowHandle, WindowId, WindowOptions,
|
||||||
};
|
};
|
||||||
use item::{
|
pub use item::{
|
||||||
FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
|
FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
|
||||||
ProjectItem, SerializableItem, SerializableItemHandle, WeakItemHandle,
|
ProjectItem, SerializableItem, SerializableItemHandle, WeakItemHandle,
|
||||||
};
|
};
|
||||||
|
@ -72,6 +72,7 @@ outline.workspace = true
|
|||||||
outline_panel.workspace = true
|
outline_panel.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
paths.workspace = true
|
paths.workspace = true
|
||||||
|
performance.workspace = true
|
||||||
profiling.workspace = true
|
profiling.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
project_panel.workspace = true
|
project_panel.workspace = true
|
||||||
|
@ -266,6 +266,7 @@ fn init_ui(
|
|||||||
welcome::init(cx);
|
welcome::init(cx);
|
||||||
settings_ui::init(cx);
|
settings_ui::init(cx);
|
||||||
extensions_ui::init(cx);
|
extensions_ui::init(cx);
|
||||||
|
performance::init(cx);
|
||||||
|
|
||||||
cx.observe_global::<SettingsStore>({
|
cx.observe_global::<SettingsStore>({
|
||||||
let languages = app_state.languages.clone();
|
let languages = app_state.languages.clone();
|
||||||
@ -315,6 +316,7 @@ fn init_ui(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
let start_time = std::time::Instant::now();
|
||||||
menu::init();
|
menu::init();
|
||||||
zed_actions::init();
|
zed_actions::init();
|
||||||
|
|
||||||
@ -326,7 +328,9 @@ fn main() {
|
|||||||
init_logger();
|
init_logger();
|
||||||
|
|
||||||
log::info!("========== starting zed ==========");
|
log::info!("========== starting zed ==========");
|
||||||
let app = App::new().with_assets(Assets);
|
let app = App::new()
|
||||||
|
.with_assets(Assets)
|
||||||
|
.measure_time_to_first_window_draw(start_time);
|
||||||
|
|
||||||
let (installation_id, existing_installation_id_found) = app
|
let (installation_id, existing_installation_id_found) = app
|
||||||
.background_executor()
|
.background_executor()
|
||||||
|
Loading…
Reference in New Issue
Block a user