Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/desktop-gpui/examples/live-edit-benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ fn percentile(sorted: &[f64], fraction: f64) -> f64 {

fn frame_number(output: &EditorFrameOutput) -> u32 {
match output {
EditorFrameOutput::Rgba(frame) => frame.frame_number,
EditorFrameOutput::Rgba(frame) | EditorFrameOutput::Bgra(frame) => frame.frame_number,
EditorFrameOutput::Nv12(frame) => frame.frame_number,
#[cfg(target_os = "macos")]
EditorFrameOutput::Surface(frame) => frame.frame_number,
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop-gpui/examples/scrub-benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ fn percentile(sorted: &[f64], fraction: f64) -> f64 {

fn frame_number(output: &EditorFrameOutput) -> u32 {
match output {
EditorFrameOutput::Rgba(frame) => frame.frame_number,
EditorFrameOutput::Rgba(frame) | EditorFrameOutput::Bgra(frame) => frame.frame_number,
EditorFrameOutput::Nv12(frame) => frame.frame_number,
#[cfg(target_os = "macos")]
EditorFrameOutput::Surface(frame) => frame.frame_number,
Expand Down
96 changes: 79 additions & 17 deletions apps/desktop-gpui/src/app_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5452,6 +5452,14 @@ fn start_preparing_editor(
let Some(image) = image else { continue };
editor_window::EditorPreviewFrame::Image(image)
}
cap_editor::EditorFrameOutput::Bgra(frame) => {
let image = cx
.background_executor()
.spawn(async move { editor_window::bgra_frame_image(frame) })
.await;
let Some(image) = image else { continue };
editor_window::EditorPreviewFrame::Image(image)
}
#[cfg(target_os = "macos")]
cap_editor::EditorFrameOutput::Surface(surface) => {
editor_window::surface_preview_frame(surface)
Expand Down Expand Up @@ -5693,16 +5701,17 @@ fn load_editor_project_candidate(
//
let state_cb = editor_window::make_state_callback(playhead);
let frame_cb = editor_window::make_frame_callback(frame_tx, frame_stats);
let shared_device = shared_wgpu_device().await;
#[cfg(target_os = "macos")]
let frame_format = cap_editor::EditorFrameFormat::BgraSurface;
#[cfg(not(target_os = "macos"))]
let frame_format = cap_editor::EditorFrameFormat::Rgba;
let frame_format =
editor_window::cpu_preview_frame_format(shared_device.as_ref());
if let Some(previous) = previous {
return previous
.recreate_preparing_candidate(state_cb, frame_cb, frame_format)
.await;
}
let shared_device = shared_wgpu_device().await;
let instance = cap_editor::EditorInstance::new_with_startup_inputs(
instance_path,
state_cb,
Expand Down Expand Up @@ -5778,30 +5787,71 @@ fn load_editor_project_candidate(
return;
}

// The pump: convert on the background executor (un-padding plus the
// BGRA swap is a few megabytes per frame), deliver on the main one.
cx.spawn({
let stats = stats.clone();
let pump_instance = Arc::downgrade(&instance);
let rgba_stream = editor_window::streaming_preview_enabled()
.then(|| gpui::StreamingImage::new(gpui::StreamingImageFormat::Rgba8));
let bgra_stream = editor_window::streaming_preview_enabled()
.then(|| gpui::StreamingImage::new(gpui::StreamingImageFormat::Bgra8));
async move |cx| {
while let Ok((output, layout)) = frame_rx.recv_async().await {
let mut streamed_rgba = None;
let mut streamed_bgra = None;
let (frame, number) = match output {
cap_editor::EditorFrameOutput::Rgba(rgba) => {
let stats = stats.clone();
let number = rgba.frame_number;
let image = cx
.background_executor()
.spawn(
async move { editor_window::frame_image_timed(&rgba, &stats) },
if rgba_stream.is_some() {
streamed_rgba = Some(rgba);
(
editor_window::EditorPreviewFrame::Stream(
rgba_stream.as_ref().expect("stream").clone(),
),
number,
)
.await;
let Some(image) = image else {
tracing::warn!(
"a rendered frame could not be converted for display"
);
continue;
};
(editor_window::EditorPreviewFrame::Image(image), number)
} else {
let stats = stats.clone();
let image = cx
.background_executor()
.spawn(async move {
editor_window::frame_image_timed(&rgba, &stats)
})
.await;
let Some(image) = image else {
tracing::warn!(
"a rendered frame could not be converted for display"
);
continue;
};
(editor_window::EditorPreviewFrame::Image(image), number)
}
}
cap_editor::EditorFrameOutput::Bgra(bgra) => {
let number = bgra.frame_number;
if bgra_stream.is_some() {
streamed_bgra = Some(bgra);
(
editor_window::EditorPreviewFrame::Stream(
bgra_stream.as_ref().expect("stream").clone(),
),
number,
)
} else {
let stats = stats.clone();
let image = cx
.background_executor()
.spawn(async move {
editor_window::bgra_frame_image_timed(bgra, &stats)
})
.await;
let Some(image) = image else {
tracing::warn!(
"a rendered frame could not be converted for display"
);
continue;
};
(editor_window::EditorPreviewFrame::Image(image), number)
}
}
cap_editor::EditorFrameOutput::Nv12(frame) => {
tracing::warn!(
Expand All @@ -5821,6 +5871,18 @@ fn load_editor_project_candidate(
if !view.holds_instance(&pump_instance) {
return;
}
if let (Some(stream), Some(rgba)) =
(&rgba_stream, streamed_rgba.take())
&& !editor_window::stream_frame_timed(stream, rgba, &stats)
{
return;
}
if let (Some(stream), Some(bgra)) =
(&bgra_stream, streamed_bgra.take())
&& !editor_window::stream_frame_timed(stream, bgra, &stats)
{
return;
}
view.frame_arrived(
editor_window::EditorFrame {
frame,
Expand Down
5 changes: 3 additions & 2 deletions apps/desktop-gpui/src/editor_preparing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,9 +525,10 @@ impl PreparingRunner {
let discard = self.frame_discard.clone();
#[cfg(target_os = "macos")]
let frame_format = EditorFrameFormat::BgraSurface;
#[cfg(not(target_os = "macos"))]
let frame_format = EditorFrameFormat::Rgba;
let shared_device = crate::app_windows::shared_wgpu_device().await;
#[cfg(not(target_os = "macos"))]
let frame_format: EditorFrameFormat =
crate::editor_window::cpu_preview_frame_format(shared_device.as_ref());
self.control.accepts_frames.store(true, Ordering::Release);
let playback = PreparingPlaybackSession::spawn_with_expected_metadata(
input,
Expand Down
81 changes: 78 additions & 3 deletions apps/desktop-gpui/src/editor_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ use gpui::{
Animation, AnimationExt as _, AppContext as _, Bounds, Context, Entity, FocusHandle,
FontWeight, Hsla, InteractiveElement, IntoElement, MouseButton, MouseDownEvent, MouseMoveEvent,
MouseUpEvent, ParentElement, Pixels, Point, Render, RenderImage, SharedString,
StatefulInteractiveElement as _, StyleRefinement, Styled, Subscription, WeakEntity, Window,
div, point, prelude::FluentBuilder, px, svg,
StatefulInteractiveElement as _, StreamingImage, StyleRefinement, Styled, Subscription,
WeakEntity, Window, div, point, prelude::FluentBuilder, px, svg,
};

use crate::i18n::tr;
Expand Down Expand Up @@ -619,10 +619,71 @@ pub fn frame_image(frame: &RenderedFrame) -> Option<Arc<RenderImage>> {
])))
}

pub fn bgra_frame_image(frame: RenderedFrame) -> Option<Arc<RenderImage>> {
let row_bytes = (frame.width as usize).checked_mul(4)?;
if frame.padded_bytes_per_row as usize != row_bytes
|| frame.data.len() != row_bytes.checked_mul(frame.height as usize)?
{
return None;
}
let pixels = Arc::try_unwrap(frame.data).unwrap_or_else(|shared| shared.as_ref().clone());
let buffer = image::RgbaImage::from_raw(frame.width, frame.height, pixels)?;
Some(Arc::new(RenderImage::new(smallvec::smallvec![
image::Frame::new(buffer)
])))
}

#[cfg(not(target_os = "macos"))]
pub(crate) fn cpu_preview_frame_format(
shared_device: Option<&cap_rendering::SharedWgpuDevice>,
) -> cap_editor::EditorFrameFormat {
if shared_device.is_some_and(|device| !device.is_software_adapter) {
cap_editor::EditorFrameFormat::Bgra
} else {
cap_editor::EditorFrameFormat::Rgba
}
}

pub(crate) fn streaming_preview_enabled() -> bool {
static ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ENABLED.get_or_init(|| {
match std::env::var_os("CAP_GPUI_STREAMING_IMAGE").as_deref() {
Some(value) => value != "0",
None => !cfg!(target_os = "macos"),
}
})
}

pub fn stream_frame_timed(
stream: &StreamingImage,
frame: RenderedFrame,
stats: &PumpStats,
) -> bool {
let started = Instant::now();
let size = gpui::size(
gpui::DevicePixels(frame.width as i32),
gpui::DevicePixels(frame.height as i32),
);
let stride = frame.padded_bytes_per_row as usize;
let result = match Arc::try_unwrap(frame.data) {
Ok(data) => stream.set_frame(size, stride, data).map(drop),
Err(data) => stream.copy_frame(size, stride, &data),
};
stats
.convert_nanos
.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed);
stats.convert_samples.fetch_add(1, Ordering::Relaxed);
if let Err(error) = &result {
tracing::warn!(%error, "a rendered frame could not be streamed to the preview");
}
result.is_ok()
}

/// What the frame pump hands the window.
#[derive(Clone)]
pub(crate) enum EditorPreviewFrame {
Image(Arc<RenderImage>),
Stream(StreamingImage),
#[cfg(target_os = "macos")]
Surface(CVPixelBuffer),
}
Expand All @@ -634,6 +695,9 @@ impl EditorPreviewFrame {
Self::Image(image) => {
let _ = window.paint_image(bounds, corners, Arc::clone(image), 0, false);
}
Self::Stream(image) => {
let _ = window.paint_streaming_image(bounds, corners, image, false);
}
#[cfg(target_os = "macos")]
Self::Surface(surface) => {
window.paint_surface_fitted(bounds, bounds, corners, surface.clone())
Expand Down Expand Up @@ -13314,8 +13378,19 @@ pub(crate) fn surface_preview_frame(frame: cap_rendering::SurfaceFrame) -> Edito
/// [`frame_image`], timed. The pump calls this so the CPU conversion's cost is
/// a number rather than a hunch.
pub fn frame_image_timed(frame: &RenderedFrame, stats: &PumpStats) -> Option<Arc<RenderImage>> {
timed_conversion(stats, || frame_image(frame))
}

pub fn bgra_frame_image_timed(frame: RenderedFrame, stats: &PumpStats) -> Option<Arc<RenderImage>> {
timed_conversion(stats, || bgra_frame_image(frame))
}

fn timed_conversion(
stats: &PumpStats,
convert: impl FnOnce() -> Option<Arc<RenderImage>>,
) -> Option<Arc<RenderImage>> {
let started = Instant::now();
let image = frame_image(frame);
let image = convert();
stats
.convert_nanos
.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed);
Expand Down
1 change: 1 addition & 0 deletions crates/editor/examples/editor-playback-benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,7 @@ async fn main() {
data.len() + metadata_bytes
}
EditorFrameOutput::Rgba(frame) => frame.data.len() + 24,
EditorFrameOutput::Bgra(frame) => frame.data.len(),
#[cfg(target_os = "macos")]
EditorFrameOutput::Surface(_) => return,
};
Expand Down
4 changes: 3 additions & 1 deletion crates/editor/examples/imported-music-playback-benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ async fn main() {
|_| {},
Box::new(move |frame, _| {
let number = match frame {
EditorFrameOutput::Rgba(frame) => frame.frame_number,
EditorFrameOutput::Rgba(frame) | EditorFrameOutput::Bgra(frame) => {
frame.frame_number
}
EditorFrameOutput::Nv12(frame) => frame.frame_number,
#[cfg(target_os = "macos")]
EditorFrameOutput::Surface(frame) => frame.frame_number,
Expand Down
2 changes: 1 addition & 1 deletion crates/editor/examples/scrub-benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fn arg_value<'a>(args: &'a [String], flag: &str) -> Option<&'a str> {

fn presented_frame(output: &EditorFrameOutput) -> u32 {
match output {
EditorFrameOutput::Rgba(frame) => frame.frame_number,
EditorFrameOutput::Rgba(frame) | EditorFrameOutput::Bgra(frame) => frame.frame_number,
EditorFrameOutput::Nv12(frame) => frame.frame_number,
#[cfg(target_os = "macos")]
EditorFrameOutput::Surface(frame) => frame.frame_number,
Expand Down
Loading
Loading