Revert FPS counter (#17485)

**UPDATE**: Response so far seems to be that this fixes the performance
issues on Intel MacBooks. So we're going to go ahead and merge it.

This reverts the FPS counter added in 11753914d (#16422) because in this
issue someone bisected recent performance regressions down to this
commit:

- https://github.com/zed-industries/zed/issues/16729

Another issue that's possibly related:

-
https://github.com/zed-industries/zed/issues/17305#issuecomment-2332316242

We're reverting this in a PR to create a bundle that people can try out.

Assets:

- Universal Binary:
https://github.com/zed-industries/zed/actions/runs/10735702994/artifacts/1900460781
- x86/Intel:
https://github.com/zed-industries/zed/actions/runs/10735702994/artifacts/1900461236
- Apple Silicon:
https://github.com/zed-industries/zed/actions/runs/10735702994/artifacts/1900460978


Release Notes:

- Removed the recently-added FPS counter since the changes it made to
the Metal renderer on macOS could lead to performance regressions on
Intel MacBooks.

Co-authored-by: Bennet <bennet@zed.dev>
This commit is contained in:
Thorsten Ball 2024-09-06 15:35:00 +02:00 committed by GitHub
parent 54dd40878f
commit 938c90fd3b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 60 additions and 524 deletions

16
Cargo.lock generated
View file

@ -7868,21 +7868,6 @@ 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 = "perplexity" name = "perplexity"
version = "0.1.0" version = "0.1.0"
@ -14275,7 +14260,6 @@ dependencies = [
"outline_panel", "outline_panel",
"parking_lot", "parking_lot",
"paths", "paths",
"performance",
"profiling", "profiling",
"project", "project",
"project_panel", "project_panel",

View file

@ -72,7 +72,6 @@ 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",
@ -246,7 +245,6 @@ 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" }

View file

@ -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, Instant}, time::Duration,
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
@ -142,12 +142,6 @@ 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)
@ -253,7 +247,6 @@ 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 {
@ -307,7 +300,6 @@ 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,
}), }),
}); });
@ -1310,14 +1302,6 @@ 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 {
@ -1481,15 +1465,6 @@ 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 {

View file

@ -16,7 +16,6 @@ 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;
@ -52,7 +51,6 @@ 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")]
@ -356,7 +354,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, on_complete: Option<oneshot::Sender<()>>); fn draw(&self, scene: &Scene);
fn completed_frame(&self) {} fn completed_frame(&self) {}
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>; fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
@ -381,7 +379,6 @@ 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>;
fn update_ime_position(&self, _bounds: Bounds<Pixels>); fn update_ime_position(&self, _bounds: Bounds<Pixels>);

View file

@ -9,7 +9,6 @@ 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")]
@ -543,12 +542,7 @@ impl BladeRenderer {
self.gpu.destroy_command_encoder(&mut self.command_encoder); self.gpu.destroy_command_encoder(&mut self.command_encoder);
} }
pub fn draw( pub fn draw(&mut self, scene: &Scene) {
&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());
@ -777,10 +771,4 @@ 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.
#[cfg_attr(any(target_os = "linux", target_os = "windows"), allow(dead_code))]
pub fn fps(&self) -> f32 {
0.0
}
} }

View file

@ -1,94 +0,0 @@
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
}
}

View file

@ -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; use futures::channel::oneshot::Receiver;
use raw_window_handle as rwh; use raw_window_handle as rwh;
use wayland_backend::client::ObjectId; use wayland_backend::client::ObjectId;
@ -831,7 +831,7 @@ impl PlatformWindow for WaylandWindow {
_msg: &str, _msg: &str,
_detail: Option<&str>, _detail: Option<&str>,
_answers: &[&str], _answers: &[&str],
) -> Option<oneshot::Receiver<usize>> { ) -> Option<Receiver<usize>> {
None None
} }
@ -938,9 +938,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, on_complete: Option<oneshot::Sender<()>>) { fn draw(&self, scene: &Scene) {
let mut state = self.borrow_mut(); let mut state = self.borrow_mut();
state.renderer.draw(scene, on_complete); state.renderer.draw(scene);
} }
fn completed_frame(&self) { fn completed_frame(&self) {
@ -1018,10 +1018,6 @@ 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>) {

View file

@ -1,3 +1,5 @@
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,
@ -7,9 +9,7 @@ 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,10 +1210,9 @@ impl PlatformWindow for X11Window {
self.0.callbacks.borrow_mut().appearance_changed = Some(callback); self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
} }
// TODO: on_complete not yet supported for X11 windows fn draw(&self, scene: &Scene) {
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, on_complete); inner.renderer.draw(scene);
} }
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> { fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
@ -1406,8 +1405,4 @@ 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
}
} }

View file

@ -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,
FpsCounter, Hsla, MonochromeSprite, PaintSurface, Path, PathId, PathVertex, PolychromeSprite, 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,7 +14,6 @@ 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};
@ -106,7 +105,6 @@ 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 {
@ -252,7 +250,6 @@ impl MetalRenderer {
instance_buffer_pool, instance_buffer_pool,
sprite_atlas, sprite_atlas,
core_video_texture_cache, core_video_texture_cache,
fps_counter: FpsCounter::new(),
} }
} }
@ -295,8 +292,7 @@ impl MetalRenderer {
// nothing to do // nothing to do
} }
pub fn draw(&mut self, scene: &Scene, on_complete: Option<oneshot::Sender<()>>) { pub fn draw(&mut self, scene: &Scene) {
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(
@ -323,24 +319,13 @@ 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 device = self.device.clone(); let block = ConcreteBlock::new(move |_| {
let fps_counter = self.fps_counter.clone(); if let Some(instance_buffer) = instance_buffer.take() {
let completed_handler = instance_buffer_pool.lock().release(instance_buffer);
ConcreteBlock::new(move |_: &metal::CommandBufferRef| { }
let mut cpu_timestamp = 0; });
let mut gpu_timestamp = 0; let block = block.copy();
device.sample_timestamps(&mut cpu_timestamp, &mut gpu_timestamp); command_buffer.add_completed_handler(&block);
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();
@ -1132,10 +1117,6 @@ impl MetalRenderer {
} }
true true
} }
pub fn fps(&self) -> f32 {
self.fps_counter.fps()
}
} }
fn build_pipeline_state( fn build_pipeline_state(

View file

@ -783,14 +783,14 @@ impl PlatformWindow for MacWindow {
self.0.as_ref().lock().bounds() self.0.as_ref().lock().bounds()
} }
fn is_maximized(&self) -> bool {
self.0.as_ref().lock().is_maximized()
}
fn window_bounds(&self) -> WindowBounds { fn window_bounds(&self) -> WindowBounds {
self.0.as_ref().lock().window_bounds() self.0.as_ref().lock().window_bounds()
} }
fn is_maximized(&self) -> bool {
self.0.as_ref().lock().is_maximized()
}
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()
} }
@ -974,6 +974,8 @@ 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
@ -1004,6 +1006,30 @@ 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 {
@ -1080,41 +1106,15 @@ 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, on_complete: Option<oneshot::Sender<()>>) { fn draw(&self, scene: &crate::Scene) {
let mut this = self.0.lock(); let mut this = self.0.lock();
this.renderer.draw(scene, on_complete); this.renderer.draw(scene);
} }
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
} }
@ -1125,10 +1125,6 @@ impl PlatformWindow for MacWindow {
let _: () = msg_send![input_context, invalidateCharacterCoordinates]; let _: () = msg_send![input_context, invalidateCharacterCoordinates];
} }
} }
fn fps(&self) -> Option<f32> {
Some(self.0.lock().renderer.fps())
}
} }
impl rwh::HasWindowHandle for MacWindow { impl rwh::HasWindowHandle for MacWindow {

View file

@ -251,12 +251,7 @@ impl PlatformWindow for TestWindow {
fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {} fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {}
fn draw( fn draw(&self, _scene: &crate::Scene) {}
&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()
@ -284,10 +279,6 @@ 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 {

View file

@ -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, on_complete: Option<oneshot::Sender<()>>) { fn draw(&self, scene: &Scene) {
self.0.state.borrow_mut().renderer.draw(scene, on_complete) self.0.state.borrow_mut().renderer.draw(scene)
} }
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> { fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
@ -679,10 +679,6 @@ impl PlatformWindow for WindowsWindow {
fn update_ime_position(&self, _bounds: Bounds<Pixels>) { fn update_ime_position(&self, _bounds: Bounds<Pixels>) {
// todo(windows) // todo(windows)
} }
fn fps(&self) -> Option<f32> {
None
}
} }
#[implement(IDropTarget)] #[implement(IDropTarget)]

View file

@ -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,
TimeToFirstWindowDraw, TransformationMatrix, Underline, UnderlineStyle, View, VisualContext, TransformationMatrix, Underline, UnderlineStyle, View, VisualContext, WeakView,
WeakView, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControls, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations,
WindowDecorations, WindowOptions, WindowParams, WindowTextSystem, SUBPIXEL_VARIANTS, 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};
@ -545,8 +545,6 @@ 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,
@ -824,7 +822,6 @@ 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,
@ -1494,29 +1491,13 @@ 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, on_complete); .draw(&self.window.rendered_frame.scene);
self.window.needs_present.set(false); self.window.needs_present.set(false);
profiling::finish_frame!(); profiling::finish_frame!();
} }
@ -3798,12 +3779,6 @@ 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")]

View file

@ -1,36 +0,0 @@
[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"] }

View file

@ -1 +0,0 @@
../../LICENSE-GPL

View file

@ -1,189 +0,0 @@
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()
}
}

View file

@ -153,17 +153,6 @@ 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,

View file

@ -75,7 +75,6 @@ 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

View file

@ -267,7 +267,6 @@ 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();
@ -317,7 +316,6 @@ fn init_ui(
} }
fn main() { fn main() {
let start_time = std::time::Instant::now();
menu::init(); menu::init();
zed_actions::init(); zed_actions::init();
@ -329,9 +327,7 @@ fn main() {
init_logger(); init_logger();
log::info!("========== starting zed =========="); log::info!("========== starting zed ==========");
let app = App::new() let app = App::new().with_assets(Assets);
.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()