add configurable vsync mode (off, on, adaptive)
- SHA
da7a230d2af8f6261206f01a9e84d112581510ab- Parents
-
6cb80f2 - Tree
d05c0d0
da7a230
da7a230d2af8f6261206f01a9e84d112581510ab6cb80f2
d05c0d0| Status | File | + | - |
|---|---|---|---|
| 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 @@ | ||
| 1 | 1 | //! Effects configuration for the compositor. |
| 2 | 2 | |
| 3 | 3 | use crate::config::LuaConfig; |
| 4 | +use crate::render::VSync; | |
| 4 | 5 | |
| 5 | 6 | /// Configuration for all compositor visual effects. |
| 6 | 7 | #[derive(Debug, Clone)] |
@@ -40,6 +41,10 @@ pub struct EffectsConfig { | ||
| 40 | 41 | pub fade_in_duration: f32, |
| 41 | 42 | /// Fade-out duration in seconds. |
| 42 | 43 | pub fade_out_duration: f32, |
| 44 | + | |
| 45 | + // VSync | |
| 46 | + /// VSync mode for frame presentation. | |
| 47 | + pub vsync: VSync, | |
| 43 | 48 | } |
| 44 | 49 | |
| 45 | 50 | impl Default for EffectsConfig { |
@@ -67,6 +72,9 @@ impl Default for EffectsConfig { | ||
| 67 | 72 | fade_enabled: false, |
| 68 | 73 | fade_in_duration: 0.1, |
| 69 | 74 | fade_out_duration: 0.1, |
| 75 | + | |
| 76 | + // VSync (adaptive by default for low latency without tearing) | |
| 77 | + vsync: VSync::Adaptive, | |
| 70 | 78 | } |
| 71 | 79 | } |
| 72 | 80 | } |
@@ -92,6 +100,8 @@ impl EffectsConfig { | ||
| 92 | 100 | let shadow_color_r: f32 = lua.get_setting("shadow_color_r").unwrap_or(0.0); |
| 93 | 101 | let shadow_color_g: f32 = lua.get_setting("shadow_color_g").unwrap_or(0.0); |
| 94 | 102 | 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); | |
| 95 | 105 | |
| 96 | 106 | Self { |
| 97 | 107 | blur_enabled, |
@@ -107,6 +117,7 @@ impl EffectsConfig { | ||
| 107 | 117 | fade_enabled, |
| 108 | 118 | fade_in_duration, |
| 109 | 119 | fade_out_duration, |
| 120 | + vsync, | |
| 110 | 121 | } |
| 111 | 122 | } |
| 112 | 123 | |
garchomp/src/render/gpu.rsmodified@@ -3,6 +3,51 @@ | ||
| 3 | 3 | use super::xlib::{XlibDisplay, XlibError, XlibWindowHandle}; |
| 4 | 4 | use thiserror::Error; |
| 5 | 5 | |
| 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 | + | |
| 6 | 51 | #[derive(Error, Debug)] |
| 7 | 52 | pub enum GpuError { |
| 8 | 53 | #[error("Xlib error: {0}")] |
@@ -34,11 +79,13 @@ pub struct GpuContext { | ||
| 34 | 79 | pub surface_config: wgpu::SurfaceConfiguration, |
| 35 | 80 | // Keep Xlib display alive for surface lifetime |
| 36 | 81 | xlib_display: XlibDisplay, |
| 82 | + // Available present modes for vsync changes | |
| 83 | + available_present_modes: Vec<wgpu::PresentMode>, | |
| 37 | 84 | } |
| 38 | 85 | |
| 39 | 86 | impl GpuContext { |
| 40 | 87 | /// 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> { | |
| 42 | 89 | // Open separate Xlib connection for GPU |
| 43 | 90 | let xlib_display = XlibDisplay::open()?; |
| 44 | 91 | let display = xlib_display.display_ptr(); |
@@ -111,17 +158,11 @@ impl GpuContext { | ||
| 111 | 158 | surface_caps.alpha_modes[0] |
| 112 | 159 | }; |
| 113 | 160 | |
| 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(); | |
| 123 | 164 | |
| 124 | - tracing::info!("Present mode: {:?}, alpha: {:?}", present_mode, alpha_mode); | |
| 165 | + tracing::info!("VSync: {:?}, present mode: {:?}, alpha: {:?}", vsync, present_mode, alpha_mode); | |
| 125 | 166 | |
| 126 | 167 | let surface_config = wgpu::SurfaceConfiguration { |
| 127 | 168 | usage: wgpu::TextureUsages::RENDER_ATTACHMENT, |
@@ -142,9 +183,25 @@ impl GpuContext { | ||
| 142 | 183 | surface, |
| 143 | 184 | surface_config, |
| 144 | 185 | xlib_display, |
| 186 | + available_present_modes, | |
| 145 | 187 | }) |
| 146 | 188 | } |
| 147 | 189 | |
| 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 | + | |
| 148 | 205 | /// Resize the surface. |
| 149 | 206 | pub fn resize(&mut self, width: u32, height: u32) { |
| 150 | 207 | if width > 0 && height > 0 { |
garchomp/src/render/mod.rsmodified@@ -10,7 +10,7 @@ mod texture; | ||
| 10 | 10 | mod xlib; |
| 11 | 11 | |
| 12 | 12 | pub use blur::{BlurPipeline, BlurTechnique}; |
| 13 | -pub use gpu::{GpuContext, GpuError}; | |
| 13 | +pub use gpu::{GpuContext, GpuError, VSync}; | |
| 14 | 14 | pub use hdr::{Colorspace, HdrConfig, HdrRenderTarget, TonemapOperator, TonemapPipeline}; |
| 15 | 15 | pub use pipeline::{CompositePipeline, Uniforms, Vertex}; |
| 16 | 16 | pub use renderer::{Renderer, WindowRenderData, WindowRenderInfo}; |
garchomp/src/render/renderer.rsmodified@@ -135,8 +135,8 @@ pub struct Renderer { | ||
| 135 | 135 | |
| 136 | 136 | impl Renderer { |
| 137 | 137 | /// 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?; | |
| 140 | 140 | |
| 141 | 141 | // Create the composite pipeline |
| 142 | 142 | let pipeline = CompositePipeline::new(&gpu.device, gpu.format()); |
@@ -278,6 +278,11 @@ impl Renderer { | ||
| 278 | 278 | self.intermediate_texture = None; |
| 279 | 279 | } |
| 280 | 280 | |
| 281 | + /// Set VSync mode. | |
| 282 | + pub fn set_vsync(&mut self, vsync: super::VSync) { | |
| 283 | + self.gpu.set_vsync(vsync); | |
| 284 | + } | |
| 285 | + | |
| 281 | 286 | /// Set the clear color (background). |
| 282 | 287 | pub fn set_clear_color(&mut self, r: f64, g: f64, b: f64, a: f64) { |
| 283 | 288 | self.clear_color = Color { r, g, b, a }; |