mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 14:28:11 +08:00
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>
This commit is contained in:
+11
-2
@@ -21,7 +21,7 @@ use crate::persist::PersistentData;
|
|||||||
use crate::preferences;
|
use crate::preferences;
|
||||||
use crate::render::{RenderError, RenderState};
|
use crate::render::{RenderError, RenderState};
|
||||||
use crate::window::Window;
|
use crate::window::Window;
|
||||||
use crate::wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, InputMessage, MouseKeys, MouseState};
|
use crate::wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, InputMessage, MouseKeys, MouseState, Preferences};
|
||||||
use crate::wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages};
|
use crate::wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages};
|
||||||
|
|
||||||
pub(crate) struct App {
|
pub(crate) struct App {
|
||||||
@@ -46,6 +46,8 @@ pub(crate) struct App {
|
|||||||
web_communication_initialized: bool,
|
web_communication_initialized: bool,
|
||||||
web_communication_startup_buffer: Vec<Vec<u8>>,
|
web_communication_startup_buffer: Vec<Vec<u8>>,
|
||||||
persistent_data: PersistentData,
|
persistent_data: PersistentData,
|
||||||
|
#[cfg_attr(not(target_os = "macos"), expect(unused))]
|
||||||
|
preferences: Preferences,
|
||||||
cli: Cli,
|
cli: Cli,
|
||||||
startup_time: Option<Instant>,
|
startup_time: Option<Instant>,
|
||||||
exiting: Arc<AtomicBool>,
|
exiting: Arc<AtomicBool>,
|
||||||
@@ -63,6 +65,7 @@ impl App {
|
|||||||
wgpu_context: WgpuContext,
|
wgpu_context: WgpuContext,
|
||||||
app_event_receiver: Receiver<AppEvent>,
|
app_event_receiver: Receiver<AppEvent>,
|
||||||
app_event_scheduler: AppEventScheduler,
|
app_event_scheduler: AppEventScheduler,
|
||||||
|
preferences: Preferences,
|
||||||
cli: Cli,
|
cli: Cli,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let ctrlc_app_event_scheduler = app_event_scheduler.clone();
|
let ctrlc_app_event_scheduler = app_event_scheduler.clone();
|
||||||
@@ -116,6 +119,7 @@ impl App {
|
|||||||
web_communication_initialized: false,
|
web_communication_initialized: false,
|
||||||
web_communication_startup_buffer: Vec::new(),
|
web_communication_startup_buffer: Vec::new(),
|
||||||
persistent_data,
|
persistent_data,
|
||||||
|
preferences,
|
||||||
cli,
|
cli,
|
||||||
startup_time: None,
|
startup_time: None,
|
||||||
exiting,
|
exiting,
|
||||||
@@ -516,7 +520,12 @@ impl ApplicationHandler for App {
|
|||||||
let window = Window::new(event_loop, self.app_event_scheduler.clone());
|
let window = Window::new(event_loop, self.app_event_scheduler.clone());
|
||||||
self.window = Some(window);
|
self.window = Some(window);
|
||||||
|
|
||||||
let render_state = RenderState::new(self.window.as_ref().unwrap(), self.wgpu_context.clone());
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
let present_mode = None;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let present_mode = if !self.preferences.vsync { Some(wgpu::PresentMode::Immediate) } else { None };
|
||||||
|
|
||||||
|
let render_state = RenderState::new(self.window.as_ref().unwrap(), self.wgpu_context.clone(), present_mode);
|
||||||
self.render_state = Some(render_state);
|
self.render_state = Some(render_state);
|
||||||
|
|
||||||
if let Some(window) = &self.window.as_ref() {
|
if let Some(window) = &self.window.as_ref() {
|
||||||
|
|||||||
+6
-4
@@ -62,6 +62,8 @@ pub fn start() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let prefs = preferences::read();
|
||||||
|
|
||||||
// Must be called before event loop initialization or native window integrations will break
|
// Must be called before event loop initialization or native window integrations will break
|
||||||
App::init();
|
App::init();
|
||||||
|
|
||||||
@@ -73,7 +75,7 @@ pub fn start() {
|
|||||||
|
|
||||||
let (cef_view_info_sender, cef_view_info_receiver) = std::sync::mpsc::channel();
|
let (cef_view_info_sender, cef_view_info_receiver) = std::sync::mpsc::channel();
|
||||||
|
|
||||||
let disable_ui_acceleration = preferences::read().disable_ui_acceleration || cli.disable_ui_acceleration;
|
let disable_ui_acceleration = prefs.disable_ui_acceleration || cli.disable_ui_acceleration;
|
||||||
if disable_ui_acceleration {
|
if disable_ui_acceleration {
|
||||||
println!("UI acceleration is disabled");
|
println!("UI acceleration is disabled");
|
||||||
}
|
}
|
||||||
@@ -95,7 +97,7 @@ pub fn start() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let app = App::new(Box::new(cef_context), cef_view_info_sender, wgpu_context, app_event_receiver, app_event_scheduler, cli);
|
let app = App::new(Box::new(cef_context), cef_view_info_sender, wgpu_context, app_event_receiver, app_event_scheduler, prefs, cli);
|
||||||
|
|
||||||
let exit_reason = app.run(event_loop);
|
let exit_reason = app.run(event_loop);
|
||||||
|
|
||||||
@@ -111,15 +113,15 @@ pub fn start() {
|
|||||||
drop(lock);
|
drop(lock);
|
||||||
|
|
||||||
match exit_reason {
|
match exit_reason {
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
app::ExitReason::Restart | app::ExitReason::UiAccelerationFailure => {
|
app::ExitReason::Restart | app::ExitReason::UiAccelerationFailure => {
|
||||||
tracing::error!("Restarting application");
|
tracing::error!("Restarting application");
|
||||||
let mut command = std::process::Command::new(std::env::current_exe().unwrap());
|
let mut command = std::process::Command::new(std::env::current_exe().unwrap());
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
let _ = std::os::unix::process::CommandExt::exec(&mut command);
|
let _ = std::os::unix::process::CommandExt::exec(&mut command);
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
tracing::error!("Failed to restart application");
|
||||||
#[cfg(not(target_family = "unix"))]
|
#[cfg(not(target_family = "unix"))]
|
||||||
let _ = command.spawn();
|
let _ = command.spawn();
|
||||||
tracing::error!("Failed to restart application");
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use wgpu::PresentMode;
|
||||||
|
|
||||||
use crate::window::Window;
|
use crate::window::Window;
|
||||||
use crate::wrapper::{TargetTexture, WgpuContext, WgpuExecutor};
|
use crate::wrapper::{TargetTexture, WgpuContext, WgpuExecutor};
|
||||||
@@ -27,7 +28,7 @@ pub(crate) struct RenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RenderState {
|
impl RenderState {
|
||||||
pub(crate) fn new(window: &Window, context: WgpuContext) -> Self {
|
pub(crate) fn new(window: &Window, context: WgpuContext, present_mode: Option<PresentMode>) -> Self {
|
||||||
let size = window.surface_size();
|
let size = window.surface_size();
|
||||||
let surface = window.create_surface(context.instance.clone());
|
let surface = window.create_surface(context.instance.clone());
|
||||||
|
|
||||||
@@ -39,10 +40,7 @@ impl RenderState {
|
|||||||
format: surface_format,
|
format: surface_format,
|
||||||
width: size.width,
|
width: size.width,
|
||||||
height: size.height,
|
height: size.height,
|
||||||
#[cfg(not(target_os = "macos"))]
|
present_mode: present_mode.unwrap_or(surface_caps.present_modes[0]),
|
||||||
present_mode: surface_caps.present_modes[0],
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
present_mode: wgpu::PresentMode::Immediate,
|
|
||||||
alpha_mode: surface_caps.alpha_modes[0],
|
alpha_mode: surface_caps.alpha_modes[0],
|
||||||
view_formats: vec![],
|
view_formats: vec![],
|
||||||
desired_maximum_frame_latency: 1,
|
desired_maximum_frame_latency: 1,
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ fn menu_items_from_wrapper(entries: Vec<WrapperMenuItem>) -> Vec<MenuItemKind> {
|
|||||||
}
|
}
|
||||||
WrapperMenuItem::SubMenu { text: name, items, .. } => {
|
WrapperMenuItem::SubMenu { text: name, items, .. } => {
|
||||||
let items = menu_items_from_wrapper(items);
|
let items = menu_items_from_wrapper(items);
|
||||||
let items = items.iter().map(|item| menu_item_kind_to_dyn(item)).collect::<Vec<&dyn IsMenuItem>>();
|
let items = items.iter().map(menu_item_kind_to_dyn).collect::<Vec<&dyn IsMenuItem>>();
|
||||||
let submenu = Submenu::with_items(name, true, &items).unwrap();
|
let submenu = Submenu::with_items(name, true, &items).unwrap();
|
||||||
menu_items.push(MenuItemKind::Submenu(submenu));
|
menu_items.push(MenuItemKind::Submenu(submenu));
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ fn replace_children<'a, T: Into<MenuContainer<'a>>>(menu: T, new_items: Vec<Menu
|
|||||||
for item in items.iter() {
|
for item in items.iter() {
|
||||||
menu.remove(menu_item_kind_to_dyn(item)).unwrap();
|
menu.remove(menu_item_kind_to_dyn(item)).unwrap();
|
||||||
}
|
}
|
||||||
let items = new_items.iter().map(|item| menu_item_kind_to_dyn(item)).collect::<Vec<&dyn IsMenuItem>>();
|
let items = new_items.iter().map(menu_item_kind_to_dyn).collect::<Vec<&dyn IsMenuItem>>();
|
||||||
menu.append_items(items.as_ref()).unwrap();
|
menu.append_items(items.as_ref()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ pub(crate) mod menu {
|
|||||||
panic!("Menu bar layout group is supposed to be a row");
|
panic!("Menu bar layout group is supposed to be a row");
|
||||||
};
|
};
|
||||||
widgets
|
widgets
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|widget| {
|
.map(|widget| {
|
||||||
let text_button = match widget.widget.as_ref() {
|
let text_button = match widget.widget.as_ref() {
|
||||||
Widget::TextButton(text_button) => text_button,
|
Widget::TextButton(text_button) => text_button,
|
||||||
@@ -79,7 +79,7 @@ pub(crate) mod menu {
|
|||||||
let enabled = !*disabled;
|
let enabled = !*disabled;
|
||||||
|
|
||||||
if !children.is_empty() {
|
if !children.is_empty() {
|
||||||
let items = convert_menu_bar_entry_children_to_menu_items(&children, root_widget_id, path.clone());
|
let items = convert_menu_bar_entry_children_to_menu_items(children, root_widget_id, path.clone());
|
||||||
return MenuItem::SubMenu { id, text, enabled, items };
|
return MenuItem::SubMenu { id, text, enabled, items };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+36
-1
@@ -387,9 +387,44 @@ impl PreferencesDialogMessageHandler {
|
|||||||
|
|
||||||
rows.push(ui_acceleration);
|
rows.push(ui_acceleration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
let vsync_description = "
|
||||||
|
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\
|
||||||
|
\n\
|
||||||
|
The application will restart for this change to take effect.\n\
|
||||||
|
\n\
|
||||||
|
*Default: Off.*
|
||||||
|
"
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
let checkbox_id = CheckboxId::new();
|
||||||
|
let vsync_checked = preferences.vsync;
|
||||||
|
|
||||||
|
let vsync = vec![
|
||||||
|
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||||
|
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||||
|
CheckboxInput::new(vsync_checked)
|
||||||
|
.tooltip_label("Enable V-Sync")
|
||||||
|
.tooltip_description(vsync_description)
|
||||||
|
.on_update(|checkbox_input: &CheckboxInput| Message::Batched {
|
||||||
|
messages: Box::new([PreferencesDialogMessage::MayRequireRestart.into(), PreferencesMessage::VSync { vsync: checkbox_input.checked }.into()]),
|
||||||
|
})
|
||||||
|
.for_label(checkbox_id)
|
||||||
|
.widget_instance(),
|
||||||
|
TextLabel::new("Enable V-Sync")
|
||||||
|
.tooltip_label("Enable V-Sync")
|
||||||
|
.tooltip_description(vsync_description)
|
||||||
|
.for_checkbox(checkbox_id)
|
||||||
|
.widget_instance(),
|
||||||
|
];
|
||||||
|
|
||||||
|
rows.push(vsync);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Layout(rows.into_iter().map(|r| LayoutGroup::row(r)).collect())
|
Layout(rows.into_iter().map(LayoutGroup::row).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_layout(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget, preferences: &PreferencesMessageHandler) {
|
pub fn send_layout(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget, preferences: &PreferencesMessageHandler) {
|
||||||
|
|||||||
@@ -6,16 +6,38 @@ use crate::messages::prelude::*;
|
|||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum PreferencesMessage {
|
pub enum PreferencesMessage {
|
||||||
// Management messages
|
// Management messages
|
||||||
Load { preferences: PreferencesMessageHandler },
|
Load {
|
||||||
|
preferences: PreferencesMessageHandler,
|
||||||
|
},
|
||||||
ResetToDefaults,
|
ResetToDefaults,
|
||||||
|
|
||||||
// Per-preference messages
|
// Per-preference messages
|
||||||
SelectionMode { selection_mode: SelectionMode },
|
SelectionMode {
|
||||||
BrushTool { enabled: bool },
|
selection_mode: SelectionMode,
|
||||||
ModifyLayout { zoom_with_scroll: bool },
|
},
|
||||||
GraphWireStyle { style: GraphWireStyle },
|
BrushTool {
|
||||||
ViewportZoomWheelRate { rate: f64 },
|
enabled: bool,
|
||||||
UIScale { scale: f64 },
|
},
|
||||||
DisableUIAcceleration { disable_ui_acceleration: bool },
|
ModifyLayout {
|
||||||
MaxRenderRegionSize { size: u32 },
|
zoom_with_scroll: bool,
|
||||||
|
},
|
||||||
|
GraphWireStyle {
|
||||||
|
style: GraphWireStyle,
|
||||||
|
},
|
||||||
|
ViewportZoomWheelRate {
|
||||||
|
rate: f64,
|
||||||
|
},
|
||||||
|
UIScale {
|
||||||
|
scale: f64,
|
||||||
|
},
|
||||||
|
MaxRenderRegionSize {
|
||||||
|
size: u32,
|
||||||
|
},
|
||||||
|
DisableUIAcceleration {
|
||||||
|
disable_ui_acceleration: bool,
|
||||||
|
},
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
VSync {
|
||||||
|
vsync: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,15 +21,23 @@ pub struct PreferencesMessageHandler {
|
|||||||
pub graph_wire_style: GraphWireStyle,
|
pub graph_wire_style: GraphWireStyle,
|
||||||
pub viewport_zoom_wheel_rate: f64,
|
pub viewport_zoom_wheel_rate: f64,
|
||||||
pub ui_scale: f64,
|
pub ui_scale: f64,
|
||||||
pub disable_ui_acceleration: bool,
|
|
||||||
pub max_render_region_size: u32,
|
pub max_render_region_size: u32,
|
||||||
|
pub disable_ui_acceleration: bool,
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
pub vsync: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PreferencesMessageHandler {
|
impl PreferencesMessageHandler {
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
pub fn needs_restart(&self, other: &Self) -> bool {
|
pub fn needs_restart(&self, other: &Self) -> bool {
|
||||||
self.disable_ui_acceleration != other.disable_ui_acceleration
|
self.disable_ui_acceleration != other.disable_ui_acceleration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
pub fn needs_restart(&self, other: &Self) -> bool {
|
||||||
|
self.disable_ui_acceleration != other.disable_ui_acceleration || self.vsync != other.vsync
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_selection_mode(&self) -> SelectionMode {
|
pub fn get_selection_mode(&self) -> SelectionMode {
|
||||||
self.selection_mode
|
self.selection_mode
|
||||||
}
|
}
|
||||||
@@ -54,8 +62,10 @@ impl Default for PreferencesMessageHandler {
|
|||||||
graph_wire_style: GraphWireStyle::default(),
|
graph_wire_style: GraphWireStyle::default(),
|
||||||
viewport_zoom_wheel_rate: VIEWPORT_ZOOM_WHEEL_RATE,
|
viewport_zoom_wheel_rate: VIEWPORT_ZOOM_WHEEL_RATE,
|
||||||
ui_scale: UI_SCALE_DEFAULT,
|
ui_scale: UI_SCALE_DEFAULT,
|
||||||
disable_ui_acceleration: false,
|
|
||||||
max_render_region_size: EditorPreferences::default().max_render_region_size,
|
max_render_region_size: EditorPreferences::default().max_render_region_size,
|
||||||
|
disable_ui_acceleration: false,
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
vsync: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,14 +122,18 @@ impl MessageHandler<PreferencesMessage, PreferencesMessageContext<'_>> for Prefe
|
|||||||
self.ui_scale = scale;
|
self.ui_scale = scale;
|
||||||
responses.add(FrontendMessage::UpdateUIScale { scale: self.ui_scale });
|
responses.add(FrontendMessage::UpdateUIScale { scale: self.ui_scale });
|
||||||
}
|
}
|
||||||
PreferencesMessage::DisableUIAcceleration { disable_ui_acceleration } => {
|
|
||||||
self.disable_ui_acceleration = disable_ui_acceleration;
|
|
||||||
}
|
|
||||||
PreferencesMessage::MaxRenderRegionSize { size } => {
|
PreferencesMessage::MaxRenderRegionSize { size } => {
|
||||||
self.max_render_region_size = size;
|
self.max_render_region_size = size;
|
||||||
responses.add(PortfolioMessage::EditorPreferences);
|
responses.add(PortfolioMessage::EditorPreferences);
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
}
|
}
|
||||||
|
PreferencesMessage::DisableUIAcceleration { disable_ui_acceleration } => {
|
||||||
|
self.disable_ui_acceleration = disable_ui_acceleration;
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
PreferencesMessage::VSync { vsync } => {
|
||||||
|
self.vsync = vsync;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.add(FrontendMessage::TriggerSavePreferences { preferences: self.clone() });
|
responses.add(FrontendMessage::TriggerSavePreferences { preferences: self.clone() });
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ pub async fn pixel_preview<'a: 'n>(
|
|||||||
let physical_scale = render_params.scale;
|
let physical_scale = render_params.scale;
|
||||||
|
|
||||||
let footprint = *ctx.footprint();
|
let footprint = *ctx.footprint();
|
||||||
let viewport_zoom = footprint.decompose_scale().x;
|
let viewport_zoom = footprint.decompose_scale().x * physical_scale;
|
||||||
|
|
||||||
if render_params.render_mode != RenderMode::PixelPreview || !matches!(render_params.render_output_type, RenderOutputTypeRequest::Vello) || viewport_zoom <= 1. {
|
if render_params.render_mode != RenderMode::PixelPreview || !matches!(render_params.render_output_type, RenderOutputTypeRequest::Vello) || viewport_zoom <= 1. {
|
||||||
let context = OwnedContextImpl::from(ctx).into_context();
|
let context = OwnedContextImpl::from(ctx).into_context();
|
||||||
|
|||||||
Reference in New Issue
Block a user