Skip to content

Commit a18b7ff

Browse files
Desktop: Add an 'Enable V-Sync' preference on Mac (#3887)
* add vsync pref * account for physical scale in pixel preview passthru check * change allow to expect attr * Update user-facing v-sync text --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
1 parent 6d0357b commit a18b7ff

File tree

9 files changed

+111
-31
lines changed

9 files changed

+111
-31
lines changed

desktop/src/app.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use crate::persist::PersistentData;
2121
use crate::preferences;
2222
use crate::render::{RenderError, RenderState};
2323
use crate::window::Window;
24-
use crate::wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, InputMessage, MouseKeys, MouseState};
24+
use crate::wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, InputMessage, MouseKeys, MouseState, Preferences};
2525
use crate::wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages};
2626

2727
pub(crate) struct App {
@@ -46,6 +46,8 @@ pub(crate) struct App {
4646
web_communication_initialized: bool,
4747
web_communication_startup_buffer: Vec<Vec<u8>>,
4848
persistent_data: PersistentData,
49+
#[cfg_attr(not(target_os = "macos"), expect(unused))]
50+
preferences: Preferences,
4951
cli: Cli,
5052
startup_time: Option<Instant>,
5153
exiting: Arc<AtomicBool>,
@@ -63,6 +65,7 @@ impl App {
6365
wgpu_context: WgpuContext,
6466
app_event_receiver: Receiver<AppEvent>,
6567
app_event_scheduler: AppEventScheduler,
68+
preferences: Preferences,
6669
cli: Cli,
6770
) -> Self {
6871
let ctrlc_app_event_scheduler = app_event_scheduler.clone();
@@ -116,6 +119,7 @@ impl App {
116119
web_communication_initialized: false,
117120
web_communication_startup_buffer: Vec::new(),
118121
persistent_data,
122+
preferences,
119123
cli,
120124
startup_time: None,
121125
exiting,
@@ -516,7 +520,12 @@ impl ApplicationHandler for App {
516520
let window = Window::new(event_loop, self.app_event_scheduler.clone());
517521
self.window = Some(window);
518522

519-
let render_state = RenderState::new(self.window.as_ref().unwrap(), self.wgpu_context.clone());
523+
#[cfg(not(target_os = "macos"))]
524+
let present_mode = None;
525+
#[cfg(target_os = "macos")]
526+
let present_mode = if !self.preferences.vsync { Some(wgpu::PresentMode::Immediate) } else { None };
527+
528+
let render_state = RenderState::new(self.window.as_ref().unwrap(), self.wgpu_context.clone(), present_mode);
520529
self.render_state = Some(render_state);
521530

522531
if let Some(window) = &self.window.as_ref() {

desktop/src/lib.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ pub fn start() {
6262
}
6363
};
6464

65+
let prefs = preferences::read();
66+
6567
// Must be called before event loop initialization or native window integrations will break
6668
App::init();
6769

@@ -73,7 +75,7 @@ pub fn start() {
7375

7476
let (cef_view_info_sender, cef_view_info_receiver) = std::sync::mpsc::channel();
7577

76-
let disable_ui_acceleration = preferences::read().disable_ui_acceleration || cli.disable_ui_acceleration;
78+
let disable_ui_acceleration = prefs.disable_ui_acceleration || cli.disable_ui_acceleration;
7779
if disable_ui_acceleration {
7880
println!("UI acceleration is disabled");
7981
}
@@ -95,7 +97,7 @@ pub fn start() {
9597
}
9698
};
9799

98-
let app = App::new(Box::new(cef_context), cef_view_info_sender, wgpu_context, app_event_receiver, app_event_scheduler, cli);
100+
let app = App::new(Box::new(cef_context), cef_view_info_sender, wgpu_context, app_event_receiver, app_event_scheduler, prefs, cli);
99101

100102
let exit_reason = app.run(event_loop);
101103

@@ -111,15 +113,15 @@ pub fn start() {
111113
drop(lock);
112114

113115
match exit_reason {
114-
#[cfg(target_os = "linux")]
115116
app::ExitReason::Restart | app::ExitReason::UiAccelerationFailure => {
116117
tracing::error!("Restarting application");
117118
let mut command = std::process::Command::new(std::env::current_exe().unwrap());
118119
#[cfg(target_family = "unix")]
119120
let _ = std::os::unix::process::CommandExt::exec(&mut command);
121+
#[cfg(target_family = "unix")]
122+
tracing::error!("Failed to restart application");
120123
#[cfg(not(target_family = "unix"))]
121124
let _ = command.spawn();
122-
tracing::error!("Failed to restart application");
123125
}
124126
_ => {}
125127
}

desktop/src/render/state.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::borrow::Cow;
2+
use wgpu::PresentMode;
23

34
use crate::window::Window;
45
use crate::wrapper::{TargetTexture, WgpuContext, WgpuExecutor};
@@ -27,7 +28,7 @@ pub(crate) struct RenderState {
2728
}
2829

2930
impl RenderState {
30-
pub(crate) fn new(window: &Window, context: WgpuContext) -> Self {
31+
pub(crate) fn new(window: &Window, context: WgpuContext, present_mode: Option<PresentMode>) -> Self {
3132
let size = window.surface_size();
3233
let surface = window.create_surface(context.instance.clone());
3334

@@ -39,10 +40,7 @@ impl RenderState {
3940
format: surface_format,
4041
width: size.width,
4142
height: size.height,
42-
#[cfg(not(target_os = "macos"))]
43-
present_mode: surface_caps.present_modes[0],
44-
#[cfg(target_os = "macos")]
45-
present_mode: wgpu::PresentMode::Immediate,
43+
present_mode: present_mode.unwrap_or(surface_caps.present_modes[0]),
4644
alpha_mode: surface_caps.alpha_modes[0],
4745
view_formats: vec![],
4846
desired_maximum_frame_latency: 1,

desktop/src/window/mac/menu.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ fn menu_items_from_wrapper(entries: Vec<WrapperMenuItem>) -> Vec<MenuItemKind> {
7777
}
7878
WrapperMenuItem::SubMenu { text: name, items, .. } => {
7979
let items = menu_items_from_wrapper(items);
80-
let items = items.iter().map(|item| menu_item_kind_to_dyn(item)).collect::<Vec<&dyn IsMenuItem>>();
80+
let items = items.iter().map(menu_item_kind_to_dyn).collect::<Vec<&dyn IsMenuItem>>();
8181
let submenu = Submenu::with_items(name, true, &items).unwrap();
8282
menu_items.push(MenuItemKind::Submenu(submenu));
8383
}
@@ -106,7 +106,7 @@ fn replace_children<'a, T: Into<MenuContainer<'a>>>(menu: T, new_items: Vec<Menu
106106
for item in items.iter() {
107107
menu.remove(menu_item_kind_to_dyn(item)).unwrap();
108108
}
109-
let items = new_items.iter().map(|item| menu_item_kind_to_dyn(item)).collect::<Vec<&dyn IsMenuItem>>();
109+
let items = new_items.iter().map(menu_item_kind_to_dyn).collect::<Vec<&dyn IsMenuItem>>();
110110
menu.append_items(items.as_ref()).unwrap();
111111
}
112112

desktop/wrapper/src/utils.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub(crate) mod menu {
1919
panic!("Menu bar layout group is supposed to be a row");
2020
};
2121
widgets
22-
.into_iter()
22+
.iter()
2323
.map(|widget| {
2424
let text_button = match widget.widget.as_ref() {
2525
Widget::TextButton(text_button) => text_button,
@@ -79,7 +79,7 @@ pub(crate) mod menu {
7979
let enabled = !*disabled;
8080

8181
if !children.is_empty() {
82-
let items = convert_menu_bar_entry_children_to_menu_items(&children, root_widget_id, path.clone());
82+
let items = convert_menu_bar_entry_children_to_menu_items(children, root_widget_id, path.clone());
8383
return MenuItem::SubMenu { id, text, enabled, items };
8484
}
8585

editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,9 +387,44 @@ impl PreferencesDialogMessageHandler {
387387

388388
rows.push(ui_acceleration);
389389
}
390+
391+
#[cfg(target_os = "macos")]
392+
{
393+
let vsync_description = "
394+
Render frames with vertical synchronization (v-sync) to prevent visual tearing within Graphite and the operating system compositor. This introduces increased input latency which is more noticeable on lower refresh rate displays. Future versions of Graphite will aim to reduce the macOS-specific latency without tearing artifacts.\n\
395+
\n\
396+
The application will restart for this change to take effect.\n\
397+
\n\
398+
*Default: Off.*
399+
"
400+
.trim();
401+
402+
let checkbox_id = CheckboxId::new();
403+
let vsync_checked = preferences.vsync;
404+
405+
let vsync = vec![
406+
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
407+
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
408+
CheckboxInput::new(vsync_checked)
409+
.tooltip_label("Enable V-Sync")
410+
.tooltip_description(vsync_description)
411+
.on_update(|checkbox_input: &CheckboxInput| Message::Batched {
412+
messages: Box::new([PreferencesDialogMessage::MayRequireRestart.into(), PreferencesMessage::VSync { vsync: checkbox_input.checked }.into()]),
413+
})
414+
.for_label(checkbox_id)
415+
.widget_instance(),
416+
TextLabel::new("Enable V-Sync")
417+
.tooltip_label("Enable V-Sync")
418+
.tooltip_description(vsync_description)
419+
.for_checkbox(checkbox_id)
420+
.widget_instance(),
421+
];
422+
423+
rows.push(vsync);
424+
}
390425
}
391426

392-
Layout(rows.into_iter().map(|r| LayoutGroup::row(r)).collect())
427+
Layout(rows.into_iter().map(LayoutGroup::row).collect())
393428
}
394429

395430
pub fn send_layout(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget, preferences: &PreferencesMessageHandler) {

editor/src/messages/preferences/preferences_message.rs

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,38 @@ use crate::messages::prelude::*;
66
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
77
pub enum PreferencesMessage {
88
// Management messages
9-
Load { preferences: PreferencesMessageHandler },
9+
Load {
10+
preferences: PreferencesMessageHandler,
11+
},
1012
ResetToDefaults,
1113

1214
// Per-preference messages
13-
SelectionMode { selection_mode: SelectionMode },
14-
BrushTool { enabled: bool },
15-
ModifyLayout { zoom_with_scroll: bool },
16-
GraphWireStyle { style: GraphWireStyle },
17-
ViewportZoomWheelRate { rate: f64 },
18-
UIScale { scale: f64 },
19-
DisableUIAcceleration { disable_ui_acceleration: bool },
20-
MaxRenderRegionSize { size: u32 },
15+
SelectionMode {
16+
selection_mode: SelectionMode,
17+
},
18+
BrushTool {
19+
enabled: bool,
20+
},
21+
ModifyLayout {
22+
zoom_with_scroll: bool,
23+
},
24+
GraphWireStyle {
25+
style: GraphWireStyle,
26+
},
27+
ViewportZoomWheelRate {
28+
rate: f64,
29+
},
30+
UIScale {
31+
scale: f64,
32+
},
33+
MaxRenderRegionSize {
34+
size: u32,
35+
},
36+
DisableUIAcceleration {
37+
disable_ui_acceleration: bool,
38+
},
39+
#[cfg(target_os = "macos")]
40+
VSync {
41+
vsync: bool,
42+
},
2143
}

editor/src/messages/preferences/preferences_message_handler.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,23 @@ pub struct PreferencesMessageHandler {
2121
pub graph_wire_style: GraphWireStyle,
2222
pub viewport_zoom_wheel_rate: f64,
2323
pub ui_scale: f64,
24-
pub disable_ui_acceleration: bool,
2524
pub max_render_region_size: u32,
25+
pub disable_ui_acceleration: bool,
26+
#[cfg(target_os = "macos")]
27+
pub vsync: bool,
2628
}
2729

2830
impl PreferencesMessageHandler {
31+
#[cfg(not(target_os = "macos"))]
2932
pub fn needs_restart(&self, other: &Self) -> bool {
3033
self.disable_ui_acceleration != other.disable_ui_acceleration
3134
}
3235

36+
#[cfg(target_os = "macos")]
37+
pub fn needs_restart(&self, other: &Self) -> bool {
38+
self.disable_ui_acceleration != other.disable_ui_acceleration || self.vsync != other.vsync
39+
}
40+
3341
pub fn get_selection_mode(&self) -> SelectionMode {
3442
self.selection_mode
3543
}
@@ -54,8 +62,10 @@ impl Default for PreferencesMessageHandler {
5462
graph_wire_style: GraphWireStyle::default(),
5563
viewport_zoom_wheel_rate: VIEWPORT_ZOOM_WHEEL_RATE,
5664
ui_scale: UI_SCALE_DEFAULT,
57-
disable_ui_acceleration: false,
5865
max_render_region_size: EditorPreferences::default().max_render_region_size,
66+
disable_ui_acceleration: false,
67+
#[cfg(target_os = "macos")]
68+
vsync: false,
5969
}
6070
}
6171
}
@@ -112,14 +122,18 @@ impl MessageHandler<PreferencesMessage, PreferencesMessageContext<'_>> for Prefe
112122
self.ui_scale = scale;
113123
responses.add(FrontendMessage::UpdateUIScale { scale: self.ui_scale });
114124
}
115-
PreferencesMessage::DisableUIAcceleration { disable_ui_acceleration } => {
116-
self.disable_ui_acceleration = disable_ui_acceleration;
117-
}
118125
PreferencesMessage::MaxRenderRegionSize { size } => {
119126
self.max_render_region_size = size;
120127
responses.add(PortfolioMessage::EditorPreferences);
121128
responses.add(NodeGraphMessage::RunDocumentGraph);
122129
}
130+
PreferencesMessage::DisableUIAcceleration { disable_ui_acceleration } => {
131+
self.disable_ui_acceleration = disable_ui_acceleration;
132+
}
133+
#[cfg(target_os = "macos")]
134+
PreferencesMessage::VSync { vsync } => {
135+
self.vsync = vsync;
136+
}
123137
}
124138

125139
responses.add(FrontendMessage::TriggerSavePreferences { preferences: self.clone() });

node-graph/nodes/gstd/src/pixel_preview.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub async fn pixel_preview<'a: 'n>(
2222
let physical_scale = render_params.scale;
2323

2424
let footprint = *ctx.footprint();
25-
let viewport_zoom = footprint.decompose_scale().x;
25+
let viewport_zoom = footprint.decompose_scale().x * physical_scale;
2626

2727
if render_params.render_mode != RenderMode::PixelPreview || !matches!(render_params.render_output_type, RenderOutputTypeRequest::Vello) || viewport_zoom <= 1. {
2828
let context = OwnedContextImpl::from(ctx).into_context();

0 commit comments

Comments
 (0)