gardesk/garchomp / da7a230

Browse files

add configurable vsync mode (off, on, adaptive)

Authored by espadonne
SHA
da7a230d2af8f6261206f01a9e84d112581510ab
Parents
6cb80f2
Tree
d05c0d0

4 changed files

StatusFile+-
M garchomp/src/compositor/config.rs 11 0
M garchomp/src/render/gpu.rs 68 11
M garchomp/src/render/mod.rs 1 1
M garchomp/src/render/renderer.rs 7 2
garchomp/src/compositor/config.rsmodified
@@ -1,6 +1,7 @@
11
 //! Effects configuration for the compositor.
22
 
33
 use crate::config::LuaConfig;
4
+use crate::render::VSync;
45
 
56
 /// Configuration for all compositor visual effects.
67
 #[derive(Debug, Clone)]
@@ -40,6 +41,10 @@ pub struct EffectsConfig {
4041
     pub fade_in_duration: f32,
4142
     /// Fade-out duration in seconds.
4243
     pub fade_out_duration: f32,
44
+
45
+    // VSync
46
+    /// VSync mode for frame presentation.
47
+    pub vsync: VSync,
4348
 }
4449
 
4550
 impl Default for EffectsConfig {
@@ -67,6 +72,9 @@ impl Default for EffectsConfig {
6772
             fade_enabled: false,
6873
             fade_in_duration: 0.1,
6974
             fade_out_duration: 0.1,
75
+
76
+            // VSync (adaptive by default for low latency without tearing)
77
+            vsync: VSync::Adaptive,
7078
         }
7179
     }
7280
 }
@@ -92,6 +100,8 @@ impl EffectsConfig {
92100
         let shadow_color_r: f32 = lua.get_setting("shadow_color_r").unwrap_or(0.0);
93101
         let shadow_color_g: f32 = lua.get_setting("shadow_color_g").unwrap_or(0.0);
94102
         let shadow_color_b: f32 = lua.get_setting("shadow_color_b").unwrap_or(0.0);
103
+        let vsync_str: String = lua.get_setting("vsync").unwrap_or("adaptive".to_string());
104
+        let vsync = VSync::from_str(&vsync_str);
95105
 
96106
         Self {
97107
             blur_enabled,
@@ -107,6 +117,7 @@ impl EffectsConfig {
107117
             fade_enabled,
108118
             fade_in_duration,
109119
             fade_out_duration,
120
+            vsync,
110121
         }
111122
     }
112123
 
garchomp/src/render/gpu.rsmodified
@@ -3,6 +3,51 @@
33
 use super::xlib::{XlibDisplay, XlibError, XlibWindowHandle};
44
 use thiserror::Error;
55
 
6
+/// VSync mode for frame presentation.
7
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8
+pub enum VSync {
9
+    /// No vsync - immediate presentation, may tear
10
+    Off,
11
+    /// VSync enabled - wait for vertical blank (Fifo)
12
+    #[default]
13
+    On,
14
+    /// Adaptive vsync - mailbox mode (low latency, no tearing)
15
+    Adaptive,
16
+}
17
+
18
+impl VSync {
19
+    /// Parse from string (for Lua config).
20
+    pub fn from_str(s: &str) -> Self {
21
+        match s.to_lowercase().as_str() {
22
+            "off" | "false" | "none" | "immediate" => Self::Off,
23
+            "on" | "true" | "fifo" => Self::On,
24
+            "adaptive" | "mailbox" => Self::Adaptive,
25
+            _ => Self::default(),
26
+        }
27
+    }
28
+
29
+    /// Convert to wgpu PresentMode.
30
+    fn to_present_mode(self, caps: &wgpu::SurfaceCapabilities) -> wgpu::PresentMode {
31
+        match self {
32
+            Self::Off => {
33
+                if caps.present_modes.contains(&wgpu::PresentMode::Immediate) {
34
+                    wgpu::PresentMode::Immediate
35
+                } else {
36
+                    wgpu::PresentMode::Fifo // Fallback
37
+                }
38
+            }
39
+            Self::On => wgpu::PresentMode::Fifo,
40
+            Self::Adaptive => {
41
+                if caps.present_modes.contains(&wgpu::PresentMode::Mailbox) {
42
+                    wgpu::PresentMode::Mailbox
43
+                } else {
44
+                    wgpu::PresentMode::Fifo // Fallback
45
+                }
46
+            }
47
+        }
48
+    }
49
+}
50
+
651
 #[derive(Error, Debug)]
752
 pub enum GpuError {
853
     #[error("Xlib error: {0}")]
@@ -34,11 +79,13 @@ pub struct GpuContext {
3479
     pub surface_config: wgpu::SurfaceConfiguration,
3580
     // Keep Xlib display alive for surface lifetime
3681
     xlib_display: XlibDisplay,
82
+    // Available present modes for vsync changes
83
+    available_present_modes: Vec<wgpu::PresentMode>,
3784
 }
3885
 
3986
 impl GpuContext {
4087
     /// Create a new GPU context for the given overlay window.
41
-    pub async fn new(window: u32, width: u32, height: u32) -> Result<Self> {
88
+    pub async fn new(window: u32, width: u32, height: u32, vsync: VSync) -> Result<Self> {
4289
         // Open separate Xlib connection for GPU
4390
         let xlib_display = XlibDisplay::open()?;
4491
         let display = xlib_display.display_ptr();
@@ -111,17 +158,11 @@ impl GpuContext {
111158
             surface_caps.alpha_modes[0]
112159
         };
113160
 
114
-        // Prefer Mailbox (low latency) or Fifo (vsync)
115
-        let present_mode = if surface_caps
116
-            .present_modes
117
-            .contains(&wgpu::PresentMode::Mailbox)
118
-        {
119
-            wgpu::PresentMode::Mailbox
120
-        } else {
121
-            wgpu::PresentMode::Fifo
122
-        };
161
+        // Select present mode based on vsync setting
162
+        let present_mode = vsync.to_present_mode(&surface_caps);
163
+        let available_present_modes = surface_caps.present_modes.clone();
123164
 
124
-        tracing::info!("Present mode: {:?}, alpha: {:?}", present_mode, alpha_mode);
165
+        tracing::info!("VSync: {:?}, present mode: {:?}, alpha: {:?}", vsync, present_mode, alpha_mode);
125166
 
126167
         let surface_config = wgpu::SurfaceConfiguration {
127168
             usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
@@ -142,9 +183,25 @@ impl GpuContext {
142183
             surface,
143184
             surface_config,
144185
             xlib_display,
186
+            available_present_modes,
145187
         })
146188
     }
147189
 
190
+    /// Set VSync mode at runtime.
191
+    pub fn set_vsync(&mut self, vsync: VSync) {
192
+        let caps = wgpu::SurfaceCapabilities {
193
+            present_modes: self.available_present_modes.clone(),
194
+            ..Default::default()
195
+        };
196
+        let new_mode = vsync.to_present_mode(&caps);
197
+
198
+        if new_mode != self.surface_config.present_mode {
199
+            tracing::info!("Changing VSync to {:?} (present mode: {:?})", vsync, new_mode);
200
+            self.surface_config.present_mode = new_mode;
201
+            self.surface.configure(&self.device, &self.surface_config);
202
+        }
203
+    }
204
+
148205
     /// Resize the surface.
149206
     pub fn resize(&mut self, width: u32, height: u32) {
150207
         if width > 0 && height > 0 {
garchomp/src/render/mod.rsmodified
@@ -10,7 +10,7 @@ mod texture;
1010
 mod xlib;
1111
 
1212
 pub use blur::{BlurPipeline, BlurTechnique};
13
-pub use gpu::{GpuContext, GpuError};
13
+pub use gpu::{GpuContext, GpuError, VSync};
1414
 pub use hdr::{Colorspace, HdrConfig, HdrRenderTarget, TonemapOperator, TonemapPipeline};
1515
 pub use pipeline::{CompositePipeline, Uniforms, Vertex};
1616
 pub use renderer::{Renderer, WindowRenderData, WindowRenderInfo};
garchomp/src/render/renderer.rsmodified
@@ -135,8 +135,8 @@ pub struct Renderer {
135135
 
136136
 impl Renderer {
137137
     /// Create a new renderer for the given overlay window.
138
-    pub async fn new(window: u32, width: u32, height: u32) -> Result<Self, GpuError> {
139
-        let gpu = GpuContext::new(window, width, height).await?;
138
+    pub async fn new(window: u32, width: u32, height: u32, vsync: super::VSync) -> Result<Self, GpuError> {
139
+        let gpu = GpuContext::new(window, width, height, vsync).await?;
140140
 
141141
         // Create the composite pipeline
142142
         let pipeline = CompositePipeline::new(&gpu.device, gpu.format());
@@ -278,6 +278,11 @@ impl Renderer {
278278
         self.intermediate_texture = None;
279279
     }
280280
 
281
+    /// Set VSync mode.
282
+    pub fn set_vsync(&mut self, vsync: super::VSync) {
283
+        self.gpu.set_vsync(vsync);
284
+    }
285
+
281286
     /// Set the clear color (background).
282287
     pub fn set_clear_color(&mut self, r: f64, g: f64, b: f64, a: f64) {
283288
         self.clear_color = Color { r, g, b, a };