Skip to content

Commit

Permalink
Central GpuReadback handling for re_viewer, experimental space view s…
Browse files Browse the repository at this point in the history
…creenshots (#1717)

EXPERIMENTAL: screenshot in context menu on spatial views
  • Loading branch information
Wumpf committed Mar 29, 2023
1 parent 1f1791a commit f0c2fde
Show file tree
Hide file tree
Showing 13 changed files with 290 additions and 31 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"nyud",
"objectron",
"Readback",
"readbacks",
"Skybox",
"smallvec",
"swapchain",
Expand Down
1 change: 1 addition & 0 deletions crates/re_renderer/src/view_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ impl Default for TargetConfiguration {
}
}

#[derive(Clone)]
pub struct ScheduledScreenshot {
pub identifier: GpuReadbackBufferIdentifier,
pub width: u32,
Expand Down
1 change: 1 addition & 0 deletions crates/re_renderer/src/wgpu_resources/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ impl WgpuResourcePools {
}
}

#[derive(Clone, Copy)]
pub struct TextureRowDataInfo {
/// How many bytes per row contain actual data.
pub bytes_per_row_unpadded: u32,
Expand Down
69 changes: 65 additions & 4 deletions crates/re_viewer/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ use re_arrow_store::DataStoreStats;
use re_data_store::log_db::LogDb;
use re_format::format_number;
use re_log_types::{ApplicationId, LogMsg, RecordingId};
use re_renderer::WgpuResourcePoolStatistics;
use re_renderer::{GpuReadbackBufferIdentifier, WgpuResourcePoolStatistics};
use re_smart_channel::Receiver;
use re_ui::{toasts, Command};

use crate::{
app_icon::setup_app_icon,
misc::{AppOptions, Caches, RecordingConfig, ViewerContext},
misc::{AppOptions, Caches, RecordingConfig, ScheduledGpuReadback, ViewerContext},
ui::{data_ui::ComponentUiRegistry, Blueprint},
viewer_analytics::ViewerAnalytics,
};
Expand Down Expand Up @@ -87,6 +87,10 @@ pub struct App {

latest_queue_interest: instant::Instant,

/// List of all data we're currently waiting for from the GPU for each application id
scheduled_gpu_readbacks_per_application:
HashMap<ApplicationId, HashMap<GpuReadbackBufferIdentifier, ScheduledGpuReadback>>,

/// Measures how long a frame takes to paint
frame_time_history: egui::util::History<f32>,

Expand Down Expand Up @@ -143,6 +147,8 @@ impl App {

latest_queue_interest: instant::Instant::now(), // TODO(emilk): `Instant::MIN` when we have our own `Instant` that supports it.

scheduled_gpu_readbacks_per_application: HashMap::default(),

frame_time_history: egui::util::History::new(1..100, 0.5),

pending_commands: Default::default(),
Expand Down Expand Up @@ -404,6 +410,37 @@ impl App {
);
});
}

fn process_gpu_readback_data(&mut self, data: &[u8], identifier: GpuReadbackBufferIdentifier) {
for (application_id, scheduled_gpu_readbacks) in
&mut self.scheduled_gpu_readbacks_per_application
{
if let Some((_, scheduled_readback)) = scheduled_gpu_readbacks.remove_entry(&identifier)
{
match scheduled_readback {
ScheduledGpuReadback::SpaceViewScreenshot {
screenshot,
space_view_id,
mode,
} => {
if let Some(blueprint) = self.state.blueprints.get_mut(application_id) {
blueprint.viewport.save_spaceview_screenshot(
&screenshot,
data,
space_view_id,
mode,
);
}
}
}
return;
}
}
re_log::warn_once!(
"Received unexpected GPU readback. (Size: {}, identifier {identifier}).",
data.len()
);
}
}

impl eframe::App for App {
Expand Down Expand Up @@ -449,8 +486,8 @@ impl eframe::App for App {
egui_ctx.set_pixels_per_point(pixels_per_point);
}

// TODO(andreas): store the re_renderer somewhere else.
let gpu_resource_stats = {
// TODO(andreas): store the re_renderer somewhere else.
let egui_renderer = {
let render_state = frame.wgpu_render_state().unwrap();
&mut render_state.renderer.read()
Expand All @@ -459,6 +496,13 @@ impl eframe::App for App {
.paint_callback_resources
.get::<re_renderer::RenderContext>()
.unwrap();

// Handle GPU readback data.
render_ctx
.gpu_readback_belt
.lock()
.receive_data(|data, identifier| self.process_gpu_readback_data(data, identifier));

// Query statistics before begin_frame as this might be more accurate if there's resources that we recreate every frame.
render_ctx.gpu_resources.statistics()
};
Expand Down Expand Up @@ -505,7 +549,7 @@ impl eframe::App for App {
let blueprint = self
.state
.blueprints
.entry(selected_app_id)
.entry(selected_app_id.clone())
.or_insert_with(|| Blueprint::new(egui_ctx));

recording_config_entry(
Expand Down Expand Up @@ -535,6 +579,9 @@ impl eframe::App for App {
self.state.show(
ui,
render_ctx,
self.scheduled_gpu_readbacks_per_application
.entry(selected_app_id)
.or_default(),
log_db,
&self.re_ui,
&self.component_ui_registry,
Expand Down Expand Up @@ -936,10 +983,12 @@ struct AppState {
}

impl AppState {
#[allow(clippy::too_many_arguments)]
fn show(
&mut self,
ui: &mut egui::Ui,
render_ctx: &mut re_renderer::RenderContext,
scheduled_gpu_readbacks: &mut HashMap<GpuReadbackBufferIdentifier, ScheduledGpuReadback>,
log_db: &LogDb,
re_ui: &re_ui::ReUi,
component_ui_registry: &ComponentUiRegistry,
Expand Down Expand Up @@ -977,6 +1026,7 @@ impl AppState {
rec_cfg,
re_ui,
render_ctx,
scheduled_gpu_readbacks,
};

let blueprint = blueprints
Expand Down Expand Up @@ -1587,6 +1637,17 @@ fn options_menu_ui(ui: &mut egui::Ui, _frame: &mut eframe::Frame, options: &mut
ui.close_menu();
}

#[cfg(not(target_arch = "wasm32"))]
{
if ui
.checkbox(&mut options.experimental_space_view_screenshots, "(experimental) Space View screenshots")
.on_hover_text("Allow taking screenshots of 2D & 3D space views via their context menu. Does not contain labels.")
.clicked()
{
ui.close_menu();
}
}

#[cfg(debug_assertions)]
{
ui.separator();
Expand Down
7 changes: 7 additions & 0 deletions crates/re_viewer/src/misc/app_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ pub struct AppOptions {
/// (Since this is serialized, even between sessions!)
#[cfg(not(target_arch = "wasm32"))]
pub zoom_factor: f32,

/// Enable the experimental feature for space view screenshots.
#[cfg(not(target_arch = "wasm32"))]
pub experimental_space_view_screenshots: bool,
}

impl Default for AppOptions {
Expand All @@ -32,6 +36,9 @@ impl Default for AppOptions {

#[cfg(not(target_arch = "wasm32"))]
zoom_factor: 1.0,

#[cfg(not(target_arch = "wasm32"))]
experimental_space_view_screenshots: false,
}
}
}
26 changes: 26 additions & 0 deletions crates/re_viewer/src/misc/gpu_readbacks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use re_renderer::ScheduledScreenshot;

use crate::ui::SpaceViewId;

#[derive(PartialEq, Eq, Clone, Copy)]
#[allow(dead_code)] // Not used on the web.
pub enum ScreenshotMode {
/// The screenshot will be saved to disc and copied to the clipboard.
SaveAndCopyToClipboard,

/// The screenshot will be copied to the clipboard.
CopyToClipboard,
}

/// A previously scheduled GPU readback, waiting for getting the result.
pub enum ScheduledGpuReadback {
SpaceViewScreenshot {
screenshot: ScheduledScreenshot,
space_view_id: SpaceViewId,
mode: ScreenshotMode,
},
// Picking {
// picking: ScheduledPicking,
// space_view_id: SpaceViewId,
// },
}
3 changes: 3 additions & 0 deletions crates/re_viewer/src/misc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ pub use {
},
};

mod gpu_readbacks;
pub use gpu_readbacks::{ScheduledGpuReadback, ScreenshotMode};

// ----------------------------------------------------------------------------

pub fn help_hover_button(ui: &mut egui::Ui) -> egui::Response {
Expand Down
7 changes: 6 additions & 1 deletion crates/re_viewer/src/misc/viewer_context.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use ahash::HashMap;
use re_data_store::{log_db::LogDb, InstancePath};
use re_log_types::{ComponentPath, EntityPath, MsgId, TimeInt, Timeline};
use re_renderer::GpuReadbackBufferIdentifier;

use crate::ui::{
data_ui::{ComponentUiRegistry, DataUi},
Expand All @@ -8,7 +10,7 @@ use crate::ui::{

use super::{
item::{Item, ItemCollection},
HoverHighlight,
HoverHighlight, ScheduledGpuReadback,
};

/// Common things needed by many parts of the viewer.
Expand All @@ -32,6 +34,9 @@ pub struct ViewerContext<'a> {
pub re_ui: &'a re_ui::ReUi,

pub render_ctx: &'a mut re_renderer::RenderContext,

/// List of all data we're currently waiting for from the GPU.
pub scheduled_gpu_readbacks: &'a mut HashMap<GpuReadbackBufferIdentifier, ScheduledGpuReadback>,
}

impl<'a> ViewerContext<'a> {
Expand Down
31 changes: 30 additions & 1 deletion crates/re_viewer/src/ui/view_spatial/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use re_renderer::renderer::OutlineConfig;

use crate::{
misc::{
space_info::query_view_coordinates, SelectionHighlight, SpaceViewHighlights, ViewerContext,
space_info::query_view_coordinates, ScreenshotMode, SelectionHighlight,
SpaceViewHighlights, ViewerContext,
},
ui::{data_blueprint::DataBlueprintTree, view_spatial::UiLabelTarget, SpaceViewId},
};
Expand Down Expand Up @@ -620,3 +621,31 @@ pub fn outline_config(gui_ctx: &egui::Context) -> OutlineConfig {
color_layer_b: selection_outline_color,
}
}

pub fn screenshot_context_menu(
_ctx: &ViewerContext<'_>,
response: egui::Response,
) -> (egui::Response, Option<ScreenshotMode>) {
#[cfg(not(target_arch = "wasm32"))]
{
if _ctx.app_options.experimental_space_view_screenshots {
let mut take_screenshot = None;
let response = response.context_menu(|ui| {
if ui.button("Screenshot (save to disk)").clicked() {
take_screenshot = Some(ScreenshotMode::SaveAndCopyToClipboard);
ui.close_menu();
} else if ui.button("Screenshot (clipboard only)").clicked() {
take_screenshot = Some(ScreenshotMode::CopyToClipboard);
ui.close_menu();
}
});
(response, take_screenshot)
} else {
(response, None)
}
}
#[cfg(target_arch = "wasm32")]
{
(response, None)
}
}
23 changes: 19 additions & 4 deletions crates/re_viewer/src/ui/view_spatial/ui_2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ use re_log_types::component_types::TensorTrait;
use re_renderer::view_builder::TargetConfiguration;

use super::{
eye::Eye, scene::AdditionalPickingInfo, ui::create_labels, SpatialNavigationMode,
ViewSpatialState,
eye::Eye,
scene::AdditionalPickingInfo,
ui::{create_labels, screenshot_context_menu},
SpatialNavigationMode, ViewSpatialState,
};
use crate::{
misc::{HoveredSpace, Item, SpaceViewHighlights},
misc::{HoveredSpace, Item, ScheduledGpuReadback, SpaceViewHighlights},
ui::{
data_ui::{self, DataUi},
view_spatial::{
Expand Down Expand Up @@ -427,6 +429,8 @@ fn view_2d_scrollable(

// ------------------------------------------------------------------------

let (response, screenshot_action) = screenshot_context_menu(ctx, response);

// Draw a re_renderer driven view.
// Camera & projection are configured to ingest space coordinates directly.
{
Expand All @@ -445,14 +449,25 @@ fn view_2d_scrollable(
return response;
};

let Ok(callback) = create_scene_paint_callback(
let Ok((callback, screenshot)) = create_scene_paint_callback(
ctx.render_ctx,
target_config, painter.clip_rect(),
scene.primitives,
&ScreenBackground::ClearColor(parent_ui.visuals().extreme_bg_color.into()),
screenshot_action.is_some(),
) else {
return response;
};
if let (Some(screenshot), Some(screenshot_action)) = (screenshot, screenshot_action) {
ctx.scheduled_gpu_readbacks.insert(
screenshot.identifier,
ScheduledGpuReadback::SpaceViewScreenshot {
space_view_id,
screenshot,
mode: screenshot_action,
},
);
}

painter.add(callback);
}
Expand Down

1 comment on commit f0c2fde

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rust Benchmark

Benchmark suite Current: f0c2fde Previous: 1f1791a Ratio
datastore/insert/batch/rects/insert 605599 ns/iter (± 2448) 604916 ns/iter (± 1704) 1.00
datastore/latest_at/batch/rects/query 1836 ns/iter (± 6) 1871 ns/iter (± 2) 0.98
datastore/latest_at/missing_components/primary 281 ns/iter (± 0) 283 ns/iter (± 1) 0.99
datastore/latest_at/missing_components/secondaries 435 ns/iter (± 0) 434 ns/iter (± 0) 1.00
datastore/range/batch/rects/query 151509 ns/iter (± 447) 153548 ns/iter (± 388) 0.99
mono_points_arrow/generate_message_bundles 43059300 ns/iter (± 528134) 44548872 ns/iter (± 764710) 0.97
mono_points_arrow/generate_messages 167580898 ns/iter (± 1263022) 181860171 ns/iter (± 1302133) 0.92
mono_points_arrow/encode_log_msg 209851844 ns/iter (± 3733358) 222431426 ns/iter (± 1062884) 0.94
mono_points_arrow/encode_total 417361966 ns/iter (± 1812551) 447867584 ns/iter (± 1669452) 0.93
mono_points_arrow/decode_log_msg 251422372 ns/iter (± 810673) 266281764 ns/iter (± 1017764) 0.94
mono_points_arrow/decode_message_bundles 85077520 ns/iter (± 727163) 95347641 ns/iter (± 1029190) 0.89
mono_points_arrow/decode_total 338052305 ns/iter (± 1368801) 362483952 ns/iter (± 1618307) 0.93
mono_points_arrow_batched/generate_message_bundles 35444065 ns/iter (± 1258314) 34624454 ns/iter (± 2336250) 1.02
mono_points_arrow_batched/generate_messages 9305538 ns/iter (± 761267) 9713815 ns/iter (± 851019) 0.96
mono_points_arrow_batched/encode_log_msg 1762443 ns/iter (± 3490) 1769737 ns/iter (± 16169) 1.00
mono_points_arrow_batched/encode_total 47352681 ns/iter (± 2488976) 47728270 ns/iter (± 2667756) 0.99
mono_points_arrow_batched/decode_log_msg 985723 ns/iter (± 2709) 971167 ns/iter (± 4061) 1.01
mono_points_arrow_batched/decode_message_bundles 18418610 ns/iter (± 1256736) 17318155 ns/iter (± 1991361) 1.06
mono_points_arrow_batched/decode_total 19568993 ns/iter (± 714999) 19165108 ns/iter (± 1065307) 1.02
batch_points_arrow/generate_message_bundles 288751 ns/iter (± 473) 286779 ns/iter (± 534) 1.01
batch_points_arrow/generate_messages 7800 ns/iter (± 19) 7956 ns/iter (± 19) 0.98
batch_points_arrow/encode_log_msg 389641 ns/iter (± 1458) 385614 ns/iter (± 1220) 1.01
batch_points_arrow/encode_total 696104 ns/iter (± 1785) 693373 ns/iter (± 1835) 1.00
batch_points_arrow/decode_log_msg 337022 ns/iter (± 782) 338533 ns/iter (± 704) 1.00
batch_points_arrow/decode_message_bundles 2935 ns/iter (± 7) 2905 ns/iter (± 15) 1.01
batch_points_arrow/decode_total 346135 ns/iter (± 731) 347684 ns/iter (± 6006) 1.00
arrow_mono_points/insert 6133941953 ns/iter (± 22892058) 6845684190 ns/iter (± 18410707) 0.90
arrow_mono_points/query 1809200 ns/iter (± 7892) 1777679 ns/iter (± 12548) 1.02
arrow_batch_points/insert 3045916 ns/iter (± 11102) 2994167 ns/iter (± 8352) 1.02
arrow_batch_points/query 17082 ns/iter (± 49) 17183 ns/iter (± 36) 0.99
arrow_batch_vecs/insert 42955 ns/iter (± 110) 43095 ns/iter (± 190) 1.00
arrow_batch_vecs/query 506159 ns/iter (± 354) 506123 ns/iter (± 722) 1.00
tuid/Tuid::random 34 ns/iter (± 0) 34 ns/iter (± 0) 1

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.